Fix prompt rail pane anchoring and narrow-width access

Merged
TE
7d ago
+477-249

Pin the prompt rail to the pane’s left edge when a right pane closes, the transcript recenters, or Session Info shifts the chat. The rail gets a token-based 8–16 px inset and compresses before it covers text. When space is exhausted, a live edge strip and keyboard focus reveal the usable rail; leaving the rail and preview hides it again. The preview remains flush with the rail’s right edge.

#25800 made the rail and its portaled preview inherit the transcript’s animated inspector shift. Remove that coupling, measure pane width through the existing resize observer, and refresh overlap placement on scoped chat-layout changes. No new observer or per-frame layout work is added.

Leo, October 28:

Likewise, there's this odd stuff going on where sometimes the left scroll rail, it doesn't return to the left when, you know, the right pane closes and whatnot.

Lawrence, relayed by Leo:

needs more left padding

Leo’s placement clarification:

Like the... well, for one, the thing should be more, you know, should be sticky along the left instead of following the agent chat section, man.

Leo’s updated narrow-width requirement:

And when it is truly too small and it would be overlapping the text, then you can just hide it until the user hovers over it. And then when it's hovered over, then they can just work normally, and then when they leave the hover area, then just, you know, it hides. But it doesn't disappear entirely. There's still that functionality.

The gallery covers wide, compressed, occluded, hovered, and focused states using the actual component. The reference is the supplied pane-edge padding crop; private reference images are not published. Existing tick style, colors, click behavior, and full minimap mode are preserved.

Validation:

  • Real Chromium: 11 tests pass, including every painted frame during pane resize and inspector shifts, actual row-edge overlap, preview alignment, and hover/click/wheel/scrub access at a 160 px pane width.
  • The new placement/resize cases fail on base 22a2728ed33eabb65eef1493a227d738a2d2f372 (7 failures).
  • Narrow/tiny rail tests, inspector tests, gallery manifest, full web check, web-lint and prepush gate pass.
  • Local static checks pass. Localization parity fails identically on base; no catalogs changed.

Risk: compressed rail hit testing and pointer travel to the preview. Chromium exercises both. The leave grace is the shared hover-intent grace (HOVER_INTENT.cardGrace, 200 ms, off the motion scale): one-shot, cancelled on re-entry or focus, skipped under Reduce Motion or on a hidden page, and cleared on unmount and on a page hide (which also closes the card). While the rail is hidden, its hover strip ends at the text edge (never under the text), so text clicks, links and selection drags stay the text's. The scoped layout listener and the visibility listener (allow-listed in check-switch-fetches: reads no data) are removed by effect cleanup. Existing resize observer cleanup is unchanged.

Takeover (2026-10-29): the original lane stalled; this lane brought the branch up to date with its base, fixed web lint (memoized rail measure), and addressed two independent review rounds. Leo, 2026-10-29: "but how about fixing up the left side scroll rail thing (moving it to the left, giving it faster scrttoll/hover over) and being able to scroll it?" This PR is the moving-it-left part; scroll speed and wheel scrolling are on #27858 and #26520.

Browser captures of the actual app entry, using a synthetic transcript (before 22a2728, after 6828f3f). The vertical height difference comes from the separately landed rail-height change.

StateBeforeAfter
Wide · 1600 pxBeforeAfter
Medium · 1084 pxBeforeAfter
Compressed · 800 pxBeforeAfter
After right-pane closeBeforeAfter
160 px, hidden160 px, hovered with working preview
HiddenHovered

Takes back #25800: it made the rail and its preview card follow Session Info's chat shift; the settled spec (design/session-rail.md, "Settled position and gutter") pins the rail to the pane's left edge instead, so this PR removes that shared translate.
Takes back #27192: its gallery-only inset variants (and their unit test) were exploratory placements for the pending railPlacement/useRailPlacement seam; this PR is that seam, and the gallery entry now shows the shipped wide, compressed, hidden, hovered and focused states.

Performance

No perf numbers: this PR moves and compresses the rail; it adds no per-token or per-row work, no observer and no frame loop (the placement reuses the existing resize measurement and a scoped layout event). Rail scroll and hover speed are #27858.

Agent session: agent session