Context
Operator-observed on apps/workstation and named as leaf L3 in the 2026-09-03 demo plan: revealing a rail item paints the preview over its own rail strip, so you cannot switch to another rail item without dismissing first. The engine's stated contract is the opposite — the overlay starts where the strip ends, which is what the Fleet cockpit and WebStudio show.
The drift is not in the consumer's stylesheet. Neither the Workstation's nor the cockpit's SCSS overrides the overlay insets (they set paint tokens only), and --dock-edge-rail-size is set nowhere under apps/. Measured live instead.
Measured (dev server, apps/workstation, dev@8c86f4478f, 2026-09-02 10:45Z, left rail revealed):
| element |
x |
right |
width |
containing block — .workstation-dock-host.neo-dashboard |
0 |
1280 |
1280 |
.neo-dashboard-dock-edge-rail-left |
12 |
26 |
14 |
.neo-dashboard-dock-reveal-overlay-left (inset: 0 auto 0 14px) |
14 |
462 |
448 |
The overlay covers 12 of the rail's 14px, leaving a 2px sliver, and it carries z-index: 20 with pointer-events: auto — so the remaining rail tabs are visually and functionally unreachable. That is exactly the reported symptom.
The Problem
.workstation-dock-host has padding: 12px. Two boxes then disagree, and the overlap equals that padding exactly:
- The rail is laid out in normal flow, so it starts inside the host's content box — offset by
padding-left, at x=12.
- The overlay is
position: absolute, so per CSS its containing block is the nearest positioned ancestor's padding box — which starts at x=0, before the padding.
The engine expresses the contract as a constant, inset-left: var(--dock-edge-rail-size), which is only equivalent to "where the strip ends" when the dock host has zero padding. The precondition is topological and is written down nowhere: the moment a consumer pads its dock host — an entirely ordinary thing to do — the rail moves inward and the overlay does not follow. overlap = host padding, capped at the rail's extent.
The cockpit and WebStudio are correct only because they happen not to pad. This is a latent engine defect that any consumer can trip, not a Workstation mistake.
Isolation test (live, reverted immediately — diagnostic only): setting position: relative on .neo-dashboard-dock-edge-zone, the box the rails actually lay out in, moved the overlay from x=14 to x=26 — flush with the rail's right edge, overlap 12 → 0.
The Architectural Reality
resources/scss/src/dashboard/Container.scss:98 — --dock-edge-rail-size: 14px, declared on .neo-dashboard.
:353 / :362 — reader one: the rail's own width / height.
:489–492 — reader two: &-left { inset: 0 auto 0 var(--dock-edge-rail-size) } and the three sibling edges.
- #17614 tokenised these six literals precisely because the two readers are coupled and "one boot cannot distinguish coupled from two literals that agree today" (#17211's Contract Ledger). The coupling it fixed was the value; this ticket is about the origin the second reader measures from.
- DOM: the overlay is a child of the rail, but the rail is
position: static, so the overlay's containing block resolves past it to .workstation-dock-host (position: relative). .neo-dashboard-dock-edge-zone and .neo-dashboard-dock-edge-row sit between them, both at x=12, both position: static.
- ADR 0029 §2.7 requires the overlay be "anchored to its edge" and "Overlay, never push". Anchoring to the host edge rather than the strip edge satisfies neither the letter of "anchored to its edge" nor the rail's usability.
The Fix
Give the reveal overlay a containing block whose padding box coincides with the box the rails are laid out in, so the existing inset: … var(--dock-edge-rail-size) rules become correct on every edge without a new token and without arithmetic:
resources/scss/src/dashboard/Container.scss — make .neo-dashboard-dock-edge-zone (or the tightest ancestor that is common to the rail and the overlay and is unaffected by host padding) the positioned containing block, and state the precondition in a comment beside the four inset rules: these measure from the strip's own origin, not the dashboard's.
Rejected alternative: calc(var(--dock-edge-rail-size) + <host padding>). It needs a token no consumer sets, re-introduces exactly the two-values-that-agree-today coupling #17614 removed, and is wrong again the moment padding is asymmetric.
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
.neo-dashboard-dock-reveal-overlay-<edge> per-edge inset |
Container.scss:489–492; the coupling contract in #17211 / #17614 |
the overlay's leading edge coincides with the rail strip's trailing edge on all four edges, independent of any padding on the dock host |
a consumer with an unpadded dock host renders byte-identically (today's cockpit/WebStudio result is the correct one and must not move) |
comment beside the four inset rules naming the origin precondition |
component arm on a padded-host fixture, red on dev |
Decision Record impact: aligned-with ADR 0029 (§2.7 auto-hide reveal contract). No amendment — this restores the behaviour §2.7 already requires.
Acceptance Criteria
Out of Scope
- The reveal's size (
defaultRevealFraction, the physical floors) — settled by #18028 / PR #18038.
- Rail paint tokens (#17633) and the reveal overlay's own border/shadow.
- The rail-reveal dismissal paths (#18067 covers the iframe case).
- Any change to
--dock-edge-rail-size's value or its two existing readers (#17614).
Avoided Traps
- Fixing it in the Workstation's stylesheet. The consumer is doing nothing unusual; padding a container is ordinary. A consumer-side correction would leave the next padded host broken and would put an app rule back into the equal-specificity tie the engine comment at
Container.scss:370 warns about.
- Removing the host's padding. That trades a real layout decision for a workaround and hides the engine defect.
- Treating this as the same defect as #18028. That one was the preview's extent (too small, transparent); this is its origin. Same element, different property, and #18028's fix is not implicated.
Related
#13158 (parent epic — QT-parity docking polish) · #17614 (tokenised --dock-edge-rail-size; established the two-reader coupling) · #17211 (its Contract Ledger) · #18028 / PR #18038 (reveal extent and opacity) · #17633 (rail paint tokens) · #18067 (rail reveal dismissal inside an iframe) · ADR 0029 §2.7
Live latest-open sweep: checked the latest 20 open issues at 2026-09-02T10:49Z; no equivalent found. A2A in-flight claim sweep over the last 30 messages, all read-states: no competing [lane-claim]/[lane-intent] on rail-preview geometry (my own [lane-intent] at 10:42Z is the earliest claim; @neo-fable announced the L4 tooltips leaf at 10:49Z, a different leaf). Memory Core rationale sweep on the symptom nouns: nearest prior art is #18028 (extent) and #17890 (drag-overlay colour), neither equivalent. Structure-map gate §1c: N/A — engine SCSS, no ai/ touch. Structural pre-flight: N/A — no new .mjs file.
Origin Session ID: 7596538d-5324-419a-b043-95c585d833ea
Retrieval Hint: "dock rail reveal overlay inset containing block padding box host padding overlap"
⚖️ Ada · @neo-opus-ada · Claude Opus 5 · Claude Code
Context
Operator-observed on
apps/workstationand named as leaf L3 in the 2026-09-03 demo plan: revealing a rail item paints the preview over its own rail strip, so you cannot switch to another rail item without dismissing first. The engine's stated contract is the opposite — the overlay starts where the strip ends, which is what the Fleet cockpit and WebStudio show.The drift is not in the consumer's stylesheet. Neither the Workstation's nor the cockpit's SCSS overrides the overlay insets (they set paint tokens only), and
--dock-edge-rail-sizeis set nowhere underapps/. Measured live instead.Measured (dev server,
apps/workstation,dev@8c86f4478f, 2026-09-02 10:45Z, left rail revealed):.workstation-dock-host.neo-dashboard.neo-dashboard-dock-edge-rail-left.neo-dashboard-dock-reveal-overlay-left(inset: 0 auto 0 14px)The overlay covers 12 of the rail's 14px, leaving a 2px sliver, and it carries
z-index: 20withpointer-events: auto— so the remaining rail tabs are visually and functionally unreachable. That is exactly the reported symptom.The Problem
.workstation-dock-hosthaspadding: 12px. Two boxes then disagree, and the overlap equals that padding exactly:padding-left, at x=12.position: absolute, so per CSS its containing block is the nearest positioned ancestor's padding box — which starts at x=0, before the padding.The engine expresses the contract as a constant,
inset-left: var(--dock-edge-rail-size), which is only equivalent to "where the strip ends" when the dock host has zero padding. The precondition is topological and is written down nowhere: the moment a consumer pads its dock host — an entirely ordinary thing to do — the rail moves inward and the overlay does not follow.overlap = host padding, capped at the rail's extent.The cockpit and WebStudio are correct only because they happen not to pad. This is a latent engine defect that any consumer can trip, not a Workstation mistake.
Isolation test (live, reverted immediately — diagnostic only): setting
position: relativeon.neo-dashboard-dock-edge-zone, the box the rails actually lay out in, moved the overlay from x=14 to x=26 — flush with the rail's right edge, overlap 12 → 0.The Architectural Reality
resources/scss/src/dashboard/Container.scss:98—--dock-edge-rail-size: 14px, declared on.neo-dashboard.:353/:362— reader one: the rail's ownwidth/height.:489–492— reader two:&-left { inset: 0 auto 0 var(--dock-edge-rail-size) }and the three sibling edges.position: static, so the overlay's containing block resolves past it to.workstation-dock-host(position: relative)..neo-dashboard-dock-edge-zoneand.neo-dashboard-dock-edge-rowsit between them, both at x=12, bothposition: static.The Fix
Give the reveal overlay a containing block whose padding box coincides with the box the rails are laid out in, so the existing
inset: … var(--dock-edge-rail-size)rules become correct on every edge without a new token and without arithmetic:resources/scss/src/dashboard/Container.scss— make.neo-dashboard-dock-edge-zone(or the tightest ancestor that is common to the rail and the overlay and is unaffected by host padding) the positioned containing block, and state the precondition in a comment beside the four inset rules: these measure from the strip's own origin, not the dashboard's.Rejected alternative:
calc(var(--dock-edge-rail-size) + <host padding>). It needs a token no consumer sets, re-introduces exactly the two-values-that-agree-today coupling #17614 removed, and is wrong again the moment padding is asymmetric.Contract Ledger
.neo-dashboard-dock-reveal-overlay-<edge>per-edgeinsetContainer.scss:489–492; the coupling contract in #17211 / #17614devDecision Record impact:
aligned-with ADR 0029(§2.7 auto-hide reveal contract). No amendment — this restores the behaviour §2.7 already requires.Acceptance Criteria
>=the rail's trailing edge; the arm is red ondev(measured overlap = the host's padding)..neo-dock-preview, whose rects are asserted before/after.dashboard/and the dock component specs stay green.Out of Scope
defaultRevealFraction, the physical floors) — settled by #18028 / PR #18038.--dock-edge-rail-size's value or its two existing readers (#17614).Avoided Traps
Container.scss:370warns about.Related
#13158(parent epic — QT-parity docking polish) ·#17614(tokenised--dock-edge-rail-size; established the two-reader coupling) ·#17211(its Contract Ledger) ·#18028/ PR#18038(reveal extent and opacity) ·#17633(rail paint tokens) ·#18067(rail reveal dismissal inside an iframe) · ADR 0029 §2.7Live latest-open sweep: checked the latest 20 open issues at 2026-09-02T10:49Z; no equivalent found. A2A in-flight claim sweep over the last 30 messages, all read-states: no competing
[lane-claim]/[lane-intent]on rail-preview geometry (my own[lane-intent]at 10:42Z is the earliest claim;@neo-fableannounced the L4 tooltips leaf at 10:49Z, a different leaf). Memory Core rationale sweep on the symptom nouns: nearest prior art is #18028 (extent) and #17890 (drag-overlay colour), neither equivalent. Structure-map gate §1c: N/A — engine SCSS, noai/touch. Structural pre-flight: N/A — no new.mjsfile.Origin Session ID: 7596538d-5324-419a-b043-95c585d833ea
Retrieval Hint: "dock rail reveal overlay inset containing block padding box host padding overlap"
⚖️ Ada ·
@neo-opus-ada· Claude Opus 5 · Claude Code