Skip to content

feat(ui): two-depth controls instrument + ambient surface + L2 widget/motion kit - #71

Merged
tamirelazar merged 59 commits into
devfrom
feat/ui-system
Jun 24, 2026
Merged

feat(ui): two-depth controls instrument + ambient surface + L2 widget/motion kit#71
tamirelazar merged 59 commits into
devfrom
feat/ui-system

Conversation

@tamirelazar

Copy link
Copy Markdown
Owner

Summary

UI-system unification. Two stacked workstreams land together:

  1. Two-depth Controls "Instrument" — Console (category master-detail) ⇄ Tuner depth via Tab; typed param registry with kinds/descriptors; remapped keybinds (Tab depth, arrows adjust, [/] category). Replaces the legacy options overlay + prototype (deleted).
  2. UI-system unification — shared L2 widget kit (gauge/sparkline/value_state/swatch/separator/legend) + promoted RowBuf; motion module (ease/lerp/dim/crossfade + persistent phase clock); ambient instrument BASE↔TUNE↔MSG state machine surfaced as a centered modal; Dashboard / config-browser / help rebuilt on the shared kit; identity polish (title-box breath, accent discipline, spacing rhythm).

Dependencies trend DOWN

  • Added then dropped ratatui (controls/config-browser hand-rolled on RowBuf/RichCell fit the single-FrameBuffer overlay doctrine better).
  • Dropped tachyonfx (motion hand-rolled).
  • tui-input kept (editable save field).

Latest fix

  • 16dc9d7 — ambient modal frame outline + background now match the Controls/Dashboard console (border_color frame over bg_color interior) instead of palette-accent over status_bar_bg.

Scope

53 commits · 44 files · +8545 / −3674.

Verification

  • cargo test --features terminal --lib — 1161 passed.
  • 56 visual-regression goldens green.
  • cargo clippy --features terminal --all-targets -- -D warnings clean · cargo fmt --check clean.
  • Release binary 2.31 MB (down from 2.47 — net dep reduction).

Known-open (tracked, not blocking)

  • When the always-on base row is enabled and a modal fires, the base row hides behind the modal (wants a 2nd composite slot).
  • Dashboard panel fixed width 84 → clips at terminals narrower than 84 cols.
  • Overlays clip silently at very short terminal heights.

Manual verification still recommended

Overlays/modals only render in the interactive TUI (print-mode goldens draw no overlays). Live-TUI eyeball of colors/layout across the 5 themes is the remaining human check.

Introduce a thin adapter that uses ratatui widgets and tui-input purely as
libraries, with no terminal backend: widgets render into a detached Buffer
and the result is blitted into the existing RenderedOverlay contract, so the
hand-rolled compositor and ANSI emission path are untouched.

Both crates are added with default-features = false, which keeps a single
crossterm in the tree and adds ~65KB to the release binary.

- buffer_to_overlay: detached ratatui Buffer -> RenderedOverlay
- build_config_browser: PanelBuilder chrome + ratatui List/ListState scroll
- build_config_save: PanelBuilder chrome + tui-input field with block caret
Wire the two interactive overlays through the adapter:

- Config browser scroll window is now driven by ratatui's ListState
  (auto keep-selection-visible) instead of the hand-rolled scroll math.
- Config-save name buffer changes from a String (append + backspace only,
  cursor pinned to the end) to a tui_input::Input driven as a pure state
  machine via InputRequest, adding mid-string insert/delete, Delete,
  Left/Right and Home/End, plus a rendered block caret.

Panel chrome is unchanged (still PanelBuilder), so the overlays render
identically; only the interactive behaviour is upgraded.
Extract two reusable adapter helpers and apply them across the config and
palette overlays:

- list_scroll_offset: ratatui ListState computes the visible window so the
  selection stays on-screen. Replaces hand-rolled scroll math in the config
  browser and fixes the palette load dialog's take(8) truncation, which hid
  selections past the 8th entry.
- stamp_caret: places a block caret on a built dialog's editable field.

The palette editor save-name buffer moves from String (append + backspace)
to tui_input::Input with mid-string insert/delete, Delete, Left/Right and
Home/End, plus a rendered caret — matching the config-save dialog. Panel
chrome stays on PanelBuilder, so the overlays render unchanged.
Replace the column-index colouriser (generate_controls_rich_lines) with
span-driven styling. Each control row is built as a ratatui Line of styled
Spans, rendered into a detached Buffer via the new render_styled_rows adapter
helper, and the resulting per-cell colours are blitted into the PanelBuilder
chrome (border/title-box/separators stay pixel-identical).

Text output is byte-identical across all six categories. Colours track the
text exactly instead of guessing fixed columns (5-7 keys, 27-38 bar band),
which fixes two latent bugs the old magic-column logic carried:

- the ESC footer fell through to param-row detection (is_esc_footer compared
  the bordered line against "ESC", never matching) and coloured blank cells;
- the Auto Normalize row lost its key/bar accent when modified, because the
  param-row detector only accepted ' ' or '✱' at col 3, not the '*' marker
  that row uses.

Binary size unchanged (reuses already-linked ratatui). 1004 lib tests, 56
visual goldens, clippy -D warnings all green.
Align the UI deps on the ratatui-core 0.1 generation so tachyonfx (effects
applied post-render to a detached Buffer, no backend) can share buffer types:

- ratatui 0.29 -> 0.30 (backend now in the separate ratatui-crossterm crate,
  so default-features=false keeps the widgets-only / no-second-crossterm setup)
- tui-input 0.10 -> 0.15 (ratatui 0.30 compatible)
- tachyonfx 0.25 added (default-features=false; crossterm is a dev-dep only)

Single crossterm copy (0.27, ours) confirmed via cargo tree. Adapter API
(Buffer, set_line, List/ListState, StatefulWidget) unchanged across the bump.
1004 lib tests + 56 visual goldens green; clippy -D warnings clean. Release
binary 2,468,816 B (+49,584; 531 KB under the 3 MB ceiling).
…igration cleanup)

Drop build_notification_panel, build_notification_rich_lines, RuntimeState.notification
field + initializer, update_notifications/current_notification/current_notification_full
methods, the OverlayType::Notification variant and OverlayCollection.notification field,
and the dead runner.rs update_notifications() call. NotificationLevel enum and all
ambient MSG code preserved intact.
… fixes

Post-SDD live-review round on the ambient surface and Controls tuner.

Ambient surface (prior round): default clean sim frame; TUNE/MSG events
render as a centered, opaque, accent-bordered modal; optional always-on
compact base row docked bottom-inside-frame, gated by a Controls APP
"Status Line" toggle. Fix F10 chrome-cycle crash (resize render buffers
before blur_field). draw_rich_overlay_dim gains a solid flag for opaque
modals.

Tuner-always-visible: param changes must reveal their tuner, but
param-adjust handlers also push an Info value-echo toast that outranks
the TUNE state (resolve: MSG > TUNE) and hid it. New ambient::surface_tune
drops the redundant Info toast then debounces/pushes the Tune; Warning and
Error messages survive and momentarily outrank it. Auto-normalize brightness
message promoted Info -> Warning so it still surfaces.

Orphan params as tuners: IntensityMapping, WindowFrame, and Chrome had no
ParamId, so their hotkeys only flashed a toast. Promote all three to
first-class registry params (rows + action_for / param_id_for_action
round-trips + value formatting); they now appear in Controls and surface a
tuner like Theme/Palette.

Console overflow fix: the new APP rows pushed that category to 10 > the
former MAX_VISIBLE_ROWS cap of 8, panicking build_console on F10 and
category navigation. Raise the cap to 10 (it is sized to the largest
category; both panes pad). Add no_category_exceeds_max_visible_rows guard.

Cross-theme color: replace two hardcoded Gruvbox grays in the expanded-chrome
footer with theme tokens (secondary row -> text_secondary; keybinds row ->
threaded muted), so the footer stays on-palette across all five themes.

Modal polish: enum/toggle tuners omitted the empty value gauge (a lone tick
read as "minimum"); add TuneView.show_gauge, true for numeric params only.

Verified: 1160 lib + 56 visual goldens + clippy --all-targets -D warnings +
fmt; live tmux QA across param/enum/frame/chrome/theme/controls/pause clean.
The centered TUNE/MSG/pause modal tinted its frame with the active
palette accent over a status_bar_bg interior, so it read as a separate
chrome layer from the Controls/Dashboard panels (border_color frame over
bg_color). Switch build_ambient_modal/modal_border_row to border_color +
bg_color so the modal's outline and background are identical to the
console. Drop the now-dead accent parameter from build_ambient_modal and
its call sites. Add a regression test asserting frame=border_color and
interior=bg_color.
Collapse the three copy-pasted overlay drawers (single-species draw_overlay,
multi-species draw_ms_overlay, and their title-box halves) into a single
TerminalRenderer::compose_overlay that both render paths forward to. Removes
the dead _border_col/_w tuple from the old get_overlay_colors closure.

Behavior-preserving: the themed panel_bg arg was already a no-op in both paths
(draw_text_overlay_with_panel only applies it when an indicator color is
supplied, which no caller does), so single- and multi-species output was
already identical. This removes the hand-sync drift hazard between the copies.

Add compose_overlay_applies_theme_pass covering the live per-char theme pass.
The console pads both panes to MAX_VISIBLE_ROWS and the body loop reads exactly
that many rows, so a category yielding more rows is silently truncated in
release (the debug_assert! in build_console is stripped). Strengthen the guard
test from two forced flags to all RegistryCtx bool combinations x all
categories, so a row added on any flag polarity is caught in CI.
Drop dim_overlay/crossfade/clip_rows/ease_out_cubic/ease_in_out and their
unit tests — they were speculative scaffolding for the overlay-fade feature
that was attempted and reverted. Keep breath + lerp_rgb (title-box accent
breathing + ambient TUNE pulse + widely-used color lerp).
The reverted overlay-fade feature left a `dim: f32` factor on
draw_rich_overlay_dim that every production caller passed as 1.0, plus a
`use crate::render::motion;` import feeding the now-unreachable lerp
branches. Collapse to draw_rich_overlay_solid(solid) — the load-bearing
flag controlling space-cell opacity — inline the fg/bg writes, drop the
motion import, and replace the dim-specific test with solid-flag coverage.
Resolve conflicts between the UI-system unification and dev's
launch-quick-keys / custom-keybinds feature (#58):

- KeyboardHintsOverlay::build_overlay now takes both `st: &PanelStyle`
  (UI-system theming) and `user_binds` (dev's live key-label lookup);
  all call sites and tests updated to the 3-arg signature.
- runner.rs: keep both the UI-system controls-dispatch/ambient helpers
  and dev's resolve_bind quick-key resolver; thread panel_style + user_binds
  into the keyboard-hints build.
- overlay.rs comparison header keeps dev's dynamic column label with the
  UI-system spacing token (add_empty_n(spacing::ROW)).
- input.rs / state.rs: keep both sides' added tests.
- README: keep both the Controls-panel-navigation and Custom-Keybinds docs.
origin/dev advanced past the first merge with the constellations preset.
Sole conflict was a both-sides test addition in runner.rs: keep the
UI-system Task-15 ambient-mode tests alongside dev's
constellation_reset_is_now_stable test.
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