From 33e727841c2ed0307219a6afd03cd2b1602b1aca Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:27:08 -0500 Subject: [PATCH] feat(tui_v3): full end-to-end slice implemented for managed sub-accounts including UI breadcrumbs and mock test validations --- docs/tui_v3_plan.md | 4 +++- memory-update.md | 1 + scripts/interactive_agent_tui_test.py | 25 ++++++++++++++++++++++--- src/seedpass/tui_v3/app.py | 23 ++++++++++++++++++++--- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/docs/tui_v3_plan.md b/docs/tui_v3_plan.md index 577e8bf2..844b17a7 100644 --- a/docs/tui_v3_plan.md +++ b/docs/tui_v3_plan.md @@ -86,8 +86,10 @@ To finalize v3, we must verify 100% functional parity with the **Legacy TUI**. - Updated `derive_pgp_key` and API to support armored PGP Public Key retrieval/copying. - Aligned copy/reveal semantics with mockup hints (e.g., SSH/PGP copy Pub by default). - **Managed Sessions:** - - Added `action_managed_load` (`m`) and `action_managed_exit` (`Shift+M`) to v3. + - Added `action_managed_load` (`m`) and `action_managed_exit` (`Shift+M`) to v3 with mock service support. - Wired `ml` and `mx` commands to the palette. + - Extended `SeedPlusScreen` to manage accurate sub-account derivation correctly. + - Added fully functioning dynamic breadcrumb UI via `BrandFingerprint` and `active_breadcrumb`. - **Search & Filters:** - Integrated `SemanticIndexService` into `EntryDataTable`. - Added `filter ` (secrets, docs, keys, 2fa) and `archived` view toggle. diff --git a/memory-update.md b/memory-update.md index 969302e1..b7b922a2 100644 --- a/memory-update.md +++ b/memory-update.md @@ -10,3 +10,4 @@ - Direct configuration modification of arrays (like `relays`) should favor using dedicated service adapters over direct `ConfigService` modifications if they aren't fully wrapped for list operations. Using `app.services["nostr"]` for relay additions/removals is the preferred pattern. - The cohesive retro palette across the TUI v3 mockups hinges on high-contrast `#999999` backgrounds, `#000000` text/borders, and crisp `#ffffff` inputs. All standalone screens (`SettingsScreen`, `MaximizedInspectorScreen`, `AddEntryScreen`, etc.) must strictly conform to these CSS constants rather than the legacy v2 neon green (`#58f29d`, `#080a0c`). - In automated Textual UI interactive parity testing (`interactive_agent_tui_test.py`), avoid explicitly sending `await pilot.press('ctrl+p')` if standard textual palettes shadow custom `CommandPalette` widgets. Directly calling `app.action_open_palette()` guarantees stable integration assertions. +- When mirroring nested manager logic (like BIP-85 sub-account loads), UI "breadcrumbs" should derive their explicit path strings from parsing the manager's `profile_stack` via a decoupled reactive property (e.g. `active_breadcrumb`), rather than mutating the core `active_fingerprint` references used for caching and view filtering. diff --git a/scripts/interactive_agent_tui_test.py b/scripts/interactive_agent_tui_test.py index 2ce30588..97f8978d 100644 --- a/scripts/interactive_agent_tui_test.py +++ b/scripts/interactive_agent_tui_test.py @@ -38,7 +38,11 @@ class AgentMockService: self.is_managed = False self.nostr_index = 0 self.offline = False - self._manager = SimpleNamespace(parent_seed="MOCK_PARENT_SEED") + self._manager = SimpleNamespace( + parent_seed="MOCK_PARENT_SEED", + current_fingerprint="EFBE51E70ED1", + profile_stack=[] + ) # --- EntryService Interface --- def search_entries(self, query="", kinds=None, include_archived=False, archived_only=False): @@ -119,8 +123,16 @@ class AgentMockService: self.nostr_index += 1 return self.nostr_index - def load_managed_account(self, entry_id): self.is_managed = True - def exit_managed_account(self): self.is_managed = False + def load_managed_account(self, entry_id): + self.is_managed = True + self._manager.profile_stack.append((self._manager.current_fingerprint, "path", "seed")) + self._manager.current_fingerprint = f"SUBACCT{entry_id}" + + def exit_managed_account(self): + self.is_managed = False + if self._manager.profile_stack: + fp, _, _ = self._manager.profile_stack.pop() + self._manager.current_fingerprint = fp # --- Secret Data --- def get_totp_code(self, entry_id): return "888999" @@ -252,10 +264,17 @@ async def run_full_walkthrough(v="v3"): await pilot.pause(0.5) print(f" [OK] Profile Loaded: {service.is_managed == True}") + # Verify breadcrumb display + brand_label = str(app.screen.query_one("#brand-fingerprint").render()) + print(f" [DEBUG] Breadcrumb text: {brand_label}") + print(f" [OK] Sub-account breadcrumb structure visible: {'>' in brand_label and 'SUBACCT3' in brand_label}") + # Press 'shift+m' to exit profile await pilot.press('shift+m') await pilot.pause(0.5) print(f" [OK] Profile Exited: {service.is_managed == False}") + brand_label_exit = str(app.screen.query_one("#brand-fingerprint").render()) + print(f" [OK] Breadcrumb reverted: {'>' not in brand_label_exit}") # 6. Data I/O and Command Palette if v == "v3": diff --git a/src/seedpass/tui_v3/app.py b/src/seedpass/tui_v3/app.py index 7d19c60c..c2fc58e9 100644 --- a/src/seedpass/tui_v3/app.py +++ b/src/seedpass/tui_v3/app.py @@ -139,11 +139,11 @@ class BrandFingerprint(Static): """Placeholder for the top left fingerprint block matching mockups.""" def render(self) -> str: app = self.app - fp = app.active_fingerprint or "No Profile" - return f"{fp[:24]}" + fp = app.active_breadcrumb or "No Profile" + return f"{fp}" def on_mount(self) -> None: - self.watch(self.app, "active_fingerprint", self.refresh) + self.watch(self.app, "active_breadcrumb", self.refresh) class MainScreen(Screen): def compose(self) -> ComposeResult: @@ -207,6 +207,7 @@ class SeedPassTuiV3(App[None]): # Shared Reactive State active_fingerprint = reactive("") + active_breadcrumb = reactive("") selected_entry_id = reactive[int | None](None) session_locked = reactive(False) search_mode = reactive("keyword") @@ -287,6 +288,22 @@ class SeedPassTuiV3(App[None]): """Refresh components when the profile changes.""" if not new_fp: return + + try: + mgr = self.services["vault"]._manager + paths = [] + for fp, _, _ in getattr(mgr, "profile_stack", []): + paths.append(fp[:8]) + current = mgr.current_fingerprint[:8] if mgr.current_fingerprint else "???" + + if paths: + paths.append(current) + self.active_breadcrumb = " > ".join(paths) + else: + self.active_breadcrumb = mgr.current_fingerprint[:24] if mgr.current_fingerprint else "No Profile" + except Exception: + self.active_breadcrumb = new_fp[:24] + # Notify sidebar and grid to refresh try: self.screen.query_one("#profile-tree")._refresh_tree()