Sub-issue of #13158 (dock layouts). The second of the dock-polish leaves the demo plan named (L2 — reveal motion); siblings: #18070 (L3, the reveal's geometry on a padded host), #18071 (L4, header-action tooltips).
Context
Clicking a rail tab reveals the auto-hidden pane as an overlay next to the strip. Today the overlay appears: it fades from 40% opacity and moves 8px, over the shared dock duration. The operator's design read (2026-09-02, the demo rehearsal): a reveal should slide in from the edge that owns it — a right rail slides right-to-left while the overlay's opacity rises, in the order of 300ms — so the eye reads where the pane came from and where it will go back to.
The Problem
The motion exists but is a nudge, not a slide. Measured on dev@5ae9358063:
resources/scss/src/dashboard/Container.scss:150-170 — .neo-dashboard-dock-reveal-overlay { animation: neo-dock-reveal-fade var(--dock-transition-duration) var(--dock-transition-easing) } with the per-edge names the rail stamps (-left/-right/-top/-bottom); the keyframes start at opacity: 0.4 and translateX(±8px) / translateY(±8px).
Container.scss:14/20 — --dock-transition-duration: var(--motion-panel, 280ms), --dock-transition-duration-fast: var(--motion-fast, 120ms); :136-140 collapses both to 0ms under prefers-reduced-motion: reduce (the token layer owns reduced motion, per #14779).
src/dashboard/dock/interaction/RevealOverlay.mjs:192-214 — the overlay listens to its own animationend, holding one MotionSignal.enter/leave entry while the reveal animates (revealMotionActive); teardown settles the producer without an end event (:234).
- The hidden state is
display: none (Container.scss:472-474): there is no exit motion, and none is asked for here.
An 8px travel from 40% opacity over 280ms reads as a flicker at pane scale (the overlay is 12rem+ wide); the pane's origin is not legible.
The Architectural Reality
- #14779 established the dock motion contract: durations come from ONE token set (
--dock-transition-*), never literals at call sites; motion is observable (dock-animating / animationend-driven completion); reduced motion collapses at the token layer. The reveal already rides that contract through the overlay's animationend → MotionSignal pair — a longer or different keyframe keeps it as long as the animation still ends on the overlay element.
- The per-edge cls (
neo-dashboard-dock-reveal-overlay-<edge>) is stamped by the rail (RevealOverlay.mjs:291-294), so the direction is already known to CSS; the change is confined to keyframes and one token.
- ADR 0029 §2.6 (auto-hide interaction layer, #13280): the reveal is runtime-only geometry; motion does not touch the document.
- Not this leaf: where the overlay starts (touching the strip vs. covering it) is #18070's geometry; a reveal inside an iframe pane is #18067.
The Fix
resources/scss/src/dashboard/Container.scss:
- One new token in the existing set:
--dock-transition-duration-reveal: var(--motion-reveal, 300ms), collapsed to 0ms in the reduced-motion block beside its siblings.
- The overlay's
animation reads that token (easing stays --dock-transition-easing, the soft ease-out).
- The four per-edge keyframes start fully outside the overlay's own box and transparent:
from { opacity: 0; transform: translateX(100%) } for -right (-100% for -left, translateY(±100%) for -bottom / -top); the edge-less fallback keeps a fade from 0.
overflow on the reveal's host must clip the travel so the overlay never paints over the strip mid-flight — read the host rule before adding one; if the rail host already clips, no change.
No JavaScript changes are expected; if MotionSignal needs the animation name (it should not — it listens to animationend on the overlay), that is a finding to record, not to work around.
Contract Ledger Matrix
| Target surface |
Source of authority |
Proposed behavior |
Fallback |
Docs |
Evidence |
--dock-transition-duration-reveal (new token, Container.scss token block) |
#14779 (one token set, no literals) |
300ms via var(--motion-reveal, 300ms); 0ms under reduced motion |
absent token → the var() default |
ADR 0029 §2.6 one line |
component arm reads animation-duration |
@keyframes neo-dock-reveal-from-{left,right,top,bottom} (Container.scss:167-170) |
this ticket |
start at opacity: 0, translate*(±100%) |
— |
same line |
component arm samples the transform at t≈0 and settled |
.neo-dashboard-dock-reveal-overlay animation (Container.scss:153) |
#14779 |
duration from the new token |
— |
— |
same arm |
RevealOverlay animationend → MotionSignal (RevealOverlay.mjs:192-214) |
#14779 / #14929 |
unchanged; must still settle after the longer animation |
— |
— |
existing unit arm + the component arm's settle wait |
Decision Record impact: aligned-with ADR 0029 (§2.6 runtime-only reveal); amends nothing.
Acceptance Criteria
Out of Scope
- Exit motion (the hidden state is
display: none; a two-phase hide is its own leaf if wanted).
- The overlay's start geometry on padded hosts (#18070), iframe dismissal (#18067), header-action tooltips (#18071).
- Pin / restore transitions (FLIP layer, #14929).
Related
Parent: #13158. Motion contract: #14779; FLIP layer: #14929; auto-hide interaction layer: #13280 / ADR 0029 §2.6. Siblings: #18070, #18067, #18071. Consumer read: neomjs/neo-agent-institution (FM cockpit) and apps/workstation.
Live latest-open sweep: checked the latest 20 open issues at 2026-09-02T11:38Z (none on reveal motion; #18070 is geometry, #18067 dismissal, #18027 the maximize FLIP flash); A2A claims of the last hour: L3 (Ada), L4 (Mnemosyne), #18067 (Vega) — none on motion; ask_knowledge_base (ticket) surfaced #14779 / #13280 as the contract, no equivalent leaf; the local ticket corpus has no reveal-motion ticket.
Origin Session ID: 91f83b9c-df95-4f72-a68f-d33f470792ac
Retrieval Hint: "dock reveal overlay slide from edge keyframes --dock-transition-duration-reveal MotionSignal"
📜 Clio
Sub-issue of #13158 (dock layouts). The second of the dock-polish leaves the demo plan named (L2 — reveal motion); siblings: #18070 (L3, the reveal's geometry on a padded host), #18071 (L4, header-action tooltips).
Context
Clicking a rail tab reveals the auto-hidden pane as an overlay next to the strip. Today the overlay appears: it fades from 40% opacity and moves 8px, over the shared dock duration. The operator's design read (2026-09-02, the demo rehearsal): a reveal should slide in from the edge that owns it — a right rail slides right-to-left while the overlay's opacity rises, in the order of 300ms — so the eye reads where the pane came from and where it will go back to.
The Problem
The motion exists but is a nudge, not a slide. Measured on
dev@5ae9358063:resources/scss/src/dashboard/Container.scss:150-170—.neo-dashboard-dock-reveal-overlay { animation: neo-dock-reveal-fade var(--dock-transition-duration) var(--dock-transition-easing) }with the per-edge names the rail stamps (-left/-right/-top/-bottom); the keyframes start atopacity: 0.4andtranslateX(±8px)/translateY(±8px).Container.scss:14/20—--dock-transition-duration: var(--motion-panel, 280ms),--dock-transition-duration-fast: var(--motion-fast, 120ms);:136-140collapses both to0msunderprefers-reduced-motion: reduce(the token layer owns reduced motion, per #14779).src/dashboard/dock/interaction/RevealOverlay.mjs:192-214— the overlay listens to its ownanimationend, holding oneMotionSignal.enter/leaveentry while the reveal animates (revealMotionActive); teardown settles the producer without an end event (:234).display: none(Container.scss:472-474): there is no exit motion, and none is asked for here.An 8px travel from 40% opacity over 280ms reads as a flicker at pane scale (the overlay is 12rem+ wide); the pane's origin is not legible.
The Architectural Reality
--dock-transition-*), never literals at call sites; motion is observable (dock-animating/animationend-driven completion); reduced motion collapses at the token layer. The reveal already rides that contract through the overlay'sanimationend→MotionSignalpair — a longer or different keyframe keeps it as long as the animation still ends on the overlay element.neo-dashboard-dock-reveal-overlay-<edge>) is stamped by the rail (RevealOverlay.mjs:291-294), so the direction is already known to CSS; the change is confined to keyframes and one token.The Fix
resources/scss/src/dashboard/Container.scss:--dock-transition-duration-reveal: var(--motion-reveal, 300ms), collapsed to0msin the reduced-motion block beside its siblings.animationreads that token (easing stays--dock-transition-easing, the soft ease-out).from { opacity: 0; transform: translateX(100%) }for-right(-100%for-left,translateY(±100%)for-bottom/-top); the edge-less fallback keeps a fade from 0.overflowon the reveal's host must clip the travel so the overlay never paints over the strip mid-flight — read the host rule before adding one; if the rail host already clips, no change.No JavaScript changes are expected; if
MotionSignalneeds the animation name (it should not — it listens toanimationendon the overlay), that is a finding to record, not to work around.Contract Ledger Matrix
--dock-transition-duration-reveal(new token,Container.scsstoken block)var(--motion-reveal, 300ms);0msunder reduced motionvar()defaultanimation-duration@keyframes neo-dock-reveal-from-{left,right,top,bottom}(Container.scss:167-170)opacity: 0,translate*(±100%).neo-dashboard-dock-reveal-overlayanimation(Container.scss:153)RevealOverlayanimationend→MotionSignal(RevealOverlay.mjs:192-214)Decision Record impact: aligned-with ADR 0029 (§2.6 runtime-only reveal); amends nothing.
Acceptance Criteria
animation-nameis the owning edge's keyframe andanimation-durationis0.3s; sampled within the first frame its transform carries the edge-ward offset, settled it is the identity — a component spec undertest/playwright/component/dashboard/on an existing dock-rail fixture, one arm per horizontal edge at least, red ondev(today's transform at t≈0 is ≤ 8px).reducedMotion: 'reduce'emulated, the same read is0s(the token layer collapses it; no per-site check).MotionSignalleaves after the animation and the existing rail / overlay unit specs stay green (test/playwright/unit/dashboard/).apps/workstation(right rail and left rail), both themes: the pane slides in from its strip and never paints over the strip mid-flight; screenshots in the PR, design-read.Out of Scope
display: none; a two-phase hide is its own leaf if wanted).Related
Parent: #13158. Motion contract: #14779; FLIP layer: #14929; auto-hide interaction layer: #13280 / ADR 0029 §2.6. Siblings: #18070, #18067, #18071. Consumer read: neomjs/neo-agent-institution (FM cockpit) and
apps/workstation.Live latest-open sweep: checked the latest 20 open issues at 2026-09-02T11:38Z (none on reveal motion; #18070 is geometry, #18067 dismissal, #18027 the maximize FLIP flash); A2A claims of the last hour: L3 (Ada), L4 (Mnemosyne), #18067 (Vega) — none on motion;
ask_knowledge_base(ticket) surfaced #14779 / #13280 as the contract, no equivalent leaf; the local ticket corpus has no reveal-motion ticket.Origin Session ID: 91f83b9c-df95-4f72-a68f-d33f470792ac
Retrieval Hint: "dock reveal overlay slide from edge keyframes --dock-transition-duration-reveal MotionSignal"
📜 Clio