Files
signer/plans/tui_flow_redesign.md
T

610 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. `signer01`), 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: signer01 │ 2026-08-18 15:05:42 unix:1000 │
│ Unix address: │ secp256k1 m/44'/1237'/0'/0/0 │
│ signer01 │ 2026-08-18 15:05:30 unix:1000 │
│ Qube address: │ - - │
│ (inactive) │ 2026-08-18 15:04:55 unix:1000 │
│ FIPS address: │ secp256k1 m/44'/1237'/0'/0/0 │
│ (inactive) │ │
│ HTTP address: │ │
│ (inactive) │ │
│ OTP pad: chksum=a1b2c3 offset=128/4096 │ │
│ │ 2026-08-18 15:03:12 unix:1000 │
├──────────────────────────────────────────┤ secp256k1 m/44'/1237'/*'/0/0 │
│ Transport │ 2026-08-18 15:02:00 unix:1000 │
│ │ - - │
│ ▸ [x] U̲nix Socket │ 2026-08-18 15:01:30 unix:1000 │
│ [ ] Qube b̲ridge │ secp256k1 m/44'/1237'/0'/0/0 │
│ [ ] F̲IPS │ 2026-08-18 15:00:22 unix:1000 │
│ [ ] H̲TTP │ secp256k1 m/44'/1237'/0'/0/0 │
│ │ 2026-08-18 15:00:10 signer started │
├──────────────────────────────────────────┤ │
│ Roles │ │
│ │ │
│ 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
(independent checkbox: toggling one only flips itself; the last
active transport cannot be disabled; 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 (independent checkbox: toggling one only flips
itself; the last active transport cannot be disabled; 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
Each activity entry shows four fields: `time uid curve path`. The
timestamp is added by `ActivityLog::add()`; the message itself is
`uid curve path`.
```
<caller_id> <curve> <key_path>
```
Examples:
- `unix:1000 secp256k1 m/44'/1237'/0'/0/0`
- `unix:1000 ed25519 m/44'/102001'/0'/0'/0'`
- `unix:1000 - -` (get_info / algorithm verbs — no role)
**Implementation:** `ServerContext::process_request` returns
`(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`
- `curve` — the role's curve string (e.g. `secp256k1`, `ed25519`)
- `key_path` — the role's derivation path via `RoleEntry::display_path()`
(e.g. `m/44'/1237'/0'/0/0` or `m/44'/1237'/*'/0/0 [0-99]`)
- For requests without a role (get_info, algorithm verbs, OTP), the
curve and path are `-`.
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, derive its
key immediately, and return to the main screen automatically — no
extra Enter needed.
- **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.
- **Columns**: Role, Purpose, Curve, Key path (derivation path via
`RoleEntry::display_path()`, e.g. `m/44'/1237'/0'/0/0` or
`m/44'/1237'/*'/0/0 [0-99]`).
### 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 toggles are independent checkboxes — toggling one only flips
itself, and multiple transports can be checked at once. The server
listens on the first active transport by priority (Unix > Qrexec > TCP
> HTTP). The last active transport cannot be disabled. This avoids
server architecture changes while allowing the user to select which
transport is active.
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 `help_scroll`,
`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 (each on an
indented row beneath the label, without `@` for Unix). Remove the
Connections sub-panel. Roles and Activity sections have a blank row
above their command lines at the bottom.
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
(independent checkbox: only flips itself; last active cannot be
disabled; 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 AddRole popup** — `draw_add_role()` renders a centered
popup over the Main screen showing the role preset menu (same 1–10
presets as current wizard). `handle_add_role_key()` processes the
multi-stage flow: `PresetMenu` → `NameEntry` (InputField with
pre-filled default) → `CurveSelect` (custom only) → `PathEntry`
(InputField with pre-filled default) → `OtpDir`/`OtpName` (OTP
only) → `Confirm`. On confirm, register the role, derive its key,
and return to `Screen::Main`. ESC cancels and returns to Main.
8. **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.
9. **Update key command bars** — each section's bottom line shows the
relevant key bindings for that section. Use underlined-key-letter
word hints (e.g. `Q̲uit`, `He̲lp`, `A̲dd`, `D̲elete`, `Cl̲ear`)
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).
10. **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".
11. **Update `run()` loop** — service server only on `Main` screen
(overlay screens `AddRole` and `Help` pause server processing).
Update the `handle_key` dispatch for the new screen enum.
12. **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.
13. **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/delete,
verify AddRole popup preset menu flow, verify Transport toggle
(4 lines, tab navigation), verify Help screen is scrollable and
shows app description + key commands, verify activity log shows
detailed request info, connect with `signer_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.