Skip to content

feat(tui): redesign shell, fix scrolling, auth and daemon polling - #12

Merged
solisoft merged 2 commits into
mainfrom
fix/tui-review-followups
Aug 23, 2026
Merged

feat(tui): redesign shell, fix scrolling, auth and daemon polling#12
solisoft merged 2 commits into
mainfrom
fix/tui-review-followups

Conversation

@solisoft

Copy link
Copy Markdown
Owner

The TUI gets a left sidebar, a mint-on-ink palette (src/tui/theme.rs), mouse support and a redrawn dashboard. Review follow-ups folded in:

Correctness

  • Login: q was bound to quit under an if password.is_empty() guard that applied to the whole or-pattern, making every password starting with q unenterable. Quit is now Esc-on-empty (plus Ctrl+C/Ctrl+D).
  • G set scroll_offset to the last index; every screen renders .skip(scroll_offset), so it collapsed the list to a single row. Added max_offset(len, visible) — the top of the last page — and a clamp pass on render so a resize or a shrinking list cannot strand the viewport.
  • Routes counted unfiltered rules for selection bounds while rendering the filtered list, and resolved the cursor back to a rule with a different predicate (matcher Debug vs format_matcher) than the one it displayed — a search could line a row up with a rule other than the one d deleted. Both now share screens::routes::filter_indices.
  • Config screen could not scroll at all: get_max_selection returned 0, so move_selection bailed. Added a text-viewport scroll path and cached the file contents instead of re-reading them every frame.
  • tail_file used read_to_string, so a seek landing mid-codepoint (or any non-UTF-8 byte) blanked the log viewer with no explanation. Reads bytes and converts lossily; open/read errors now name the path and cause.

Responsiveness

  • Daemon metrics moved off the render thread into a background poller. The inline block_on froze the UI for the request timeout every tick, worst exactly when the daemon was down. The client is built once, not per tick.
  • Mouse reporting uses ?1000/?1006 rather than crossterm's EnableMouseCapture, which also enables ?1003 any-motion tracking: a redraw per pointer movement, and terminal text selection taken away from the user (the OSC-52 fallback).
  • The event loop only repaints when something changed.

Reporting

  • daemon_ok: bool -> DaemonStatus. A proxy running with admin.enabled = false is healthy; it was reported as "daemon down". Metric panes now show why they are empty instead of a confident 0.
  • Dashboard regained listen/https/tls/admin/auth/scripts, in-flight, bytes in/out, TLS connections, half-open circuits and the apps overflow hint.

Docs

  • docs/tui-screenshots depicted a UI that was never built (top tabs, cyan palette, panels since removed). Regenerated against the shipped layout, with a README stating they are mockups and where each element's source of truth lives.

solisoft and others added 2 commits August 23, 2026 17:53
The TUI gets a left sidebar, a mint-on-ink palette (src/tui/theme.rs), mouse
support and a redrawn dashboard. Review follow-ups folded in:

Correctness
- Login: `q` was bound to quit under an `if password.is_empty()` guard that
  applied to the whole or-pattern, making every password starting with `q`
  unenterable. Quit is now Esc-on-empty (plus Ctrl+C/Ctrl+D).
- `G` set scroll_offset to the last *index*; every screen renders
  `.skip(scroll_offset)`, so it collapsed the list to a single row. Added
  `max_offset(len, visible)` — the top of the last page — and a clamp pass on
  render so a resize or a shrinking list cannot strand the viewport.
- Routes counted unfiltered rules for selection bounds while rendering the
  filtered list, and resolved the cursor back to a rule with a *different*
  predicate (matcher Debug vs format_matcher) than the one it displayed — a
  search could line a row up with a rule other than the one `d` deleted. Both
  now share `screens::routes::filter_indices`.
- Config screen could not scroll at all: get_max_selection returned 0, so
  move_selection bailed. Added a text-viewport scroll path and cached the file
  contents instead of re-reading them every frame.
- tail_file used read_to_string, so a seek landing mid-codepoint (or any
  non-UTF-8 byte) blanked the log viewer with no explanation. Reads bytes and
  converts lossily; open/read errors now name the path and cause.

Responsiveness
- Daemon metrics moved off the render thread into a background poller. The
  inline block_on froze the UI for the request timeout every tick, worst
  exactly when the daemon was down. The client is built once, not per tick.
- Mouse reporting uses ?1000/?1006 rather than crossterm's EnableMouseCapture,
  which also enables ?1003 any-motion tracking: a redraw per pointer movement,
  and terminal text selection taken away from the user (the OSC-52 fallback).
- The event loop only repaints when something changed.

Reporting
- daemon_ok: bool -> DaemonStatus. A proxy running with `admin.enabled = false`
  is healthy; it was reported as "daemon down". Metric panes now show why they
  are empty instead of a confident 0.
- Dashboard regained listen/https/tls/admin/auth/scripts, in-flight, bytes
  in/out, TLS connections, half-open circuits and the apps overflow hint.

Docs
- docs/tui-screenshots depicted a UI that was never built (top tabs, cyan
  palette, panels since removed). Regenerated against the shipped layout, with
  a README stating they are mockups and where each element's source of truth
  lives.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`cargo clippy -- -D warnings` failed on `result_large_err`: the function
returns a `u16` on success against a `Response<BoxBody>` (128+ bytes) on
failure, so every call paid for the error variant.

Pre-existing — main fails the same lint. It surfaced now because CI pins
`dtolnay/rust-toolchain@stable`, which has moved past the clippy release the
last green run used, while mise.toml pins 1.93.0 locally.

Every caller immediately returns the error as its own response, so boxing it
costs an allocation only on the error path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@solisoft
solisoft merged commit 2a60219 into main Aug 23, 2026
8 checks passed
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