Context
Part of #628 — chat UI polish.
Blocked by #630 (persistent dot overlay must exist before this slice can land).
This slice adds the visual finishing layer on top of the overlay structure: a continuous rail line for the active turn, left inset alignment, and a crossfade transition when the turn completes.
Acceptance Criteria
Key Decisions
| Decision |
Rationale |
| Overlay draws the rail line for active turns |
A single <span> from overlay top to bottom — no per-row segments needed while streaming, eliminates gap math |
| Per-row rail segments only for completed turns |
Once a turn is done, it uses the existing static per-row lines (no overlay exists for it anymore) |
md:pl-3 on TimelineRowFrame |
Simplest fix — shifts content rightward at medium breakpoints, aligning rail gutter inside the composer interior |
Crossfade via data-state="completed" + CSS transition |
On turn completion, overlay gets attribute; CSS opacity: 0 over 150ms ease-out; after transitionend, remove overlay DOM |
| Rail and dot share timing (250ms same bezier) |
Move in lockstep — the rail height extends to the new dot position at the same rate the dot slides |
Testing Decisions
- Crossfade timing: overlay opacity reaches 0 within 150ms of turn completion (verify via
transitionend event or computed style)
- Rail inset: at md+ viewport, rail line left position is > 0 relative to the compositor visible boundary
- No seams: during a streaming turn, only one continuous line element exists for the active turn (not N per-row segments)
- Lockstep: rail height transition and dot top transition use identical
transition declaration
Constraints & Invariants
Prior Art
Context
Part of #628 — chat UI polish.
Blocked by #630 (persistent dot overlay must exist before this slice can land).
This slice adds the visual finishing layer on top of the overlay structure: a continuous rail line for the active turn, left inset alignment, and a crossfade transition when the turn completes.
Acceptance Criteria
prefers-reduced-motiondisables crossfade (instant swap)Key Decisions
<span>from overlay top to bottom — no per-row segments needed while streaming, eliminates gap mathmd:pl-3on TimelineRowFramedata-state="completed"+ CSS transitionopacity: 0over 150ms ease-out; aftertransitionend, remove overlay DOMTesting Decisions
transitionendevent or computed style)transitiondeclarationConstraints & Invariants
transitionendcleanup must be resilient to rapid turn succession (if a new turn starts before fade completes, cancel the outgoing fade and destroy immediately)LINE_Xconstant as per-row segmentsPrior Art
packages/app/src/pages/session/timeline/thought-rail.tsx— current per-row rail segments with negative-top bridging,LINE_Xconstantpackages/app/src/index.css— current[data-slot="thought-rail-line"]transition (150ms ease-out)