Skip to content

feat(gui): move the gui toolbar into the tile header as a measured fold - #963

Merged
sahil-noon merged 4 commits into
sahil87:mainfrom
ashish-kumar-noon:260912-lut4-gui-toolbar-header-fold
Sep 13, 2026
Merged

sahil-noon merged 4 commits into
sahil87:mainfrom
ashish-kumar-noon:260912-lut4-gui-toolbar-header-fold

Conversation

@ashish-kumar-noon

@ashish-kumar-noon ashish-kumar-noon commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

@sahil87 — ready for review.

Meta

Change ID Type Confidence Plan Review
lut4 feat 3.9/5.0 14/14 tasks, 27/27 acceptance ✓ ✓ 1 cycle
Impact +/− Net
raw +6112 / −1217 +4895
true +2477 / −1169 +1308
└ impl +1809 / −829 +980
└ tests +668 / −340 +328

excludes fab/, docs/ · generated by fab-kit v2.26.4

Pipeline: intake ✓ → apply ✓ → review ✓ → hydrate ✓ → ship → review-pr

What this does

Moves the gui tile's session toolbar out of the floating pill and into the tile header as a width-adaptive measured fold. The pill is deleted.

The pill overlaid the framebuffer, self-hid 3s after the last interaction, and a fine-pointer viewer only got it back via an invisible 24px hover target at the tile's top edge — while the gui tile's own header had an empty flex-1 spring that the tty tile already fills with kind-specific controls.

How it folds

One priority ladder, cut to fit: size → zoom → quality → actions. Labels degrade one step before an item folds (1920×1080 ▾1920 ▾, ◐ Balanced); whatever doesn't fit goes into a panel that renders only when something actually folded.

The fold is measured, not breakpointed — a hidden probe row plus one ResizeObserver, with the arithmetic in a dependency-free lib/gui-toolbar-fold.ts. This is the third instance of the pattern lib/top-bar-overflow.ts and lib/crumb-collapse.ts already establish, including the latter's one-sided expand-edge hysteresis. The two old constants (TOOLBAR_OVERFLOW_MIN_PX, TOOLBAR_SHORT_LABEL_MAX_PX) can't work in a header, where the budget is tile width minus the name, meta chip, pinned block and layout verbs — each varying independently.

Notable

  • Fullscreen now targets the tile, so the header travels into it and the pill dies outright rather than surviving for one case. keyboardLock() chaining is untouched.
  • Pinned block — controls that never fold, width reserved before fitting (two-pass: no reserve on the first pass, so doesn't reserve space for itself when nothing folds). A follow-up keyboard-capture change drops into this slot.
  • Cluster glyphs are ControlGlyph SVGs in top-bar-icons.tsx, matching the adjacent verbs at 14px — previously 11px Unicode text.
  • New wm · display meta chip on the gui tile (tileMeta returned null for gui).
  • Mobile has no tile header, so the pinned renders into the top bar beside the switch group.
  • Constitution V holds — every chip and panel row stays a by-id pickGuiActions mirror; no toolbar-only actions.

Verification

  • npx tsc --noEmit clean
  • just test-frontend — 223 files / 4628 tests
  • just test-e2egui-toolbar-fold 4/4 (real measured widths), gui-surface 24/24 incl. the live Xvnc rig, plus a 13-spec adjacent sweep
  • just test-backend — 37 packages · just build OK
  • surface-focus-chords has 3 failures that reproduce identically on the pre-change baseline (code-iframe focus race) — pre-existing, not a regression

Design rationale, rejected options and the interactive fold demo are in docs/wiki/gui-toolbar-header-studies.html (and -exploration.html), both indexed in docs/specs/index.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_015zcfMsAWgTPeicetw212dA

@ashish-kumar-noon
ashish-kumar-noon force-pushed the 260912-lut4-gui-toolbar-header-fold branch from 43fec9a to 3cc9815 Compare September 12, 2026 21:12
ashish-kumar-noon and others added 3 commits September 13, 2026 09:55
Replaces the floating pill with a width-adaptive cluster in the gui tile's
header spring. The fold is measured (hidden probe row + ResizeObserver +
expand-edge hysteresis) rather than breakpointed, following the top bar's
two existing overflow implementations; the pure arithmetic lives in
lib/gui-toolbar-fold.ts.

Fullscreen now targets the tile, so the header travels into it and the pill
is deleted outright. Cluster controls are re-rendered in header chrome as
ControlGlyph SVGs, and the gui tile gains a `wm · display` meta chip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zcfMsAWgTPeicetw212dA
Memory now describes the header measured fold as present truth: the toolbar
pill is retired across gui.md and the ui/ sub-domain, with the two-pass
reserve, the priority ladder, the overflow panel, rk-gui-toolbar, the mobile
top-bar block, tile-targeted fullscreen, the ControlGlyph set and the
wm · display meta chip recorded. Indexes regenerated.

Also clears the three should-fix findings from review: stale pill references
in touched files, transition narration in the gui-toolbar docblock, and the
resolution-menu row mapping duplicated between the size chip's menu and the
overflow panel (now one shared helper).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zcfMsAWgTPeicetw212dA
… by 2px

The header-fold conversion turned every glyph-only control into an SVG, which
was too broad. Three of them were never glyphs:

- `−` / `fit` / `+` are a stepper, not three icons. Magnifier glyphs put two
  near-identical lenses either side of a word, where the sign carries the whole
  message, and `fit` names a mode rather than a direction. All three go back to
  text; ZoomIn/ZoomOut/ZoomFitGlyph are removed with their last caller.
- Send key was drawn as a keyboard, which reads as "the keyboard" rather than
  "send one chord" — and is indistinguishable from a keyboard glyph at the
  rendered 14px. It returns to the ⌥ option symbol it carried before.

Items are separated by 2px (`mx-[1px]` a side) rather than sitting flush; the
probe row measures margins already, so the fold arithmetic is unaffected.
`-mr-1.5` on the cluster root cancels the tile header's own 6px gap on that one
seam, which otherwise left the pinned block's right side wider than its left.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zcfMsAWgTPeicetw212dA
@ashish-kumar-noon
ashish-kumar-noon force-pushed the 260912-lut4-gui-toolbar-header-fold branch from 7051f78 to 56a199c Compare September 13, 2026 09:57
A wide gui tile left the cluster hugging the right edge with a large empty
spring beside it. It now sits at the header's TRUE midpoint while there is
clear air on both sides, and snaps back to right-aligned (today's layout, with
the fold) the moment there is not.

Centring is decided BEFORE the fold, because the two modes have different
budgets: centred, the cluster may only use the symmetric middle
(headerWidth - 2*max(leftBlock, rightBlock) - 2*clearance), which is narrower
than the spring. Fitting against the wrong budget would place items that then
overlap a neighbour.

Centring is also required to be FREE. Fitting against the narrower middle would
otherwise always "succeed" by folding harder — measured at 860px it cost four
controls, at 720px all but one. The decision now compares the centred fold
against the right-aligned fold and refuses to centre if it would hide or
degrade anything extra.

Entering centred mode carries the same one-sided hysteresis the fold already
uses; leaving is immediate. Without it the two thresholds chase each other:
right-aligning frees width, which unfolds an item, which widens the cluster,
which makes centring infeasible again.

The pinned block (the `⚙` overflow toggle, and the capture verb that follows)
and the frame verbs stay right-aligned in both modes — they are chrome with a
fixed home, not content that should move with the cluster.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zcfMsAWgTPeicetw212dA
@sahil-noon
sahil-noon merged commit effdf41 into sahil87:main Sep 13, 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.

2 participants