Skip to content

Commit a4812a2

Browse files
mizu-junclaudehappy-otter
authored
feat(client): the three list pickers adopt the chrome type ramp (UI/UX v3 P4e) (#98)
P4b deferred the command palette, host manager and macro picker as "safe in principle, deferred to keep the diff reviewable". Measuring them found one thing that was not safe. Both list pickers aligned their two columns with `{:<20}` / `{:<22}` — a count of characters, which lines up only because the chrome borrows the terminal's monospace font. That is the assumption the ramp removes, and it was already broken for any name past the pad width and for CJK names. `name_column_width` replaces it: the widest measured name plus a gap, clamped so the detail column keeps a floor, so a long name shortens nothing but its own column. Two more defects the migration closes: - None of the three truncated, so a long palette action or host name drew past the panel edge. Every row now goes through `draw_picker_run`, which truncates and draws from one measurement — the pickers' `draw_row_run`. - The selection marker moved out of the label. `"> "` and `" "` are the same width only in a monospace font, so a prefixed marker would have shifted every selected row's text sideways; it is its own run now and both states start their label at the same x. No hit-region work, for the same reason the dialog buttons needed none: none of the three pickers is mouse-hit-tested at all — `mouse.rs` does not mention `palette`, `host_manager` or `macro_picker`. Panel sizes and row pitch stay in cells, so nothing moves. Observed and deliberately not fixed: the host manager and macro picker draw their row fills and every text colour from hard-coded literals — G11 residue that the token migration missed, so those rows do not answer to the scheme and were never contrast-corrected in P5. Changing hue and type size in one PR would make a visual regression impossible to attribute; it wants its own change. 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>
1 parent f2c22b0 commit a4812a2

4 files changed

Lines changed: 374 additions & 69 deletions

File tree

docs/plans/2026-08-29-p4-iconography-and-chrome-typography.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,46 @@ Still deferred: the command palette, host manager, macro picker, the
468468
hand-written parts of `ssh_tab.rs` / `keybindings_tab.rs`, the context menu and
469469
the status bar; tab-bar labels (N-3); a chrome font family (N-5).
470470

471+
### 8.3 P4e — the three list pickers (2026-08-29)
472+
473+
§5.2 deferred the palette, host manager and macro picker as "safe in
474+
principle". Measuring them first found one thing that was not:
475+
476+
- **Both list pickers aligned their columns with `{:<20}` / `{:<22}`.** That is
477+
a count of *characters*, and it lines up only because the chrome borrows the
478+
terminal's monospace font — the assumption the ramp exists to remove. It was
479+
already broken for any name past the pad width and for CJK names, whose
480+
cells are twice as wide. The name column is now `name_column_width`: the
481+
widest *measured* name plus a gap, clamped so the detail column keeps a
482+
floor. A long name shortens nothing but its own column.
483+
- **None of the three truncated.** A long palette action or host name simply
484+
drew past the panel edge. Every row now goes through `draw_picker_run`,
485+
which truncates and draws from one measurement — the pickers' equivalent of
486+
the widget layer's `draw_row_run`.
487+
- **The selection marker moved out of the label.** `"> "` and `" "` are the
488+
same width only in a monospace font, so a prefixed marker would have shifted
489+
every selected row's text sideways once the rows drew proportionally. It is
490+
its own run now, and both states start their label at the same x.
491+
- **No hit-region work**, again: none of the three pickers is mouse-hit-tested
492+
at all (`mouse.rs` does not mention `palette`, `host_manager` or
493+
`macro_picker`). They are keyboard- and AccessKit-driven.
494+
495+
Panel sizes and row pitch stay in cells, so nothing moves.
496+
497+
Observed and **not** fixed: the host manager and macro picker draw their row
498+
fills and every text colour from hard-coded literals (`[0.35, 0.15, 0.50, 1.0]`
499+
and friends) — G11 residue that the token migration missed, and which means
500+
those rows do not answer to the scheme and were never contrast-corrected in P5.
501+
Left alone deliberately: changing hue and type size in one PR would make a
502+
visual regression impossible to attribute. It wants its own change, sized as a
503+
G11/P5 follow-up rather than a typography one.
504+
505+
Still deferred after P4e: the status bar (cell-aligned by design — the Lua
506+
status format is column-oriented), tab-bar labels (N-3), the SFTP file-transfer
507+
dialog (its field boxes are cell-derived geometry), the context menu, the
508+
hand-written parts of `ssh_tab.rs` / `keybindings_tab.rs`, and a chrome font
509+
family (N-5).
510+
471511
## 9. Decisions
472512

473513
All three questions this spec opened are decided; no PR below is gated on

docs/plans/ui-ux-modernization-v3.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,13 @@ gated behind a spike.
715715
reset a category. Both are now `Role::Button` nodes labelled from the
716716
same module the renderer draws from, the reset node appearing only
717717
while the category is resettable (spec §8.2, 2026-08-29)
718+
- [x] P4e — the three list pickers (palette / host manager / macro
719+
picker) adopt the ramp, truncate to the panel, and align their two
720+
columns by measurement instead of `{:<20}` character padding, which
721+
only ever lined up in a monospace font. None of the three is
722+
mouse-hit-tested, so again no hit-region work. Their hard-coded row
723+
colours are left as G11 residue for a separate change (spec §8.3,
724+
2026-08-29)
718725
- [x] P5 contrast everywhere + high-contrast scheme (P5a–P5d, 2026-08-29)
719726
- [x] P6 InfoBar consolidation (P6a–P6d, 2026-08-29; consent reclassification
720727
dropped, spec §4). Appearance stays on the on-device backlog below — the

nexterm-client-gpu/CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Guidance for working inside the GPU client crate. The repo-wide rules — langua
1919
- `settings/hover.rs``HoverDwell`, the pointer-dwell timer that gates tooltips (500 ms).
2020
- `renderer/overlay/settings/footer.rs` — the footer's two links, `↗ Open config.toml` and `↺ Reset category` (UI/UX v3 P4c). Their labels, ramp step, measurement and rects live here and nowhere else: `footer_links` is the one call both `settings/mod.rs` (draws it) and `event_handler/settings_panel_hit.rs` (tests against it) make, so the click target cannot drift from the glyphs. The pure `place_links` takes measured widths, in the shape `place_tooltip` established. Adding a footer link means an entry here, not a formula in the hit-test — that mirrored formula is exactly what kept these two on the cell path until P4c. `accessibility.rs` announces them from the same module (P4d): `Role::Button` at `SETTINGS_FOOTER_OPEN_ID` / `SETTINGS_FOOTER_RESET_ID`, labelled with `footer::open_text()` / `reset_text()` — the link text without its decorative glyph — and the reset node is present only while `category_resettable()`. Before P4d neither link was in the tree at all.
2121
- `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, with `StackLayout::more_label` reporting the rest on the bottom bar. Accessibility is driven from the same enum (P6c): `accessibility::build_info_bar_nodes` emits one `Role::Alert` per queued bar — including bars past the drawn cap — at the stable id `info_bar_node_id(slot)`, so a new slot cannot be added without a node. Motion is per bar (P6d): each carries its own entrance and exit `Timed`, so one bar can fade out while the one under it is still arriving, and `apply_surface_fade` is therefore called *inside* the builder's loop rather than around it. Dismissal never deletes — `ClientState::dismiss_info_bar` starts the exit, `retire_info_bars` drops the bar once it finishes, and `expire_info_bars` applies the info severity's 20 s deadline (`INFO_BAR_TTL`); both are called from the tick in `lifecycle.rs`. A dismissed bar is out of everything but the renderer at once: it takes no key, holds no slot, and leaves the AccessKit tree immediately.
22+
- `renderer/overlay/picker.rs` — the three list pickers (command palette, host manager, macro picker) plus the SFTP transfer dialog. Rows draw through `draw_picker_run` (UI/UX v3 P4e): one measurement shared by the truncation and the draw, vertically centred in a cell-high row. Two-column rows take their name column from `name_column_width` — measured, not `{:<20}`-padded, because character padding only aligns in a monospace font. The selection marker is its own run so selected and unselected labels start at the same x. Panel sizes and row pitch stay in cells; none of the three pickers is mouse-hit-tested. Their row fills and text colours are still hard-coded literals (G11 residue), so a colour change there is a separate concern from the typography.
2223
- `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.
2324
- `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.
2425
- `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.

0 commit comments

Comments
 (0)