refactor(tui): reimplement stats dashboard on tuika - #78
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
The
--tuistats 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 flexboxFlex/Dimension; the alternate-screen lifecycle, input translation, and redraw scheduling are owned by tuika'sAsyncRunner, which drives the whole dashboard as a singletokio::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'sRatatuiViewinterop — so the rendered dashboard is pixel-identical. Keys (q/Escquit,rrefresh) and the/llmsim/statscontract 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
AsyncRunnerspecifically collapses the earlier synchronous-runner bridge —spawn_blockingrender thread + sharedLive/RwLock+Notify+AtomicBoolstop flag — into one async loop over a plain localDashboardData, 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
tuifeature; the server, API, and library paths are untouched (server smoke suite + full unit/integration suite green).crosstermdep (now transitive via tuika'sasyncfeature) fortuika = "0.4"from crates.io — a pinned release, no floating git ref. tuika unifies onto the sameratatui0.30.2 already in use, so theBuffer/Rectinterop types line up.Checklist
DashboardDataingest / history-cap / error-clearing unit tests;StatsEndpointCRLF-rejection tests retained)--tuibehavior and the stats endpoint are identical)Generated by Claude Code