diff --git a/docs/tui_v2_ui_refresh_plan.md b/docs/tui_v2_ui_refresh_plan.md index 55b72d74..ce66c1d7 100644 --- a/docs/tui_v2_ui_refresh_plan.md +++ b/docs/tui_v2_ui_refresh_plan.md @@ -1268,3 +1268,26 @@ Validation: 1. `poetry run pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py` 2. Result: `58 passed`. + +## 36) Progress Update (2026-03-03, Slice: UX Bug Fixes from Walkthrough) + +Completed: + +1. Fixed `open ` showing broken empty board: + - Now checks for empty dict (not just None) from retrieve_entry. + - Shows clear "Entry #N not found" message in inspector. + - Clears selection state so action strip doesn't show stale context. +2. Fixed tree navigation silently ignoring commands when focus is on wrong pane: + - `profile-tree-next`, `profile-tree-prev`, `profile-tree-open` now show + "Focus left pane first (press 1)" instead of silently returning. + - Also shows "Sidebar is collapsed. Press Ctrl+B to expand." when sidebar is collapsed. + +Regression coverage: + +1. Added `test_tui2_textual_open_nonexistent_entry_shows_not_found`. +2. Added `test_tui2_textual_tree_nav_feedback_when_wrong_pane`. + +Validation: + +1. `poetry run pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py` +2. Result: `60 passed`. diff --git a/memory-update.md b/memory-update.md index db5abab5..4f3d78c9 100644 --- a/memory-update.md +++ b/memory-update.md @@ -221,3 +221,10 @@ ### Verification - Focused TUI v2 gate: `58 passed`. + +## Session Update (2026-03-03, Slice 10) + +### UX bug fixes from interactive walkthrough +- Fixed `open ` rendering broken empty board (now shows "not found" and clears selection). +- Fixed tree nav commands (`profile-tree-next/prev/open`) silently ignoring when focus is on wrong pane (now shows guidance). +- Added 2 regression tests. Gate: `60 passed`. diff --git a/src/seedpass/tui_v2/app.py b/src/seedpass/tui_v2/app.py index 357cdd13..ea60716d 100644 --- a/src/seedpass/tui_v2/app.py +++ b/src/seedpass/tui_v2/app.py @@ -2449,13 +2449,18 @@ def launch_tui2( self._set_totp_board_visible(False) try: entry = self._service.retrieve_entry(entry_index) - if not isinstance(entry, dict): - self.query_one("#entry-detail", Static).update("Entry not found.") + if not isinstance(entry, dict) or not entry: + self.query_one("#entry-detail", Static).update( + f"Entry #{entry_index} not found.\n\nThe entry may have been deleted or the ID is invalid." + ) self.query_one("#link-detail", Static).update( "Links: entry not found." ) self._current_links = [] self._current_link_cursor = 0 + self._selected_entry_id = None + self._selected_entry = None + self._update_action_strip() self._set_status(f"Entry {entry_index} not found") return self._selected_entry_id = int(entry_index) @@ -5635,8 +5640,10 @@ def launch_tui2( def action_profile_tree_next(self) -> None: if self._sidebar_collapsed: + self._set_status("Sidebar is collapsed. Press Ctrl+B to expand.") return if self._focus_pane != "left": + self._set_status("Focus left pane first (press 1)") return self._refresh_profile_tree() nodes = self._profile_tree_visible_nodes() @@ -5650,8 +5657,10 @@ def launch_tui2( def action_profile_tree_prev(self) -> None: if self._sidebar_collapsed: + self._set_status("Sidebar is collapsed. Press Ctrl+B to expand.") return if self._focus_pane != "left": + self._set_status("Focus left pane first (press 1)") return self._refresh_profile_tree() nodes = self._profile_tree_visible_nodes() @@ -5665,8 +5674,10 @@ def launch_tui2( def action_profile_tree_open(self) -> None: if self._sidebar_collapsed: + self._set_status("Sidebar is collapsed. Press Ctrl+B to expand.") return if self._focus_pane != "left": + self._set_status("Focus left pane first (press 1)") return self._refresh_profile_tree() nodes = self._profile_tree_visible_nodes() diff --git a/src/tests/test_tui_v2_textual_interactions.py b/src/tests/test_tui_v2_textual_interactions.py index e1ebe41b..25c486f4 100644 --- a/src/tests/test_tui_v2_textual_interactions.py +++ b/src/tests/test_tui_v2_textual_interactions.py @@ -1583,6 +1583,45 @@ async def test_tui2_textual_password_board_uses_card_sections() -> None: assert "+- Quick Actions " in board +@pytest.mark.anyio +async def test_tui2_textual_open_nonexistent_entry_shows_not_found() -> None: + service = FakeEntryService( + [{"id": 1, "kind": "password", "label": "Login 1", "length": 16}] + ) + app = _build_app(service) + + async with app.run_test() as pilot: + await pilot.pause() + app._run_palette_command("open 999") + await pilot.pause() + board = _widget_text(app, "#entry-detail") + assert "not found" in board.lower() + status_text = _widget_text(app, "#status") + assert "not found" in status_text.lower() + + +@pytest.mark.anyio +async def test_tui2_textual_tree_nav_feedback_when_wrong_pane() -> None: + service = FakeEntryService( + [{"id": 1, "kind": "password", "label": "Login 1", "length": 16}] + ) + profile_service = FakeProfileService(["fp-default"]) + app = _build_app(service, profile_service=profile_service) + + async with app.run_test() as pilot: + await pilot.pause() + # Focus is on center by default, tree-next should give feedback + app._run_palette_command("profile-tree-next") + await pilot.pause() + status_text = _widget_text(app, "#status") + assert "focus" in status_text.lower() or "left" in status_text.lower() + + app._run_palette_command("profile-tree-open") + await pilot.pause() + status_text = _widget_text(app, "#status") + assert "focus" in status_text.lower() or "left" in status_text.lower() + + @pytest.mark.anyio async def test_tui2_textual_filters_panel_tracks_selected_entry() -> None: service = FakeEntryService(