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.
This commit is contained in:
thePR0M3TH3AN committed 2026-03-02 23:08:58 -05:00
1 parent af177a8516
commit d5b418711b
2 files changed
+121 -2

No files matched your search

+51 -2
View File
@@ -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."""
+70
View File
@@ -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__()