LearnNewsExamplesServices
Frontmatter
id16424
titleActive tab indicator runs beneath the floating overflow control
stateClosed
labels
bugai
assigneesneo-fable
createdAtAug 3, 2026, 1:49 AM
updatedAtAug 4, 2026, 1:05 PM
githubUrlhttps://github.com/neomjs/neo/issues/16424
authorneo-opus-grace
commentsCount2
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 4, 2026, 12:58 PM

Active tab indicator runs beneath the floating overflow control

neo-opus-grace
neo-opus-grace commented on Aug 3, 2026, 1:49 AM

Context

Operator observation 2026-08-02 on the workstation demo, after correcting my first reading of it:

"not 'over' the tab title, but right next to the tab title text. the active part of the tab strip goes indeed below the ... button."

So the overflow control's position is fine — it sits beside the tab label as intended. What is wrong is that the active-tab indicator runs underneath it: the 3px strip segment that marks the active tab extends into the space the floating ... control occupies.

Evidence level, stated plainly: the mechanism is confirmed in source; the symptom is operator-observed; I did NOT reproduce it live. I could not stage an overflowing tab bar in my session — every workstation header carries 1–2 tabs that still fit at viewports down to 460px, so the control never rendered. Filing on a source-grounded candidate mechanism rather than a measurement, and AC1 is "reproduce it first".

Live latest-open sweep 2026-08-02T23:4xZ (newest #16423, my own). Neighbours checked: #6246 (CLOSED — tab strip indicator color), #16406 (tab drag paint artifact), #15649 (CLOSED — FM cockpit card breakpoint). None owns indicator geometry against the overflow control.

The Problem

Two consumers derive layout from one geometry, and only one of them knows the overflow control exists.

  • The overflow plugin reserves the control's width when it decides which tabs to hide. src/tab/plugin/Overflow.mjs:57 describes its core as "active-never-hidden packing with overflow-only control-width reservation" — the packing math explicitly subtracts the control.
  • The active-tab indicator derives its geometry purely from the active tab button's rect, with no clamp and no knowledge of that reservation.

src/tab/Strip.mjs:99-128:

moveActiveIndicator(rects) {
    let tabStripRect = rects.shift(),
        rect         = rects[1] || rects[0];
    ...
    activeTabIndicator.style = {
        left : `${rect.left - tabStripRect.left}px`,
        width: `${rect.width}px`
    };

rect is the active tab button's rect. Nothing bounds left + width against the strip's usable width — the width that remains once the control is placed.

Why the control does not push the indicator out of the way by itself. src/tab/plugin/Overflow.mjs:17 documents it as an "OUT-OF-COLLECTION floating button rooted at document.body, deliberately NOT a member of owner.items". Being body-rooted and floating, it takes no space in the toolbar's flow — it is painted over the header. So an active tab button may legitimately extend beneath it, and an indicator sized from that button follows it there.

That composition — reserved-for-packing but not-reserved-for-painting — is a coherent explanation for exactly what the operator sees.

The Architectural Reality

  • src/tab/Strip.mjs:99-128moveActiveIndicator; the unclamped left/width assignment above. Owns the indicator.
  • src/tab/plugin/Overflow.mjs:17 — the control is body-rooted and out-of-collection, so it does not consume flow width.
  • src/tab/plugin/Overflow.mjs:57-126 — the pure packing decision that does reserve control width, and the natural-width measurement discipline it depends on.
  • src/dashboard/DockLayoutAdapter.mjs:7 — imports the plugin; the dock composition is where a header realistically accumulates enough tabs to overflow.
  • apps/workstation/view/Workspace.mjs:757 / :2030 / :2123 — the dense tour drives the real overflow control via getPlugin('tab-overflow'), which is the cheapest known route to the overflowing state.

The fix belongs in src/tab/, not in the workstation app: any tab.Container with the overflow plugin is exposed, and the workstation is only where it was noticed.

The Fix

(Prescription — pick one deliberately.)

  1. Clamp the indicator to the strip's usable width. moveActiveIndicator bounds left + width by the strip width minus the reserved control width, so the segment stops where the control begins. Smallest change; keeps one owner for the geometry. Needs the reservation to be readable from the Strip, which today it is not.
  2. Publish the reservation as strip geometry. The plugin already computes the control-width reservation for packing; expose it so the Strip consumes the same number rather than re-deriving one. Removes the second-source-of-truth risk rather than papering over it — the cost is a contract between plugin and Strip that does not exist yet.
  3. Give the control flow space. Make it a real toolbar member so it occupies width and the active button can never extend under it. Cleanest geometrically, but Overflow.mjs:17 records the out-of-collection rooting as deliberate — reversing it would need that rationale re-examined, not just overridden.

My read is 2: the defect is precisely that one reservation has two would-be consumers and only one of them can see it, so publishing it fixes the class rather than this instance. 1 is the acceptable smaller step if the contract in 2 is judged too heavy. 3 should not be taken without first reading why the control was rooted out-of-collection — that decision predates this ticket and likely has a reason.

Acceptance Criteria

  • Reproduce first. A staged overflowing tab bar (enough tabs in one narrow header that the control renders) with the active tab adjacent to the control. Record the indicator rect and the control rect, and confirm they intersect. If they do not, this ticket's mechanism is falsified and it should be closed rather than fixed.
  • The active-tab indicator's painted segment does not intersect the overflow control's rect while the control is visible.
  • The indicator still spans the full active-tab width whenever the control is not visible — the clamp must not shorten the ordinary case.
  • The reserved control width used by the indicator is the same value the packing decision uses, not a second derivation. A spec asserts they agree.
  • Covered for both tabBarPosition: 'top' and 'bottom'; the left/right branch of moveActiveIndicator sets height/top instead and needs its own verdict — either the same clamp on the vertical axis, or an explicit statement that the control does not overlap there.

Out of Scope

  • #16423 — workstation card sizing. Same screenshot, unrelated mechanism.
  • #16406 — tab drag label paint. Drag-time FLIP, not steady-state geometry.
  • The overflow plugin's packing algorithm itself. It reserves the control width correctly; the gap is that nothing else can read that reservation.
  • Re-litigating the body-rooted control. Option 3 touches it, and that is exactly why option 3 carries a read-first condition.

Avoided Traps

  • Probing the toolbar's children for the control. I did this first and measured zero intersections at two viewports, which reads as "no defect" and is simply the wrong tree — the control is rooted at document.body. A probe that cannot contain the answer returns a confident false negative.
  • Testing for clipping when the defect is overlap. My first instrument compared scrollWidth to clientWidth on the tab buttons. That measures truncation, not intersection, and the operator's correction is what exposed it. Recording both failed probes so the next person starts from the right instrument.
  • Reading strip.firstElementChild as the painted indicator. It reported width: 0 at every viewport I measured, including ones where the underline is plainly visible in a screenshot — so that selector is wrong too, and any measurement built on it is unreliable. Whoever reproduces this should first establish which element actually paints the segment.
  • Assuming the indicator is "just cosmetic". It is the only affordance showing which tab is active; a segment running under an opaque floating control misreports the active tab's extent.

Related

  • #16423 — workstation cards size from the viewport, not their pane (same operator screenshot, different defect)
  • #16406 — tab drag paints the tab header label over the content label
  • #6246 — CLOSED; tab strip indicator color (different concern, same element)
  • src/tab/Strip.mjs, src/tab/plugin/Overflow.mjs, src/dashboard/DockLayoutAdapter.mjs

Decision Record impact: none. Agent OS structure-map gate: N/A (no ai/, MCP, Memory Core, or skill surface; no new or relocated .mjs).

Origin Session ID: 8c150fe3-e8a4-4475-8694-a6f92115dce9

Retrieval Hint: query_raw_memories("tab strip active indicator runs under floating overflow control body-rooted out-of-collection unclamped width")