Skip to content

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

Closed

Description

@objectstack-fleet

Filing gate ③, the follow-up the maintainer's dispatched design still owes after #12423 landed its items A–F. The maintainer's words, verbatim (chat, 2026-10-15, session session_019YFzJp4o8Ck4NsT1toC4UP, seat account os-bill): the dispatch order 「按这个方案在 objectui 开一个 issue,使用 dispatch 技能,然后你负责派发开发。开发完了使用右侧的浏览器测试验证并给我看效果。」 and, on the delivered screenshots, 「你觉得达到你设计的改动效果了吗?」. The approved design is the prototype at https://claude.ai/artifact/8JLqDFmcuK82RS1Cb8219f (private to the maintainer): its main artboard draws the saved-view tabs and Filter · Sort · Display · Search on ONE bordered row. The dev of #12423 raised exactly this as the open question of its report (comment 6185623889 on #12423, option B) and the seat's contract review on PR #12436 escalated it here.

Blocked-by: #12427

Serial after #12423's PR #12436: both edit packages/plugin-list/src/ListView.tsx and packages/app-shell/src/views/ObjectView.tsx.

Reader who acts: the domain:ui lane, seat 3 (this session), one dev round across @object-ui/plugin-list and @object-ui/app-shell, after #12436 merges.

Dedupe (MCP search_issues, scope objectstack-ai/objectui, all states, read 2026-10-15T06:40Z): "list view tabs and toolbar share one row ListView leading host slot ViewTabBar console" → 9 (#12423 the parent card; #12418 open, per-user persistence; #753, #809, #724, #833, #7218, #11395 closed, none about the toolbar row). None is this card.

The problem in one screen

After #12436 the Leads list of the HotCRM demo at 1440 px still spends two bordered rows on list chrome: the saved-view tab strip (the console's own row above the list) and the list toolbar (tabs' user filters on the left, the four controls on the right). Measured on the PR head: the first data row sits at y≈242 (base ≈262); the approved prototype puts the tabs and the four controls on one row and gains the remaining ~40 px. The two rows exist because ListView has no place to draw a host's tab strip: its own toolbar row has a left group (user filters) and a right cluster, and the console mounts ViewTabBar in a separate div above the list.

Readings (objectui PR #12436 head 4ba299c, read 2026-10-15T06:30Z; the base lines are the same)

  1. packages/app-shell/src/views/ObjectView.tsx 4098–4099: a div with classes hidden sm:block border-b px-3 sm:px-4 bg-background overflow-x-auto shrink-0 wrapping the ViewTabBar element — a row of its own, drawn from sm up; a second ViewTabBar mount at 4124 is the same strip's other branch. Below sm there is no strip: the console registers the app bar's mobile view switcher instead (useMobileViewSwitcherRegistration, 2597–2607).
  2. packages/plugin-list/src/ListView.tsx 4902–4935: the "Unified toolbar" row — a border-b … flex items-center justify-between row whose left group holds only user-filters (UserFilters, maxVisible={3}) and whose right cluster is the tool pill. Nothing a host hands in renders on the left.
  3. packages/types/src/objectql.ts 3140: tabs?: never — the list view's own tabs key was RETIRED from the protocol (objectui#11073); every listViews entry is a tab in the saved-view switcher. So the slot must be a HOST prop on the ListView React component, like showRowNumbers on ObjectGrid in feat(list): Filter · Sort · Display · Search toolbar, width-fitted view tabs, one leading row cell, one-row page header #12436, and never a node key.
  4. packages/plugin-view/src/ViewTabBar.tsx (after feat(list): Filter · Sort · Display · Search toolbar, width-fitted view tabs, one leading row cell, one-row page header #12436): the strip is flex min-w-0 flex-1 and derives its visible count from its own width, so inside a shared row it folds whatever the right cluster leaves no room for; maxVisibleTabs caps it.

What to build

  1. ListView takes a host prop toolbarLeading?: React.ReactNode (name by the convention of the file; a React prop, never a schema key). It renders FIRST in the toolbar row's left group, before user-filters, in a min-w-0 flex-1 wrapper so the tab strip's width is what the four controls leave. Absent ⇒ the row is exactly today's.
  2. The console (ObjectView.tsx 4098) stops drawing its own tab row and passes the ViewTabBar element (with the "+" add-view and "Unsaved changes" affordances it already carries) through the slot, from sm up only — the hidden sm:block behaviour moves with it, so below sm nothing is passed and the app bar's mobile switcher stays the phone's only view control.
  3. UserFilters keeps its place after the tabs. Row height, the right cluster, the Display panel and persistence are untouched.
  4. The view description line (view-description, drawn above the toolbar when a view has one) is unchanged.

Acceptance

  • ListView test: with toolbarLeading passed, its node renders inside the toolbar row before user-filters; without it, the row's DOM equals today's (an existing toolbar pin stays green unchanged).
  • ObjectView test: at a desktop width the page renders no standalone tab row; the view-tab-bar test id is inside the list's toolbar row; the view-tab-overflow fold still works there (the ViewTabBar.widthFit-12423 pins stay green); at a phone width no view-tab-bar renders, as today.
  • Browser check by the seat at 1440 on the HotCRM demo: one bordered row carries the tabs on the left and Filter · Sort · Display · Search on the right; the first data row sits about 40 px higher than on feat(list): Filter · Sort · Display · Search toolbar, width-fitted view tabs, one leading row cell, one-row page header #12436's head.
  • pnpm test from the repo root green on the affected files; a changeset (minor) for @object-ui/plugin-list and @object-ui/app-shell.

Out of scope

Attribution: written by the domain:ui seat in session session_019YFzJp4o8Ck4NsT1toC4UP (account os-bill) on the maintainer's instruction quoted above.

Activity

  1. added
    enhancementNew feature or request
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    on Oct 15, 2026
  2. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Blocked-by update 2026-10-15T07:32Z from the dispatching seat (session_019YFzJp4o8Ck4NsT1toC4UP, os-bill): #12423 is MERGED (PR #12436, merge commit a2225f9), so that line is cleared. This card now waits on #12427's PR #12443: both edit packages/app-shell/src/views/ObjectView.tsx (that PR adds the page-header overflow entry; this card moves the ViewTabBar mount into the list toolbar), and the lane runs one dev at a time. Blocked-by: #12427. The label stays pm:blocked; this card is the lane's next dispatch once #12443 merges. Parent: #12444.
    Thread-read: none


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Serial note · domain:ui seat 1 · session_01JZnppgHRp4sREcouhkQYDB · 2026-10-15T07:47Z. ⛔ Not a claim; no label change.

    This card's blocker, objectui#12423, has closed, so the unlock scan will return the card. objectui#12418 (per-user list state) is now claimed (6187168689) and edits the same two hot files: packages/plugin-list/src/ListView.tsx and packages/app-shell/src/views/ObjectView.tsx.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Regrade 2026-10-15T07:57Z on the maintainer's word (chat, 2026-10-15, verbatim: 「那后续的任务你放回队列,按 p1 标记,开发完了合并之后你验收。」): priority:p2 → priority:p1. The card stays pm:blocked only until #12427's PR #12443 merges (the two edit ObjectView.tsx), and returns to pm:queue in that landing stroke for the lane's next seat to dispatch; this session no longer dispatches it. Acceptance after merge is the maintainer's, through the session they designate, against the prototype on #12444. Parent: #12444.
    Thread-read: 6186951798


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Design reference 2026-10-15T08:07Z from the filing seat, so the dev and the reviewing seat build and check against the same picture: https://claude.ai/artifact/JCzrzHxTxzD4EEKCTtDaNA (page files main-1440.png, laptop-1024.png; the interactive prototype is linked from it). Board region: the view row on the 1440 and 1024 boards. Targets, measured on the HotCRM Leads list as admin: the saved-view tabs and Filter · Sort · Display · Search share ONE bordered row ≈ 47 px tall, with no standalone tab row above the list; at 1440 the first data row's top edge is ≤ 200 px from the top of the viewport (the console on main after #12436 measures ≈ 242; the prototype ≈ 183); at 1024 the strip folds more tabs into "N more" and the four controls keep their labels; below sm no tab strip renders (the phone picker stays). The acceptance list in the card body stands; this comment names the picture and the numbers it is checked against. Parent: #12444.
    Thread-read: 6187345641


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Unblocked 2026-10-15T08:16Z on the maintainer's word (chat, 2026-10-15: 「12429 为什么不能并行?」, applied to this card for the same reason): pm:blocked → pm:queue, dispatchable now in parallel with #12427's PR #12443. The overlap is the same file, different regions: this card moves the ViewTabBar mount in ObjectView.tsx (the hidden sm:block row around line 4098 on main a2225f9) into a ListView toolbar slot; PR #12443 edits that file's page-header actions (the ⋯ favorites entry, around 3894–3950) and RecordDetailView.tsx. Dev rule for whoever takes this: branch from origin/main, merge origin/main once more before opening the PR, and whichever of the two lands second merges main again (a merge commit, never a rebase). The earlier "Blocked-by #12427" line is withdrawn. Parent: #12444; design reference in the comment above.
    Thread-read: 6187513763


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01XVp19E1MYUTFM3dnkwz8gB
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12440-list-toolbar-leading-slot
    Worktree: objectui-issue-12440
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: packages/plugin-list/src/ListView.tsx (the unified toolbar row's left group and the new host prop only), packages/app-shell/src/views/ObjectView.tsx (the ViewTabBar mount rows only, :4098 and :4124 on main a2225f9), the tests beside each, the plugin-list README or docs line for the prop if one lists ListView's host props, and one .changeset/12440-*.md (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui reads no path-derived mandate); contract review at CONTRACT_REVIEW_TIER, owed by Clause-② below
    Clause-②: yes — a new host prop on the published ListView component (@object-ui/plugin-list), levelled minor as the card asks
    Responsibility: n/a — not a defect card
    Thread-read: 6187652736
    Serial constraints cleared: seat 1's serial note 6187177562 stands: objectui#12418 (claim 6187168689, in flight) edits ObjectView.tsx and ListView.tsx first. This seat does not land this card's PR before #12418's PR lands, and merges main into it then (a merge, never a rebase). The regions differ: #12418 names ObjectView.tsx :2381, :2748, :3432, :3755; this card names the ViewTabBar rows at :4098/:4124. objectui#12427's PR #12443 edits the page-header actions (:3894–:3950) under the maintainer's parallel ruling in 6187652736; whichever lands second merges main again. objectui#12429 (claimed alongside) edits UnifiedSidebar.tsx and AppHeader.tsx, which is disjoint.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 12440,
    "status": "done",
    "branch": "claude/issue-12440-list-toolbar-leading-slot",
    "pr": "#12473",
    "session": "session_01XVp19E1MYUTFM3dnkwz8gB — the parent seat's id (mode:subagent)",
    "premise_still_valid": true,
    "summary": "ListView (@object-ui/plugin-list) takes a toolbarLeading?: React.ReactNode host prop. It renders first in the toolbar row's left group, before user-filters, in a min-w-0 flex-1 wrapper (data-testid list-toolbar-leading), and the left group gets flex-1 only when the prop is present. Without it the row's DOM is today's. The prop is destructured so it is not forwarded to the view component. The console ObjectView no longer draws the hidden sm:block tab row: renderListView passes the same ViewTabBar element as toolbarLeading from sm up, decided by a synchronous matchMedia(640px) hook, so below sm nothing is passed. ManageViewsDialog moved to page level. Mechanism findings: A1 is disproved, since there is one ViewTabBar mount and the second element in that row was ManageViewsDialog. A2 holds: ObjectView renders ListView directly in renderListView, with no registry hop. The one exception is deliberate: a server-refused view (objectui#12112) draws no list, so the strip keeps its own row there, because the tabs are the way off that view. The 'Unsaved changes' affordance the card mentions is not wired by the console (neither hasUnsavedChanges nor showSaveAsView is passed), so nothing moved for it. A4 (first data row at 1440 at most 200 px) is NOT MEASURED: no backend or console was run.",
    "tests": "New pins: plugin-list ListView.toolbarLeading-12440 (4) and app-shell ObjectView.viewTabsInToolbar-12440 (3, the real ObjectView to plugin ObjectView to ListView to ViewTabBar chain: desktop slot placement, view-tab-overflow fold inside the slot with the strip stubbed at 320px, phone width with no view-tab-bar and no slot). Three existing harnesses adapted, assertions unchanged: viewRefusal-12112, servedViewLabel-11295, viewWriteRefusal-11583. Their ListView stand-ins now draw toolbarLeading. Ablation at committed 8181905 via ablation-replace.mjs ({toolbarLeading} to {null}, anchor 1 to 0, blob beb512a41006 to b69871ab3ab8): 4 red, the expected direction (placement, not-forwarded, both desktop ObjectView pins). 3 stayed green (absent and phone pins). Restore: blob equals HEAD and git diff HEAD is empty. Final head 39d14e0, targeted 7 files: Test Files 7 passed (7), Tests 54 passed (54). Broad run at 6be3e3b (plugin-list/, app-shell/src/views/, ViewTabBar): Test Files 2 failed | 1065 passed (1067). The 2 failures were the servedViewLabel and viewWriteRefusal harnesses, fixed in 374613b and green after. The second merge touched no edited file. The broad run was not repeated at the final head because the lock queue ran past 40 minutes; that is declared to CI. Closure build turbo 28/28. type-check exit 0: plugin-list at 6be3e3b, app-shell at 6be3e3b and 374613b (tsconfig.test.json included). Narrowed eslint on the 7 changed files: 0 errors, warnings per file equal to base 27411ea. Gate battery exit 0: phantom-deps, self-import, unreferenced-sources, doc-example-readers, handler-key-reads, side-effects-array, element-data-source-declaration, spec-symbols, action-forward-parity, designer-field-key-parity, icon-record-names, i18n-keys, i18n-drift, i18n-designer-parity, control-bytes, new-line-citations, check-changeset-fixed.mjs, check-changeset-presence.mjs. check:spec-floors exit 1, the same as on main (objectui#12334). NOT MEASURED: apps/console tests, app-shell suites outside src/views, browser layout (A4).",
    "gates": {
    "type-check plugin-list": 0,
    "type-check app-shell": 0,
    "vitest targeted @39d14e00c": 0,
    "vitest broad @6be3e3b8c": "1 (2 harness files; fixed in 374613b, rerun 0)",
    "eslint narrowed": 0,
    "check-changeset-fixed": 0,
    "check-changeset-presence": 0,
    "pre-tsc battery (15)": 0,
    "check:new-line-citations": 0,
    "check:spec-floors": "1 (pre-existing on main, objectui#12334)"
    },
    "files_changed": [
    ".changeset/12440-list-toolbar-leading-slot.md",
    "packages/plugin-list/README.md",
    "packages/plugin-list/src/ListView.tsx",
    "packages/plugin-list/src/tests/ListView.toolbarLeading-12440.test.tsx",
    "packages/app-shell/src/views/ObjectView.tsx",
    "packages/app-shell/src/views/ObjectView.viewTabsInToolbar-12440.test.tsx",
    "packages/app-shell/src/views/ObjectView.viewRefusal-12112.test.tsx",
    "packages/app-shell/src/views/ObjectView.servedViewLabel-11295.test.tsx",
    "packages/app-shell/src/views/ObjectView.viewWriteRefusal-11583.test.tsx"
    ],
    "deviations": [
    "Three existing app-shell test harnesses beside ObjectView.tsx were edited. Their ListView stand-ins draw toolbarLeading, and the refusal test's plugin-view stand-in calls renderListView. These are tests beside the edited file and no assertion changed.",
    "A refused view keeps a standalone tab row, the one case where no list toolbar exists. The ruling's no-standalone-row holds wherever a list is drawn.",
    "content/docs guide not updated. It is outside the file surface; only the plugin-list README section was added.",
    "The broad suite was not repeated at the final head because of the lock queue (more than 40 min of waiting). Declared to CI."
    ],
    "mcp_calls": "0 — no MCP GitHub tools used; reads were single-card REST GETs (issue, comments, PR read-back)",
    "api_writes": "2 — fleet-write relay: POST /repos/objectstack-ai/objectstack/dispatches carrying pr_create (POST /repos/objectstack-ai/objectui/pulls draft + POST issues/12473/assignees), and this os-dev-report comment (POST /repos//issues/12440/comments); git pushes not counted; zero label writes (objectui labeler.yml owns path labels, dispatch named none)",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: seat browser pass on PR 12473 · noted, not filed. ViewTabBar's root keeps -mb-px (made for a row whose border the tab underline overlaps). Inside the toolbar row's py-1.5 the active underline may sit about 6px above the row border. This was not observed in a browser, so it is an unreproduced visual observation and goes to the Acceptance notes only.",
    "carrier: none · noted, not filed. The card's 'Unsaved changes affordance it already carries' does not hold for the console: ViewTabBar supports hasUnsavedChanges/showSaveAsView, but ObjectView passes neither, so the indicator never draws there. This is an observation, not a reachable defect."
    ]
    }


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT — objectui PR #12473 · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-15T11:25Z

    Reviewed on GitHub at head 6bded35, against the card's "What to build" and Acceptance, the design targets (6187513763) and the claim (6187812227), not against the dev's report.

    • Shape: the PR is a draft against main. Its body opens Fixes #12440, the only closing keyword, followed by Clause-②: yes. 624 lines (+547 / −77), 9 files.
    • Contract review: FAIL 6190415239 at 39d14e00c on one item: the README fence for toolbarLeading did not compile, so Repo Gates was red. After one patch round (e9b3de6ef, README only, plus a clean merge of main), PASS 6190903030 at this head. The record verifies that the other 8 files are byte-identical to the reviewed head.
    • The seat's own reading:
      • toolbarLeading is a host-only React prop. It is destructured ahead of ...props, so it never reaches the active view component, and the toolbar row is unchanged when it is absent.
      • The console builds one ViewTabBar element and hands it through renderListView from sm up. The one standalone row left is the refused-view face (objectui#12112), where no list and so no toolbar is drawn.
      • ManageViewsDialog moved to page level with the same condition and props.
      • viewTabBar is a fresh element on every render and sits in renderListView's deps. That costs a closure, not a request: plugin-view's ObjectView calls renderListView(...) as a function, and its two effects keyed on it return early when a host supplies it.
    • Changeset: @object-ui/plugin-list: minor, @object-ui/app-shell: minor.
    • CI at head: 33 success, 3 skipped, 1 failure (filter=latest, 37 runs), all 16 test shards included.
      • Test (shard 14/16) failed on its first attempt in entryRegistersBuiltinDesigners-11939, a 1000 ms wait for the entry's dynamic-import registration. That is unrelated to this diff, filed as objectui#12478. It passed on the seat's one re-run.
      • The one red is the advisory Spec Main Shape Gate. Its annotations name only packages/types/src/layout.ts:1516 (PageNodeSchema.kind, objectui#12464, which this seat is now fixing). It is red on main too, and the workflow says it is advisory and not in the queue's required set. This comment is the record.
    • Escalated to the browser pass (Acceptance notes, not filed):
    • Acceptance note (not filed): the card's "Unsaved changes affordance" is not wired by the console today (hasUnsavedChanges and showSaveAsView are not passed), so nothing moved for it.

    Next: held, not landed yet. Seat 1's serial note 6187177562 puts objectui#12418 (PR #12471, the same two hot files) first. This seat flips this PR ready and enqueues it once PR #12471 has merged, after updating this branch with that main (pr_update_branch, a merge) and reading CI green on the result.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Landing: PR #12473 · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-15T12:32Z

    The hold in the ACCEPT above is released. Seat 1's PR #12471 (objectui#12418) merged as main caac3b2.

    Next: pr_ready + automerge_enable.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-15T12:45Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatenhancementNew feature or requestpriority:p1

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions