diff --git a/memory-update.md b/memory-update.md index edc86fc6..598970a6 100644 --- a/memory-update.md +++ b/memory-update.md @@ -528,3 +528,23 @@ - `scripts/tui2_check_smoke.sh` => `tui2 --check smoke: ok` - `bash -n scripts/tui2_check_smoke.sh scripts/run_ci_tests.sh` => clean - `.venv/bin/pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py src/tests/test_typer_cli.py` => `86 passed`. + +## 2026-03-02 TUI v2 UX fixes: stale selection panel, clipped nav/help, and sensitive feedback clarity +- Reproduced screenshot-reported UX issues in TUI v2: + - Left panel could show stale `Selected: (none)` even while entry details were loaded. + - Left panel command text could clip/wrap aggressively and hide actionable hints. + - Sensitive reveal/QR confirmation paths could appear like no-op when only status line changed. + - QR output could become visually distorted by line wrapping in the secret panel. +- Fixes implemented in `src/seedpass/tui_v2/app.py`: + - `_show_entry(...)` now refreshes the filters panel immediately so selected state is always in sync. + - Added `on_list_view_highlighted(...)` to keep selected entry aligned with list highlight navigation. + - Left filters pane now uses `height: 1fr` + `overflow: auto` to prevent clipping. + - Secret panel now sets `text-wrap: nowrap` to preserve QR matrix geometry. + - Footer top border removed to avoid visual crowding at the bottom status/footer region. + - Filters/nav action copy trimmed to avoid forced line wraps in narrow terminals. + - Confirmation-required reveal/QR actions now also write explicit instructions into `#secret-detail`. +- Added regression coverage in `src/tests/test_tui_v2_textual_interactions.py`: + - `test_tui2_textual_filters_panel_tracks_selected_entry` ensures left-panel selected summary updates after opening a different entry. +- Validation: + - `pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py src/tests/test_tui_v2_helpers.py` -> `33 passed`. + - `python scripts/ai_tui2_agent_test.py --scenario core --verbose` -> `[PASS] core`. diff --git a/src/seedpass/tui_v2/app.py b/src/seedpass/tui_v2/app.py index 44bc860c..df7e0fcd 100644 --- a/src/seedpass/tui_v2/app.py +++ b/src/seedpass/tui_v2/app.py @@ -138,7 +138,6 @@ def launch_tui2( Footer { background: #0b0f13; color: #97b8a6; - border-top: solid #274533; } Input { background: #0d1114; @@ -181,6 +180,10 @@ def launch_tui2( #left { width: 34; border: solid #1a3024; padding: 1; background: #0d1114; } #center { width: 1fr; border: solid #1a3024; padding: 1; background: #0d1114; } #right { width: 1fr; border: solid #1a3024; padding: 1; background: #0d1114; } + #filters { + height: 1fr; + overflow: auto; + } #search { margin-bottom: 1; } #quick-jump { margin-bottom: 1; } #entry-list { height: 1fr; } @@ -203,6 +206,7 @@ def launch_tui2( border: solid #58f29d; padding: 1; margin-top: 1; + text-wrap: nowrap; } #totp-board { height: 1fr; @@ -608,12 +612,11 @@ def launch_tui2( "- Ctrl+S save doc", "- Ctrl+P command palette", "- add-* create entries", - "- add-seed/add-ssh/add-pgp/add-nostr/add-managed-account", - "- notes-set/notes-clear, tag-add/tag-rm/tags-set/tags-clear", - "- field-add/field-rm, set-field/clear-field, doc-export", + "- tags/notes/fields via palette", + "- docs: edit/save/export", "- 6 toggle dedicated 2FA board", - "- profile/relay/sync + setting-* commands", - "- checksum/db/totp export + parent seed backup commands", + "- profile/relay/sync/settings", + "- checksum/db/totp/seed backup", "- l cycle link relation", "- [ / ] select link", "- o open link target", @@ -764,6 +767,7 @@ def launch_tui2( "Sensitive data hidden. Use 'v' to reveal 🔑 or 'g' for QR ▦." ) self._update_links_panel() + self._update_filters_panel() self._set_status(f"Selected entry {entry_index}") except Exception as exc: self.query_one("#entry-detail", Static).update( @@ -1092,10 +1096,20 @@ def launch_tui2( and not confirm ): if include_qr: + self._set_secret_panel( + "Sensitive QR hidden.\n" + "Confirmation required for this action.\n" + "Run: qr private confirm" + ) self._set_status( "Sensitive QR requires confirmation. Run: qr private confirm" ) else: + self._set_secret_panel( + "Sensitive reveal hidden.\n" + "Confirmation required for this action.\n" + "Run: reveal confirm" + ) self._set_status( "Sensitive reveal requires confirmation. Run: reveal confirm" ) @@ -3336,6 +3350,13 @@ def launch_tui2( if isinstance(item, EntryListItem): self._show_entry(item.entry_index) + def on_list_view_highlighted(self, event: ListView.Highlighted) -> None: + if self.editing_document: + return + item = event.item + if isinstance(item, EntryListItem): + self._show_entry(item.entry_index) + app = SeedPassTuiV2() if callable(app_hook): app_hook(app) diff --git a/src/tests/test_tui_v2_textual_interactions.py b/src/tests/test_tui_v2_textual_interactions.py index 11444715..d59cd192 100644 --- a/src/tests/test_tui_v2_textual_interactions.py +++ b/src/tests/test_tui_v2_textual_interactions.py @@ -712,6 +712,29 @@ async def test_tui2_textual_reveal_and_qr_flow() -> None: assert "TOTP #3 QR" in _widget_text(app, "#secret-detail") +@pytest.mark.anyio +async def test_tui2_textual_filters_panel_tracks_selected_entry() -> None: + service = FakeEntryService( + [ + {"id": 1, "kind": "password", "label": "Login 1", "length": 16}, + {"id": 2, "kind": "managed_account", "label": "Acct 2", "index": 2}, + ] + ) + app = _build_app(service) + + async with app.run_test() as pilot: + await pilot.pause() + filters_text = _widget_text(app, "#filters") + assert "Selected: #1" in filters_text + assert "Login 1" in filters_text + + app._run_palette_command("open 2") + await pilot.pause() + filters_text = _widget_text(app, "#filters") + assert "Selected: #2" in filters_text + assert "Acct 2" in filters_text + + @pytest.mark.anyio async def test_tui2_textual_sensitive_confirm_and_secret_mode_clipboard() -> None: service = FakeEntryService(