Agent chat: bookmarked rail ticks stay grey (ChatGPT's states); ticks ease in with a short stagger
Leo, 2026-10-25: "when I bookmark something, it shouldn't be just at the full brightness ... It should be, like, grayed out. That's how ChatGPT does it ... the animation ... those sort of lines pop up. There should be a little bit more easing".
Items I and J of the rail push are both small, so they ship as one PR. The design spec (design/session-rail.md) is a separate leo-workspace PR.
What changes
I. The four tick states, with ChatGPT's states and our tokens. ChatGPT's rail draws a bookmarked tick grey with a small grey dot, and the current one as a long bright line. Ours drew the bookmarked tick and dot at full brightness, the brightest thing on the rail.
| state | tick length | colour | dot | on hover |
|---|---|---|---|---|
| rest | 6 px | --agent-muted at 40% | none | text, 100%, fisheye |
| current (on screen) | 6 px | --agent-text at 75% (--acpmux-minimap-lit) | none | text, 100% |
| bookmarked | 6 px | --agent-muted at 70% (--acpmux-minimap-mark) | 2 px, same grey | text, 100%, tick and dot, at once |
| hovered / focused | 26 px (fisheye 20/14/10) | --agent-text at 100% | if marked, text 100% | n/a |
A bookmarked prompt on the lit band stays lit like its neighbours, and its dot takes the lit opacity. The full-height map gets the same grey step. Every colour is a theme token with an opacity; nothing is a hard-coded grey.
Gallery. There are four bookmarked looks, as experiment thread-minimap-mark (mark.experiment.ts) with arms grey-55, grey-70 (picked), dot-only and full (the old look). The new gallery entry agent-pane.thread-minimap-mark shows rest, a hovered bookmarked tick and a hovered resting tick for each arm. I picked grey-70: the mark reads clearly over rest (40%) and stays under the lit band (75%) and hover (100%). At 55% the dot was hard to find, and dot-only lost the tick.
J. The entrance eases in. Before, ticks appeared with only the rail's single 120 ms fade, so each new line popped in. Now each tick's bar grows from the rail's edge on the existing hover spring (--acpmux-minimap-spring, with a cubic-bezier(0.25,1,0.5,1) fallback) and fades in over --cmux-motion-micro (120 ms). The dot fades in ease-out. The stagger follows the row's place among the rows on screen: clamp(0, (row - first) * 72ms / rows, 72ms). That caps it at 72 ms, so the whole rail is in by 192 ms. Rows above the view start at once.
- Stop condition and cleanup: it is a CSS animation that runs once per mounted tick and stops at its end. There is no timer, observer or JS loop, so nothing needs cleanup. Re-renders do not restart it (the keys are stable).
- Reduce Motion: the entrance rules sit inside
@media (prefers-reduced-motion: no-preference), so turning the system setting on while the rail is mounted stops the entrance for any tick added afterwards. (Review round 1: the entrance selector outranked the reduce override, and the rail's owndata-reduceis read only at mount, hover and focus.) It is also off under:root[data-cmux-motion="reduced"|"off"]and under the rail'sdata-reduce. The rail now readsreducedMotion()at mount as well, so the very first paint respects the app's gate. - No new per-token work. The only additions are three CSS variables on the list and rail, and one per tick (its index).
Files: threadMinimap.css (bookmark rules, entrance keyframes), ThreadMinimap.tsx (CSS vars, reduce initial state), mark.experiment.ts, threadMinimapMark.gallery.ts, registry.ts, and the test harness (a bookmark transport).
Tests
New: test/thread-minimap-tick-states.test.ts (Chromium, real pane harness).
- The bookmarked tick has the same computed colour as a resting tick, with opacity above rest and below 0.75. Its dot matches. Hovered, it becomes the text at opacity 1. Fails on base (opacity 1).
- Every tick's entrance has a non-linear easing and is past 70% at half its duration. Delays never decrease down the rail, the largest is ⤠80 ms, and delay plus duration is ⤠200 ms. Fails on base (no tick animation).
- No tick animation under the system's Reduce Motion, and none under
data-cmux-motion="reduced". - Reduce Motion switched on after mount (
page.emulateMedia), then a new prompt added: the new tick has no entrance animation, while the rail'sdata-reduceis still unset. Fails at b4162805 (1 animation), passes now. - The state test now waits until nothing is running over two painted frames before reading colours. It had once caught a resting tick in the middle of the lit band's colour glide. Three runs in a row pass 5/5.
Also run: the thread-minimap jsdom suites (53 pass), typecheck, check:design-tokens, check:motion-scale, check:layers. gallery-coverage fails the same two ways on base (turn-error-card), so that red is not from this PR.
Captures (pane harness, dark reference theme; prompts 4 and 13 bookmarked)
Each strip shows rest, then hovering bookmarked prompt 13, then hovering resting prompt 16.
Before (bookmarked ticks and dots at full brightness):

After (grey-70: bookmarked ticks grey with a grey dot; they turn the text only on hover):

Bookmarked looks in the gallery, from left: grey-55, grey-70 (picked), dot-only, full (the old look):

Mid-entrance, 70 ms in (upper rows nearly full length, lower rows still growing and fading):

Risk
Low, visual only. Bookmarks are dimmer at rest, by design. The entrance replays when a tick remounts, for example after a tab switch remounts the transcript; it is ⤠192 ms and eased. Textual overlap with #24525, #24131 and #24137 (same files, different rules), so a rebase may be needed.
Agent session: huge-blue
š¤ Generated with Claude Code