LearnNewsExamplesServices
Frontmatter
id19086
titleWorkstationNL dense control: the dark splitter fails the :active read on press
stateClosed
labels
bugaitestingdashboard
assigneesneo-fable
createdAtSep 23, 2026, 4:54 AM
updatedAtSep 23, 2026, 12:23 PM
githubUrlhttps://github.com/neomjs/neo/issues/19086
authorneo-fable
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 23, 2026, 12:23 PM

WorkstationNL dense control: the dark splitter fails the :active read on press

Closed v13.2.0/archive-v13-2-0-chunk-22 bugaitestingdashboard
neo-fable
neo-fable commented on Sep 23, 2026, 4:54 AM

Context

test/playwright/e2e/workstation/WorkstationNL.spec.mjs — "the real tour keeps density, data, Canvas output, themes, rails, and identities live" — reds deterministically at :1294: expect(darkDockActive.horizontal.active, 'the real dark-mode splitter owns the active pointer target').toBe(true) receives false. Three sightings, one run each, all 8/9 with this sole red: PR #19083's head 4cae0c08bc (01:27Z), clean dev@a186e92cfa (01:29Z), and the review-response head of the same PR (02:51Z). Found while running the film tour's dense-tour control (#19070 AC-4); the film PR touches neither this spec, the dense screenplay, the splitter nor the theme.

The Problem

The light-mode arm at :1164 reads the same probe — readDockChrome (:364) returns element.matches(':active') for .neo-dashboard-dock-split-horizontal > .neo-dashboard-dock-splitter-horizontal — after the same mouse.move + mouse.down, and passes. The dark arm at :1294, later in the same test, fails. The engine's active paint contract (resources/scss/src/dashboard/Container.scss:456: &:active background, ring and handle tokens) is therefore not the difference; something about the second press is. Until it is classified, every Workstation whitebox run carries one red, and #19070's AC-4 can only be stated as baseline parity.

The Architectural Reality

  • The probe reads the UA pseudo-class, not an engine class: :active holds only while the pressed pointer's target chain includes the splitter. The press at :1286–1290 aims at dragSplitterBox, measured at :1260 after the dark toggle at :1172; the same test installs a MutationObserver on the root's class list just before the press (:1273–1283).
  • DockSplitter#onDragStart (src/dashboard/dock/interaction/DockSplitter.mjs:534) projects the splitter's paint onto a drag proxy that mounts outside the splitter, and the main-thread DragDrop addon owns the mousedown → release bracket. If the proxy or a preview surface takes the pointer target on the second press, :active leaves the splitter while its background may still differ from hover — the assertion order would report exactly this red.
  • #17889 (drag sensors → Pointer Events with pointer capture) changes that bracket; a fix here must not pre-empt it.

Classification (2026-09-23, instrumented runs 08:48–09:13Z on the seat's own bridge per #19066)

(b) target taken — by the theme toggle's view transition, not a drag proxy. At the dark press, document.elementsFromPoint(x, y) at the splitter's centre returned only <html> while the splitter was connected with its box exactly where it was measured (live = measured); the pressed chain was [html]. Workspace#toggleWorkspaceTheme (apps/workstation/view/Workspace.mjs:685) runs DomAccess.startViewTransition with a 100 ms capture window and a 500 ms circular reveal (src/main/DomAccess.mjs:1143, src/main/DomUtils.mjs:82), and while the transition runs its ::view-transition pseudo-tree owns hit-testing: at the press the ::view-transition-group/old/new(root) animations stood at t = 100 ms, and the splitter became hit-testable 394–415 ms later. So :active never reached the splitter and the drag never started — the later the user drag commits a semantic resizeSplit document change assertion read a delta of 0. The light arm passed only because its unrelated 1.2 s listener-readiness wait outlasts the reveal. The proxy hypothesis was falsified: no [class*="proxy"] element existed at either press.

Once the press landed, the tour ran further than it had in weeks and unmasked three more layers, each classified: (2) the monitor counted every .neo-tab-header-toolbar under the root (8), but each rail's RevealOverlay composes a runtime-only tab.header.Toolbar (idle-hidden, zero rect, in the DOM from boot) — a test premise, fixed by scoping to the containers' own headers; (3) one sampled frame during splitNode(security → below scale) shows two visible overflow controls, the newborn single-tab Security container's beside the heavy group's — deterministic, engine, filed as #19094; (4) dark splitter active feedback is distinct from hover red 1 in 3 because the splitter's background-color transitions over --dock-transition-duration-fast (120 ms, Container.scss:395) and both reads could catch the colour being left — fixed by settling the four hover/active reads on getAnimations().length === 0; (5) the Canvas receipt (the exact scale Canvas changes after its worker receives new data) reds in 5 of 9 runs with the App-worker values changed and the same, still-transferred canvas node never repainting — captured as a defect-note (09:14Z), mechanism unknown, falsified as unrelated to a held press.

The Fix

Both presses wait until a hit test at the splitter's centre returns the splitter (expectSplitterUnderPointer), so the dark arm keeps its honest :active read; the four hover/active colour reads settle first (readSettledDockChrome); the tour monitor tracks .neo-tab-container > .neo-tab-header-toolbar; the duplicate-overflow sample names a control's real toolbar via closest() (the control is the toolbar's own action contribution). Test-only; no engine change.

Acceptance Criteria

  • AC-1 The cause is classified from the instrumented read (stale box / target taken / engine gap) and recorded here.
  • AC-2 The dark-mode press lands on the splitter and its drag commits a resizeSplit document change — the arm's :active read and its active-vs-hover colour read pass in three consecutive runs on dev. Restated 2026-09-23 after classification: the original 9/9 target is blocked by two reds this fix unmasked, #19094 (deterministic, engine) and the Canvas receipt flake (defect-note), both classified with owners and not this ticket's.
  • AC-3 The light-mode arm keeps its meaning (a press shows the active feedback); no assertion is removed without a replacement.

Out of Scope

  • #17889's sensor migration.
  • The film tour (#19070 / PR #19083), which only reports this red as its control's baseline.
  • #19094 (the newborn container's one-frame overflow control) and the Canvas receipt flake — unmasked here, owned elsewhere.

Related

#19070 (its AC-4 names this ticket as the owner of the control's red) · PR #19083 · #17889 · #19084 (tonight's unrelated unit flake, same CI archaeology)

Sweeps: live latest-20 open at 2026-09-23T02:52:44Z — none equivalent (#17889 is the sensor migration, not this symptom); A2A claims in the last 60 min — none on this surface; query_raw_memories on the symptom — no prior decision (nearest hit: an unrelated 08-30 overlay measurement); own open assignments (#19070, #19055, #15252) — none. Title searches "splitter active" and "dark-mode splitter": no open match.

Origin Session ID: 610d2189-0b28-46e2-89f3-3c42eae5de21 Retrieval Hint: query_raw_memories("WorkstationNL dark splitter :active held pointer dense control red readDockChrome")

Ownership: self-assigned — mine after PR #19083 lands; the dock group may take it earlier by reassigning with a note here.

🪢 Mnemosyne (Claude Fable 5.1 · Claude Code) · session 610d2189-0b28-46e2-89f3-3c42eae5de21 · classification and fix in session 522f6841-f568-4800-bff8-7ee8e1f99dd3

tobiu referenced in commit 5424760 - "test(workstation): the dense control presses once the theme reveal settles and monitors the containers' own headers (#19086) (#19095) on Sep 23, 2026, 12:23 PM
tobiu closed this issue on Sep 23, 2026, 12:23 PM