592 lines
31 KiB
Markdown
592 lines
31 KiB
Markdown
# Plan: TUI Flow Redesign
|
||
|
||
## Goal
|
||
|
||
Simplify the signer TUI from a multi-screen setup wizard into a single
|
||
main screen with full-screen overlays. The seed phrase is entered once
|
||
at startup in a popup; all subsequent editing (roles, transport, lock)
|
||
is done from full-screen overlays reached from the main screen. Borders
|
||
are collapsed for a cleaner look. A dedicated Commands screen provides
|
||
keyboard navigation through all available actions with a visible
|
||
cursor. Command hints show only the word with the key letter
|
||
underlined (e.g. "Quit" with Q underlined, not "Q quit").
|
||
|
||
## Current state
|
||
|
||
- [`src/tui.rs`](../src/tui.rs:1) — 1398 lines. Seven screens:
|
||
`Unlock`, `GenerateMnemonic`, `RoleWizard`, `TransportSelection`,
|
||
`Main`, `Connections`, `Lock`. Setup is a linear wizard: Unlock →
|
||
(Generate) → RoleWizard → TransportSelection → Main. The main screen
|
||
has a title bar reading `signer v0.0.1 > Main Menu`, a two-column
|
||
body (Information + Roles on the left, Activity on the right), and a
|
||
Commands bar at the bottom. Connections and Lock are rendered as
|
||
sub-panels inside the main screen's right column.
|
||
- [`src/main.rs`](../src/main.rs:129) — `server_main` constructs `App`
|
||
and calls `run()`. Non-interactive mode uses `run_headless()`.
|
||
- [`src/server.rs`](../src/server.rs:80) — `ServerContext` supports
|
||
`Unix`, `Qrexec`, `Tcp`, `Http`, `Stdio` listen modes. Only one mode
|
||
active at a time (the `start_server` method picks the first toggled
|
||
transport).
|
||
|
||
## Key design decisions
|
||
|
||
### 1. Rename "client name" → "signer name"
|
||
|
||
The user asked whether "client name" should be renamed since the
|
||
signer is more a server than a client. **Decision: rename to "signer
|
||
name".** The field shows the socket name (e.g. `nsigner01`), which is
|
||
the name clients use to connect. Calling it "signer name" is clearer
|
||
than "client name" and consistent with the program name.
|
||
|
||
### 2. Single main screen, no setup wizard
|
||
|
||
The current linear wizard (Unlock → RoleWizard → TransportSelection →
|
||
Main) is replaced by:
|
||
|
||
1. **Startup popup** — seed phrase entry (and optional generation).
|
||
This is the *only* popup in the entire TUI.
|
||
2. **Main screen** — shows Information, Roles, Activity, and a Commands
|
||
bar. From here the user can:
|
||
- Open the Roles screen (add/remove roles) — full-screen overlay
|
||
- Open the Transport screen (toggle transports on/off at any time) —
|
||
full-screen overlay
|
||
- Lock the session — full-screen overlay
|
||
- Open the Commands screen — full-screen overlay
|
||
- Quit
|
||
|
||
### 3. Collapsed borders
|
||
|
||
Use ratatui's `MergeStrategy::Exact` with `Spacing::Overlap(1)` so
|
||
adjacent blocks share borders instead of drawing double lines. The
|
||
selected/focused pane gets a thick border for visual distinction. See
|
||
[`ratatui/ratatui-widgets/examples/collapsed-borders.rs`](../ratatui/ratatui-widgets/examples/collapsed-borders.rs:1)
|
||
for the reference implementation.
|
||
|
||
### 4. Command hint style
|
||
|
||
Command hints at the bottom of each screen show only the word with the
|
||
key command letter underlined — not "Q quit" but "Quit" with the Q
|
||
underlined. The underlined letter is the actual key binding, which may
|
||
not be the first letter. For example, if `b` is the key for "Qube
|
||
bridge", it would be rendered as "Qube bridge" with the `b`
|
||
underlined. This is achieved with
|
||
`Span::raw("Qube ")` + `Span::styled("b", Style::default().add_modifier(Modifier::UNDERLINED))`
|
||
+ `Span::raw("ridge")`.
|
||
|
||
Each command hint is a word (or short phrase) with exactly one letter
|
||
underlined — the letter the user presses to activate that command.
|
||
|
||
## Target layout
|
||
|
||
### Startup popup — seed phrase entry
|
||
|
||
A centered popup (not full screen) over a blank terminal. This is the
|
||
only popup in the entire TUI:
|
||
|
||
```
|
||
┌──────────────────────────────────────────────┐
|
||
│ Signer v0.0.1 │
|
||
│ │
|
||
│ Enter seed phrase or G to generate new: │
|
||
│ > abandon abandon abandon abandon abandon │
|
||
│ abandon abandon abandon abandon abandon │
|
||
│ abandon abandon │
|
||
│ │
|
||
│ Invalid mnemonic. Attempts: 1/10 │
|
||
└──────────────────────────────────────────────┘
|
||
```
|
||
|
||
- If the user types `g` and presses Enter, generate a 12-word
|
||
mnemonic, display it in the same popup, then press Enter to
|
||
continue.
|
||
- On successful load, derive keys for any pre-registered roles (the
|
||
default `main` role is auto-registered), start the server with
|
||
default transport (Unix), and transition to the main screen.
|
||
- Invalid mnemonic shows an error line and retries (max 10 attempts).
|
||
|
||
### Main screen — full mockup
|
||
|
||
Title is `Signer v0.0.1` centered on its own line — no border box
|
||
around it, no "Main Menu" text. Use ratatui's `Line::from(...).centered()`
|
||
to center the title. Collapsed borders between the body sections.
|
||
Left column has three sections: Information (top), Transport (middle),
|
||
Roles (bottom). Right column has Activity (scrollable, newest first).
|
||
Commands bar at the bottom with key command letters underlined.
|
||
|
||
Below is the full main screen showing all sections in detail. The
|
||
`▸` cursor in the Transport section shows the currently selected
|
||
transport line. Active transports are shown in **bold** (rendered as
|
||
reversed video or bold in the actual TUI). The Activity column shows
|
||
the C-format log entries (newest first).
|
||
|
||
```
|
||
Signer v0.0.1
|
||
├──────────────────────────────────────────┬──────────────────────────────────────────┤
|
||
│ Information │ Activity │
|
||
│ │ │
|
||
│ signer name: nsigner01 │ 2026-08-18 15:05:42 unix:1000 │
|
||
│ Unix address: │ sign_event(main) ALLOWED:no-auth │
|
||
│ nsigner01 │ 2026-08-18 15:05:30 unix:1000 │
|
||
│ Qube address: │ get_info() ALLOWED:no-auth │
|
||
│ (inactive) │ 2026-08-18 15:04:55 unix:1000 │
|
||
│ FIPS address: │ nip44_encrypt(main) ALLOWED:no-auth │
|
||
│ (inactive) │ │
|
||
│ HTTP address: │ │
|
||
│ (inactive) │ │
|
||
│ OTP pad: chksum=a1b2c3 offset=128/4096 │ │
|
||
│ │ nip44_encrypt(main) ALLOWED:no-auth │
|
||
├──────────────────────────────────────────┤ 2026-08-18 15:03:12 unix:1000 │
|
||
│ Transport │ sign_event(nostr_range,0) │
|
||
│ │ ALLOWED:no-auth │
|
||
│ ▸ [x] U̲nix Socket │ 2026-08-18 15:02:00 unix:1000 │
|
||
│ [ ] Qube b̲ridge │ get_info() ALLOWED:no-auth │
|
||
│ [ ] F̲IPS │ 2026-08-18 15:01:30 unix:1000 │
|
||
│ [ ] H̲TTP │ sign_event(main) ALLOWED:no-auth │
|
||
│ │ 2026-08-18 15:00:22 unix:1000 │
|
||
├──────────────────────────────────────────┤ sign_event(main) ALLOWED:no-auth │
|
||
│ Roles │ 2026-08-18 15:00:10 nsigner started │
|
||
│ │ │
|
||
│ Role Purpose Curve │ │
|
||
│ ───────────── ──────── ──────────── │ │
|
||
│ main nostr secp256k1 │ │
|
||
│ nostr_range nostr secp256k1 │ │
|
||
│ ssh ssh ed25519 │ │
|
||
│ │ │
|
||
│ A̲dd D̲elete │ Cl̲ear ▲ │
|
||
├──────────────────────────────────────────┴──────────────────────────────────────────┤
|
||
│ He̲lp Q̲uit │
|
||
└─────────────────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
Each section has its own commands on the bottom line, left-aligned:
|
||
- **Information**: no commands (display only)
|
||
- **Transport**: no separate command — each transport line is a toggle
|
||
button. The `[x]` / `[ ]` indicator shows on/off state. The key
|
||
command letter is underlined in each label (`U̲nix Socket`,
|
||
`Q̲ube bridge`, `F̲IPS`, `H̲TTP`). Tab or Up/Down moves between
|
||
lines, Enter or the underlined key toggles that transport on/off
|
||
(radio-button: one active at a time, server restarts immediately).
|
||
Active transport is also shown in bold/reversed.
|
||
- **Roles**: `A̲dd D̲elete` — add a new role, delete the selected role
|
||
- **Activity**: `Cl̲ear` — clear the activity log (with a blank row
|
||
above the command)
|
||
- **Bottom bar**: `He̲lp Q̲uit` — global navigation (Help opens a
|
||
help screen, Quit exits)
|
||
|
||
**Section details:**
|
||
|
||
**Information** — each transport address is shown as a label row
|
||
followed by an indented value row (since addresses can be long):
|
||
|
||
| Label row | Indented value row | Source |
|
||
|-----------|-------------------|--------|
|
||
| `signer name: <socket_name>` | (same line) | `self.socket_name` |
|
||
| `Unix address:` | ` <socket_name>` (without @) | if Unix active, else ` (inactive)` |
|
||
| `Qube address:` | ` (one request per invocation)` | if Qrexec active, else ` (inactive)` |
|
||
| `FIPS address:` | ` <bind_addr>` | if TCP active, else ` (inactive)` |
|
||
| `HTTP address:` | ` <bind_addr>` | if HTTP active, else ` (inactive)` |
|
||
| `OTP pad: chksum=… offset=N/M` | (same line) | `otp_pad::global_status()` (only if bound) |
|
||
|
||
Note: Unix addresses are always displayed without the `@` symbol.
|
||
The indented value row uses 2-space indentation.
|
||
|
||
**Transport** — 4 toggle-button lines, each showing `[x]` or `[ ]`
|
||
indicator plus the transport name with the key letter underlined
|
||
(`U̲nix Socket`, `Qube b̲ridge`, `F̲IPS`, `H̲TTP`). No separate
|
||
command line — each line is its own toggle. Tab or Up/Down moves
|
||
between lines, Enter or the underlined key letter toggles that
|
||
transport on/off (radio-button: one active at a time, server
|
||
restarts immediately). Active transport also shown in bold/reversed.
|
||
|
||
Key assignments (all unique across the main screen — this is the
|
||
canonical set shown in the mockup):
|
||
- `U` — Unix Socket
|
||
- `B` — Qube bridge
|
||
- `F` — FIPS
|
||
- `H` — HTTP
|
||
- `A` — Add role
|
||
- `D` — Delete role
|
||
- `C` — Clear activity log
|
||
- `L` — Help screen
|
||
- `Q` — Quit
|
||
|
||
**Roles** — table with columns: Role, Purpose, Curve. (Derivation path
|
||
is omitted from the main screen to save space — it's visible on the
|
||
Roles overlay screen.) Shows all registered roles. A blank row
|
||
separates the table from the commands at the bottom: `A̲dd D̲elete`.
|
||
|
||
**Activity** — scrollable, newest first. Each entry is a timestamped
|
||
log line in the C format (see "Activity log format" below). Scrollbar
|
||
on the right. A blank row separates the log from the commands at the
|
||
bottom: `Cl̲ear`.
|
||
|
||
**Bottom bar** — `He̲lp Q̲uit` with key letters underlined.
|
||
|
||
### Activity log format
|
||
|
||
The current Rust implementation logs only "request handled" — it does
|
||
not show what was actually handled. The C implementation logs detailed
|
||
request information. Copy the C format:
|
||
|
||
```
|
||
<caller_id> <method>(<role_name>[,<concrete_path>]) <verdict>:<source_label>
|
||
```
|
||
|
||
Examples:
|
||
- `unix:1000 sign_event(main) ALLOWED:no-auth`
|
||
- `unix:1000 sign_event(nostr_range,0) ALLOWED:no-auth`
|
||
- `unix:1000 get_info() ALLOWED:no-auth`
|
||
- `tcp:[::1]:12345 nip44_encrypt(main) ALLOWED:no-auth`
|
||
|
||
**Implementation:** `ServerContext::process_request` must return an
|
||
activity description string alongside the JSON response. Change the
|
||
return type to `(String, String)` — the response and the activity log
|
||
message. The activity message is constructed from:
|
||
- `caller.caller_id` — e.g. `unix:1000` or `tcp:[::1]:12345`
|
||
- `method` — the JSON-RPC method (e.g. `sign_event`, `get_info`)
|
||
- `role_name` — from the resolved selector (if a role was matched)
|
||
- `concrete_path` — if the role has a path template with `%d`, the
|
||
concrete index (e.g. `0`)
|
||
- `verdict` — `ALLOWED` (since we removed policy, all valid requests
|
||
are allowed; denied requests get `DENIED` with the error reason)
|
||
- `source_label` — `no-auth` (since we removed policy/authorization)
|
||
|
||
The `service_server()` method in `App` passes this message to
|
||
`activity_log.add()` instead of the generic "request handled".
|
||
|
||
### Roles section (on main screen)
|
||
|
||
Roles are managed directly in the Roles section on the main screen —
|
||
there is no separate Roles overlay screen. The `▸` cursor shows the
|
||
selected role.
|
||
|
||
- **Add** (`A`): opens the `AddRole` popup showing the role preset
|
||
menu (same 1–10 presets as current wizard). Select a preset (or
|
||
custom), then enter role name and path template via `InputField`
|
||
with pre-filled defaults. On confirm, register the role and derive
|
||
its key immediately, then return to the main screen.
|
||
- **Delete** (`D`): deletes the currently selected role immediately —
|
||
no confirmation overlay. The role is removed from the table and its
|
||
derived key is wiped. The selection moves to the next role.
|
||
- **Select**: Up/Down arrows or Tab move selection through the role
|
||
list. The `▸` cursor shows the selected role.
|
||
|
||
### Help screen
|
||
|
||
Opened by pressing `L` from the main screen. Full-screen overlay that
|
||
describes what the app does, what transports are, what roles are, and
|
||
lists the key commands at the end. It is a scrollable screen (the
|
||
content can exceed the visible height). Commands at the bottom:
|
||
|
||
```
|
||
Signer v0.0.1
|
||
┌─────────────────────────────────────────────────────────────────────────────────────┐
|
||
│ │
|
||
│ Signer is an attended Nostr signing daemon. It holds your keys and signs │
|
||
│ requests from clients over one or more transports. │
|
||
│ │
|
||
│ Transports │
|
||
│ ────────── │
|
||
│ A transport is a way for clients to reach the signer. │
|
||
│ - Unix Socket: local same-machine access via an abstract socket. │
|
||
│ - Qube bridge: access from other Qubes via qrexec. │
|
||
│ - FIPS: TCP listener for framed JSON over a network. │
|
||
│ - HTTP: HTTP listener for curl-friendly requests. │
|
||
│ │
|
||
│ Roles │
|
||
│ ───── │
|
||
│ A role binds a name to a derivation path and curve. Clients address │
|
||
│ requests by role name, which also serves as the password. │
|
||
│ │
|
||
│ Key commands │
|
||
│ ───────────── │
|
||
│ U Toggle Unix Socket transport │
|
||
│ B Toggle Qube bridge transport │
|
||
│ F Toggle FIPS transport │
|
||
│ H Toggle HTTP transport │
|
||
│ A Add a role │
|
||
│ D Delete the selected role │
|
||
│ C Clear the activity log │
|
||
│ L Open the Help screen │
|
||
│ Q Quit │
|
||
│ │
|
||
├──────────────────────────────────────────────────────────────────────────────────────┤
|
||
│ B̲ack │
|
||
└──────────────────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
- The content is scrollable — Up/Down arrows (or Page Up/Down) scroll
|
||
through the help text. A scrollbar is shown on the right when the
|
||
content overflows.
|
||
- ESC / `B` (Back): return to main screen.
|
||
- This is a reference screen — no actions are executed from here.
|
||
|
||
## Screen enum (updated)
|
||
|
||
```rust
|
||
pub enum Screen {
|
||
/// Startup popup — seed phrase entry
|
||
SeedEntry,
|
||
/// Startup popup — showing generated mnemonic
|
||
SeedDisplay,
|
||
/// Main status screen (Information + Transport + Roles + Activity + bottom bar)
|
||
Main,
|
||
/// Add-role popup — role preset selection (over the Main screen)
|
||
AddRole,
|
||
/// Help — full-screen overlay describing the app and key commands
|
||
Help,
|
||
}
|
||
```
|
||
|
||
`Connections`, `Transport`, `Lock`, and `Commands` screens are removed.
|
||
Transport is now a section on the main screen (between Information and
|
||
Roles). Connection info is shown inline in the Information section
|
||
(transport addresses). Lock functionality is removed entirely. The
|
||
Commands screen is replaced by a Help screen. Only `SeedEntry` and
|
||
`SeedDisplay` are popups; all other screens are full-screen overlays.
|
||
|
||
## App struct changes
|
||
|
||
```rust
|
||
pub struct App {
|
||
// ... existing fields ...
|
||
|
||
/// Scroll offset for the Help screen content
|
||
pub help_scroll: usize,
|
||
/// Currently selected role index in the Roles section
|
||
pub role_cursor: usize,
|
||
/// Currently selected transport line index in the Transport section
|
||
pub transport_cursor: usize,
|
||
/// Whether the seed entry popup is in "generate" mode
|
||
pub seed_generate_mode: bool,
|
||
|
||
// Remove: wizard_stage, wizard_choice, wizard_default_*,
|
||
// wizard_role_name, wizard_path, wizard_otp_dir,
|
||
// wizard_otp_name, wizard_roles_created
|
||
// Add: role_add_stage (for the add-role popup flow)
|
||
pub role_add_stage: RoleAddStage,
|
||
pub role_add_input: InputField,
|
||
pub role_add_choice: i32,
|
||
}
|
||
|
||
pub enum RoleAddStage {
|
||
PresetMenu,
|
||
NameEntry,
|
||
CurveSelect,
|
||
PathEntry,
|
||
OtpDir,
|
||
OtpName,
|
||
Confirm,
|
||
}
|
||
```
|
||
|
||
## Server changes
|
||
|
||
Currently `ServerContext` supports only one listen mode at a time. The
|
||
transport screen uses radio-button behavior: only one transport can be
|
||
active at a time. Toggling one on turns the others off. This matches
|
||
the current C behavior and avoids server architecture changes.
|
||
|
||
Additionally, `process_request` must return an activity log message
|
||
alongside the JSON response (see "Activity log format" above). Change
|
||
the return type from `String` to `(String, String)` where the first
|
||
element is the JSON response and the second is the activity description.
|
||
`handle_one` returns this message to the caller so the TUI can log it.
|
||
|
||
## Event loop changes
|
||
|
||
The `run()` loop stays the same structure (draw → poll key → service
|
||
server). The key change is that `service_server()` is called only when
|
||
the screen is `Main`. The overlay screens (`AddRole`, `Help`) pause
|
||
server processing while the user is actively configuring. The startup
|
||
popups (`SeedEntry`, `SeedDisplay`) also pause server processing since
|
||
the server hasn't started yet.
|
||
|
||
```mermaid
|
||
flowchart TD
|
||
Start[Start] --> SeedEntry[Popup: Seed Entry]
|
||
SeedEntry -->|g| SeedDisplay[Popup: Show Generated Mnemonic]
|
||
SeedDisplay -->|Enter| Main
|
||
SeedEntry -->|Enter valid phrase| Main[Main Screen]
|
||
Main -->|A| AddRole[Popup: Add Role preset menu]
|
||
AddRole -->|confirm| Main
|
||
Main -->|L| Help[Help Screen - full screen]
|
||
Help -->|ESC/B| Main
|
||
Main -->|Q| Quit[Quit]
|
||
```
|
||
|
||
Note: Transport and Roles are not separate screens — they are sections
|
||
on the main screen. The user tabs/arrow-keys between the 4 transport
|
||
lines and toggles them directly on the main screen. Roles are added
|
||
and deleted directly in the Roles section.
|
||
|
||
## Key bindings summary
|
||
|
||
### Main screen — global commands
|
||
|
||
| Key | Action |
|
||
|-----|--------|
|
||
| `L` | Open Help screen |
|
||
| `Q` / `ESC` | Quit |
|
||
| `↑` / `↓` | Scroll activity log |
|
||
|
||
### Transport section (on main screen)
|
||
|
||
| Key | Action |
|
||
|-----|--------|
|
||
| `TAB` / `↑` / `↓` | Move between transport lines |
|
||
| `ENTER` | Toggle selected transport on/off |
|
||
| `U` | Toggle Unix Socket on/off |
|
||
| `B` | Toggle Qube bridge on/off |
|
||
| `F` | Toggle FIPS on/off |
|
||
| `H` | Toggle HTTP on/off |
|
||
|
||
### Roles section (on main screen)
|
||
|
||
| Key | Action |
|
||
|-----|--------|
|
||
| `A` | Add role (opens AddRole popup with preset menu) |
|
||
| `D` | Delete the currently selected role (immediate, no confirmation) |
|
||
| `↑` / `↓` / `TAB` | Select role |
|
||
|
||
### Activity section (on main screen)
|
||
|
||
| Key | Action |
|
||
|-----|--------|
|
||
| `C` | Clear the activity log |
|
||
| `↑` / `↓` | Scroll activity log |
|
||
|
||
### Help screen
|
||
|
||
| Key | Action |
|
||
|-----|--------|
|
||
| `↑` / `↓` | Scroll help content |
|
||
| `PAGE UP` / `PAGE DOWN` | Scroll help content by page |
|
||
| `ESC` / `B` | Back to main |
|
||
|
||
## Files to change
|
||
|
||
| File | Change |
|
||
|------|--------|
|
||
| [`src/tui.rs`](../src/tui.rs:1) | Major rewrite: new `Screen` enum, startup popup for seed entry, AddRole popup, Help overlay, Transport and Roles as inline sections on main screen with toggle buttons, collapsed borders, underlined-key-letter command hints at bottom of each section, centered title. Remove `WizardStage`/wizard flow, `Connections` screen, Lock screen, Commands screen, Roles overlay screen. Remove session/derived from Information. Unix addresses without @. |
|
||
| [`src/main.rs`](../src/main.rs:129) | Minor: `App::new` call stays the same. The `listen_override` path may need adjustment since transport is now chosen from the main screen, not a setup screen. |
|
||
| [`src/server.rs`](../src/server.rs:80) | Change `process_request` return type to `(String, String)` — JSON response + activity log message. `handle_one` returns the activity message to the caller. Construct the activity message from caller_id, method, role_name, concrete_path, verdict, and source_label (matching C format). |
|
||
|
||
## Implementation steps
|
||
|
||
1. **Update `Screen` enum** — replace the seven screens with the new
|
||
set: `SeedEntry`, `SeedDisplay`, `Main`, `AddRole`, `Help`.
|
||
(No separate Transport, Roles, Lock, or Commands screen — Transport
|
||
and Roles are sections on Main, Lock is removed entirely, Commands
|
||
replaced by Help.)
|
||
|
||
2. **Update `App` struct** — remove wizard fields, add `cmd_cursor`,
|
||
`role_cursor`, `transport_cursor`, `seed_generate_mode`,
|
||
`RoleAddStage` enum and fields. Update `App::new` to start on
|
||
`Screen::SeedEntry`.
|
||
|
||
3. **Implement seed entry popup** — `draw_seed_entry()` renders a
|
||
centered popup. `handle_seed_key()` processes input: `g` → generate,
|
||
Enter → load mnemonic, derive keys, start server, transition to
|
||
`Main`. `draw_seed_display()` shows the generated phrase.
|
||
|
||
4. **Rewrite `draw_main`** — title is `Signer v0.0.1` centered on its
|
||
own line (use `Line::from(...).centered()`, no border box, no "Main
|
||
Menu"). Use `MergeStrategy::Exact` + `Spacing::Overlap(1)` for
|
||
collapsed borders. Left column has three sections: Information (top),
|
||
Transport (middle), Roles (bottom). Right column has Activity
|
||
(scrollable, newest first). Information section shows "signer name"
|
||
(renamed from "client name") + transport addresses. Remove the
|
||
Connections sub-panel.
|
||
|
||
5. **Implement Transport section on main screen** — renders 4
|
||
toggle-button lines, each showing `[x]` or `[ ]` indicator plus
|
||
the transport name with the key letter underlined (`U̲nix Socket`,
|
||
`Qube b̲ridge`, `F̲IPS`, `H̲TTP`). The `▸` cursor shows the
|
||
selected line (`transport_cursor`). Active transport is also shown
|
||
in bold/reversed. Tab/Up/Down moves between lines, Enter or the
|
||
underlined key letter (`U`/`B`/`F`/`H`) toggles that transport
|
||
(radio-button: one active at a time, toggling restarts the server).
|
||
No separate command line for this section. All key commands on the
|
||
main screen must be unique: U, B, F, H (transport), A, D (roles),
|
||
C (clear activity), L (help), Q (quit).
|
||
|
||
6. **Implement Roles section on main screen** — renders the role table
|
||
with a selection cursor (`role_cursor`). `A` opens the `AddRole`
|
||
popup (preset menu → name → path → confirm), `D` deletes the
|
||
currently selected role immediately (no confirmation), `↑`/`↓`/Tab
|
||
moves the cursor. On add/delete, re-derive keys.
|
||
|
||
7. **Implement Help screen** — `draw_help()` renders a scrollable
|
||
`Paragraph` describing what the app does, what transports are, what
|
||
roles are, and listing the key commands at the end. Track a
|
||
`help_scroll` offset. `handle_help_key()`: Up/Down (and Page
|
||
Up/Down) scroll the content, ESC/B returns to main. A scrollbar is
|
||
shown when content overflows. This is a reference screen — no
|
||
actions executed from here.
|
||
|
||
8. **Update key command bars** — each screen's bottom bar shows the
|
||
relevant key bindings for that screen. Use underlined-first-letter
|
||
word hints (e.g. `Q̲uit`, `R̲oles`) instead of "Q quit" style.
|
||
Replace the old `key_span` helper with a new `cmd_hint` helper that
|
||
produces a `Span` with the key command letter underlined (which may
|
||
not be the first letter of the word).
|
||
|
||
9. **Update activity log format** — change `ServerContext::process_request`
|
||
to return `(String, String)` (response + activity message). Construct
|
||
the activity message from `caller_id`, `method`, `role_name`,
|
||
`concrete_path`, `verdict`, and `source_label` matching the C format:
|
||
`<caller_id> <method>(<role>[,<path>]) <verdict>:<source>`. Update
|
||
`handle_one` to return the activity message. Update
|
||
`service_server()` in `App` to log this message instead of
|
||
"request handled".
|
||
|
||
10. **Update `run()` loop** — service server only on `Main` screen
|
||
(all overlay screens pause server processing). Update the
|
||
`handle_key` dispatch for the new screen enum.
|
||
|
||
11. **Update `main.rs`** — adjust `App::new` call if needed. The
|
||
`listen_override` path: if `--listen` is given, skip seed entry
|
||
popup and go straight to main with the specified transport. But
|
||
still need a mnemonic — so `--listen` with interactive mode should
|
||
still show the seed entry popup, then go to main with the transport
|
||
pre-selected.
|
||
|
||
12. **Test** — `cargo test` (unit tests unaffected). Manual test:
|
||
start signer, verify seed entry popup, verify main screen with
|
||
collapsed borders and centered title, verify Roles add/remove,
|
||
verify Transport toggle (4 lines, tab navigation), verify Help
|
||
screen shows all key commands, verify activity log shows detailed
|
||
request info, connect with `nsigner_client`.
|
||
|
||
## What stays the same
|
||
|
||
- `ActivityLog` struct and ring-buffer logic.
|
||
- `InputField` widget and `edit_key` helper.
|
||
- All non-TUI code: `dispatcher.rs`, `role_table.rs`, `mnemonic.rs`,
|
||
`key_store.rs`, etc. (server.rs gets a return-type change but its
|
||
logic stays the same).
|
||
- The event loop structure (draw → poll → service).
|
||
- Non-interactive / headless mode (`run_headless`).
|
||
|
||
## What gets removed
|
||
|
||
- `Screen::Unlock`, `Screen::GenerateMnemonic`, `Screen::RoleWizard`,
|
||
`Screen::TransportSelection`, `Screen::Connections`, `Screen::Lock`,
|
||
`Screen::Commands`, `Screen::Roles` — replaced by the startup popup,
|
||
the AddRole popup, the Help overlay, and inline sections. Lock
|
||
functionality is removed entirely. Commands screen replaced by Help
|
||
screen. Roles screen replaced by an inline Roles section.
|
||
- `WizardStage` enum and all wizard-related fields/methods.
|
||
- `draw_unlock`, `draw_generate`, `draw_wizard`, `draw_transport`
|
||
(old full-screen versions), `draw_connections_in`, `draw_lock_in`,
|
||
`handle_lock_key`, `draw_roles` (old overlay version).
|
||
- The `> Main Menu` text in the title bar.
|
||
- The bordered title bar box — title is now a plain line.
|
||
- The old `key_span` helper that produced "Q quit" style hints —
|
||
replaced by underlined-key-letter word hints.
|
||
- Session and derived count rows from the Information section.
|
||
- The `@` symbol prefix from Unix address display.
|
||
- The `R` (open Roles screen) and `E` (clear) key commands — replaced
|
||
by inline Roles management and `C` for clear.
|