Sub-issue of #13158 (dock layouts). The operator's exploration request on the Workstation, 2026-09-02 15:50Z, after the exit motion (#18116) landed on the branch: "if we click on one rails item, and then directly (before closing) on a different rails item (same rail) => this could trigger a 'sliding cross-fade'. done right, a lot of eye candy."
What happens today
RevealStateMachine: revealed* + tabClick(other item) → revealed-focused(other) — the reveal retargets, it does not close and reopen. The overlay's afterSetRevealedItem runs syncRevealMotion (no visibility boundary is crossed, so no motion window opens) and syncSnapshot (the title swaps); the rail's pane sync swaps the slot's content (revealOverlay.paneSlot.add(...) from its pane cache). Net effect: the header title and the pane change in one frame while the overlay root stays where it is. Nothing slides, nothing fades.
Two tiers
T1 — the incoming pane slides in from the edge (small, demo-safe). On an item change while visible, re-run the entry keyframes on the header and the pane slot: the root stays (no fade), the new content emerges from under the strip exactly as a fresh reveal does. Mechanism: a CSS animation restarts when its animation-name changes, so the overlay alternates a swap generation class (…-swap-0 / …-swap-1, two identical copies of the per-edge keyframes) on each retarget — no reflow tricks, no timers, the same token duration as the entry. The outgoing pane is under the incoming slide from its first frame (the slot is one element, so the swap itself is instant), which reads as "the new pane slides over the old". The motion window: beginRevealMotion on the swap, settled by the root's end event as today (the root can carry a 0-travel keyframe so its own end fires) or by the slot's end with target identity extended to the slot.
T2 — a true sliding cross-fade (design, post-demo). The outgoing pane stays rendered for one duration, fading and travelling toward the strip while the incoming slides in over it. That needs two layers in the overlay: an outgoing layer that keeps the previous pane mounted (a reparent of a live component — an iframe pane reloads on reparent, a grid does not) or a visual stand-in (a snapshot is not available to the worker). The honest T2 question is which panes may be reparented for a quarter second, and whether the reveal's pane cache (revealPaneCache) already keeps the outgoing instance alive so only its DOM position changes. #13280's motion contract applies: tokens only, neo-dashboard-dock-animating observable, reduced motion collapses through the token.
Acceptance Criteria (T1)
Out of Scope (T1)
- T2's outgoing layer — its own leaf once the reparent question is answered.
- Retargets across different rails (a new reveal on another edge is a fresh entry already).
Related
#18116 (the exit; T1 stacks on it), #18074 / PR #18079 (the entry), #13280 (the motion contract).
Live latest-open sweep at 2026-09-02T15:53Z: no retarget-motion ticket; A2A: none; Knowledge Base: #13280 and #13158 only.
Authored by Mnemosyne (@neo-fable, Claude Fable 5.1, Claude Code) · session 37bbc610-f0cd-4abb-95c2-eb70336a86f3
Sub-issue of #13158 (dock layouts). The operator's exploration request on the Workstation, 2026-09-02 15:50Z, after the exit motion (#18116) landed on the branch: "if we click on one rails item, and then directly (before closing) on a different rails item (same rail) => this could trigger a 'sliding cross-fade'. done right, a lot of eye candy."
What happens today
RevealStateMachine:revealed*+tabClick(other item)→revealed-focused(other)— the reveal retargets, it does not close and reopen. The overlay'safterSetRevealedItemrunssyncRevealMotion(no visibility boundary is crossed, so no motion window opens) andsyncSnapshot(the title swaps); the rail's pane sync swaps the slot's content (revealOverlay.paneSlot.add(...)from its pane cache). Net effect: the header title and the pane change in one frame while the overlay root stays where it is. Nothing slides, nothing fades.Two tiers
T1 — the incoming pane slides in from the edge (small, demo-safe). On an item change while visible, re-run the entry keyframes on the header and the pane slot: the root stays (no fade), the new content emerges from under the strip exactly as a fresh reveal does. Mechanism: a CSS animation restarts when its
animation-namechanges, so the overlay alternates a swap generation class (…-swap-0/…-swap-1, two identical copies of the per-edge keyframes) on each retarget — no reflow tricks, no timers, the same token duration as the entry. The outgoing pane is under the incoming slide from its first frame (the slot is one element, so the swap itself is instant), which reads as "the new pane slides over the old". The motion window:beginRevealMotionon the swap, settled by the root's end event as today (the root can carry a0-travel keyframe so its own end fires) or by the slot's end with target identity extended to the slot.T2 — a true sliding cross-fade (design, post-demo). The outgoing pane stays rendered for one duration, fading and travelling toward the strip while the incoming slides in over it. That needs two layers in the overlay: an outgoing layer that keeps the previous pane mounted (a reparent of a live component — an iframe pane reloads on reparent, a grid does not) or a visual stand-in (a snapshot is not available to the worker). The honest T2 question is which panes may be reparented for a quarter second, and whether the reveal's pane cache (
revealPaneCache) already keeps the outgoing instance alive so only its DOM position changes. #13280's motion contract applies: tokens only,neo-dashboard-dock-animatingobservable, reduced motion collapses through the token.Acceptance Criteria (T1)
-hidden, never-leaving), the title reads B, and the header + pane slot carry a running entry animation after the retarget; red ondev(no animation after the retarget).DockRevealOverlay.spec.mjs): an item change while visible flips the swap generation and opens one motion window; an item change while hidden does not.Out of Scope (T1)
Related
#18116 (the exit; T1 stacks on it), #18074 / PR #18079 (the entry), #13280 (the motion contract).
Live latest-open sweep at 2026-09-02T15:53Z: no retarget-motion ticket; A2A: none; Knowledge Base: #13280 and #13158 only.
Authored by Mnemosyne (@neo-fable, Claude Fable 5.1, Claude Code) · session 37bbc610-f0cd-4abb-95c2-eb70336a86f3