fix(tui-v2): handle non-existent entries and tree nav focus feedback

Opening a non-existent entry now shows "Entry not found" instead of
rendering a broken empty board. Tree navigation commands now give
clear feedback when focus is on wrong pane or sidebar is collapsed.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.6 committed 2026-03-02 21:26:30 -05:00
1 parent 08e71bd1b4
commit 6f2b9a6d18
4 files changed
+82 -2

No files matched your search

+23
View File
@@ -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 <non-existent-id>` 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`.
+7
View File
@@ -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 <non-existent-id>` 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`.
+13 -2
View File
@@ -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()
@@ -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(