Skip to content

refactor(tui): reimplement stats dashboard on tuika - #78

Merged
chaliy merged 5 commits into
mainfrom
claude/tui-tuika-reimplementation-gu6vpk
Jul 24, 2026
Merged

chaliy merged 5 commits into
mainfrom
claude/tui-tuika-reimplementation-gu6vpk

Conversation

@chaliy

@chaliy chaliy commented Jul 24, 2026

Copy link
Copy Markdown
Owner

What changed

The --tui stats dashboard is now built on tuika (a composable terminal-UI toolkit over ratatui) instead of a hand-rolled ratatui + crossterm host. Layout is expressed with tuika's flexbox Flex/Dimension; the alternate-screen lifecycle, input translation, and redraw scheduling are owned by tuika's AsyncRunner, which drives the whole dashboard as a single tokio::select! loop on the existing Tokio runtime. The data panels (RPS/token sparklines, models bar chart, request/token/latency/error tables) remain ratatui widgets, drawn through tuika's RatatuiView interop — so the rendered dashboard is pixel-identical. Keys (q/Esc quit, r refresh) and the /llmsim/stats contract are unchanged.

Why

Dogfood tuika on a real app and validate its ratatui-interop path, while moving the fiddly terminal lifecycle (raw mode, alt screen, mouse capture, panic-safe restore) and the event loop out of llmsim into a maintained library. The AsyncRunner specifically collapses the earlier synchronous-runner bridge — spawn_blocking render thread + shared Live/RwLock + Notify + AtomicBool stop flag — into one async loop over a plain local DashboardData, removing five concurrency primitives and the cross-thread shutdown handshake.

Before / After

No observable behavior change — this is a refactor. The dashboard renders identically and responds to the same keys. Verified by recording the dashboard under live traffic before and after the change: header, panels, sparklines, bar chart, colors, borders, and layout all match. (Recordings available on request; not committed to keep the repo free of large binaries.)

Risk

  • Low
  • Surface is limited to the opt-in, binary-only tui feature; the server, API, and library paths are untouched (server smoke suite + full unit/integration suite green).
  • Dependency change: swaps the direct crossterm dep (now transitive via tuika's async feature) for tuika = "0.4" from crates.io — a pinned release, no floating git ref. tuika unifies onto the same ratatui 0.30.2 already in use, so the Buffer/Rect interop types line up.

Checklist

  • Tests added or updated (DashboardData ingest / history-cap / error-clearing unit tests; StatsEndpoint CRLF-rejection tests retained)
  • Backward compatibility considered (no public API change; --tui behavior and the stats endpoint are identical)

Generated by Claude Code

chaliy added 5 commits July 23, 2026 15:46
Rebuild the `--tui` dashboard on top of tuika, a composable terminal UI
toolkit over ratatui (flexbox layout, alternate-screen host, focus, live
redraw). The rendered dashboard is unchanged; only the composition and
host machinery are replaced.

- Layout: ratatui `Layout`/`Frame`/`Terminal` composition is replaced with
  tuika `Flex`/`Dimension` (the 1-cell margin, the row/column splits, and
  the 60/40 and 50/50 panels). The data panels (tables, sparklines, bar
  chart) stay ratatui widgets, drawn through `RatatuiView` interop so they
  render into tuika's clipped surface and stay pixel-identical.
- Host loop: the hand-rolled crossterm raw-mode/alt-screen/event loop is
  replaced with tuika's `Runner`. The synchronous runner drives the
  terminal on a `spawn_blocking` thread; the stats poller stays on the
  async runtime and feeds a redraw-on-write `Live` value, with a `Notify`
  for the manual `r` refresh. `run_dashboard` remains `async` so the caller
  still races it against the server with `tokio::select!`.
- Deps: the `tui` feature drops the direct `crossterm` dependency (now
  owned by tuika) and adds `tuika`; `ratatui` stays for the widget code and
  unifies onto tuika's pinned 0.30.2.

Keys (`q`/`Esc` quit, `r` refresh) and the stats endpoint contract are
unchanged.
Adopt tuika's new async runner (currently on `main`, not yet released) so
the dashboard runs as a single `tokio::select!` loop on the caller's
runtime instead of bridging the synchronous `Runner` across threads.

The dashboard state is now a plain local `DashboardData` the runner owns:
`view` reads it to build each frame, `update` mutates it (awaiting the
stats fetch inline) in response to a tick or a key. This removes the
`spawn_blocking` render thread, the background poller task, and the
`Live`/`RwLock` + `Notify` + `AtomicBool` machinery that existed only to
feed the blocking runner from async code — a net simplification over both
the previous tuika port and the original hand-rolled loop.

tuika is pinned to a git dependency on `main` until the async runner ships
in a crates.io release; its `async` feature pulls tokio, tokio-stream, and
crossterm's event-stream transitively, so no direct crossterm dep returns.

Behaviour and rendering are unchanged: `q`/`Esc` quit, `r` refreshes, the
first tick loads stats on start, and the panels are pixel-identical.
The async runner shipped in tuika 0.4.0 on crates.io, so drop the interim
git dependency on `main` and track the published release. Removes the
non-reproducible floating git ref (Cargo.lock is gitignored here) and keeps
the `tuika/async` feature that provides `AsyncRunner`.
Unit-test the state folding introduced with the tuika port: token-rate
derivation only after a baseline snapshot, the 60-sample cap on both rolling
histories, and that a fetch failure marks disconnected while the next
successful ingest clears it.
The Security Audit job installs cargo-audit fresh on the pinned 1.95
toolchain. cargo-audit's transitive dep kstring 2.0.4 was published with
an MSRV of rustc 1.96, so the unlocked install fails to compile before it
can audit anything — breaking the job on every PR regardless of its diff.

`--locked` builds each tool from its own bundled lockfile, which pins
compatible dependency versions, so a downstream MSRV bump no longer breaks
the install. Applied to cargo-deny too, which installs the same way.
@chaliy
chaliy merged commit 465aeea into main Jul 24, 2026
11 checks passed
@chaliy
chaliy deleted the claude/tui-tuika-reimplementation-gu6vpk branch July 24, 2026 18:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant