Context
Reported by @tobiu on 2026-08-30 against apps/workstation: the drop-zone overlays for top, bottom and centre read correctly — a green border over a slightly tinted fill — while left and right render a far darker background, "almost looks like the full background gets the border color with no transparency."
He also asked why the e2e suite did not catch it. That half is answered below and is the more actionable finding.
Live latest-open sweep: latest 20 open issues checked at 2026-08-30T18:21:10Z; no equivalent. Structure-map gate: N/A, no ai/ surface and no new .mjs outside test/.
Why nothing caught it — three layers, each independently sufficient
| layer |
what it asserts |
why it cannot fail here |
test/playwright/e2e/** |
— |
e2e runs in no workflow at all. No test-e2e / playwright.config.e2e reference exists under .github/workflows. It has never executed in CI |
e2e/workstation/WorkstationDockPreviewSymmetryNL.spec.mjs |
"compares the four thicknesses directly" (its own header) |
geometry only — structurally incapable of failing on a fill or opacity defect, even if it ran |
unit/dashboard/DockPreview.spec.mjs |
fs.readFileSync(Preview.scss) + expect(scss).toContain(…) |
asserts the stylesheet as source text. A test that greps its own SCSS never observes what a browser composites |
Nothing in the suite renders an affordance and reads a computed colour. That is the defect this ticket must close, independent of the visual cause.
What has been measured, and eliminated
All measured against the running workstation, driving the live dock-preview instance over RMA and reading getComputedStyle:
| candidate |
result |
| per-edge stylesheet asymmetry |
eliminated — all four edges compute rgba(94,234,212,0.2) fill, identical border colour, opacity: 0.85, one 4px cut side |
split group hitting the solid-bar rule |
eliminated — split-before/split-after also carry region and paint the same translucent fill |
| wrong per-edge classes |
eliminated — each edge carries its own edge-<dir> and the correctly inverted cut-<dir> |
| more than one preview host stacking |
RETRACTED 2026-08-30 — this row was scoped too narrowly. It searched for duplicate .neo-dock-preview hosts and found one. It never searched for a different overlay painting the same region, so it cannot support "nothing else stacks here". See candidate 2 below |
| affordance nodes accumulating |
eliminated — Preview.mjs:374 replaces vdom.cn with a single-node array |
the neo-preview-lang-signal skin |
eliminated — the modifier is not active on this app |
| edge-band tokens using the wrong axis |
eliminated — left/right use inline-size, top/bottom use block-size, correctly |
splitter currentColor divergence |
eliminated — every splitter resolves rgb(240,242,240) and the same background in both orientations |
So the drop-preview overlay is not what renders dark. Something else paints during a live left/right drag.
CORRECTED 2026-08-31 — this conclusion was wrong, and the table above is why. Every row was measured against a current checkout, so the whole table answers "does HEAD paint asymmetrically" — not "what was @tobiu's browser painting". The drop-preview overlay is what rendered dark; it was applying a stale compiled stylesheet the source no longer matched. A source-vs-source elimination cannot see a source-vs-build skew. See the root cause section below.
The two surviving candidates (2026-08-30)
Candidate 1 — a split affordance reaching the solid-bar rule. .neo-dock-preview-split:not(.neo-dock-preview-region) paints the accent colour as a solid background. Mutating getAffordanceVdom's region predicate to group === 'edge' reproduces the report exactly:
Expected: "rgba(94, 234, 212, 0.2)"
Received: "rgb(94, 234, 212)"
rgb(94, 234, 212) is the accent at full opacity — "the full background gets the border color with no transparency", verbatim. Against it: resultRegionPreviews defaults true and no caller in apps/workstation sets it false, so the path that would drop the class is unidentified.
Candidate 2 — DockDropIndicators, a second overlay. apps/workstation/view/Workspace.mjs:462 mounts it as a persistent sibling of the preview. Container.scss:493 paints .neo-dashboard-dock-drop-indicator / -chip with var(--dock-preview-ground, rgb(28 33 40 / 92%)) — a near-opaque dark ground — inside a container at z-index: 30, inset: 0. A dark chip overlapping the left/right region but not the top/bottom one reads exactly as "a WAY darker background overlay".
Left/right resolve as in-pane split, top/bottom as root edge; either candidate can ride that asymmetry. The discriminator is elementsFromPoint under the pointer during a held drag, reading every layer's computed background — candidate 1 shows one node whose own fill lost its alpha, candidate 2 shows two stacked nodes.
Live held-drag measurement — 2026-08-31 (candidate 1 eliminated; defect DID NOT reproduce)
The held-drag reading AC-3 asks for has now been taken, against the running workstation with a real 1600×1000 viewport. Method: real pointerdown → incremental pointermove → read getComputedStyle while the button is still down → cancel.
| zone |
affordance classes |
computed fill |
opacity |
rect |
| LEFT (in-pane) |
split split-before accepted **region** cut-right |
rgba(94, 234, 212, 0.2) |
0.85 |
333×688 — left half |
| TOP (root) |
edge edge-top accepted **region** cut-bottom |
rgba(94, 234, 212, 0.2) |
0.85 |
665×344 — top half |
Identical fill, identical opacity, correct half-region geometry on both. The reported divergence did not reproduce in this environment (theme neo-theme-neo-dark, dev server, resultRegionPreviews: true read live off neo-dock-preview-1).
- Candidate 1 — ELIMINATED. A live in-pane split does carry
neo-dock-preview-region, so it never reaches the solid-bar rule. The mutation still reproduces the symptom exactly, which means the rule is the right description of what @tobiu saw — but nothing in the live config path drives a split there.
- Candidate 2 — not supported as reported. The indicators exist and their chips do compute a near-opaque ground (
rgba(26,33,44,0.92) dark / rgba(247,249,252,0.94) light), but the set is symmetric: 5 indicators (center/top/right/bottom/left) + 4 chips (top/right/bottom/left), all -off when idle. No left/right-only overlay.
- Theme masking — eliminated. Both scopes define the tokens with no fallback: dark
#5eead4 / rgba(94,234,212,0.20); light #0f766e / rgba(15,118,110,0.14).
Preview.scss:54 is the only rule in the repo that paints the accent as a solid background, and it is reachable only via a split without region, or via tab-before / tab-after. So whatever @tobiu saw came through one of those two doors.
Latent defect found on the way (real, separate from the report)
.neo-dock-preview-split:not(.neo-dock-preview-region),
.neo-dock-preview-tab-before,
.neo-dock-preview-tab-after {The :not(.neo-dock-preview-region) guard binds to the first selector only. tab-before/tab-after are unguarded — and getAffordanceVdom never adds region for the tab group, so today they always paint solid, which is the intended thin-bar presentation. It is correct by accident: if tab placements ever gain region mode, this rule silently paints a full region solid. Worth pinning with an arm regardless of the reported bug.
✅ ROOT CAUSE — 2026-08-31: a STALE COMPILED THEME CSS, not a code defect
Full zone-grammar sweep (one held drag, sampling every zone) in a current checkout: all nine zones compute rgba(94,234,212,0.2) at opacity: 0.85. Nothing paints solid. It confirms the ticket's placement asymmetry — pane left/right resolve to split-before/split-after, pane top/bottom to edge-top/edge-bottom, centre to tab-into — but every one of them lands in the translucent family.
The divergence was the JS and the CSS coming from different revisions.
60592e6a85 ("Split previews paint the resulting region, not the cut line", #17870) landed 08-30 08:43 and did two things: added the region class + :not(.neo-dock-preview-region) guard, and changed split geometry from a 6px line to a half-pane region. @tobiu reported at ~18:00; his dist/ theme CSS was not rebuilt until 23:56, hours after the report.
In dev the JS is served from source, so getAffordanceVdom was already emitting region and the new region-sized geometry — while the browser still had the pre-60592e6a85 stylesheet, which read:
.neo-dock-preview-edge, .neo-dock-preview-tab-into { … accept-FILL (translucent) }
.neo-dock-preview-split, .neo-dock-preview-tab-* { … accept (SOLID) } Zone for zone, that reproduces the report exactly:
| reported |
resolves to |
old CSS |
matches report |
| top / bottom "look nice" |
edge-* |
translucent |
✅ |
| full zones "look nice" |
tab-into |
translucent |
✅ |
| left / right "WAY darker… border color with no transparency" |
split-* |
SOLID accent |
✅ |
The old rule had no region selector, so the new class was inert; the new geometry meanwhile made splits paint a half-pane block, so the solid accent covered a large area instead of a 6px guide. Hence "a WAY darker background overlay" rather than a bar.
Confirmed fixed: @tobiu's compiled dist/development/css/src/dashboard/dock/interaction/Preview.css is now byte-identical to a current build (4 :not(region) guards), which is precisely why the defect could not be reproduced.
The real lesson — a cross-artifact contract with no guard
60592e6a85 was a coupled change: a JS class/geometry change whose correctness depends on a separately built stylesheet. Nothing fails when only one half is current — the app silently renders the old visual grammar against new geometry. That is the actual defect class worth owning, and it is not specific to this commit.
Reframed remaining work
- The reported visual bug needs no code fix; it is a rebuild.
- The coverage gap is still real and still worth closing (PR #17893) — and note it would NOT have caught this, since the suite builds its own CSS. Recorded so nobody claims otherwise.
- The stale-build hazard deserves its own ticket rather than being smuggled in here.
The instrument the remaining work needs
Every measurement above was taken with the overlay driven directly, because the affordance only exists while a drag is held open — and a completed drag destroys it before it can be read.
test/playwright/component/dashboard/DockSplitterIframePane.spec.mjs already proves the technique: page.mouse.down(), move, measure while the button is still down, then up(). Applying that to a dock drag and reading every painted layer under the pointer — not just the affordance — is what will name the cause.
The Fix
- Land the missing coverage (this ticket's committed deliverable):
test/playwright/component/dashboard/DockPreviewRegionRender.spec.mjs mounts the real renderer, drives one affordance per edge plus both split positions through the real config, and asserts computed fill, border and opacity equality — the property no existing layer observes. It runs in the components suite, which does run in CI.
- Extend it to a held drag so the measurement covers what a user actually triggers, and read every layer under the pointer rather than the affordance alone.
- Fix whatever that names.
Acceptance Criteria
Restated 2026-08-31 after the root cause landed, following a reviewer intake gate on PR #17893 that Advances #17890 is not a valid close target. AC-3 and AC-4 as originally written presumed a code defect that does not exist: the reported divergence was a stale compiled theme CSS, not a rendering bug (see ROOT CAUSE above). Leaving them as-is would have made this ticket permanently uncloseable against a cause that was never real. The originals are struck through with what actually satisfied them, so nothing is quietly dropped.
Not in this ticket, and deliberately so: the latent :not(...) selector-binding trap recorded above is real but unrelated to the report — it needs its own leaf rather than riding this one to green.
Out of Scope
- Wiring e2e into CI. It is the root of layer 1 and outlives this bug; it needs its own ticket rather than being smuggled in here.
- #17889 (Pointer Events migration) and #17883 (iframe drag freeze) — adjacent drag work, different defects.
Avoided Traps
- Trusting the symmetric stylesheet as proof of a symmetric render. The rules are provably identical; the reported defect is still real. Reading source and reading a composited pixel are different measurements, and this ticket exists because the suite only ever did the former.
- Asserting the fill by grepping the SCSS. That is precisely how the current unit coverage misses it.
- Measuring a completed drag. The overlay is gone by then; the measurement must be taken with the button down.
Related
- #17883 / PR #17887 — iframe drag freeze; same surface, different defect.
- #17889 — Pointer Events + capture migration.
- #17836 — DockLayouts rebuild.
Handoff Retrieval Hints
Retrieval Hint: "left/right dock drop overlay renders darker, stylesheet symmetric, no test reads a computed overlay colour"
Retrieval Hint: resources/scss/src/dashboard/dock/interaction/Preview.scss; src/dashboard/dock/interaction/Preview.mjs:374
Origin Session ID: 7fc710e4-f796-47bc-97b7-b61d1414b3a3
Context
Reported by @tobiu on 2026-08-30 against
apps/workstation: the drop-zone overlays for top, bottom and centre read correctly — a green border over a slightly tinted fill — while left and right render a far darker background, "almost looks like the full background gets the border color with no transparency."He also asked why the e2e suite did not catch it. That half is answered below and is the more actionable finding.
Live latest-open sweep: latest 20 open issues checked at 2026-08-30T18:21:10Z; no equivalent. Structure-map gate: N/A, no
ai/surface and no new.mjsoutsidetest/.Why nothing caught it — three layers, each independently sufficient
test/playwright/e2e/**test-e2e/playwright.config.e2ereference exists under.github/workflows. It has never executed in CIe2e/workstation/WorkstationDockPreviewSymmetryNL.spec.mjsunit/dashboard/DockPreview.spec.mjsfs.readFileSync(Preview.scss)+expect(scss).toContain(…)Nothing in the suite renders an affordance and reads a computed colour. That is the defect this ticket must close, independent of the visual cause.
What has been measured, and eliminated
All measured against the running workstation, driving the live
dock-previewinstance over RMA and readinggetComputedStyle:rgba(94,234,212,0.2)fill, identical border colour,opacity: 0.85, one 4px cut sidesplitgroup hitting the solid-bar rulesplit-before/split-afteralso carryregionand paint the same translucent filledge-<dir>and the correctly invertedcut-<dir>.neo-dock-previewhosts and found one. It never searched for a different overlay painting the same region, so it cannot support "nothing else stacks here". See candidate 2 belowPreview.mjs:374replacesvdom.cnwith a single-node arrayneo-preview-lang-signalskininline-size, top/bottom useblock-size, correctlycurrentColordivergencergb(240,242,240)and the same background in both orientationsSo the drop-preview overlay is not what renders dark. Something else paints during a live left/right drag.CORRECTED 2026-08-31 — this conclusion was wrong, and the table above is why. Every row was measured against a current checkout, so the whole table answers "does HEAD paint asymmetrically" — not "what was @tobiu's browser painting". The drop-preview overlay is what rendered dark; it was applying a stale compiled stylesheet the source no longer matched. A source-vs-source elimination cannot see a source-vs-build skew. See the root cause section below.
The two surviving candidates (2026-08-30)
Candidate 1 — a split affordance reaching the solid-bar rule.
.neo-dock-preview-split:not(.neo-dock-preview-region)paints the accent colour as a solid background. MutatinggetAffordanceVdom's region predicate togroup === 'edge'reproduces the report exactly:rgb(94, 234, 212)is the accent at full opacity — "the full background gets the border color with no transparency", verbatim. Against it:resultRegionPreviewsdefaultstrueand no caller inapps/workstationsets it false, so the path that would drop the class is unidentified.Candidate 2 —
DockDropIndicators, a second overlay.apps/workstation/view/Workspace.mjs:462mounts it as a persistent sibling of the preview.Container.scss:493paints.neo-dashboard-dock-drop-indicator/-chipwithvar(--dock-preview-ground, rgb(28 33 40 / 92%))— a near-opaque dark ground — inside a container atz-index: 30, inset: 0. A dark chip overlapping the left/right region but not the top/bottom one reads exactly as "a WAY darker background overlay".Left/right resolve as in-pane
split, top/bottom as rootedge; either candidate can ride that asymmetry. The discriminator iselementsFromPointunder the pointer during a held drag, reading every layer's computed background — candidate 1 shows one node whose own fill lost its alpha, candidate 2 shows two stacked nodes.Live held-drag measurement — 2026-08-31 (candidate 1 eliminated; defect DID NOT reproduce)
The held-drag reading AC-3 asks for has now been taken, against the running workstation with a real 1600×1000 viewport. Method: real
pointerdown→ incrementalpointermove→ readgetComputedStylewhile the button is still down → cancel.split split-before accepted **region** cut-rightrgba(94, 234, 212, 0.2)edge edge-top accepted **region** cut-bottomrgba(94, 234, 212, 0.2)Identical fill, identical opacity, correct half-region geometry on both. The reported divergence did not reproduce in this environment (theme
neo-theme-neo-dark, dev server,resultRegionPreviews: trueread live offneo-dock-preview-1).neo-dock-preview-region, so it never reaches the solid-bar rule. The mutation still reproduces the symptom exactly, which means the rule is the right description of what @tobiu saw — but nothing in the live config path drives a split there.rgba(26,33,44,0.92)dark /rgba(247,249,252,0.94)light), but the set is symmetric: 5 indicators (center/top/right/bottom/left) + 4 chips (top/right/bottom/left), all-offwhen idle. No left/right-only overlay.#5eead4/rgba(94,234,212,0.20); light#0f766e/rgba(15,118,110,0.14).Preview.scss:54is the only rule in the repo that paints the accent as a solid background, and it is reachable only via a split withoutregion, or viatab-before/tab-after. So whatever @tobiu saw came through one of those two doors.Latent defect found on the way (real, separate from the report)
.neo-dock-preview-split:not(.neo-dock-preview-region), .neo-dock-preview-tab-before, .neo-dock-preview-tab-after {The
:not(.neo-dock-preview-region)guard binds to the first selector only.tab-before/tab-afterare unguarded — andgetAffordanceVdomnever addsregionfor the tab group, so today they always paint solid, which is the intended thin-bar presentation. It is correct by accident: if tab placements ever gain region mode, this rule silently paints a full region solid. Worth pinning with an arm regardless of the reported bug.✅ ROOT CAUSE — 2026-08-31: a STALE COMPILED THEME CSS, not a code defect
Full zone-grammar sweep (one held drag, sampling every zone) in a current checkout: all nine zones compute
rgba(94,234,212,0.2)atopacity: 0.85. Nothing paints solid. It confirms the ticket's placement asymmetry — pane left/right resolve tosplit-before/split-after, pane top/bottom toedge-top/edge-bottom, centre totab-into— but every one of them lands in the translucent family.The divergence was the JS and the CSS coming from different revisions.
60592e6a85("Split previews paint the resulting region, not the cut line", #17870) landed 08-30 08:43 and did two things: added theregionclass +:not(.neo-dock-preview-region)guard, and changed split geometry from a 6px line to a half-pane region. @tobiu reported at ~18:00; hisdist/theme CSS was not rebuilt until 23:56, hours after the report.In dev the JS is served from source, so
getAffordanceVdomwas already emittingregionand the new region-sized geometry — while the browser still had the pre-60592e6a85stylesheet, which read:.neo-dock-preview-edge, .neo-dock-preview-tab-into { … accept-FILL (translucent) } .neo-dock-preview-split, .neo-dock-preview-tab-* { … accept (SOLID) } // no :not(region) guardZone for zone, that reproduces the report exactly:
edge-*tab-intosplit-*The old rule had no
regionselector, so the new class was inert; the new geometry meanwhile made splits paint a half-pane block, so the solid accent covered a large area instead of a 6px guide. Hence "a WAY darker background overlay" rather than a bar.Confirmed fixed: @tobiu's compiled
dist/development/css/src/dashboard/dock/interaction/Preview.cssis now byte-identical to a current build (4:not(region)guards), which is precisely why the defect could not be reproduced.The real lesson — a cross-artifact contract with no guard
60592e6a85was a coupled change: a JS class/geometry change whose correctness depends on a separately built stylesheet. Nothing fails when only one half is current — the app silently renders the old visual grammar against new geometry. That is the actual defect class worth owning, and it is not specific to this commit.Reframed remaining work
The instrument the remaining work needs
Every measurement above was taken with the overlay driven directly, because the affordance only exists while a drag is held open — and a completed drag destroys it before it can be read.
test/playwright/component/dashboard/DockSplitterIframePane.spec.mjsalready proves the technique:page.mouse.down(), move, measure while the button is still down, thenup(). Applying that to a dock drag and reading every painted layer under the pointer — not just the affordance — is what will name the cause.The Fix
test/playwright/component/dashboard/DockPreviewRegionRender.spec.mjsmounts the real renderer, drives one affordance per edge plus both split positions through the real config, and asserts computed fill, border and opacity equality — the property no existing layer observes. It runs in the components suite, which does run in CI.Acceptance Criteria
A held-drag arm measures the overlay stack under the pointer during a real left/right drag and during a top/bottom drag.→ Restated: a held-drag measurement of the live overlay is taken and recorded on this ticket. Satisfied — the full zone-grammar sweep above (one held drag, all nine zones sampled withgetComputedStylewhile the button is down) is that measurement. It is deliberately not shipped as a spec: it measured a runtime environment, and the defect it was written to catch turned out not to live in the code the spec would guard.elementsFromPointalso proved the wrong instrument — it skipspointer-events: none, so the overlays must be read directly.The visual divergence @tobiu reported is reproduced by that arm, then fixed.→ Restated: the divergence is root-caused and its remedy identified. Satisfied — cause: new JS against a pre-60592e6a85stylesheet, reproduced zone-for-zone above; remedy: the theme rebuild @tobiu already ran, after which his compiled CSS is byte-identical to a current build. No code fix is required, and none is shipped. Durable prevention is a separate leaf: #17900 / PR #17902 (npm run check-themes).Not in this ticket, and deliberately so: the latent
:not(...)selector-binding trap recorded above is real but unrelated to the report — it needs its own leaf rather than riding this one to green.Out of Scope
Avoided Traps
Related
Handoff Retrieval Hints
Retrieval Hint: "left/right dock drop overlay renders darker, stylesheet symmetric, no test reads a computed overlay colour" Retrieval Hint:
resources/scss/src/dashboard/dock/interaction/Preview.scss;src/dashboard/dock/interaction/Preview.mjs:374Origin Session ID: 7fc710e4-f796-47bc-97b7-b61d1414b3a3