150 lines
7.0 KiB
Markdown
150 lines
7.0 KiB
Markdown
# TUI Analysis: C `tui_continuous` vs Rust `tui.rs`
|
|
|
|
## C TUI architecture
|
|
|
|
The C version uses a vendored library `tui_continuous` (v0.0.9) from `resources/tui_continuous/`. It provides:
|
|
|
|
### Rendering primitives
|
|
- `tui_clear_continuous(height)` — clear scrollback
|
|
- `tui_render_top_frame(&frame, width)` — draws a full-width box (╔═╗) with app name, version, breadcrumb
|
|
- `tui_render_table(&table)` — renders a table with header, dashed separator, aligned columns
|
|
- `tui_render_menu(&menu, left_col)` — renders menu items with shortcut keys
|
|
- `tui_render_content_screen(&frame, title)` — renders a content screen (approval, unlock, wizard)
|
|
- `tui_anchor_prompt(filler_lines, left_col)` — positions the input cursor
|
|
- `tui_print(fmt, ...)` — printf-like with hotkey markup (`^_X^` = underline, `^*X^` = bold, `^:` = reset)
|
|
|
|
### Input primitives
|
|
- `tui_init()` / `tui_cleanup()` — raw mode management
|
|
- `tui_get_key()` — single key press (returns TUI_KEY_EOF, TUI_KEY_RESIZE, or 0-255)
|
|
- `tui_read_line(buf, len)` — read a line with editing
|
|
- `tui_resize_pending()` — check for SIGWINCH
|
|
- `tui_terminal_size()` — get terminal dimensions
|
|
- `tui_install_resize_handler()` — install SIGWINCH handler
|
|
|
|
### Key types
|
|
- `TuiFrame` — `{app_name, app_version, breadcrumb}`
|
|
- `TuiMenu` — `{items[], count}`
|
|
- `TuiMenuItem` — `{label, shortcut}`
|
|
- `TuiTable` — `{columns, column_count, row_count, get_cell, ...}`
|
|
- `TuiColumn` — `{name, width, right_align}`
|
|
- `TuiSize` — `{width, height}`
|
|
|
|
## Current Rust state
|
|
|
|
The Rust `tui.rs` and `main.rs` use simple `println!` and `tprint!` (custom macro) with `crossterm` for key input. It does NOT use the `tui_continuous` conventions.
|
|
|
|
## Gap analysis
|
|
|
|
### 1. Frame rendering — missing
|
|
C calls `tui_render_top_frame(&frame, size.width)` which draws:
|
|
```
|
|
╔══════════════════════════════════════════════════════════════╗
|
|
║ n_signer v0.1.0 > Main Menu ║
|
|
╚══════════════════════════════════════════════════════════════╝
|
|
```
|
|
|
|
Rust does this manually with `tprint!` — the box art is there but not to spec.
|
|
|
|
**Fix**: Port `tui_render_top_frame()` to Rust. The C source is in `resources/tui_continuous/tui_continuous.c`.
|
|
|
|
### 2. Table rendering — missing
|
|
C uses `tui_render_table()` with `TuiColumn` config. The status screen shows:
|
|
```
|
|
Role Purpose Curve Derivation path
|
|
---- ------- ----- ---------------
|
|
main nostr secp256k1 m/44'/1237'/0'/0/0
|
|
```
|
|
|
|
Rust uses fixed-format `println!` without proper column alignment logic.
|
|
|
|
**Fix**: Port `tui_render_table()` to Rust.
|
|
|
|
### 3. Menu rendering — missing
|
|
C uses `tui_render_menu()` which renders:
|
|
```
|
|
^_l^: lock/reunlock ^_r^: refresh ^_d^: display connections ^_q^:/x quit
|
|
```
|
|
|
|
Rust uses a plain `tprint!("[d] connection details [q] quit")` without menu struct or hotkey marking.
|
|
|
|
**Fix**: Port `tui_render_menu()` to Rust.
|
|
|
|
### 4. Content screen rendering — missing
|
|
C uses `tui_render_content_screen()` for approval/unlock/wizard screens. This draws a full-screen box with breadcrumb title.
|
|
|
|
**Fix**: Port `tui_render_content_screen()` to Rust.
|
|
|
|
### 5. Hotkey markup (`^_X^`, `^*X^`, `^:`) — missing
|
|
C parses `^_` → underline, `^*` → bold, `^:` → reset. All menu labels use this. Rust doesn't support markup.
|
|
|
|
**Fix**: Port `tui_print()` with markup parsing to Rust.
|
|
|
|
### 6. `tui_get_key()` with resize detection — missing
|
|
C's `tui_get_key()` returns `TUI_KEY_RESIZE` on SIGWINCH. Rust's `poll_key()` doesn't handle this — it just returns `TuiKey::Other`.
|
|
|
|
**Fix**: Add resize detection to `poll_key()`.
|
|
|
|
### 7. Activity log — missing
|
|
C maintains a `g_activity_log` with timestamps (e.g., "2024-01-15 10:30:45 signed event"). The status screen shows "Activity (latest first):". Rust doesn't have this.
|
|
|
|
**Fix**: Add an `ActivityLog` struct and integrate it into the render loop.
|
|
|
|
### 8. Lock/reunlock ('l' key) — missing
|
|
C supports pressing 'l' to lock the session (wipe keys, unload mnemonic) and re-prompt for the mnemonic. Rust doesn't have this.
|
|
|
|
**Fix**: Add the 'l' key handler in the main loop.
|
|
|
|
### 9. Refresh ('r' key) — missing
|
|
C re-renders the status screen on 'r'. Rust doesn't handle 'r'.
|
|
|
|
**Fix**: Add the 'r' key handler.
|
|
|
|
### 10. `tui_read_line()` — missing
|
|
C has `tui_read_line()` for line input in raw mode. Rust has `read_line_raw()` which is a basic implementation. C's version handles backspace, Ctrl-U (kill line), Ctrl-W (kill word), etc.
|
|
|
|
**Fix**: Enhance `read_line_raw()` to match C's `tui_read_line()`.
|
|
|
|
### 11. Connection info struct — missing
|
|
C uses `connection_info_entry_t` with `title`, `connection_string`, `example`, `extra` fields. Rust's `render_connections()` is hardcoded.
|
|
|
|
**Fix**: Port the connection info struct and builder functions.
|
|
|
|
### 12. `tui_confirm()` — missing
|
|
C has `tui_confirm()` for [y/n] prompts. Rust doesn't have this.
|
|
|
|
**Fix**: Port `tui_confirm()` to Rust.
|
|
|
|
### 13. `tui_press_enter()` — missing
|
|
C has `tui_press_enter()` for "Press Enter to continue..." prompts. Rust uses inline `read_line()`.
|
|
|
|
**Fix**: Port `tui_press_enter()` to Rust.
|
|
|
|
### 14. `tui_has_stdin_pipe()` — missing
|
|
C detects if stdin is a pipe vs TTY. Rust doesn't have this check.
|
|
|
|
**Fix**: Port `tui_has_stdin_pipe()` to Rust.
|
|
|
|
## Summary of changes needed
|
|
|
|
| # | Feature | C function | Rust status | Effort |
|
|
|---|---------|-----------|-------------|--------|
|
|
| 1 | Frame rendering | `tui_render_top_frame()` | Manual box art | Medium |
|
|
| 2 | Table rendering | `tui_render_table()` | Fixed `println!` | Medium |
|
|
| 3 | Menu rendering | `tui_render_menu()` | Hardcoded string | Small |
|
|
| 4 | Content screen | `tui_render_content_screen()` | Missing | Medium |
|
|
| 5 | Hotkey markup | `tui_print()` | Missing | Medium |
|
|
| 6 | Key input with resize | `tui_get_key()` | Basic `poll_key()` | Small |
|
|
| 7 | Activity log | `activity_log_t` | Missing | Medium |
|
|
| 8 | Lock/reunlock | 'l' key handler | Missing | Medium |
|
|
| 9 | Refresh | 'r' key handler | Missing | Small |
|
|
| 10 | Line input | `tui_read_line()` | Basic `read_line_raw()` | Small |
|
|
| 11 | Connection info struct | `connection_info_entry_t` | Hardcoded | Small |
|
|
| 12 | Confirm prompt | `tui_confirm()` | Missing | Small |
|
|
| 13 | Press Enter | `tui_press_enter()` | Inline code | Small |
|
|
| 14 | Pipe detection | `tui_has_stdin_pipe()` | Missing | Small |
|
|
| 15 | SIGWINCH handling | `tui_install_resize_handler()` | Missing | Small |
|
|
| 16 | Approval callback | `tui_approval_cb()` | Inline in server.rs | Medium |
|
|
|
|
## Implementation strategy
|
|
|
|
The cleanest approach is to port the `tui_continuous` C library to Rust as a self-contained module, then update `tui.rs` and `main.rs` to use it. This avoids the formatting mismatch because the C version's `tui_print()` with `\r\n` is the correct approach for raw mode. |