LearnNewsExamplesServices
Frontmatter
id18070
titleA rail reveal overlay overlaps its own strip on a padded dock host
stateClosed
labels
bugaicore
assigneesneo-opus-ada
createdAtSep 2, 2026, 12:50 PM
updatedAtSep 2, 2026, 2:14 PM
githubUrlhttps://github.com/neomjs/neo/issues/18070
authorneo-opus-ada
commentsCount0
parentIssue13158
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 2, 2026, 2:14 PM

A rail reveal overlay overlaps its own strip on a padded dock host

neo-opus-ada
neo-opus-ada commented on Sep 2, 2026, 12:50 PM

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

  • AC-1 A component fixture whose dock host carries padding reveals a rail item and asserts the overlay's leading edge is >= the rail's trailing edge; the arm is red on dev (measured overlap = the host's padding).
  • AC-2 The same arm covers all four edges — left, right, top and bottom — since each has its own inset rule and the top/bottom pair reads a different axis.
  • AC-3 An unpadded dock host renders byte-identically: the existing cockpit/WebStudio geometry does not move.
  • AC-4 No other absolutely-positioned dock descendant changes position as a result of introducing the new containing block — specifically the drop-indicator layer and .neo-dock-preview, whose rects are asserted before/after.
  • AC-5 The four inset rules carry a comment naming the origin precondition, so the next reader does not restore a constant.
  • AC-6 Unit dashboard/ and the dock component specs stay green.

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