LearnNewsExamplesServices
Frontmatter
id12321
titlerefactor(tab): drop column-reverse inversion for dock:''left'' tab headers — keep real item order
stateClosed
labels
enhancementairefactoringarchitecturemodel-experience
assigneesneo-gpt
createdAtJun 1, 2026, 8:22 PM
updatedAtJun 7, 2026, 7:18 PM
githubUrlhttps://github.com/neomjs/neo/issues/12321
authorneo-opus-ada
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtJun 1, 2026, 10:21 PM

refactor(tab): drop column-reverse inversion for dock:'left' tab headers — keep real item order

Closed v13.0.0/archive-v13-0-0-chunk-15 enhancementairefactoringarchitecturemodel-experience
neo-opus-ada
neo-opus-ada commented on Jun 1, 2026, 8:22 PM

Context

Portal.view.shared.TabContainer (and others) use tabBarPosition: 'left'. For a left-docked tab header, src/tab/header/Toolbar.mjs getLayoutConfig() returns {direction: 'column-reverse', pack: 'end'}column-reverse positions the buttons at the top of the vertical bar, but as a side effect it renders the items array inverted: the LAST array entry becomes the FIRST (top) tab. So a left-docked container's items array is the reverse of its visual order.

The Problem

The inversion is a cognitive footgun — "not easy to spot." In the news tab-order work it directly caused two bugs in a single change:

  • #12319 / PR #12320: tabs ordered wrong because new views were appended at the array bottom (→ rendered at top).
  • #12320 cycle-1: after reordering the array, TabContainerController still mapped routes to the old indexes → routes activated the wrong content.

It is disproportionately confusing for LLM/agent maintainers: a human edits the array, sees the rendered output, and corrects course; an agent reasons about array order purely from source and cannot see that a non-obvious CSS column-reverse has flipped it. It couples every consumer's array order — and any controller route→index mapping — to an invisible reverse. This is a model-experience (MX) friction worth removing.

The Fix (operator-directed)

Make dock: 'left' keep the real (top-to-bottom display) item order — i.e. items[0] = top tab — by positioning the buttons at the top with a CSS technique that does not reverse the list (e.g. {direction: 'column', pack: 'start'}, or equivalent alignment). Array order == display order; no inversion.

Blast Radius / Migration (IMPORTANT)

src/tab/header/Toolbar.mjs is framework-wide: dropping column-reverse flips the display of every left-docked Neo.tab.Container, so the change MUST be coordinated with a consumer migration. The true blast radius is Neo.tab.Container consumers with tabBarPosition:'left'three portal views:

  • apps/portal/view/shared/TabContainer.mjs (the shared base — sets tabBarPosition:'left'; subclasses inherit)
  • apps/portal/view/news/TabContainer.mjs (items reversed per PR #12320 → re-order to real: Release Notes, Tickets, Discussions, Blog, Medium, Pull Requests)
  • apps/portal/view/examples/TabContainer.mjs (inherits tabBarPosition:'left' from the shared base → does NOT match a literal tabBarPosition grep; live at apps/portal/view/Viewport.mjs:55; 4 env tabs [devmode, dist_dev, dist_esm, dist_prod] + a controller tabItems array that MUST mirror items order, and dragResortable:true so it's on the SortZone reverse-direction path)

Correction (2026-06-01, @neo-opus-ada): apps/{covid,sharedcovid}/view/country/{Table,HistoricalDataTable}.mjs are NOT affected — their dock:'left' is on table columns (left-pinned), served by the separate src/table/header/Toolbar.mjs getLayoutConfig(dock), not tab.header.Toolbar. My original ledger over-scoped them via a bare dock:'left' grep.

Contract Ledger

(Backfilled per learn/agentos/contract-ledger.md — this changes a consumed core framework surface + migrates consumers. Rows incorporate @neo-gpt's ticket-intake suggestions, #12321 comment.)

Target Surface Source of Authority Proposed Behavior Fallback / Edge Case Docs Evidence
Neo.tab.header.Toolbar#getLayoutConfig() for dock:'left' This ticket + source V-B-A (src/tab/header/Toolbar.mjs) Returns a non-reversing column layout (e.g. {direction:'column', pack:'start'}); header buttons render in config array order, top-to-bottom — no column-reverse inversion Buttons still align at the TOP of the left bar; top/bottom/right docks unchanged getLayoutConfig JSDoc Unit coverage for the left-dock layout config + button order
Neo.tab.Container left-docked consumers (the items→display contract) This ticket + src/tab/Container.mjs tabBarPosition:'left' consumers author items in real top-to-bottom order (array order == display order) Route→activeIndex activation stays correct after consumer reorder; obsolete inversion comments removed Consumer comments Portal news route→activeIndex spec (TabContainerController.spec.mjs) stays green
The 3 portal tab.Container left-docked consumers (shared base, news, examples) Consumer sweep (§Blast Radius) Visual order preserved after each consumer's items (+ examples' controller tabItems) is migrated to real order; covid/sharedcovid are table-column docks, NOT affected Non-tab dock:'left' (table columns / toolbars) unaffected Local comments L3 / visual per affected app
Tab-header drag-resort (src/draggable/tab/header/toolbar/SortZone.mjs) @neo-gpt intake flag + V-B-A Drag/resort still computes correct target positions after the layout de-reversal No explicit reverse/direction keyword handling found in SortZone → likely DOM-position-based (reverse-agnostic); confirm at implementation N/A Existing/new drag-sort unit OR targeted manual verification

Acceptance Criteria

  • AC1 — A dock:'left' tab header renders items in array order (items[0] = top tab); no inversion.
  • AC2 — Buttons remain at the TOP of the left bar (the original intent of column-reverse+pack:end preserved via the non-reversing technique).
  • AC3 — All 3 portal tab.Container left-docked consumers (shared base, news, examples — incl. examples' controller tabItems mirror) audited and migrated to real display order; no visual regression (L3 per app). (covid/sharedcovid table-column docks excluded — see Correction.)
  • AC4 — The news TabContainerController route→activeIndex resolution (route-lookup, PR #12320) needs no change — validating that route/id-lookup is reorder-safe (regression test stays green).
  • AC5 — Remove the now-obsolete inversion doc-comment from Portal.view.news.TabContainer items.
  • AC6 — Tab-header drag-resort (SortZone) verified to compute correct positions after the layout de-reversal (no reverse-layout regression).

Out of Scope

  • Any broader tab-header redesign beyond removing the inversion.

Related

MX note

This is an agent-experience friction-to-gold: the column-reverse inversion is low-cost for humans (who see the render) but high-cost for agents (who edit from source). Removing it makes left-docked tab containers reason-about-able from source alone.

tobiu referenced in commit ebbb852 - "fix(tab): keep left docked tabs in source order (#12321) (#12324) on Jun 1, 2026, 10:21 PM
tobiu closed this issue on Jun 1, 2026, 10:21 PM