7.0 KiB
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 scrollbacktui_render_top_frame(&frame, width)— draws a full-width box (╔═╗) with app name, version, breadcrumbtui_render_table(&table)— renders a table with header, dashed separator, aligned columnstui_render_menu(&menu, left_col)— renders menu items with shortcut keystui_render_content_screen(&frame, title)— renders a content screen (approval, unlock, wizard)tui_anchor_prompt(filler_lines, left_col)— positions the input cursortui_print(fmt, ...)— printf-like with hotkey markup (^_X^= underline,^*X^= bold,^:= reset)
Input primitives
tui_init()/tui_cleanup()— raw mode managementtui_get_key()— single key press (returns TUI_KEY_EOF, TUI_KEY_RESIZE, or 0-255)tui_read_line(buf, len)— read a line with editingtui_resize_pending()— check for SIGWINCHtui_terminal_size()— get terminal dimensionstui_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.