Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# Settings File Error Banner
|
||||
|
||||
## Summary
|
||||
|
||||
When the user's `settings.toml` file contains errors — either the entire file is syntactically invalid TOML, or individual setting values cannot be deserialized into their expected types — Warp shows a dismissible warning banner at the top of the workspace. The banner tells the user what went wrong and provides a button to open the file in Warp's editor so they can fix it.
|
||||
|
||||
## Problem
|
||||
|
||||
Today, when `settings.toml` has errors, the user gets no feedback. Settings silently fall back to defaults, and the user has no way to know that their customizations aren't being applied.
|
||||
|
||||
## Goals
|
||||
|
||||
- Surface settings file errors to the user in a clear, non-blocking way.
|
||||
- Handle both error types: entire file unparsable (TOML syntax error) and individual values invalid (wrong type for a setting).
|
||||
- Show a single banner regardless of how many errors exist.
|
||||
- Automatically clear the banner when the user fixes the file.
|
||||
- Provide a direct action to open the settings file for editing.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Per-setting inline error annotations (future work).
|
||||
- Automatic correction or migration of invalid values.
|
||||
- Surfacing errors for other config files (tab configs already have their own toast system).
|
||||
- A settings file editor with TOML syntax highlighting or validation (uses the existing code editor).
|
||||
|
||||
## Figma
|
||||
|
||||
Figma: none provided. The banner follows the existing `WorkspaceBanner` visual pattern (colored bar at the top of the workspace with text and an optional button).
|
||||
|
||||
## User experience
|
||||
|
||||
### Banner appearance
|
||||
|
||||
The banner appears at the top of the workspace, below the tab bar, using the existing workspace banner system. It uses the warning color scheme (amber/orange background, white text) — the same style as the reauth banner.
|
||||
|
||||
### Banner priority
|
||||
|
||||
The settings error banner sits just below the reauth banner in priority. If the reauth banner is active, it takes precedence and the settings error banner is hidden until reauth is resolved. The settings error banner takes precedence over all other workspace banners (version deprecated, unable to update, crash recovery) because it's more important that the user is notified their settings file is broken than that they continue to see those other banners.
|
||||
|
||||
### Error messages
|
||||
|
||||
- **Entire file unparsable**: "Your settings file (settings.toml) could not be loaded: {parse_error}" — where `{parse_error}` is the specific TOML parse error (e.g. line number and description from the TOML parser).
|
||||
- **Single invalid value**: "Invalid value for '{key}' in settings.toml. The default value is being used."
|
||||
- **Multiple invalid values**: "Invalid values in settings.toml for: {key1}, {key2}, ... Default values are being used."
|
||||
|
||||
### "Open settings file" button
|
||||
|
||||
The banner includes an "Open settings file" button styled consistently with other workspace banner buttons. Clicking it opens `settings.toml` in a new editor pane (using Warp's built-in code editor, same as opening any other file).
|
||||
|
||||
### Dismiss behavior
|
||||
|
||||
- The banner has a close (✕) button for temporary dismissal.
|
||||
- Clicking the close button hides the banner for the current session.
|
||||
- If the user edits the settings file and introduces a **new** error (different from the one that was dismissed), the banner reappears.
|
||||
- If the user fixes the file (hot-reload succeeds with no errors), the banner disappears automatically regardless of dismissal state.
|
||||
|
||||
### Startup behavior
|
||||
|
||||
- If `settings.toml` has a TOML syntax error on startup, all settings fall back to defaults and the banner is shown on the first frame.
|
||||
- If `settings.toml` is syntactically valid but contains individual invalid values, those settings fall back to defaults and the banner is shown on the first frame.
|
||||
|
||||
### Hot-reload behavior
|
||||
|
||||
- When the user modifies `settings.toml` and the file watcher detects the change:
|
||||
- If the file cannot be parsed: the previous in-memory settings are kept and the banner appears (or updates).
|
||||
- If the file parses but some values are invalid: valid settings are updated, invalid ones fall back to defaults, and the banner appears (or updates).
|
||||
- If the file parses and all values are valid: settings are updated and the banner disappears.
|
||||
|
||||
## Success criteria
|
||||
|
||||
1. When `settings.toml` contains invalid TOML syntax on startup, a warning banner is visible after the workspace loads.
|
||||
2. When `settings.toml` contains a valid TOML structure but with an invalid value for a known setting on startup, a warning banner is visible.
|
||||
3. When a running Warp instance detects a file change that introduces a TOML syntax error, the banner appears within a few seconds.
|
||||
4. When a running Warp instance detects a file change that introduces an invalid setting value, the banner appears within a few seconds.
|
||||
5. When the user fixes the file and the watcher picks up the change, the banner disappears automatically.
|
||||
6. The "Open settings file" button opens `settings.toml` in Warp's code editor.
|
||||
7. The close (✕) button dismisses the banner until a new error event arrives.
|
||||
8. The banner does not appear when there are no errors in the settings file.
|
||||
|
||||
## Validation
|
||||
|
||||
- **Unit tests**: Verify `reload_all_public_settings` returns failed keys for invalid values and empty for valid values. Verify `validate_all_public_settings` detects invalid stored values without modifying state.
|
||||
- **Integration tests**: Four integration tests covering the startup × reload and whole-file × individual-value error matrix. Each test asserts the banner is visible (or not) by checking `has_settings_file_error_banner()` on the workspace view. The reload-with-fix test also verifies the banner clears.
|
||||
- **Manual validation**: Break `settings.toml` with a syntax error, launch Warp, confirm the banner is visible with the correct message and button. Fix the file, confirm the banner clears. Repeat with an invalid value (e.g. `font_size = "abc"`).
|
||||
|
||||
## Follow-ups
|
||||
|
||||
- Not included in this PR, but once we have the skill for an agent to edit the settings file, we should have some kind of "Oz auto-fix" button.
|
||||
@@ -0,0 +1,148 @@
|
||||
# Settings File Error Banner — Tech Spec
|
||||
|
||||
## Problem
|
||||
|
||||
`settings.toml` errors are invisible to the user. When the file has a TOML syntax error, the app silently falls back to defaults. When individual values are the wrong type, they silently revert to defaults. There is no mechanism to surface these errors to the user or to recover gracefully.
|
||||
|
||||
This required two coordinated changes:
|
||||
1. **Startup recovery** (prerequisite PR) — Always create `TomlBackedUserPreferences` even on parse failure, so the hot-reload watcher is wired up and can recover when the file is fixed.
|
||||
2. **Error banner** — Capture errors from both startup and hot-reload paths, propagate them to the workspace, and render a dismissible warning banner.
|
||||
|
||||
## Relevant code
|
||||
|
||||
- `crates/warpui_extras/src/user_preferences/toml_backed.rs` — TOML preferences backend (`new()`, `reload_from_disk()`)
|
||||
- `app/src/settings/init.rs` — `init_public_user_preferences()`, `init()`, `handle_warp_config_change()`
|
||||
- `app/src/settings/mod.rs` — `SettingsFileError` enum
|
||||
- `crates/settings/src/manager.rs` — `SettingsManager::reload_all_public_settings()`, `validate_all_public_settings()`
|
||||
- `app/src/user_config/mod.rs` — `WarpConfigUpdateEvent::SettingsErrors` / `SettingsErrorsCleared`
|
||||
- `app/src/workspace/view.rs` — `WorkspaceBanner::InvalidSettings`, `render_settings_error_banner()`, `subscribe_to_settings_errors()`
|
||||
- `app/src/workspace/action.rs` — `WorkspaceAction::OpenSettingsFile`
|
||||
- `app/src/global_resource_handles.rs` — `settings_file_error` field for startup propagation
|
||||
- `app/src/lib.rs` — threading startup parse error through `initialize_app()`
|
||||
|
||||
## Current state
|
||||
|
||||
### Before these changes
|
||||
|
||||
- `TomlBackedUserPreferences::new()` returned `Result<Self, Error>`. On parse failure, `init_public_user_preferences()` fell back to `InMemoryPreferences`.
|
||||
- `InMemoryPreferences::is_settings_file()` returned `false`, so the hot-reload watcher subscription in `init()` was never set up. The user was stuck on defaults permanently.
|
||||
- `reload_all_public_settings()` returned `()`, logging per-setting failures but not collecting them.
|
||||
- No mechanism existed to surface settings errors to the user.
|
||||
|
||||
### After these changes
|
||||
|
||||
- `TomlBackedUserPreferences::new()` returns `(Self, Option<Error>)`. It always succeeds, starting with an empty document on parse failure.
|
||||
- `reload_all_public_settings()` returns `Vec<String>` of failed storage keys.
|
||||
- `validate_all_public_settings()` provides read-only startup validation.
|
||||
- Errors are propagated via `WarpConfigUpdateEvent` to the workspace banner system.
|
||||
|
||||
## Proposed changes (as implemented)
|
||||
|
||||
### Prerequisite: Startup fallback (`TomlBackedUserPreferences::new()`)
|
||||
|
||||
Changed `new()` to `(Self, Option<Error>)`. On parse failure, starts with `DocumentMut::new()` and returns the error separately. The caller always gets a working `TomlBackedUserPreferences` that can recover via `reload_from_disk()`. (Separate PR.)
|
||||
|
||||
### Settings error banner
|
||||
|
||||
#### Error type
|
||||
|
||||
```rust
|
||||
// app/src/settings/mod.rs
|
||||
pub enum SettingsFileError {
|
||||
FileParseFailed(String),
|
||||
InvalidSettings(Vec<String>),
|
||||
}
|
||||
```
|
||||
|
||||
#### Error capture — hot-reload path
|
||||
|
||||
`handle_warp_config_change()` in `app/src/settings/init.rs`:
|
||||
- On `reload_from_disk()` failure → emits `WarpConfigUpdateEvent::SettingsErrors(FileParseFailed(...))`
|
||||
- On success → calls `reload_all_public_settings()`. If failed keys returned → emits `SettingsErrors(InvalidSettings(...))`. If empty → emits `SettingsErrorsCleared`.
|
||||
|
||||
#### Error capture — startup path
|
||||
|
||||
- `init_public_user_preferences()` returns `(Model, Option<user_preferences::Error>)`. The parse error flows through `lib.rs` → `initialize_app()` → `settings::init()`, which stringifies it when wrapping into `SettingsFileError::FileParseFailed`.
|
||||
- `validate_all_public_settings()` re-reads each public setting from preferences and attempts deserialization via `equals_fn`, returning failed keys.
|
||||
- Both errors are combined into `SettingsFileError` and stored in `UserDefaultsOnStartup.settings_file_error`, which flows through `GlobalResourceHandles` to `Workspace::new()`.
|
||||
|
||||
#### Event plumbing
|
||||
|
||||
Two new `WarpConfigUpdateEvent` variants:
|
||||
- `SettingsErrors(SettingsFileError)` — emitted when errors are detected
|
||||
- `SettingsErrorsCleared` — emitted when a reload succeeds with no errors
|
||||
|
||||
#### Workspace banner
|
||||
|
||||
- `WorkspaceBanner::InvalidSettings` variant added to the enum. Returns `true` from `is_dismissible()`.
|
||||
- `Workspace` fields: `settings_file_error: Option<SettingsFileError>`, `settings_error_banner_dismissed: bool`.
|
||||
- `render_settings_error_banner()` produces `WorkspaceBannerFields` with the appropriate message and an "Open settings file" button.
|
||||
- `subscribe_to_settings_errors()` subscribes to `WarpConfig` model for `SettingsErrors`/`SettingsErrorsCleared` events.
|
||||
- `WorkspaceAction::OpenSettingsFile` opens `settings.toml` in a code editor pane via `add_tab_for_code_file()`.
|
||||
|
||||
## End-to-end flow
|
||||
|
||||
### Startup with broken file
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant lib.rs
|
||||
participant TomlBackedUserPreferences
|
||||
participant SettingsInit as settings::init
|
||||
participant Workspace
|
||||
|
||||
lib.rs->>TomlBackedUserPreferences: new(path)
|
||||
TomlBackedUserPreferences-->>lib.rs: (prefs, Some(parse_error))
|
||||
Note over TomlBackedUserPreferences: write_inhibited = true
|
||||
lib.rs->>SettingsInit: init(Some(parse_error), ctx)
|
||||
SettingsInit->>SettingsInit: validate_all_public_settings()
|
||||
SettingsInit-->>lib.rs: UserDefaultsOnStartup { settings_file_error: FileParseFailed }
|
||||
lib.rs->>Workspace: new(GlobalResourceHandles { settings_file_error })
|
||||
Note over Workspace: Banner visible on first frame
|
||||
```
|
||||
|
||||
### Hot-reload with error → fix
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant FileWatcher
|
||||
participant WarpConfig
|
||||
participant Handler as handle_warp_config_change
|
||||
participant Workspace
|
||||
|
||||
FileWatcher->>WarpConfig: Settings event
|
||||
WarpConfig->>Handler: WarpConfigUpdateEvent::Settings
|
||||
Handler->>Handler: reload_from_disk() → Err
|
||||
Handler->>WarpConfig: emit SettingsErrors(FileParseFailed)
|
||||
WarpConfig->>Workspace: SettingsErrors → banner appears
|
||||
|
||||
Note over FileWatcher: User fixes file
|
||||
FileWatcher->>WarpConfig: Settings event
|
||||
WarpConfig->>Handler: WarpConfigUpdateEvent::Settings
|
||||
Handler->>Handler: reload_from_disk() → Ok
|
||||
Handler->>Handler: reload_all_public_settings() → []
|
||||
Handler->>WarpConfig: emit SettingsErrorsCleared
|
||||
WarpConfig->>Workspace: SettingsErrorsCleared → banner disappears
|
||||
```
|
||||
|
||||
## Risks and mitigations
|
||||
|
||||
- **Watcher reliability**: The filesystem watcher (notify) may not reliably detect file creation on all platforms. Mitigation: for file *modification* (the common case when the user edits their file), detection is reliable. The startup path catches errors independently of the watcher.
|
||||
- **Validate via equals_fn**: `validate_all_public_settings` uses the `equals_fn` closure (which round-trips through `serde_json::from_str`) rather than the `load_fn`. This is intentional — it's a read-only check that doesn't modify in-memory state. Risk: if a setting has a custom `file_deserialize` that accepts values the `equals_fn` rejects, or vice versa, there could be false positives/negatives. Mitigation: the `equals_fn` path matches the `load_fn` path for standard serde-based settings.
|
||||
|
||||
## Testing and validation
|
||||
|
||||
### Unit tests (`crates/settings/src/mod_tests.rs`)
|
||||
- `test_reload_returns_failed_keys_for_invalid_values` — reload with bad value returns the key
|
||||
- `test_reload_returns_empty_vec_on_success` — reload with valid values returns empty
|
||||
- `test_validate_detects_invalid_values` — startup validation catches bad values
|
||||
- `test_validate_returns_empty_when_all_valid` — startup validation passes for good values
|
||||
|
||||
### Unit tests (`crates/warpui_extras/src/user_preferences/toml_backed_tests.rs`)
|
||||
- `test_new_with_invalid_toml_returns_error_and_recovers_on_reload` — parse failure → recovery
|
||||
|
||||
### Integration tests (`crates/integration/src/test/settings_file_errors.rs`)
|
||||
- `test_settings_error_banner_on_startup_with_invalid_toml` — startup banner for broken file
|
||||
- `test_settings_error_banner_on_startup_with_invalid_value` — startup banner for bad value
|
||||
- `test_settings_error_banner_on_reload_with_invalid_toml` — reload banner + auto-clear on fix
|
||||
- `test_settings_error_banner_on_reload_with_invalid_value` — reload banner for bad value
|
||||
Reference in New Issue
Block a user