You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Browse filesBrowse the repository at this point in the historyBrowse files
authored
refactor(client): migrate the three banners onto the InfoBar stack (UI/UX v3 P6b) (#93)
Nexterm shipped three top-of-screen banners — update notice, offline and
server error — as three `Option` fields on `ClientState`, three vertex
builders totalling 232 structurally identical lines, and three copies of the
stacking arithmetic, with the error banner re-deriving its own offset by
testing the other two. They are now three kinds of one surface.
The three fields are removed rather than wrapped, so the compiler enumerated
every call site: the update poller and the connect loop queue through
`push_info_bar`, a successful connect clears the offline slot, and
`ServerToClient::Error` replaces the error slot instead of overwriting a
field. `bar_rects` from P6a is the only function that computes a bar's `y`,
and `stack_order` the only one that decides which bar is on top — a source
gate over `ui_verts.rs` fails if a second stacking expression comes back.
Three user-visible changes come with the consolidation:
- The stack draws below the tab bar instead of over it. Chrome hiding chrome
is the worse of the two costs — the tab bar is how the user navigates, and
hiding it while an error is up is exactly when they need it (D2).
- At most two bars are drawn. The stack overlays terminal rows that are
neither reflowed nor scrolled, so it must not grow without bound; the
count suffix for the rest needs a new string and lands in P6c (D-cap).
- `Enter` opens the release page only while the update bar is on top, so an
error above it now takes priority (D4). `Esc` still clears the error bar
before the update bar, because it walks the same order the stack is drawn
in, and the offline bar is skipped rather than dismissed — its content is
"still not connected", and it no longer advertises an `[Esc]` that did
nothing.
Accessibility is unchanged on purpose: the update bar keeps its `Role::Alert`
node and the tree hash now reads the stack (excluding the offline bar's
elapsed seconds, as before). Giving every kind a node — which is what finally
makes a failed shell launch announceable — is P6c, kept separate so that fix
is reviewable without this migration around it.
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
Copy file name to clipboardExpand all lines: nexterm-client-gpu/CLAUDE.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -17,9 +17,10 @@ Guidance for working inside the GPU client crate. The repo-wide rules — langua
17
17
-`settings_<tab>.rs` — one module per migrated category, each exposing `<tab>_widget_descs` (semantics), `build_<tab>_widgets` (semantics + geometry) and `apply_<tab>_action` (the single state transition the mouse, keyboard and AccessKit paths all call). All nine categories are migrated: Theme, Window, Font, Startup, Blocks, Security, Profiles, Ssh and Keybindings. The three list-shaped tabs (Profiles, Ssh, Keybindings) window their entry list via `layout::list_window`, and reserve index 0 for the entry list itself (which entry is selected lives in `selected_host_index` / `selected_key_index`, because that outlives focus moving to the fields); their delete-confirmation dialogs stay hand-written, because a modal over the panel is not a settings row.
18
18
- **Focus** is one field: `SettingsPanel.focused_widget_index` (a `WidgetId.index` for the current category), replacing the seven `<tab>_field_focus` counters. What an index means is defined by that category's descriptor builder — its `row` constants where it has them, otherwise the order descriptors are pushed. Every category change goes through `SettingsPanel::set_category`, which resets the index, the scroll offset and any in-flight field edit; do not assign `panel.category` directly. Keyboard ↑/↓ walk the descriptors via `widgets/navigation.rs` (`focus_next` / `focus_prev`), skipping anything that is not a focus stop — `!enabled`, `Label`, and `Swatch` (a swatch duplicates the cycler row above it, so nine extra stops would buy nothing). A new category therefore gains keyboard navigation by describing its controls, with no navigation code of its own. Ssh and Keybindings are the exception and keep bespoke arrow arms, because index 0 addresses their entry list as a whole rather than a described widget.
19
19
-`settings/hover.rs` — `HoverDwell`, the pointer-dwell timer that gates tooltips (500 ms).
20
+
-`renderer/overlay/infobar.rs` — the InfoBar stack (UI/UX v3 P6): every non-blocking status message — update notice, offline, server error — as one `InfoBarKind` queued on `ClientState.info_bars`, one slot per kind. `bar_rects` is the only function that computes a bar's `y`, and `stack_order` the only one that decides which bar is on top; a new message type is an enum arm, not a fourth builder, and it must not gain stacking arithmetic of its own. Drawn by `ui_verts::build_info_bar_verts` below the tab bar, capped at two visible bars.
20
21
-`palette.rs` — Command palette (`Ctrl+Shift+P`). Fuzzy search via `SkimMatcherV2`. Sprint 5-7 / Phase 3-3 covers all 25 actions in `execute_action` (Quit, ClosePane, NewWindow, QuickSelect, SetBroadcastOn/Off, …) and persists usage history at `~/.local/state/nexterm/palette_history.json` (atomic write, mode 0600). The pure `rank_actions` function orders by history when the query is empty (last_used desc → use_count desc) and combines fuzzy score with a `history_bonus` (use_count×10 capped at 100, +100 within 1 day, +50 within 1 week) when a query is present. `record_use` records the selection.
21
22
-`host_manager.rs` — SSH host manager UI. `load_history()` / `save_history()` persist connection frequency to `host_history.json`. The `PasswordModal` struct handles the password prompt for `auth_type="password"` hosts.
22
-
-`update_checker.rs` — Polls the GitHub Releases API five seconds after startup. Disabled by `auto_check_update = false`. Results land in `ClientState.update_banner`; `Esc` dismisses, `Enter` opens the release page.
23
+
-`update_checker.rs` — Polls the GitHub Releases API five seconds after startup. Disabled by `auto_check_update = false`. Results are queued onto `ClientState.info_bars` as `InfoBarKind::UpdateAvailable`; `Esc` dismisses, `Enter` opens the release page while it is the top bar.
23
24
-`platform.rs` — Platform-specific utilities. `apply_backdrop` applies the configured `window.backdrop` material: Windows via `DwmSetWindowAttribute(DWMWA_SYSTEMBACKDROP_TYPE)` (Windows 11 build 22621+; a no-op below that), macOS via `window-vibrancy`, Linux not at all. The value mapping, `dwm_backdrop_value`, is a plain `const fn` compiled on every platform so it is testable without Windows. `open_releases_url` opens the release page in the default browser.
24
25
-`renderer/background_pass.rs` — Background image rendering (Sprint 5-7 / Phase 3-1). When `WindowConfig.background_image` is set, the image is loaded at startup and each frame draws clear → background image → cell backgrounds → text. NDC + UV computation for each fit mode (cover/contain/stretch/center/tile) lives in the pure function `compute_background_quad`, with 11 unit tests. Images larger than 4096×4096 are downscaled with Lanczos3. Tile mode falls back to stretch when the tile count exceeds 256 (defensive). Reuses the existing `image_pipeline` (used for Sixel/Kitty) instead of introducing a separate one. Supported formats: PNG / JPEG (whichever features are enabled in the workspace `image` crate).
25
26
- `animations/` — UI animation foundation. `mod.rs` holds `AnimationManager` and the spring physics (`SpringState`) for the tab accent and per-pane dim; `easing.rs` the time-based helpers (`ease_out_cubic`, `compute_progress`); `curve.rs` the nine Fluent 2 cubic-bezier curves and eight duration steps, transcribed from `microsoft/fluentui` `packages/tokens` (do not re-derive them by eye); `timed.rs` the `Timed { start, duration_ms, curve }` value type; `surface.rs` the `SurfaceMotion` open/close pair shared by every overlay surface (UI/UX v3 P3b). Springs are for motion interrupted by a new target; `Timed` is for transitions with a known start, end and duration. A zero duration — what `AnimationsConfig::scaled_duration_ms` returns when `enabled = false` or `intensity = "off"` — makes a `Timed` finished on creation, which is the whole reduced-motion path. `ClientState::has_active_animation` is the one place that decides whether the event loop requests another frame; a surface that gains a `Timed` adds a clause there or it will simply never animate.
0 commit comments