Files
Zachary D. Rowitsch 38e6dcc34a chore: archive v1.0 phase directories to milestones/v1.0-phases/
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 01:33:15 -04:00

147 lines
14 KiB
Markdown

---
phase: 02-interactive-tui
verified: 2026-03-22T04:08:30Z
status: human_needed
score: 12/12 must-haves verified
re_verification: false
human_verification:
- test: "Launch sudo ./target/debug/tcptop on Linux VM and visually confirm all 9 columns render (Proto, Local Addr:Port, Remote Addr:Port, PID, Process, State, Rate In, Rate Out, RTT) with live data"
expected: "A ratatui TUI fills the terminal with a 3-region layout: summary header, connection table, status bar. Rows update every second."
why_human: "Cannot verify live rendering, terminal colours, or column alignment without running the binary on a real system with network traffic"
- test: "Press 'r' to sort by Rate In, press 'r' again to reverse. Then press Tab/Right arrow, then Enter to sort by the highlighted column."
expected: "Sort arrow indicator moves in the column header. Rows reorder each tick. Tab advances the highlighted column header, Enter sorts by it."
why_human: "Interactive TUI keyboard behaviour and visual feedback cannot be verified by static code inspection"
- test: "Press '/' and type 'ssh'. Observe table filtering live. Press Esc."
expected: "Table instantly filters to only rows matching 'ssh' in remote addr or process name. Esc clears filter and restores all rows."
why_human: "Live filter-as-you-type behaviour requires runtime observation"
- test: "Press 'c' to toggle extra columns, then '?' for help overlay, then 'q' to quit."
expected: "Extra columns (Bytes In/Out, Pkts In/Out) appear. Help overlay renders with all keybinding entries. 'q' exits cleanly and restores terminal."
why_human: "Visual rendering of overlays and terminal restoration require human observation"
- test: "Run sudo ./target/debug/tcptop --port 22 --tcp --interval 2 on Linux VM with an active SSH session"
expected: "Only TCP connections on port 22 are shown. Table refreshes at 2-second intervals."
why_human: "CLI filter behaviour against real kernel data requires runtime verification"
---
# Phase 02: Interactive TUI Verification Report
**Phase Goal:** Interactive TUI — ratatui terminal with live connection table, summary header, status bar, sorting, filtering, bandwidth coloring, and CLI flags
**Verified:** 2026-03-22T04:08:30Z
**Status:** human_needed
**Re-verification:** No — initial verification
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | User sees a live-updating table with columns Proto, Local, Remote, PID, Process, State, Rate In, Rate Out, RTT | VERIFIED | `draw_table` in draw.rs builds exactly these 9 base columns; wired through `terminal.draw()` in main.rs on every `tick.tick()` |
| 2 | User sees a 3-4 line summary header with TCP/UDP counts and aggregate bandwidth | VERIFIED | `draw_header` renders 3 lines: connection count with TCP/UDP breakdown, aggregate bandwidth via `format_rate`, separator |
| 3 | User sees a bottom status bar showing current sort column and direction | VERIFIED | `draw_status_bar` renders sort column name + direction arrow in Normal mode; filter text with cursor in Filter mode |
| 4 | User can quit with 'q' or Ctrl-C and terminal restores cleanly | VERIFIED | `handle_event` returns `Action::Quit` on 'q' or Ctrl-C; main.rs calls `ratatui::restore()` after event loop exits |
| 5 | Connections are color-coded by bandwidth intensity (dim to bright) | VERIFIED | `bandwidth_style()` implements 4-tier colour mapping: DarkGray / White / White+Bold / Yellow+Bold; applied per row in `draw_table` |
| 6 | Display refreshes at a configurable interval (--interval flag, default 1s) | VERIFIED | `Cli.interval: u64` with `default_value = "1"`; `tokio::time::interval(Duration::from_secs(cli.interval))` in run_linux |
| 7 | User can sort the table by any column using mnemonic keys and Tab+Enter | VERIFIED | event.rs handles r/R/p/n/s/t/a/A/P; Tab/Right/Left/BackTab cycle `selected_header_col`; Enter calls `toggle_sort(column_at_index(...))` |
| 8 | User can press '/' to enter filter mode, type to filter live by IP/port/process, Esc to clear | VERIFIED | Filter state machine: '/' sets `InputMode::Filter`; Char(c) appends to `filter_text`; Esc clears and resets to Normal; `filter_records` applied each tick |
| 9 | User can launch with --port 443 and see only connections on port 443 | VERIFIED | `filter_records` checks `r.key.local_port != port && r.key.remote_port != port`; `CliFilters.port` populated from `cli.port` in main.rs |
| 10 | User can launch with --pid 1234 or --process nginx to see only matching connections | VERIFIED | `filter_records` checks pid exact match and process_name case-insensitive contains; both wired from Cli struct |
| 11 | User can launch with --tcp or --udp to see only one protocol | VERIFIED | `filter_records` checks `cli_filters.tcp_only` and `cli_filters.udp_only` against `r.key.protocol`; flags wired from `cli.tcp`/`cli.udp` |
| 12 | User can launch with --interface eth0 (flag accepted, warning logged, not yet wired to collector) | VERIFIED | `Cli.interface: Option<String>` exists; main.rs issues `log::warn!` if `cli.interface.is_some()`; behaviour documented as intentional deferral |
**Score:** 12/12 truths verified
### Required Artifacts
| Artifact | Expected | Status | Details |
|----------|----------|--------|---------|
| `tcptop/src/tui/mod.rs` | TUI module root | VERIFIED | Declares `pub mod app`, `pub mod draw`, `pub mod event` |
| `tcptop/src/tui/app.rs` | App state with sort/filter/highlight | VERIFIED | 237 lines; exports `App`, `InputMode`, `SortColumn`, `CliFilters`; implements `toggle_sort`, `sort_records`, `filter_records`, `update_highlights`, `column_at_index` |
| `tcptop/src/tui/draw.rs` | Rendering functions for header, table, status bar | VERIFIED | 289 lines; exports `draw()`; contains `draw_header`, `draw_table`, `draw_status_bar`, `draw_help_overlay`, `bandwidth_style`, `centered_rect` |
| `tcptop/src/tui/event.rs` | Keyboard event handler | VERIFIED | 97 lines; exports `Action` enum and `handle_event`; covers all specified key bindings |
| `tcptop/src/main.rs` | Terminal init/restore, EventStream in select loop, Cli struct | VERIFIED | 171 lines; contains `ratatui::init()`, `ratatui::restore()`, `EventStream::new()`, `event_stream.next()`, `#[derive(Parser)]` Cli struct |
| `Cargo.toml` (workspace) | ratatui, crossterm, futures workspace deps | VERIFIED | All three declared with correct versions and features |
| `tcptop/Cargo.toml` | Crate deps referring to workspace | VERIFIED | `{ workspace = true }` for all three |
| `tcptop/src/lib.rs` | `pub mod tui;` declared | VERIFIED | Line 6: `pub mod tui;` |
### Key Link Verification
| From | To | Via | Status | Details |
|------|----|-----|--------|---------|
| `main.rs` | `tui/app.rs` | `App::new(cli_filters)` and `app.update_highlights`, `app.filter_records`, `app.sort_records` | WIRED | Lines 113-120, 140-143 of main.rs |
| `main.rs` | `tui/draw.rs` | `terminal.draw(\|frame\| tcptop::tui::draw::draw(frame, &mut app, &filtered))` | WIRED | Line 154-156 of main.rs |
| `main.rs` | `crossterm::event::EventStream` | `event_stream.next()` in `tokio::select!` | WIRED | Lines 126, 158 of main.rs |
| `draw.rs` | `output.rs` | `format_rate`, `format_rtt`, `format_bytes` imported and used | WIRED | Line 10 of draw.rs: `use crate::output::{format_bytes, format_rate, format_rtt}`; used at draw.rs lines 53-54, 143-145, 160-161 |
| `event.rs` | `tui/app.rs` | `app.toggle_sort(...)` and `app.input_mode` mutations | WIRED | event.rs lines 44-52, 64-66, 80-84, 86-87 |
| `main.rs` | `tui/event.rs` | `handle_event(&mut app, evt)` returning `Action::Quit` | WIRED | Line 159 of main.rs |
| `main.rs` | `CliFilters` | CLI args mapped to `CliFilters` struct, passed to `App::new` | WIRED | Lines 113-120 of main.rs: `CliFilters { port: cli.port, pid: cli.pid, ... }` |
### Requirements Coverage
| Requirement | Source Plan | Description | Status | Evidence |
|-------------|-------------|-------------|--------|----------|
| DISP-01 | 02-01 | Real-time sortable table with one row per connection showing all stats | SATISFIED | `draw_table` in draw.rs renders all 9 standard columns; `sort_records` operates on full `ConnectionRecord` fields |
| DISP-02 | 02-01 | Summary header with total connection count and aggregate bandwidth | SATISFIED | `draw_header` renders connection count (TCP/UDP breakdown) and summed rate_in/rate_out |
| DISP-03 | 02-02 | User can sort by any column via keyboard | SATISFIED | 9 mnemonic keys + Tab/Left/Right/Enter in event.rs; `sort_records` handles all 13 `SortColumn` variants |
| DISP-04 | 02-01 | Local addr, local port, remote addr, remote port per connection | SATISFIED | draw.rs: `format!("{}:{}", record.key.local_addr, record.key.local_port)` and remote equivalent; 4 pieces of data in 2 columns |
| DISP-05 | 02-01 | Refresh interval configurable via CLI flag (default 1s) | SATISFIED | `--interval` flag in Cli struct; `Duration::from_secs(cli.interval)` passed to `tokio::time::interval` |
| DISP-06 | 02-01 | Connections color-coded by bandwidth usage (heat map style) | SATISFIED | `bandwidth_style(rate_in + rate_out)` with 4 tiers from DarkGray to Yellow+Bold |
| DISP-07 | 02-02 | Search/filter with '/' key (IP, port, process name) | SATISFIED | Filter state machine in event.rs; `filter_records` does case-insensitive match on local, remote, and process strings |
| DISP-08 | 02-01 | Quit with 'q' or Ctrl-C | SATISFIED | Both handled in event.rs; `ratatui::restore()` called on exit path in main.rs |
| FILT-01 | 02-02 | Filter by port via CLI `--port` (source, destination, or either) | SATISFIED | `filter_records` checks `local_port != port && remote_port != port` |
| FILT-02 | 02-02 | Filter by process via `--pid` or `--process` | SATISFIED | `filter_records` checks pid exact match and process_name case-insensitive substring |
| FILT-03 | 02-02 | Select network interface via `--interface` | SATISFIED (partial) | Flag accepted by clap; `log::warn!` issued if provided; actual per-interface filtering deferred to later phase per design decision |
| FILT-04 | 02-02 | Filter by protocol via `--tcp` / `--udp` | SATISFIED | `tcp_only` and `udp_only` fields in `CliFilters`; both checks in `filter_records` |
Note: FILT-03 is intentionally partial — the `--interface` flag is accepted and logged but not yet wired to the eBPF collector. This is documented in both PLAN and SUMMARY as a deliberate deferral, not an oversight. The requirement is satisfied at the CLI surface level.
### Anti-Patterns Found
No anti-patterns detected. The `_ => {}` wildcard arms in event.rs (lines 75, 92) are correct Rust match exhaustion patterns for keyboard events, not stubs — they intentionally ignore unrecognised keys.
No TODOs, FIXMEs, placeholder comments, or empty implementations found in any TUI source file.
### Human Verification Required
All automated checks passed. The following items require runtime verification on a Linux VM with live network traffic (see `.claude/memory/reference_dev_debian.md` for VM sync instructions):
#### 1. Live table rendering and column layout
**Test:** Sync code to dev-debian VM, build with `cargo xtask build-ebpf && cargo build`, run `sudo ./target/debug/tcptop`.
**Expected:** Full-terminal TUI appears with 3 regions. Connection table rows populate with live data. All 9 columns (Proto, Local Addr:Port, Remote Addr:Port, PID, Process, State, Rate In, Rate Out, RTT) are visible and correctly aligned.
**Why human:** Cannot verify live rendering, terminal colour output, or column alignment without running on a real system with active connections.
#### 2. Sort interaction with visual feedback
**Test:** Press 'r' — verify Rate In column header gets an arrow indicator and rows reorder. Press 'r' again — verify arrow toggles direction. Press Tab/Right several times — verify the highlighted column advances across headers. Press Enter — verify table sorts by that column.
**Expected:** Visual sort indicator present on correct column. Rows actually reorder each tick. Tab/Enter header navigation is clearly visible.
**Why human:** Interactive keyboard behaviour and visual feedback require runtime observation.
#### 3. Filter-as-you-type
**Test:** Press '/', type a process name (e.g., 'curl' or 'ssh'). Observe table update. Press Esc.
**Expected:** Table immediately shows only matching rows on the next tick. Esc clears the filter and restores all rows. Status bar shows "Filter: {text}_" while in filter mode.
**Why human:** Live filter behaviour and status bar display require runtime observation.
#### 4. Extra columns, help overlay, and clean quit
**Test:** Press 'c' to toggle extra columns. Press '?' for help overlay. Press 'q' to quit.
**Expected:** 4 extra columns (Bytes In, Bytes Out, Pkts In, Pkts Out) appear. Help overlay renders centred over the table with all documented keybindings including the asterisk explanation. 'q' exits cleanly and restores the terminal prompt.
**Why human:** Visual rendering of overlays, column expansion, and terminal state after exit require human observation.
#### 5. CLI filter flags with real data
**Test:** `sudo ./target/debug/tcptop --port 22 --tcp --interval 2` on a VM with an active SSH session.
**Expected:** Only TCP connections matching port 22 appear. Refresh interval is visibly slower (2 seconds between updates).
**Why human:** CLI filter effectiveness requires real kernel data to confirm filtering actually works against live connections.
### Gaps Summary
No gaps found. All 12 observable truths are verified in the codebase. All artifacts exist and are substantive. All key links are wired. All 12 requirement IDs are accounted for across the two plans with no orphaned requirements.
The phase goal is fully implemented at the code level. The remaining human verification items are standard runtime checks for an interactive TUI tool — they cannot be automated by static analysis but there is no evidence in the code that they would fail.
---
_Verified: 2026-03-22T04:08:30Z_
_Verifier: Claude (gsd-verifier)_