LearnNewsExamplesServices
Frontmatter
id18356
titleHeader overflow repartition publishes no settle signal to the DOM
stateClosed
labels
enhancementaiarchitecturedashboard
assigneesneo-opus-vega
createdAtSep 5, 2026, 2:38 PM
updatedAtSep 5, 2026, 5:07 PM
githubUrlhttps://github.com/neomjs/neo/issues/18356
authorneo-opus-vega
commentsCount0
parentIssue18151
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 5, 2026, 5:07 PM

Header overflow repartition publishes no settle signal to the DOM

Closed v13.2.0/archive-v13-2-0-chunk-19 enhancementaiarchitecturedashboard
neo-opus-vega
neo-opus-vega commented on Sep 5, 2026, 2:38 PM

Context

The fourth seam of #18151, measured 2026-09-03 (issuecomment-5527971880) on a downstream pointer-driven e2e suite at engine ae9640376f. Neo.tab.plugin.Overflow#applySplit (src/tab/plugin/Overflow.mjs:808) marks overflowing header buttons vdom.removeDom = true (:895) and calls owner.update() (:902), so a header action that is visible, enabled and rect-stable can be detached before a click dispatches. The engine knows exactly when the repartition is idle — whenProjectionIdle() (:515) resolves the projectionIdleWaiters (:240, :505–508) once the measuring latch releases and the coalesced rerun has settled — and nothing publishes that to the DOM.

The dock already solved the same problem for motion: projection/MotionSignal.mjs keeps neo-dashboard-dock-animating on the workspace component while any motion is in flight — counted, instance-keyed, fail-safe — and resources/scss/src/dashboard/Container.scss:5 documents it as the observable motion lifecycle. The header repartition is a second mechanism with no signal.

Measured consumer cost: three sites failed as three different lies — a bare 2-minute beforeEach timeout, a bare 4-minute timeout, and a waitForEvent "page" 45 s timeout that reads as a tear-out vessel defect. A consumer-side bounded click plus a two-sample rect-stability wait moved 6/8 → 7/8 green at n=8, which is not a fix: element is not stable (motion, waitable) and element was detached from the DOM with the node stable (a replacement, unpredictable from outside) are two failure modes, and retrying is unsafe because half the targets are toggles.

The Problem

A consumer polls rectangles to approximate a boolean the engine holds. Every engine dock spec drives operations through the Neural Link and none clicks a header action with a pointer, so the gap is invisible from inside — the same structure as the epic's other seams. The worker-side promise has one consumer (src/draggable/tab/header/toolbar/SortZone.mjs:147), no Neural Link surface exposes it, and the DOM carries nothing.

The Architectural Reality

  • src/tab/plugin/Overflow.mjs is a GENERIC tab plugin (Neo.tab.plugin.Overflow, attaching to Neo.tab.header.Toolbar). It imports no dock module and must not start; it fires no events today.
  • The dock installs it in projection/LayoutAdapter.mjs:1253 ({module: TabOverflowPlugin, projectAsAction: true}) inside the tab header toolbar config the adapter builds with the workspace in its projection context; the Reconciler reaches the plugin through getPlugin('tab-overflow') (:414, :575, :731).
  • MotionSignal: static enter(component) / leave(component), counted per instance, FAIL_SAFE_MS = 2000, SIGNAL_CLS = 'neo-dashboard-dock-animating'. Producers today: the Workspace FLIP commit (Workspace.mjs:3009/:3018), RevealOverlay (:239/:250), plugin/Maximize (:471/:480), TabEnterButton (:139/:150). Its docblock's reason to exist is one lifecycle to observe no matter how many mechanisms animate; the observe_motion e2e specs key off the class.
  • src/dashboard/dock/Workspace.mjs is under the standing stop-adding directive (PR #18213): the subscription does not land there.

The Fix

Generic producer first, dock consumer second:

  1. src/tab/plugin/Overflow.mjs publishes the repartition lifecycle as events on its owner toolbar — overflowProjectionStart when the measuring latch arms and overflowProjectionIdle when the idle waiters resolve, exactly the two moments whenProjectionIdle() already brackets. No class, no dock import; whenProjectionIdle() unchanged.
  2. projection/LayoutAdapter.mjs: the toolbar config built at :1253 gains listeners for the two events that route into MotionSignal.enter(toolbar) / MotionSignal.leave(toolbar) — the header toolbar itself is the carrier, the producer's own component, as TabEnterButton already does. (Amended 2026-09-05 from "on the workspace": toggling the class on the workspace root from inside a header pass forces a whole-tree vnode sync while that header's buttons are being replaced; at boot on the dock-maximize fixture that sync failed on a retired button id and stranded the projection — the root is not a safe carrier for a producer that replaces nodes under it.) Counted: a repartition nested in a motion on the same toolbar holds until the last producer leaves. Fail-safe: a lost idle clears within FAIL_SAFE_MS. Instance-keyed: a re-projected header cannot decrement a predecessor's entry. A consumer waits for the class to be absent from the dock host's subtree, for motion and repartition alike.
  3. Docs: the MotionSignal docblock and Container.scss:5 name the repartition as the second producer class.

Contract Ledger Matrix

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
Neo.tab.plugin.Overflow owner events overflowProjectionStart / overflowProjectionIdle (new) whenProjectionIdle() :515 brackets the same two moments fired on the owner toolbar at latch-arm and at idle; additive none needed — no consumer exists today; generic tab containers keep their behaviour plugin docblock unit on a plain tab container: the pair balances across success, a coalesced rerun and a thrown measure
MotionSignal.SIGNAL_CLS semantics MotionSignal.mjs docblock; Container.scss:5 "any dock motion" widens to "any dock motion or header repartition"; class name unchanged consumers keyed on the class gain coverage and lose nothing both docblocks dock unit arm + component arm
LayoutAdapter tab header toolbar config :1253 the config that installs the plugin listeners route the two events into MotionSignal on the header toolbar (amended from the workspace, see The Fix) — adapter JSDoc unit: two headers, one entry each, a nested producer counted; component: class present inside the dock host during a real activation, absent after

Decision Record impact

aligned-with ADR 0029 — presentation tier only (the JSON-first guardrail): no persisted shape, no operation, no Neural Link tool.

Acceptance Criteria

  • While an overflow repartition is in flight on a projected tab header of a dock workspace, that header toolbar carries neo-dashboard-dock-animating; it leaves when the repartition and any concurrent producer on the toolbar have settled, so a consumer finds the class absent from the dock host's subtree only when every header is idle. (Amended 2026-09-05: carrier = the header toolbar, not the workspace root — see The Fix.) Unit: two headers repartitioning concurrently each hold their class until idle; a nested producer on one toolbar holds it until the last leaves; red on dev.
  • src/tab/plugin/Overflow.mjs imports no dock module (its import list is unchanged) and publishes start/idle as owner events; whenProjectionIdle() and the header SortZone consumer keep their behaviour (existing arms green).
  • A measure pass that throws cannot wedge the signal: the plugin's latch releases (existing contract) and the signal clears through MotionSignal's fail-safe within FAIL_SAFE_MS. Unit with an injected throwing getDomRect and injected timers.
  • A re-projected header — a new plugin instance under the same ids — can neither strand a predecessor's entry nor strip a live one; the instance-keyed contract holds through one dock re-projection. Unit.
  • Component tier (component/dashboard, real browser): a focus-driven repartition sets the class and clears it, and a pointer click on a header action gated on the class leaving lands on the intended, attached node. Red-first control: on dev the class never appears during the repartition.
  • MotionSignal's docblock and Container.scss name the repartition producer; no new tool, no new persisted state.

Out of Scope

A DOM class on non-dock tab containers (events only there; a follow-up if a consumer asks); the overflow partition algorithm; new Neural Link tools; the consumer's own click bounding.

Avoided Traps

  • Toggling a class inside applySplit. Uncounted, without a fail-safe, keyed by id — the class MotionSignal exists to replace. Route through the owner.
  • A second class (neo-dashboard-dock-projecting). Consumers would wait on two; "do not click yet" is one question.
  • Importing MotionSignal into src/tab. The generic plugin would depend on the dock projection tier.
  • Landing the subscription in Workspace.mjs. The stop-adding directive; the adapter already owns the toolbar config and the workspace context.
  • Reading the rect-stability workaround as the fix. 7/8 at n=8 is a coin's width from luck, and the replacement failure mode is unwaitable from outside.
  • An arm that asserts the event fired or the class was added while nothing repartitions (the #18134 trap). The component arm asserts the click's target and the class's absence on dev.

Related

Part of #18151 — the fourth seam, folded 2026-09-03 (issuecomment-5527971880); the epic closeout matrix (issuecomment-5551818531) carries this as its one open row. #14973 — the dock-animating signal brackets real motion, the precedent this extends. #18306 / PR #18347 — HeaderActionPolicy owns header state; the repartition is a legitimate re-presentation, not a regression. #18134 — the assert-while-nothing-animates trap.

Proposed owner: @neo-opus-vega after #18284; claimable by any seat before that.

Origin Session ID: 87064b54-7440-441c-8bbf-f29fcd6899d5

Retrieval Hint: "header overflow repartition settle signal MotionSignal whenProjectionIdle dock-animating"

Live latest-open sweep: checked the latest 20 open issues at 2026-09-05T12:35Z; no equivalent found. A2A claim sweep (last 60 min, all read states): no overlapping claim. Structure-map gate: N/A — src/tab/** and src/dashboard/dock/** are engine Body surface.