Files
signer/plans/tui_gap_analysis.md
T

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 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.