From d5b418711bb82f701b28c665e3094d4dfb8aab44 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 2 Mar 2026 23:08:58 -0500 Subject: [PATCH] feat(tui-v3): implement Command Palette system (Ctrl+P) Added top-docked CommandPalette widget with status labeling. Implemented CommandProcessor to route palette strings to SeedPass services. Wired Ctrl+P shortcut to toggle the palette. Integrated command execution into the main app message loop. Supported v3 commands: help, stats, lock, refresh, settings, maximize. --- src/seedpass/tui_v3/app.py | 53 ++++++++++++++++++- src/seedpass/tui_v3/widgets/palette.py | 70 ++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 src/seedpass/tui_v3/widgets/palette.py diff --git a/src/seedpass/tui_v3/app.py b/src/seedpass/tui_v3/app.py index 50ce7d61..56982a2a 100644 --- a/src/seedpass/tui_v3/app.py +++ b/src/seedpass/tui_v3/app.py @@ -13,11 +13,52 @@ from .widgets.header import RibbonHeader from .widgets.sidebar import SidebarContainer from .widgets.grid import GridContainer from .widgets.inspector import BoardContainer +from .widgets.palette import CommandPalette from .screens.settings import SettingsScreen from .screens.inspector import MaximizedInspectorScreen +class CommandProcessor: + """Handles logic for palette commands in TUI v3.""" + def __init__(self, app: SeedPassTuiV3): + self.app = app + + def execute(self, raw: str) -> None: + import shlex + try: + parts = shlex.split(raw) + except Exception as e: + self.app.notify(f"Parse error: {e}", severity="error") + return + + if not parts: return + cmd = parts[0].lower() + args = parts[1:] + + if cmd == "help": + self.app.notify("v3 commands: stats, lock, refresh, settings, maximize") + elif cmd == "stats": + self.app.notify("Calculating stats...") + # We reuse the existing stats logic + if "vault" in self.app.services: + stats = self.app.services["vault"].stats() + self.app.notify(f"Total entries: {stats.get('total_entries', 0)}") + elif cmd == "lock": + if "vault" in self.app.services: + self.app.services["vault"].lock() + self.app.session_locked = True + self.app.notify("Vault locked") + elif cmd == "refresh": + self.app.action_refresh() + elif cmd == "settings": + self.app.action_toggle_settings() + elif cmd == "maximize": + self.app.action_maximize_inspector() + else: + self.app.notify(f"Unknown v3 command: {cmd}", severity="warning") + class MainScreen(Screen): def compose(self) -> ComposeResult: + yield CommandPalette(id="palette") yield Static("SeedPass ◈ UI v3", id="brand-strip") yield RibbonHeader(id="top-ribbon") with Vertical(id="body"): @@ -108,6 +149,7 @@ class SeedPassTuiV3(App[None]): def on_mount(self) -> None: """Initialize services and push the main screen.""" + self.processor = CommandProcessor(self) for name, factory in self.factories.items(): if factory: try: @@ -120,6 +162,10 @@ class SeedPassTuiV3(App[None]): # Global UI Heartbeat (for 2FA ticking etc) self.set_interval(1.0, self.action_refresh_ui_quiet) + def on_command_palette_command_executed(self, message: CommandPalette.CommandExecuted) -> None: + """Handle command from palette.""" + self.processor.execute(message.command) + def action_refresh_ui_quiet(self) -> None: """Background refresh for dynamic elements (2FA).""" try: @@ -157,8 +203,11 @@ class SeedPassTuiV3(App[None]): self.notify("UI Refreshed") def action_open_palette(self) -> None: - # Placeholder for unified command palette - self.notify("Palette coming soon in v3") + """Toggle the command palette.""" + try: + self.query_one("#palette").toggle() + except Exception: + pass def action_toggle_settings(self) -> None: """Push the full-screen settings screen.""" diff --git a/src/seedpass/tui_v3/widgets/palette.py b/src/seedpass/tui_v3/widgets/palette.py new file mode 100644 index 00000000..cbdd32fa --- /dev/null +++ b/src/seedpass/tui_v3/widgets/palette.py @@ -0,0 +1,70 @@ +from __future__ import annotations +from typing import Any +from textual.app import ComposeResult +from textual.widgets import Input, Static +from textual.containers import Vertical +from textual.message import Message + +class CommandPalette(Vertical): + """ + The TUI v3 Command Palette. + Docks at the top and provides a unified entry point for all actions. + """ + + DEFAULT_CSS = """ + CommandPalette { + dock: top; + height: 4; + background: #0b0f13; + color: #daf2e5; + border-bottom: solid #2abf75; + display: none; + padding: 0 1; + } + CommandPalette.visible { + display: block; + } + #palette-label { + height: 1; + color: #58f29d; + text-style: bold; + margin-left: 1; + } + #palette-input { + background: #0d1114; + border: solid #1a3024; + color: #e4fff2; + } + #palette-input:focus { + border: heavy #58f29d; + } + """ + + def compose(self) -> ComposeResult: + yield Static("COMMAND PALETTE (type 'help' for list)", id="palette-label") + yield Input(placeholder="enter command...", id="palette-input") + + def on_mount(self) -> None: + self.input = self.query_one("#palette-input", Input) + + def toggle(self) -> None: + if self.has_class("visible"): + self.remove_class("visible") + self.app.set_focus(None) + else: + self.add_class("visible") + self.input.focus() + + def on_input_submitted(self, event: Input.Submitted) -> None: + """Forward the command to the main app.""" + cmd = event.value.strip() + if cmd: + self.app.post_message(self.CommandExecuted(cmd)) + self.input.value = "" + self.toggle() # Close after execution + + class CommandExecuted(Message): + """Sent when a command is submitted.""" + def __init__(self, command: str) -> None: + self.command = command + super().__init__()