LearnNewsExamplesServices
Frontmatter
id18691
titleTabRestoreNL counts the dock reveal-overlay header as a tab bar
stateClosed
labels
bugaitestingdashboard
assigneesneo-opus-grace
createdAtSep 14, 2026, 1:38 AM
updatedAtSep 14, 2026, 2:36 AM
githubUrlhttps://github.com/neomjs/neo/issues/18691
authorneo-opus-grace
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 14, 2026, 2:36 AM

TabRestoreNL counts the dock reveal-overlay header as a tab bar

Closed v13.2.0/archive-v13-2-0-chunk-21 bugaitestingdashboard
neo-opus-grace
neo-opus-grace commented on Sep 14, 2026, 1:38 AM

Context

WorkstationTabRestoreNL has been red in every #17844 census I have measured — at 3044d7f86b and at 28584754ff — and it fails in isolation as well as inside the 281-test battery, taking the full 60-second timeout each time. It never reaches its gesture, so the behaviour it exists to prove (a tab dragged out of its group is not restored into the source header) has been unwitnessed for as long as it has been failing.

The arm was one of the 14 unowned reds in tonight's census triage. This ticket takes it.

The Problem

Its readiness gate (:117-124) waits for the rendered tab bars to equal the committed tabs nodes:

const tabsNodes = Object.values(doc?.nodes || {}).filter(node => node.type === 'tabs').length,
      bars      = await page.locator('.neo-tab-header-toolbar').count();

return tabsNodes > 0 && bars === tabsNodes

Instrumented at rest, before any gesture, six identical poll samples:

tabsNodes 6  [scale-tabs, heavy-tabs, left-tabs, right-top-tabs, right-bottom-tabs, bottom-tabs]
bars      8  ["", "Queues", "100k Matrix", "Priority Alert Observatory", "MetricsAudit", "Commits", "Live Event Stream", ""]

Nothing is missing a header. There are two surplus bars, both empty, and the count equality fails in the opposite direction from what the arm's own message asserts — "every committed tabs node must have a rendered header before the gesture" sends the next reader hunting for an absent header. It cost me exactly that detour tonight.

The first surplus bar, named from the DOM:

cls       neo-dashboard-dock-reveal-header neo-tab-container-inline neo-tab-header-toolbar neo-toolbar
ancestors neo-dashboard-dock-reveal-overlay-1 → neo-dashboard-dock-rail-1[neo-dashboard-dock-edge-rail-left]
rect      0×0, visible false, buttons 1, text ""

The dock's edge-rail reveal overlay carries neo-tab-header-toolbar in its class list. It is hidden, it is not a committed tabs node, and it never will be. The second surplus bar is last in the list with the same shape and emptiness — the right rail's overlay is the obvious candidate, but my capture truncated before it, so that one is inferred rather than measured.

The Architectural Reality

  • test/playwright/e2e/workstation/WorkstationTabRestoreNL.spec.mjs:117-124 — the predicate, with a 60s timeout.
  • src/tab/header/Toolbar.mjs:30 — baseCls: ['neo-tab-header-toolbar', 'neo-toolbar'], the real header.
  • src/draggable/tab/header/toolbar/SortZone.mjs:24 — the same two classes on a drag surface, a second source of matches.
  • The reveal overlay reuses the header's classes deliberately: #18014 / PR #18019 replaced its standalone ghost button composition with a real Neo.tab.header.Toolbar so the reveal preview matches inline tab chrome. The chrome is correct; the spec's assumption that the class implies a dock tabs node is not.

The Fix

In the spec only:

  1. Scope the predicate to dock tab headers — count within the projected dock tabs containers, or exclude .neo-dashboard-dock-reveal-header — so a reveal overlay can neither satisfy nor break the gate.
  2. Make the failure message name both sides of the comparison, so a future mismatch reads in the direction it actually failed.
  3. Let the gesture run, and record what the arm's own assertions then report.

Acceptance Criteria

  • AC-1 The readiness predicate counts only dock tab headers. Red-first: the current predicate fails at rest on dev with 8 against 6; the repaired one passes.
  • AC-2 The failure message names both sides, so a mismatch says which direction it failed in.
  • AC-3 The arm reaches its gesture and its own assertions decide the result. Whatever they report is recorded here — a pass, or a separate defect with its receipt.
  • AC-4 A mutation that hides one dock tab header reds the repaired predicate, so it is not vacuous.

Out of Scope

  • The other 13 unowned reds in #17844's triage.
  • WorkstationTabDragLabelOverlapNL's "the Metrics tab header button must be visible". Its snapshot shows no Metrics tab at all, which is a different shape; grouping the two on the word "header" is the error this ticket exists to undo.
  • The reveal overlay's use of the header classes, which is intended chrome.

Related

  • #17844 — the census that red-listed it; tonight's triage comment carries the unowned set.
  • #18014 / PR #18019 — gave the reveal overlay a real tab header toolbar.

Live latest-open sweep: latest 20 open issues at 2026-09-13T23:37Z, no equivalent. A2A claim sweep: last 30 messages, no competing claim; my own defect-note declares this intent. Memory Core sweep: only this session's own attribution, no prior decision. Own-assignment sweep: 12 open, none covers it.

Origin Session ID: d010413d-0559-4755-b8f7-34cbcdc8d0b3

Retrieval Hint: "TabRestoreNL readiness predicate reveal overlay header counted as tab bar 8 against 6"