One of #17844's unowned reds, picked up after @neo-opus-grace triaged the two tab-header failures apart from @neo-gpt-emmy's header text-loss family and left this one unclaimed. Her sibling finding is #18691, which is a different spec and a different mechanism; only the surface is shared.
The Problem
WorkstationTabDragLabelOverlapNL exists to witness a drag defect. It never reaches the drag. It dies in its precondition, and not in the way the failure output first suggests.
> 53 | metricsBox = await page.locator('.neo-tab-header-button', {hasText: 'Metrics'}).first().boundingBox();
55 | expect(auditBox, 'the Audit tab header button must be visible').toBeTruthy();
56 | expect(metricsBox, 'the Metrics tab header button must be visible').toBeTruthy();
The obvious reading is that line 56 fails on a falsy box. It does not. Playwright's caret sits on line 53: boundingBox() itself times out. That distinction matters, because boundingBox() does not return null for a hidden-but-attached element — it waits for one. So the arm is not observing an absent button; it is blocking on a handle that never becomes measurable.
Measured, reproducing headless in about 40 seconds (the spec's docblock asks for --headed, but the failure precedes any gesture and needs neither the display nor trusted input):
Read
Result
document.querySelectorAll at rest
Metrics button present, visible, 32px high, at 1048,155, inside a proper neo-tab-header-toolbar
Same read after clicking Audit, as the spec does
identical — Metrics still present and visible, same position
Playwright locator, 1 match
rect 0x0 at 0,0, closest('.neo-tab-header-toolbar')null, computed display empty string
boundingBox() on that match
timed out at 90s
A later sample in the same run
no attached element whose text is Metrics
An element with no computed style, no toolbar ancestor and zero size is detached. So one read finds an attached visible button and the next finds a detached one or none at all.
The button is being removed and re-added. That single mechanism explains every row: the direct DOM scans catch it while attached, Playwright resolves a handle that is then replaced, and boundingBox() waits forever for a node no longer in the document. It also explains why two runs of the identical probe disagreed — one timed out at 90s, the next passed in 1.4s.
Activation is not the variable. The spec clicks Audit before looking for Metrics, which is the obvious suspect, and the before/after scans are identical down to the pixel coordinates.
The Architectural Reality
WorkstationTabDragLabelOverlapNL:53 is the blocking call; :40-52 is the setup that precedes it.
The spec pins viewport 1280x800 with screen emulated to match, so the overflow-collapse threshold recorded on #15239 is not in play — the button is rendered at full size when it is rendered at all.FALSE, corrected in the AC-2 comment. Overflow IS in play. The governing width is the right-top-tabs ZONE, not the viewport; widen the board to 2560 and the same click keeps the button. This bullet reasoned from the wrong measurement and sent the investigation looking for a re-render defect that does not exist.
Red at 3044d7f86b and 28584754ff alike per @neo-opus-grace's triage, so this predates tonight's merges.
Adjacent but distinct from #18691: that one miscounts hidden reveal-overlay headers as tab bars in a different spec. This one is a live button leaving the DOM.
The Fix
Known as of the AC-2 comment: the tab header toolbar overflow repartition evicts the non-active tab button once the projected header action set plus both labels exceed the zone width. Nothing in src/dashboard/dock/projection/ is owed a repair — the document, the projection and the reconcile are all correct. The spec is pinned to a stage that cannot host its own experiment. Original text, kept because its reasoning still binds:
Unknown, and deliberately not guessed. The next step is to establish why the button is recreated, not to make the spec tolerate it — a wait or a retry here would hide a real re-render and leave the drag witness asserting against whichever instant it happened to sample.
Acceptance Criteria
AC-1: the recreation is characterised — what destroys and re-adds that header button, how often, and whether it settles. A mutation observer on the tab bar across a fixed window, reported as counts and timings rather than described.
AC-2: the cause is named at its owner. If it is a projection or reconciliation re-render, it is named in src/; if it is app-level, in apps/workstation. "The spec is flaky" does not satisfy this.
AC-3: met — 15 of 15 reach the gesture and run (PR #18705 at ebe1502618). The severed pass/fail question — 12 of 15 at that head, heterogeneous, unpredicted by anything the spec records — belongs to #16406re-homed to #17844: #16406 is completed (@neo-gpt-emmy's correction), so this delegation pointed at nothing. #17844's census already carries this arm as a row; the measurements are posted there at IC 5676455886 so closing this ticket does not orphan it.
AC-4: done, and the noun corrected. "The churn" presumed a defect; the measured cause is a pre-settle read of a contracted repartition — reproduced at 1 run in 4 across a 160ms window before the repair, absent after. A green run alone still does not close this, which is why the receipt is the interval rather than the pass.
Out of Scope
#18691, @neo-opus-grace's reveal-overlay predicate in WorkstationTabRestoreNL.
#16406's actual label-overlap defect, which this arm cannot currently reach.
Making the spec wait, retry or tolerate.Reconciled, see the AC-3/AC-4 closeout. This still forbids papering over a defect — but the repartition it was written against turned out to be contracted behaviour (#18015, operator-resolved), and the kicker is not the header settle point (measured: 1 run in 4 reads 160ms early). Waiting for a contracted repartition to settle is measuring the state the witness is entitled to; tolerating an unexplained re-render is what stays out of scope.
Avoided Traps
Rejected approach
Why
Read the failure as "the button is missing".
Playwright's caret is on the boundingBox() call, not the assertion. It is a timeout on a stale handle, and the button is demonstrably present at rest.
Blame the Audit activation.
Before and after scans are identical, including coordinates.
Blame overflow collapse at this viewport.
The button renders at full 32px height when present; the collapse threshold does not apply.
Add a waitFor or retry to the locator.
That hides a live re-render and makes the drag witness sample an arbitrary instant.
Different spec, different mechanism. Grouping on the shared surface is the error @neo-opus-grace named on #18547 — two causes becoming one wrong ticket.
Related
#17844 (the census this came from) · #18691 (@neo-opus-grace, adjacent, no overlap) · #16406 (the defect the blocked arm exists to witness) · #18547 (the triage discipline this follows)
Retrieval Hint: query_raw_memories on "a tab header button is present to a DOM scan and detached to a Playwright locator, so boundingBox waits instead of returning null". Source anchors: test/playwright/e2e/workstation/WorkstationTabDragLabelOverlapNL.spec.mjs:53.
tobiu referenced in commit 67b517d - "fix(testing): the tab-label witness waits for the header it measures (#18693) (#18705) on Sep 15, 2026, 10:11 AM
Context
One of #17844's unowned reds, picked up after @neo-opus-grace triaged the two tab-header failures apart from @neo-gpt-emmy's header text-loss family and left this one unclaimed. Her sibling finding is #18691, which is a different spec and a different mechanism; only the surface is shared.
The Problem
WorkstationTabDragLabelOverlapNLexists to witness a drag defect. It never reaches the drag. It dies in its precondition, and not in the way the failure output first suggests.> 53 | metricsBox = await page.locator('.neo-tab-header-button', {hasText: 'Metrics'}).first().boundingBox(); 55 | expect(auditBox, 'the Audit tab header button must be visible').toBeTruthy(); 56 | expect(metricsBox, 'the Metrics tab header button must be visible').toBeTruthy();The obvious reading is that line 56 fails on a falsy box. It does not. Playwright's caret sits on line 53:
boundingBox()itself times out. That distinction matters, becauseboundingBox()does not return null for a hidden-but-attached element — it waits for one. So the arm is not observing an absent button; it is blocking on a handle that never becomes measurable.Measured, reproducing headless in about 40 seconds (the spec's docblock asks for
--headed, but the failure precedes any gesture and needs neither the display nor trusted input):document.querySelectorAllat rest1048,155, inside a properneo-tab-header-toolbarrect 0x0 at 0,0,closest('.neo-tab-header-toolbar')null, computeddisplayempty stringboundingBox()on that matchMetricsAn element with no computed style, no toolbar ancestor and zero size is detached. So one read finds an attached visible button and the next finds a detached one or none at all.
The button is being removed and re-added. That single mechanism explains every row: the direct DOM scans catch it while attached, Playwright resolves a handle that is then replaced, and
boundingBox()waits forever for a node no longer in the document. It also explains why two runs of the identical probe disagreed — one timed out at 90s, the next passed in 1.4s.Activation is not the variable. The spec clicks Audit before looking for Metrics, which is the obvious suspect, and the before/after scans are identical down to the pixel coordinates.
The Architectural Reality
WorkstationTabDragLabelOverlapNL:53is the blocking call;:40-52is the setup that precedes it.The spec pinsFALSE, corrected in the AC-2 comment. Overflow IS in play. The governing width is theviewport 1280x800withscreenemulated to match, so the overflow-collapse threshold recorded on #15239 is not in play — the button is rendered at full size when it is rendered at all.right-top-tabsZONE, not the viewport; widen the board to 2560 and the same click keeps the button. This bullet reasoned from the wrong measurement and sent the investigation looking for a re-render defect that does not exist.3044d7f86band28584754ffalike per @neo-opus-grace's triage, so this predates tonight's merges.The Fix
Known as of the AC-2 comment: the tab header toolbar overflow repartition evicts the non-active tab button once the projected header action set plus both labels exceed the zone width. Nothing in
src/dashboard/dock/projection/is owed a repair — the document, the projection and the reconcile are all correct. The spec is pinned to a stage that cannot host its own experiment. Original text, kept because its reasoning still binds:Unknown, and deliberately not guessed. The next step is to establish why the button is recreated, not to make the spec tolerate it — a wait or a retry here would hide a real re-render and leave the drag witness asserting against whichever instant it happened to sample.
Acceptance Criteria
src/; if it is app-level, inapps/workstation. "The spec is flaky" does not satisfy this.ebe1502618). The severed pass/fail question — 12 of 15 at that head, heterogeneous, unpredicted by anything the spec records —belongs to #16406re-homed to #17844: #16406 is completed (@neo-gpt-emmy's correction), so this delegation pointed at nothing. #17844's census already carries this arm as a row; the measurements are posted there at IC 5676455886 so closing this ticket does not orphan it.Out of Scope
WorkstationTabRestoreNL.Making the spec wait, retry or tolerate.Reconciled, see the AC-3/AC-4 closeout. This still forbids papering over a defect — but the repartition it was written against turned out to be contracted behaviour (#18015, operator-resolved), and the kicker is not the header settle point (measured: 1 run in 4 reads 160ms early). Waiting for a contracted repartition to settle is measuring the state the witness is entitled to; tolerating an unexplained re-render is what stays out of scope.Avoided Traps
boundingBox()call, not the assertion. It is a timeout on a stale handle, and the button is demonstrably present at rest.waitForor retry to the locator.Related
#17844 (the census this came from) · #18691 (@neo-opus-grace, adjacent, no overlap) · #16406 (the defect the blocked arm exists to witness) · #18547 (the triage discipline this follows)
Origin Session ID: 8df80158-6bd7-40b2-a78b-55fdb3d70f30
Retrieval Hint:
query_raw_memorieson "a tab header button is present to a DOM scan and detached to a Playwright locator, so boundingBox waits instead of returning null". Source anchors:test/playwright/e2e/workstation/WorkstationTabDragLabelOverlapNL.spec.mjs:53.