From d4cbd0424297d276139aaa9f08e0a4a0d753558c Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:55:52 -0500 Subject: [PATCH] tui2: add phase1 shell scaffold for mockup-aligned refresh --- docs/tui_v2_ui_refresh_plan.md | 32 +++++++++++++ memory-update.md | 17 +++++++ src/seedpass/tui_v2/app.py | 86 ++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) diff --git a/docs/tui_v2_ui_refresh_plan.md b/docs/tui_v2_ui_refresh_plan.md index bc10e050..c428a269 100644 --- a/docs/tui_v2_ui_refresh_plan.md +++ b/docs/tui_v2_ui_refresh_plan.md @@ -330,3 +330,35 @@ Before ending any session touching this refresh: 4. Append summary to `memory-update.md`. Current next slice (recommended): **Phase 1 - Shell Restructure**. + +## 13) Progress Update + +### 2026-03-02 - Phase 1 scaffold (initial pass) landed + +Implemented in `src/seedpass/tui_v2/app.py`: + +1. Added top status ribbon (`#top-ribbon`) with live summary: +- fingerprint +- managed session indicator +- visible entry count +- active kind/archive filters +- lock/session state +- last sync summary + +2. Added structural section framing: +- `Entry Grid` heading above center list/search area +- `Inspector Board` heading above right detail area + +3. Added persistent bottom action strip (`#action-strip`) with keyboard hint rail. + +4. Preserved all existing command behavior and service-layer flows. + +Validation: + +1. `poetry run pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py` +2. Result: `28 passed`. + +Notes: + +1. This is a layout/chrome scaffold only; no table/board template conversion yet. +2. Next slice should implement Phase 2 grid modernization without regressing interaction tests. diff --git a/memory-update.md b/memory-update.md index 47254d79..9a805d2e 100644 --- a/memory-update.md +++ b/memory-update.md @@ -901,3 +901,20 @@ - `docs/tui_v2_parity_backlog.md` - Recommended next implementation slice documented as: - Phase 1: shell restructure (layout scaffolding only, no behavior regressions). + +## 2026-03-02 UI refresh implementation slice: Phase 1 shell scaffold (initial pass) +- Implemented mockup-aligned shell scaffolding in `src/seedpass/tui_v2/app.py`: + - added `#top-ribbon` (live operational summary line) + - added `Entry Grid` and `Inspector Board` section headers + - added `#action-strip` persistent keyboard/action hint rail +- Operational summary now updates with: + - fingerprint, managed-session state, result count, active kind/archive scope, session lock state, last sync summary +- Sync commands now update top-ribbon sync summary: + - `sync-now` + - `sync-bg` +- Fixed Textual markup conflict in action-strip hint text by using plain-text tokens (`/:Search`, `J:Jump`, etc.). +- Validation: + - `poetry run pytest -q src/tests/test_tui_v2_textual_interactions.py src/tests/test_tui_v2_action_matrix.py` + - Result: `28 passed`. +- Refresh plan updated with explicit phase progress and next step: + - `docs/tui_v2_ui_refresh_plan.md` (Phase 2 grid modernization next). diff --git a/src/seedpass/tui_v2/app.py b/src/seedpass/tui_v2/app.py index fdd13808..7535bf9e 100644 --- a/src/seedpass/tui_v2/app.py +++ b/src/seedpass/tui_v2/app.py @@ -169,6 +169,15 @@ def launch_tui2( background: #0d1114; color: #daf2e5; } + #top-ribbon { + height: 1; + padding: 0 1; + margin: 0 1; + border: solid #274533; + background: #0b0f13; + color: #daf2e5; + text-style: bold; + } #body { height: 1fr; } #status { height: 1; @@ -177,9 +186,35 @@ def launch_tui2( background: #0d1114; color: #daf2e5; } + #action-strip { + height: 1; + padding: 0 1; + margin: 0 1; + border: solid #274533; + background: #0b0f13; + color: #97b8a6; + } #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; } + #grid-heading { + height: 1; + margin-bottom: 1; + border: solid #274533; + background: #0b0f13; + color: #daf2e5; + text-style: bold; + padding: 0 1; + } + #inspector-heading { + height: 1; + margin-bottom: 1; + border: solid #274533; + background: #0b0f13; + color: #daf2e5; + text-style: bold; + padding: 0 1; + } #filters { height: 1fr; overflow: auto; @@ -318,11 +353,13 @@ def launch_tui2( classes="hidden", ) yield Static("", id="help-overlay", classes="hidden") + yield Static("", id="top-ribbon") with Horizontal(id="body"): with Vertical(id="left"): yield Static("", id="filters") yield Static("", id="activity") with Vertical(id="center"): + yield Static("Entry Grid", id="grid-heading") yield Input( placeholder="Search entries (Enter to apply)", id="search" ) @@ -330,6 +367,7 @@ def launch_tui2( yield ListView(id="entry-list") with Vertical(id="right"): with Vertical(id="right-view"): + yield Static("Inspector Board", id="inspector-heading") yield Static("", id="entry-detail") yield Static("", id="link-detail") yield Static("", id="secret-detail") @@ -355,6 +393,7 @@ def launch_tui2( id="doc-edit-help", ) yield Static("Ready", id="status") + yield Static("", id="action-strip") yield Footer() def on_mount(self) -> None: @@ -384,6 +423,7 @@ def launch_tui2( self._totp_rows: list[dict[str, Any]] = [] self._session_locked = False self._managed_session_entry_id: int | None = None + self._last_sync_text = "(none)" self._totp_tick = self.set_interval(1.0, self._tick_totp_board) try: self._service = ( @@ -452,6 +492,8 @@ def launch_tui2( self._update_help_overlay() self._update_activity_panel() self._apply_focus_style() + self._update_top_ribbon() + self._update_action_strip() self._set_secret_panel( "Sensitive data hidden. Use 'v' to reveal 🔑 or 'g' for QR ▦." ) @@ -492,6 +534,45 @@ def launch_tui2( def _set_secret_panel(self, text: str) -> None: self.query_one("#secret-detail", Static).update(text) + def _update_top_ribbon(self) -> None: + lock_state = "locked" if self._session_locked else "unlocked" + managed = ( + str(self._managed_session_entry_id) + if self._managed_session_entry_id is not None + else "0" + ) + total = len(self._all_results) + text = ( + f"FP: {fingerprint or '(default)'}" + f" | Managed Users: {managed}" + f" | Entries: {total}" + f" | Kind: {self.filter_kind}" + f" | Archive: {self.archive_scope}" + f" | Session: {lock_state}" + f" | Last Sync: {self._last_sync_text}" + ) + self.query_one("#top-ribbon", Static).update(text) + + def _update_action_strip(self) -> None: + kind = "" + if isinstance(self._selected_entry, dict): + kind = str( + self._selected_entry.get("kind") + or self._selected_entry.get("type") + or "" + ).strip() + context = ( + f"V:Reveal G:QR A:Archive E:Edit ({kind})" + if kind + else "V:Reveal G:QR A:Archive E:Edit" + ) + text = ( + "/:Search J:Jump F:Filter H:ArchiveScope L:LinkFilter " + "Ctrl+P:Palette X:Retry " + f"{context}" + ) + self.query_one("#action-strip", Static).update(text) + def _quickstart_text(self) -> str: return self._onboarding_text() @@ -729,6 +810,8 @@ def launch_tui2( ] ) self.query_one("#filters", Static).update(text) + self._update_top_ribbon() + self._update_action_strip() def _render_entry_label( self, idx: int, label: str, etype: str, archived: bool @@ -2995,9 +3078,11 @@ def launch_tui2( result = self._sync_service.sync() self._clear_failure() if result is None: + self._last_sync_text = "completed (no publish result)" self._set_status("Sync completed (no publish result)") else: manifest = getattr(result, "manifest_id", "unknown") + self._last_sync_text = f"manifest {manifest}" self._set_status(f"Sync completed manifest {manifest}") except Exception as exc: self._record_failure( @@ -3018,6 +3103,7 @@ def launch_tui2( try: self._sync_service.start_background_vault_sync() self._clear_failure() + self._last_sync_text = "background started" self._set_status("Background sync started") except Exception as exc: self._record_failure(