Repository navigation
console list page: the saved-view tab strip inside the toolbar's leading slot folds to one tab and "N more" at 1440 — the bar's root does not grow, so the width the fit is derived from is its own content's width (regression of #12440) #12518
Description
Filing gate ① (a product defect, measured on main), filed on the maintainer's word (chat, 2026-10-15, session session_019YFzJp4o8Ck4NsT1toC4UP, seat account os-bill): the seat ran a functional audit of the HotCRM console on objectui main 720f6d4 (report, private to the maintainer: https://claude.ai/artifact/XPrn5eZMsQaKUgasMJEMmR, finding F1), and the maintainer answered 「同意开卡,使用 pm-dispatch 创建卡片」. Quota note: the audit's cards exceed the filing gate's three-per-fire quota on that word, which approved the report's card list as a set.
Parent: objectstack-ai/objectstack#25945. A regression of #12440 (PR #12473, d4bc81e). Blocks nothing; nothing blocks it.
Reader who acts: the domain:ui lane's next dispatch (seat post #9800), one dev round in @object-ui/plugin-view (one class on one element, one test), size S.
Dedupe (MCP search_issues, scope objectstack-ai/objectui, all states, read 2026-10-15T14:14Z): "saved view tab strip folds to one tab and N more inside the list toolbar leading slot width fit ViewTabBar flex-1 regression" → 10, top 8 read: #12504 (open, retiring the view-write family), #12440 and #12423 (closed, this strip's two parents), #11992, #11395, #10987, #10209, #2959 (closed, unrelated). None is this card.
What happens
On the HotCRM Leads list at 1440 px the tab strip shows one tab, "All Leads", and a "7 more" trigger, although the toolbar's leading slot is 873 px wide and all seven tabs fit in it (the design reference on objectstack-ai/objectstack#25945 shows them). Accounts shows "All Accounts · 5 more", Opportunities "Open Deals · 9 more", Tasks "My Open Tasks · 7 more". A window resize of one pixel changes the count to two; shrinking to 1024 and back leaves one. The same page, loaded on another run, showed all seven: the count depends on when the strip was measured.
Readings (objectui origin/main 720f6d4, read 2026-10-15T14:15Z)
packages/plugin-view/src/ViewTabBar.tsxline 977: the bar's root isflex min-w-0 items-center gap-0.5 -mb-px, with noflex-1orw-full. Line 985: the strip inside it isflex min-w-0 flex-1 items-center gap-0.5 overflow-x-clip. Lines 424–428:fitFromWidthsderives the count fromlayoutWidth(stripRef.current); lines 444–452: aResizeObserveron the strip re-fits on every size change.packages/plugin-list/src/ListView.tsxline 4986: the host slot isflex min-w-0 flex-1 items-center(data-testid="list-toolbar-leading"), inside the toolbar's left group (flex flex-1 … min-w-0).packages/app-shell/src/views/ObjectView.tsxline 3267 passesViewTabBarinto that slot with noclassName.- Browser reading (1440, Leads): computed
flex-growdown the chain is strip 1 → bar 0 → leading slot 1 → left group 1; widths 190 → 224 → 873 → 873 px. On the run where seven tabs showed, the strip measured 663 px inside a 697 px bar: the bar's width is the sum of whatever tabs are drawn.
So inside the slot the bar is content-sized, the strip's flex-1 is a share of its own content's width, and the fit is self-referential: once a measure happens while one tab is drawn (the strip is 190 px), one tab is all that fits, and the observer confirms it on every resize. Before #12440 the bar sat in a row of its own and took the row's width.
What to build
- The bar's root takes its container's width when hosted in a flex slot:
flex-1 min-w-0on the root (or the equivalentw-full), so the strip's width is the slot's free width whatever is drawn in it. No change to the fit algorithm, the overflow trigger or the host slot. - A test in
packages/plugin-viewthat mounts the bar inside a fixed-width flex host (the slot's classes) with the measured widths stubbed, and pins that the visible count follows the host's width, not the drawn tabs' width: at an 852 px host with seven 90 px tabs, all seven show; at 400 px, four show and "3 more"; growing the host back re-fits to seven.
Acceptance
- The test above, green; the existing
ViewTabBarfit tests unchanged. - Browser reading by the landing seat at 1440 on the HotCRM Leads list: all seven tabs show with no overflow trigger (the tracking: console list page and shell layout refresh after the 2026-10-15 prototype — parent of #12423, #12427, #12440 and #12429, with the items not yet carded objectstack#25945 reference's main board); at 1024 four tabs and "3 more" (the laptop board); a 1440 → 1439 → 1440 resize ends with seven; a reload ends with seven.
pnpm testfrom the repo root green on the affected files; a changeset (patch) for@object-ui/plugin-view.
Out of scope
- The fit algorithm, the "N more" menu, the mobile view picker; the console list page: the saved-view tab strip and the toolbar's four controls share one row — ListView takes a leading toolbar slot as a host prop and the console passes ViewTabBar into it #12440 host slot's own classes.
Attribution: written by the domain:ui seat in session session_019YFzJp4o8Ck4NsT1toC4UP (account os-bill) on the maintainer's words quoted above.
Screenshots (added 2026-10-15T14:45Z, on the maintainer's word 「专用分支存图,卡里嵌 raw 链接」)
The images live on the attachment branch claude/ui-audit-2026-10-15-shots of objectstack-ai/objectui (commit 918a251, never merged; index at https://github.com/objectstack-ai/objectui/blob/918a2510a54bd6875b90eb6996d098c3ec0451dd/docs/design/console-audit-2026-10-15/README.md). Full set and figure numbers: the audit report linked above.
Leads list at 1440 on 720f6d4: one tab and "7 more" in an 873 px slot
Leads list at 1024: one tab and "7 more"
Accounts list at 1440: one tab and "5 more"



Activity
objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 1 (
priority:p1; a regression of objectui#12440, filed on the maintainer's 「同意开卡,使用 pm-dispatch 创建卡片」)Session:
session_01JZnppgHRp4sREcouhkQYDBAccount:
sunhaolinBranch:
claude/issue-12518-view-tab-strip-widthWorktree:
objectui-issue-12518Domain:
domain:uiSeat:
domain:ui#1File surface:
packages/plugin-view/src/ViewTabBar.tsx, the bar's root element only (:977onmain720f6d4), and theViewTabBartests beside it; one.changeset/12518-*.md. The fit algorithm, the "N more" menu, the mobile view picker,ListView.tsx's host slot andObjectView.tsxare outside the surface (stop on breach; explain in the report).Container & model:
S,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuinames no path-derived mandate)Clause-②: no
Responsibility:
@object-ui/plugin-view'sViewTabBarroot, which does not grow in a flex host.lgand above (HotCRM Leads, Accounts, Opportunities and Tasks, measured).Thread-read: none
Serial constraints cleared: read at objectui
mainc3de0bc73d. Nopm:dispatchedclaim in the lane namesViewTabBar.tsx: this seat's objectui#12483, and seat 2's objectui#12474, Studio Data pillar: the Records grid draws fewer rows than its footer counts, with a stretched gap before the last drawn row (HotCRM crm_account, 10 records, 1280 x 900) #12480, security(app-shell): AiChatPage auto-sends ?handoffPrompt= from the URL as the build agent's first message, so a crafted link makes a signed-in user author (and, with autoPublishAiBuilds, publish) metadata in one click #12493 and Studio flow checker: a flow CEL guard readingvars.xorcurrent_user.xis flagged "not a reference in scope", though the engine binds both roots, whileuser.x, which the engine does not bind, passes silently #12499, read in this act. objectui#12474 and Studio Data pillar: the Records grid draws fewer rows than its footer counts, with a stretched gap before the last drawn row (HotCRM crm_account, 10 records, 1280 x 900) #12480 nameObjectView.tsx/ListView.tsx, other files.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 12518,
"status": "done",
"branch": "claude/issue-12518-view-tab-strip-width",
"pr": "#12537",
"session": "session_01JZnppgHRp4sREcouhkQYDB — the dispatching PM session (subagent = parent's), as named in claim 6194570270",
"premise_still_valid": true,
"summary": "ViewTabBar's root now carries w-full (one class on one element, in packages/plugin-view/src/ViewTabBar.tsx). In the list toolbar's leading flex slot the strip's width is therefore the slot's free width, so the fit no longer reads the tabs it is drawing and collapses to one tab and 'N more'. w-full was chosen over flex-1 after measuring every host in real Chromium. A2: the one JSX mount (app-shell ObjectView) places the bar in two hosts, the list-toolbar-leading flex row and the refused-view view-tab-row block; hotcrm and objectstack have no mounts; the mobile picker does not mount it. The block host is byte-identical in width with and without the change, and a column-flex host keeps its 38 px height under w-full but grows to 199.5 px under flex-1. Deviations from the dispatch: the new test narrows to 500 px rather than 400 (400 px leaves 366 px of strip, 4 x 92 + 80 = 448 exceeds 366, so the fit answers three, not four), and A3 is pinned as 200 → 852 → 851 → 852 ending at seven. A1 held on c3de0bc. No new prop, export or type; ListView.tsx and ObjectView.tsx untouched.",
"tests": "All at HEAD 947e3c2. (1) New packages/plugin-view/src/tests/ViewTabBar.hostWidth-12518.test.tsx: a one-line row-flex layout model read from the rendered classes, plus a ResizeObserver stand-in that delivers until the layout settles. Alone: 'Test Files 1 passed (1) / Tests 3 passed (3)', VERDICT command-exit 0. (2) Ablation, one-off with no permanent file, through objectstack scripts/ablation-replace.mjs (wrap mode): it put the pre-change root class string back ('anchor x1 → x0', 'replace x0 → x1', blob cb01b5f8b12b → cf7ebf1549fc). Result 'Tests 3 failed (3)': 'expected 164 to be 818' (strip collapsed to one 90 px tab + 72 px trigger + gap), "expected [ 'v1' ] to deeply equal [ 'v1', 'v2', 'v3', 'v4' ]", "expected [ 'v1' ] to have a length of 7 but got 1". Restore: 'blob after restore cb01b5f8b12b == blob at HEAD', 'git diff HEAD empty'; my own check gave diff-lines=0 and anchor counts 1/0. Direction: turned red, as expected. The test imports ../ViewTabBar relative to src, so no dist/ was in the path. (3) Union, one vitest invocation under the lock: the whole packages/plugin-view/ (every ViewTabBar* suite, readonlyViewMenus-10209), plugin-list ListView.toolbarLeading-12440, the 20 app-shell ObjectView.* files that reference the bar (incl. viewTabsInToolbar-12440 and viewRefusal-12112, which assert on its test ids, and relayRungCensus-7559), the census suites iconOnlyButtonCensus-12143 and ariaLabelLiteralCensus-12145, and the changeset suites changeset-polarity-census and check-changeset-{claims,no-major,presence,fixed,overwrite}. Result: 'Test Files 106 passed (106) / Tests 1205 passed (1205)', VERDICT command-exit 0. (4) One-off real-Chromium harness, deleted afterwards: the console Vite dev server, the real component and the real Tailwind build, before = className w-auto. Toolbar host after: 7 tabs at 1440; 1440→1439→1440 ends at 7; 3 tabs + '4 more' at 1024 (harness slot 484 px); back to 1440 = 7; reload = 7; load at 1024 then 1440 = 7. Before: 1 tab + '6 more' at every step, bar 218.4 / strip 184.4 inside a 900 px slot. Harness note: my pre-start port check used ss, which is absent here, so its '0' was not a reading; vite --strictPort was the guard that held, and the server was stopped by its own PIDs.",
"gates": "Local subset at 947e3c2; exit codes were captured to files before any pipe. objectui has no scripts/pm/dispatch-gates.mjs, so the list was derived by hand from package.json check:* and lint.yml / ci.yml / changeset-guard.yml / changeset-presence.yml / line-citation-gate.yml / repo-gates.yml / performance-budget.yml, and no --ran reconciliation exists for this repo. (a) pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-view^...' build, then pnpm --filter @object-ui/plugin-view type-check (echoed 'tsc --noEmit && tsc -p tsconfig.test.json'): VERDICT command-exit 0; tsc -p tsconfig.test.json --listFiles counts the new test file (1 hit, 71 plugin-view test files). (b) eslint --no-inline-config on the two changed .tsx files: EXIT=0, 0 errors; 3 warnings, identical on the base blob. (c) EXIT=0 each, with its own pass line: check:control-bytes ('OK (scanned 8864 tracked text file(s)'), check:new-line-citations ('0 new citation(s)'), check:changeset-claims, check:pending-changeset-literals, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:i18n-keys, check:shell-escape-residue, scripts/check-changeset-no-major.mjs, check-changeset-presence.mjs ('2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'), check-changeset-fixed.mjs, check-changeset-overwrite.mjs. (d) check-governed-queue-guard --test on the 3 paths: NOT GOVERNED. CI, read once at PR open (36 runs): Changeset Fixed Group Check success; Changeset Bump Policy success; label success; dependabot, Test (coverage) and the coverage-shard matrix template skipped; Type Check, Lint, Test (shards 1-16/16), Test (dist pins), Build & E2E, Build Docs, Repo Gates, Line Citation Gate, Changeset Declaration, Changeset Claim Re-read, Changeset Overwrite Report, Bundle Analysis, Spec Main Shape Gate, Governed Surface Queue Guard and Live E2E (informational) in_progress. CI convergence belongs to the PM. The PR is 'behind' main by 2 commits (objectui#12474 touched ListView/ObjectView but not the slot or the mount); git merge-tree against the new main is clean, so no merge was made.",
"mcp_calls": "5 — read-only: issue_read (get #12518; get_comments #12518; get_comments #12518 for this report's read-back), pull_request_read (get #12537 for the body read-back; get_check_runs #12537). No MCP write tool used.",
"api_writes": "2 — both through the fleet relay (scripts/pm/fleet-write/dispatch.mjs, as objectstack-fleet[bot]): POST /repos/objectstack-ai/objectstack/dispatches carrying pr_create (executed POST /repos/objectstack-ai/objectui/pulls, draft, and POST /repos//issues/12537/assignees [sunhaolin]); POST /repos/objectstack-ai/objectstack/dispatches carrying this comment (POST /repos//issues/12518/comments). No label writes. git pushes of the branch (not REST): empty-branch probe plus three commit pushes.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted in PR 12537's Acceptance notes, not filed · fitFromWidths adds the strip's 2 px gap after every tab, the last included, so 'everything fits' needs 2 px of slack past the drawn content; in a content-sized strip that alone tipped the first fit (642 px of strip for 644 px of tabs); harmless once the bar takes the slot's width · observation, no reach"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
Landed ·
domain:uiseat 1 ·session_01JZnppgHRp4sREcouhkQYDB· 2026-10-15T17:22Zmain92318202a1at 2026-10-15T17:21Z. It has one parent,646307f37a(objectui#12520's landing), and is an ancestor oforigin/main.92318202a1to the accepted head947e3c2001.Fixes objectstack-ai/objectui#12518. The only issue closed with the merge is this card, ascompleted.w-full min-w-0) instead of folding to one tab and "N more".6197754861). It notes the card's seven-tab expectation against HotCRM's eight views.6195471981, ACCEPT6197754861.Generated by Claude Code
fix(plugin-view): the view tab bar takes its host's width, so the lis…