Test-only remainder of #19285. The engine fix and its unit arms shipped; the observable document.activeElement outcome does not have a landed arm, and the staging is the hard part, so this ticket carries what was measured rather than leaving the next attempt to re-derive it.
What the arm must witness
A non-active tab holds focus, the header narrows until it overflows, and after the projection: the tab is still in the DOM with a real box, document.activeElement is still inside the tab container, and no action node the focused header owned before the projection was withdrawn. The last clause is the one that matters — a contextually inactive action has NO DOM node at all, so a dropped node is the observable, not a class or a style.
The fix it witnesses, and why no other shape can
The partition keeps the focused node. Handing focus to the More tabs control cannot work: syncControl creates the control with autoInitVnode + autoMount, a promise, so focus() resolves its node through DomAccess.getElement and gets null; and the plugin already observes the control mounted edge (Overflow.mjs:1245-1249), which fires a render round trip AFTER button.hidden = true removed the focused node. The actions withdrawal is therefore causal, not a race — a late hand-off restores activeElement and cannot un-withdraw the actions. An arm that only asserts activeElement is inside the container would pass a build that still drops the partition.
What was measured, and why the arm is not landed
Staged against WorkstationTabOverflowCapNL, which is the right host: same predicate discipline, same staging machinery, and a documented precedent for exactly this failure. Five staging facts, each measured:
- The stage must be self-sufficient. Run in the full file, the arm passed its overflow poll; run alone, it failed it. The cap arm above leaves the viewport at 760, so a hard-coded
setViewportSize(760) is a NO-OP — no width change, no geometry change, no pass scheduled. Compute the narrowing from the current width and assert it actually changed.
- The focus premise must be asserted before the stage. Focusing the raw DOM node (not
Neo.get, which is a worker-side namespace and absent from window) drives manager.Focus through the real event path. Without asserting document.activeElement before the projection, the arm can pass on a build where the tab never held focus and the partition was never at risk.
- The focus-driven reveal is asynchronous, so the baseline must be read after it settles. Snapshotted immediately after the focus, the partition read 1 action; 150ms later it read 6. That is the reveal arriving, not the projection changing anything. Poll for a stable count (two equal reads) before taking the baseline, or the arm asserts a count INCREASE and convicts a correct build.
- The partition count must be asserted as set membership, not equality. Across the narrowing it went 6 to 7 and the extra node was a DIFFERENT action the narrower header legitimately adds. The defect REMOVES nodes; an equality assertion would convict a correct build for a responsive addition. Assert that every id present before is still present after.
- The obvious overflow staging is the wrong one. Widening some OTHER tab does not exercise the fix: with both ids protected the widened ACTIVE tab wins the displacement and the focused tab is withheld either way, so the arm is red on a correct build. Widening the FOCUSED tab is correct, because in-order packing overflows the LAST tab regardless of its width —
used + width <= usable fails on the width, not on its being large — so the extra width buys no survival on its own.
Where it stopped
With all of the above applied, the selected header (neo-tab-header-toolbar-5, two tabs) still produced no overflow control inside it within the poll, despite a real narrowing and a widened label. That is a staging gap against the app CURRENT composition, not evidence about the fix — and the host spec already carries the scar of two peers spending a bisect on a staging defect that looked like a product regression. So the arm was reverted rather than landed half-staged, and the unit arms (red-first, 6 red on base / 54 green at head, 161 across tab + toolbar + DockLayoutAdapter) carry the fix in the meantime.
The open question for whoever takes it: the app non-reveal headers may need a THIRD tab to stage this at all, since a two-tab header ends with both ids protected and nothing withheld — the joint-overflow bound documented on #19285, where no control is torn down because nothing overflows.
Acceptance Criteria
Related
#19285 (the engine fix + the unit arms), #17844 (the NL battery this arm joins), WorkstationTabOverflowCapNL.spec.mjs (the host), manager.Focus#setComponentFocus, Overflow.mjs syncControl / applySplit.
unowned-rationale: the engine half is merged under #19285 and this is its test-only remainder; Vega released #19285 to me, and the staging above is the expensive part, so it is deliberately left open rather than half-landed.
Test-only remainder of #19285. The engine fix and its unit arms shipped; the observable
document.activeElementoutcome does not have a landed arm, and the staging is the hard part, so this ticket carries what was measured rather than leaving the next attempt to re-derive it.What the arm must witness
A non-active tab holds focus, the header narrows until it overflows, and after the projection: the tab is still in the DOM with a real box,
document.activeElementis still inside the tab container, and no action node the focused header owned before the projection was withdrawn. The last clause is the one that matters — a contextually inactive action has NO DOM node at all, so a dropped node is the observable, not a class or a style.The fix it witnesses, and why no other shape can
The partition keeps the focused node. Handing focus to the
More tabscontrol cannot work:syncControlcreates the control withautoInitVnode+autoMount, a promise, sofocus()resolves its node throughDomAccess.getElementand getsnull; and the plugin already observes the controlmountededge (Overflow.mjs:1245-1249), which fires a render round trip AFTERbutton.hidden = trueremoved the focused node. The actions withdrawal is therefore causal, not a race — a late hand-off restoresactiveElementand cannot un-withdraw the actions. An arm that only assertsactiveElementis inside the container would pass a build that still drops the partition.What was measured, and why the arm is not landed
Staged against
WorkstationTabOverflowCapNL, which is the right host: same predicate discipline, same staging machinery, and a documented precedent for exactly this failure. Five staging facts, each measured:setViewportSize(760)is a NO-OP — no width change, no geometry change, no pass scheduled. Compute the narrowing from the current width and assert it actually changed.Neo.get, which is a worker-side namespace and absent fromwindow) drivesmanager.Focusthrough the real event path. Without assertingdocument.activeElementbefore the projection, the arm can pass on a build where the tab never held focus and the partition was never at risk.used + width <= usablefails on the width, not on its being large — so the extra width buys no survival on its own.Where it stopped
With all of the above applied, the selected header (
neo-tab-header-toolbar-5, two tabs) still produced no overflow control inside it within the poll, despite a real narrowing and a widened label. That is a staging gap against the app CURRENT composition, not evidence about the fix — and the host spec already carries the scar of two peers spending a bisect on a staging defect that looked like a product regression. So the arm was reverted rather than landed half-staged, and the unit arms (red-first, 6 red on base / 54 green at head, 161 across tab + toolbar + DockLayoutAdapter) carry the fix in the meantime.The open question for whoever takes it: the app non-reveal headers may need a THIRD tab to stage this at all, since a two-tab header ends with both ids protected and nothing withheld — the joint-overflow bound documented on #19285, where no control is torn down because nothing overflows.
Acceptance Criteria
activeElementonbodyand/or the focused node absent from the DOM.Related
#19285 (the engine fix + the unit arms), #17844 (the NL battery this arm joins),
WorkstationTabOverflowCapNL.spec.mjs(the host),manager.Focus#setComponentFocus,Overflow.mjssyncControl/applySplit.unowned-rationale: the engine half is merged under #19285 and this is its test-only remainder; Vega released #19285 to me, and the staging above is the expensive part, so it is deliberately left open rather than half-landed.