docs/internal/settings_ui_improvement_plan_track_two.md
This document presents findings from a comprehensive UX audit of the Settings UI dialog,
focusing on the LSP Configuration section. Testing was conducted via tmux-scripted
interaction with the debug build, across two rounds (pre- and post-rebase on latest master).
Round 1 uncovered 9 bugs and 6 usability issues. Round 2 (post-rebase) confirmed that 5 of 9 original bugs are now fixed, but uncovered 1 new critical regression and 2 remaining bugs.
| ID | Status | Summary |
|---|---|---|
| Bug 1 | FIXED | Text input now works — auto-edit mode on character input |
| Bug 2 | FIXED | Tab now toggles Fields ↔ Buttons via toggle_focus_region() |
| Bug 3 | FIXED | Button focus indicator (>) renders when buttons are focused |
| Bug 4 | FIXED | Enter on booleans no longer toggles; Space toggles booleans |
| Bug 5 | PARTIALLY FIXED | Down navigation still has inconsistencies (double-visits) |
| Bug 6 | FIXED | Ctrl+S saves entry dialog from any mode |
| Issue 7 | FIXED | LSP entries now show command preview (e.g., pylsp) |
| Issue 8 | PARTIALLY FIXED | Fields reordered by importance; "Advanced" separator added |
| NEW | CRITICAL | ObjectArray [+] Add new unreachable via keyboard |
cargo build (debug profile, post-rebase on latest master)tmux session, 160×50, TERM=xterm-256color/tmp/fresh-test/test.pytmux send-keys for input, tmux capture-pane -p for screen captureWhat changed: handle_entry_dialog_navigation() (input.rs:454–480) now handles
KeyCode::Char(c) for Text, TextList, and Number fields. It calls dialog.start_editing()
then forwards the character to handle_entry_dialog_text_editing().
Verified: Typing a on the Command field immediately appends it: pylsp → pylspa.
Multiple characters in rapid succession all register correctly.
What changed: Tab now calls dialog.toggle_focus_region() (entry_dialog.rs:380–401)
which toggles between focus_on_buttons = true/false. This matches the status bar hint
Tab:Fields/Buttons.
Verified: Tab cycles between the item fields and the button bar. The cycle is:
Note: If a text field is in edit mode, the first Tab exits edit mode; the second Tab toggles to buttons. This is correct behavior but could use a visual hint.
What changed: Buttons now show > [ Save ] when focused. The rendering code at
render.rs:3067–3075 was already correct; the fix was in Tab navigation (Bug 2) which
now actually sets focus_on_buttons = true.
Verified: When Tab reaches buttons, > [ Save ] is visible with bold styling.
What changed: Enter and Space are now separate handlers (input.rs:352–453):
Enter (line 352): Activates control (opens nested dialog, starts editing) or
triggers button action. On boolean fields, it advances to the next field.Space (line 425): Only toggles booleans and dropdowns. Does nothing on buttons.Verified: Enter on Enabled advances to Name. Space on Enabled toggles the
checkbox.
What changed: handle_entry_dialog_input() (input.rs:86–92) now checks for
Ctrl+S before routing to mode-specific handlers. The status bar shows Ctrl+S:Save.
Verified: Pressing Ctrl+S in the Edit Item dialog saves and closes it, returning to the Edit Value dialog.
[1 items]: FIXEDWhat changed: LSP entries in the main settings list now show the command name
(e.g., pylsp, rust-analyzer, clangd) instead of [1 items].
Verified: All LSP language entries display their server command as the preview.
What changed: Fields are now reordered by importance:
Command (first, most important)
Enabled
Name
Args
Auto Start
Root Markers
── Advanced ──
Env
Language Id Overrides
Initialization Options
Only Features
Except Features
Process Limits
An ── Advanced ── separator is now visible. However, the Advanced section is not
collapsible — all fields are always visible.
[+] Add new Unreachable via Keyboard (CRITICAL / NEW)Reproduction: Settings → LSP → python → Edit Value → try to navigate to [+] Add new.
Observed: The Down arrow cycle in the Edit Value dialog is:
Value (ObjectArray label) → [Buttons: Save, Delete, Cancel] → Value → ...
The [+] Add new row inside the ObjectArray is never focused. It is visually
rendered but completely unreachable via keyboard navigation.
Impact: Users cannot add a second LSP server for any language via keyboard.
This is a blocking workflow issue. For example, adding pyright alongside pylsp for
Python is impossible without mouse interaction or manual JSON editing.
Root Cause: The focus_next() method (entry_dialog.rs:316–343) was simplified
during the recent refactor. It now treats the entire ObjectArray as a single item:
} else if self.selected_item + 1 < self.items.len() {
self.selected_item += 1;
self.sub_focus = None;
} else {
self.focus_on_buttons = true;
self.focused_button = 0;
}
The old code had explicit ObjectArray sub-focus logic that navigated through entries and
the [+] Add new row before exiting the control. This logic was removed. Since the
Edit Value dialog has only one editable item (the ObjectArray), focus_next() immediately
transitions to buttons.
The same issue affects any ObjectArray in the Edit Item dialog (e.g., the inner ObjectArray if one existed), though in practice most composite controls in the Edit Item dialog are Maps or TextLists which have their own sub-focus issues.
Recommended Fix:
focus_next()/focus_prev().entry₁ → entry₂ → ... → [+] Add new → (exit to next item/buttons).[+] Add new as a separate virtual item in the dialog's item
list, so selected_item += 1 naturally reaches it.Reproduction: Open Edit Item for any LSP server → press Down repeatedly.
Observed navigation trace (25 steps):
Step 1: Command (auto-enters edit mode)
Step 2: Command (Down exits edit mode, stays on Command)
Step 3: Enabled
Step 4: Name (auto-enters edit mode)
Step 5: Args (TextList label)
Step 6: Args (sub-focus)
Step 7: Auto Start
Step 8: Env (Map)
Step 9: Language Id Overrides (Map)
Step 10: Language Id Overrides (sub-focus)
Step 11: Initialization Options (JSON)
Step 12: Except Features (JSON)
Step 13: Process Limits (JSON)
Step 14: [Save button]
Step 15: [Save button]
Step 16: [Delete button]
Step 17: Command (wrap)
...
Issues identified:
start_editing()), and the second
Down exits edit mode and advances. This makes navigation feel sluggish — each text
field requires 2 Down presses to pass.Root Cause: The auto-edit mode for Text fields (input.rs:454–480) is triggered
by KeyCode::Char, but Down arrow while in edit mode calls
handle_entry_dialog_text_editing() which handles Down differently (e.g., moving cursor
in JSON editors, or navigating TextList items). This creates inconsistent behavior
depending on whether the field auto-entered edit mode.
Recommended Fix:
focus_next() from a text field that is NOT in edit mode simply advances to
the next item without entering edit mode.Reproduction: Tab to buttons → Left or Right arrow.
Observed: Left/Right immediately jumps back to the items region instead of navigating between buttons (Save ↔ Delete ↔ Cancel).
Root Cause: In handle_entry_dialog_navigation() (input.rs:334–350):
KeyCode::Left => {
if !dialog.focus_on_buttons {
dialog.decrement_number();
} else if dialog.focused_button > 0 {
dialog.focused_button -= 1;
}
}
KeyCode::Right => {
if !dialog.focus_on_buttons {
dialog.increment_number();
} else if dialog.focused_button + 1 < dialog.button_count() {
dialog.focused_button += 1;
}
}
The Left handler on the first button (index 0) does nothing (correctly), but the Right
handler should advance to the next button. However, testing shows that Right immediately
returns to fields. This suggests focus_on_buttons is being reset somewhere, or the
event is being consumed by a different handler path.
Recommended Fix: Debug the event flow when focus is on buttons and Left/Right is
pressed. Ensure focus_on_buttons remains true during button navigation.
Process Limits, Except Features, Only Features, and Initialization Options are
still rendered as raw JSON text editors. This was not addressed in the rebase.
No autocomplete or validation for the Command field. Users can type any string.
The ── Advanced ── separator is purely visual. All advanced fields are always visible
and must be scrolled past. The separator cannot be toggled to collapse/expand the section.
| # | Issue | File(s) | Effort |
|---|---|---|---|
| 1 | Restore ObjectArray sub-focus in focus_next()/focus_prev() | entry_dialog.rs | Medium |
| 2 | Ensure [+] Add new is reachable in ALL ObjectArray controls | entry_dialog.rs | Medium |
| 3 | Add test: verify python LSP → Add new server is reachable | Integration test | Small |
Acceptance Criteria:
[+] Add new → buttons.[+] Add new opens the Add Item dialog.| # | Issue | File(s) | Effort |
|---|---|---|---|
| 4 | Fix text fields consuming extra Down press | input.rs, entry_dialog.rs | Medium |
| 5 | Fix Left/Right arrow exiting button region | input.rs | Small |
| 6 | Ensure consistent Down/Up cycle visits all items exactly once | entry_dialog.rs | Medium |
| 7 | Add integration test for complete navigation cycle | Test file | Medium |
| # | Issue | File(s) | Effort |
|---|---|---|---|
| 8 | Collapsible Advanced section (accordion) | New widget, entry_dialog.rs | Large |
| 9 | Structured editors for Process Limits | items.rs, schema | Medium |
| 10 | Feature checklist for Only/Except Features | New widget | Large |
| # | Issue | File(s) | Effort |
|---|---|---|---|
| 11 | $PATH validation for Command field | New validation module | Medium |
| 12 | Add visual hint when text field is in edit mode vs navigation | render.rs | Small |
| Principle | Status | Notes |
|---|---|---|
| Dialog Modality | ✅ Pass | Entry dialog isolates input |
| Visual Hierarchy | ✅ Pass | Rounded borders, padding, Advanced separator |
| "Where Am I?" Focus Rule | ⚠️ Partial | Focus indicator works but text auto-edit creates ambiguity |
| Strict Tab Loop | ✅ Pass | Tab toggles Fields ↔ Buttons |
| Read-Only Skip | ✅ Pass | Read-only Key field skipped |
| Composite Bypass | ❌ Fail | ObjectArray sub-items unreachable |
| Esc = Abort Context | ✅ Pass | Esc closes dialogs / exits edit mode |
| Global Save Shortcut | ✅ Pass | Ctrl+S works in entry dialog |
| Collapsible Sections | ⚠️ Partial | Separator exists but not collapsible |
| File | Role |
|---|---|
crates/fresh-editor/src/view/settings/entry_dialog.rs | Entry dialog state, focus_next/prev, toggle_focus_region |
crates/fresh-editor/src/view/settings/input.rs | Input routing, entry dialog navigation/text/Ctrl+S handling |
crates/fresh-editor/src/view/settings/render.rs | All rendering including entry dialog, buttons, Advanced separator |
crates/fresh-editor/src/view/settings/state.rs | Main settings state, panel focus management |
crates/fresh-editor/src/view/settings/schema.rs | JSON schema parsing, x-display-field, x-section |
crates/fresh-editor/src/view/settings/items.rs | Schema → SettingItem/SettingControl conversion |
crates/fresh-editor/src/view/controls/map_input/mod.rs | MapState, get_display_value (now shows command preview) |
crates/fresh-editor/plugins/config-schema.json | LSP schema (LspLanguageConfig array, LspServerConfig) |
Value (ObjectArray) → [Save] → [Delete] → [Cancel] → Value (wrap) → ...
MISSING: pylsp entry sub-focus, [+] Add new — never visited
1: Command (auto-edit) | 14: [Save]
2: Command (exit edit) | 15: [Save]
3: Enabled | 16: [Delete]
4: Name (auto-edit) | 17: Command (wrap)
5: Args | 18: Enabled
6: Args (sub-focus) | 19: Enabled
7: Auto Start | 20: Name
8: Env | 21: Auto Start
9: Language Id Overrides | 22: Root Markers
10: Language Id Overrides | 23: Env
11: Initialization Options | 24: Env
12: Except Features | 25: Language Id Overrides
13: Process Limits |
Note: Only Features and Root Markers appear inconsistently between cycles.
Text fields consume an extra Down press due to auto-edit mode.
Goal: Add pyright as a second LSP server alongside pylsp for Python.
Expected flow:
[+] Add new → Enter (opens Add Item dialog)pyright-langserver, Args: --stdio, Enabled: ✓Actual result: Step 2 fails — [+] Add new cannot be focused via keyboard.
Down goes directly from the ObjectArray label to the Save button, skipping all
internal entries and the Add new row.
Workaround: None via keyboard. Users must either:
[+] Add new