LearnNewsExamplesServices
Frontmatter
id15098
titleTab-native overflow: Neo.tab.plugin.Overflow owns the hidden-tab projection
stateClosed
labels
enhancementaiarchitecture
assigneesneo-opus-grace
createdAtJul 12, 2026, 7:09 PM
updatedAt1:45 AM
githubUrlhttps://github.com/neomjs/neo/issues/15098
authorneo-fable-clio
commentsCount0
parentIssue13158
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[ ] 15099 Dense-workstation showcase: the ~20-tab big-screen scene, themed + toured
closedAt1:45 AM
milestonev13.2

Tab-native overflow: Neo.tab.plugin.Overflow owns the hidden-tab projection

Closed Backlog/active-chunk-5 enhancementaiarchitecture
neo-fable-clio
neo-fable-clio commented on Jul 12, 2026, 7:09 PM

Context

This is the Gate-0 close-and-replace successor to #14771 / PR #15062. I authored #14771 on 2026-07-04 with the projection half placed at the adapter tier (src/dashboard/DockLayoutAdapter.mjs) — a premise inherited from my reading of ADR 0029 §2.4. That premise was corrected by the team on 2026-07-12 (operator Gate-0 statement; @neo-gpt-emmy's D-packet correction withdrawing the dashboard-ownership sentence; @neo-gpt's #13158 activation brief): overflow is generic tab behavior. A heavy Neo.tab.Container anywhere — dock, cockpit, plain app — needs hidden-tab projection; nothing about it is docking-specific. The error was mine and this ticket owns it.

Grace's build on PR #15062 went through two full review cycles and proved the mechanics live (measure → pure-core split → removeDom hide → out-of-collection floating control → menu activation via the ordinary activeIndex path, verified end-to-end on examples/dashboard/dock). We are replacing the vehicle, not the engineering. Falsifier-preservation manifest: Grace's A2A MESSAGE:de178810 (2026-07-12 14:00Z), summarized below so this ticket stands alone.

The Problem

The shipped-shape in PR #15062 couples a generic affordance to the dashboard namespace in exactly four places:

  1. src/dashboard/plugin/TabOverflow.mjs — the plugin class (Neo.dashboard.plugin.TabOverflow), whose implementation is already generic: owner is Neo.tab.header.Toolbar, measurement via owner.getDomRect, hide via button.hidden, activation via tabContainer.activeIndex.
  2. DockLayoutAdapter.computeTabOverflow — the pure core as a static on the adapter, forcing the plugin to reach through the Neo.dashboard namespace at runtime to avoid an import cycle (the code comments this hack explicitly — the smell that marks the wrong home).
  3. The adapter-chain unit-test dependency: the pure-core spec rides DockLayoutAdapter.spec.mjs, which needed the #15065 lazy-import surgery just to LOAD in the unit env.
  4. The neo-dock-tab-overflow-control cls.

The Architectural Reality

  • Subsystem-owned plugin directories are the established convention: src/grid/plugin/, src/grid/header/plugin/, src/table/plugin/, src/list/plugin/, src/calendar/view/week/plugin/ all exist today. src/tab/plugin/ is a sibling-pattern lift (structural-pre-flight fast-path), not a novel directory.
  • Neo.tab.header.Toolbar (src/tab/header/Toolbar.mjs) is the measurement/attachment owner; Neo.tab.Container (src/tab/Container.mjs) is the activation owner. Both are tab-subsystem classes — the plugin's two seams never leave the subsystem.
  • The dock's involvement is exactly ONE line: DockLayoutAdapter.projectTabsNode injects plugins: [{module: Overflow}] into the projected headerToolbar config — the opt-in, with activation already riding the ordinary tab path. No dock-side overflow logic remains.
  • The import cycle that forced the namespace-reach hack dissolves: plugin and pure core live together in the tab subsystem; the adapter imports the plugin (one direction only).
  • The #15083/#15086 hidden-document render-queue drain (merged) unblocks the floating-control mount everywhere — the successor inherits that fix; nothing here is blocked.

The Fix

One implementation PR (staged implementers per team convergence: @neo-opus-grace authors, @neo-gpt reviews/pairs — self-select/swap valid) plus one carve-out PR that can land immediately, before the successor:

Carve-out PR (framework fixes, placement-independent — land first, do not hold hostage to the vehicle swap):

  • RA-9: resources/scss/src/button/Base.scss.neo-button.neo-floating {position: fixed} cascade fix.
  • RA-10: src/main/DomAccess.mjs — null-offsetParent guards on ResizeObserver.observe/unobserve (fixed-positioned subjects have no offsetParent).

Successor PR (the vehicle swap):

  • src/tab/plugin/Overflow.mjsNeo.tab.plugin.Overflow, ntype plugin-tab-overflow: Grace's plugin verbatim minus namespace; the pure core moves onto this class as static computeOverflow(...) (direct import in specs — the adapter chain and its #15065/#15017 loader pain exit the test path entirely).
  • RA-13 restoration: the re-align-on-sync cut in 306874 is load-bearing (author's own flag) — restore it; ping Grace for the author walk-through on activation.
  • DockLayoutAdapter: delete computeTabOverflow + the plugin file under dashboard/; keep the one-line projected-headerToolbar opt-in, now importing Neo.tab.plugin.Overflow.
  • cls rename: neo-tab-overflow-control (grep the e2e).
  • Tests carried per the falsifier-preservation manifest: the 6 re-entrancy specs (live-owner-error surfaced / teardown-race swallowed / concurrent-coalesce / all-fit teardown / error→success latch release / recreation) re-pointed at the tab-native class; the 7 pure-core specs move to a tab-subsystem spec with a direct import; the e2e assertion set (partition: visible+hidden === all tabs · owner-exact geometry · menu-selection activates · pageErrors === []) is preserved with a minimal generic tab.Container harness in-slice (a plain overflowing tab container — keeps the slice self-verifying); the full dock journey re-homes to the dense-workstation demo scene (see successor's sibling ticket, linked below).
  • examples/dashboard/dock keeps its heavy 7-tab main-tabs workspace (the dock opt-in's demonstrable surface).

Contract Ledger Matrix

Target Surface Source of Authority Behavior Fallback / Edge Case Docs Evidence
Neo.tab.plugin.Overflow this ticket (successor of PR #15062 class) measure natural widths once-visible → pure-core split → hidden (removeDom) + one floating out-of-collection control at document.body teardown race swallowed; live-owner throw surfaced; coalesced re-run drains last state class JSDoc 6 re-entrancy specs + generic e2e
Overflow.computeOverflow (static) moved from DockLayoutAdapter.computeTabOverflow (PR #15062, behavior FROZEN) active-never-hidden packing; control width reserved only on overflow; hidden preserves items order; fail-soft on malformed widths degenerate single-wide-item stays visible method JSDoc the 7 pure-core specs, direct-import
headerToolbar.plugins opt-in seam DockLayoutAdapter.projectTabsNode dock opts in; activation rides ordinary activeIndex non-dock users opt in with the same config on any tab.Container headerToolbar adapter comment projection-wiring assertion (existing)
.neo-tab-overflow-control cls this ticket replaces .neo-dock-tab-overflow-control none — new surface, no consumers yet SCSS comment e2e selector

Acceptance Criteria

  • Carve-out PR: RA-9 + RA-10 land independently (may merge before the successor).
  • Neo.tab.plugin.Overflow ships with the pure core as its own static; src/dashboard/plugin/ and DockLayoutAdapter.computeTabOverflow are gone; the adapter's only overflow trace is the opt-in config line.
  • RA-13 re-align-on-sync restored (author walk-through with Grace).
  • All 13 carried unit specs green via direct tab-subsystem imports (no adapter-chain loading).
  • Generic e2e: a plain tab.Container harness proves the four preserved assertions; dock example still demonstrates the opt-in visually.
  • Zero new persisted state / zero new operations (unchanged binding constraint from #14771).
  • Cross-family review; PR #15062 closed with a pointer here once the successor opens.

Out of Scope

The dense-workstation demo scene (sibling ticket, carries the re-homed full dock journey) · grouped drag (#14770) · auto-hide rails · any behavior change to the frozen pure-core contract.

Decision Record impact

amends ADR 0029 §2.4 — placement clause only: the projection half's owner becomes the tab subsystem; the adapter opts in. The behavior contract ("projection affordance, NOT a model change"; selection via existing activeItemId; no dock-specific tab-container fork) is unchanged. The ADR §2.4 text edit rides the successor PR. Authority for the amendment: operator Gate-0 statement + @neo-gpt-emmy correction (2026-07-12 13:33Z) + team convergence (13:32Z).

Avoided Traps

  • Built into tab.Container (config-only, no plugin): rejected — forces a DOM-measurement runtime concern into the declarative container and violates pay-for-what-you-use; the plugin system exists for exactly this (Responsive/Resizable precedents).
  • src/plugin/TabOverflow.mjs (generic plugin home): rejected — "a generic plugin that happens to target tabs" is weaker ownership than the grid/table/list subsystem-dir convention; Emmy's correction language ("genuinely owned by the tab subsystem") names the stronger form.
  • Rework PR #15062 in place: rejected — close-and-replace is the settled team disposition; a fresh PR gives the relocation clean provenance and lets the carve-out land independently.
  • Keeping the pure core on the adapter: rejected — it is the source of both the namespace-reach hack and the unit-env loader pain (#15065/#15017).

Related

Replaces #14771 (closing with pointer) · freezes-and-supersedes PR #15062 (mechanics preserved) · parent #13158 · unblocked by merged #15083/#15086 · falsifier manifest: A2A MESSAGE:de178810 · consumed by the dense-workstation demo scene (sibling, blocked on this) · ADR 0029 §2.4.

Live latest-open sweep: latest 20 open at 2026-07-12T17:05Z + fresh latest-10 at 17:08Z, no equivalent; searches "overflow"/"tab-native" surface only #14771 itself. A2A in-flight sweep: full mailbox window read 17:00Z, [lane-claim] broadcast sent 17:08Z (MESSAGE:312bbc25), no competing claim — Grace's manifest is an explicit baton.

Origin Session ID: fc11bd7a-b599-46f1-b635-bdd1cd3b7a5e Retrieval Hint: "tab-native overflow Gate-0 close-and-replace Neo.tab.plugin.Overflow falsifier preservation"

tobiu referenced in commit 67bdf19 - "feat(tab): tab-native overflow plugin — Neo.tab.plugin.Overflow (#15098) (#15108) on 1:45 AM
tobiu closed this issue on 1:45 AM