Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

console list page: a list_toolbar action that acts on a selection leaves the page header for the selection bar — the bar floats when rows are selected, next to the view's bulkActions, and the header keeps only New, Import and the ⋯ #12462

Closed

Description

@objectstack-fleet

Ruled: 6190946913 · letter A-narrow · 2026-10-15T11:26Z

Filing gate ③, on the maintainer's word (chat, 2026-10-15, session session_019YFzJp4o8Ck4NsT1toC4UP, seat account os-bill): after the post-merge screenshots the maintainer said 「和你的设计图差距还是比较大,后续还有哪些任务你给我具体解释一下」, the seat listed the remaining gaps, and the maintainer answered 「同意开卡」. The design is the main board of the reference page https://claude.ai/artifact/JCzrzHxTxzD4EEKCTtDaNA (main-1440.png; the prototype's interaction note: 「选中后底部浮出批量操作条:Add to Campaign / Change Owner / Delete;页头不再常驻 Add to Campaign」).

Parent: objectstack-ai/objectstack#25945. Blocks nothing; nothing blocks it. Shares ObjectView.tsx (the header actions region, lines 3964–3972) and ListView.tsx with #12440 (the tab strip region and the toolbar row): different regions, dispatchable in parallel; the second to land merges main again.

Reader who acts: the domain:ui lane's next dispatch, one dev round across @object-ui/app-shell, @object-ui/plugin-list and @object-ui/plugin-grid, size M. No packages/spec change: the keys this card reads exist.

Dedupe (MCP search_issues, scope objectstack-ai/objectui, all states, read 2026-10-15T08:58Z): "bulk action bar floating selection bar list_toolbar actions move when rows selected Add to Campaign change owner delete" → 3, all closed: #12105 (a promoted action's param on the selection bar), #3056 (bulk delete leaves checkboxes ticked), #751 (the original BulkActions bar). None is this card; #12105 shows the selection bar already carries promoted actions, which is the mechanism this card extends.

The problem

On the Leads list, "Add to Campaign" is a list_toolbar action the HotCRM app declares, and the console draws it permanently in the page header beside New and Import, whatever is selected. It acts on records, so with nothing selected it is a button that leads to a picker, and with rows selected it is far from them. The design draws it where the records are: a bar that floats at the bottom when rows are selected, carrying the view's bulkActions (Change Owner, Delete) and the selection-taking toolbar actions; the header keeps New, Import and the ⋯.

Readings (objectui origin/main a8e5b73, read 2026-10-15T08:58Z)

  1. packages/app-shell/src/views/ObjectView.tsx lines 2022–2070 build localizedToolbarActions (the app's list_toolbar actions, actionRendersAt(a, 'list_toolbar')); lines 3964–3972 draw them in the page header through SchemaRenderer schema={toolbarBar} (or EnvironmentListToolbar on the environment list).
  2. packages/plugin-grid/src/ObjectGrid.tsx lines 83–87 and 3359–3464: the grid's selection bar is components/BulkActionBar.tsx, fed by resolveBulkActions from the view's bulkActions (packages/types/src/objectql.ts 651–713: names resolved against objectDef.actions and promoted; the four-layer gate of objectui#12082); resolvePromotedBulkParams handles a promoted action's params (objectui#12105).
  3. packages/plugin-list/src/ListView.tsx lines 1902–1957: the non-grid views' own bulk bar, gated the same way (permittedBulkActions, partitionBulkRows).
  4. The action shape in packages/types/src/ui-action.ts and objectql.ts line 2411 carries the selection scope an action declares (the dev names the exact key in the PR; it is read, not minted).

What to build

  1. A list_toolbar action whose declaration says it acts on a selection is not drawn in the page header. It is drawn in the selection bar when one or more rows are selected, after the view's bulkActions, through the same promotion path those use (resolveBulkActions / the promoted-params handling of objectui#12105), so its gate, its confirm step and its params behave as a declared bulk action's do. An action with no selection scope stays in the header as today.
  2. The grid's BulkActionBar and the non-grid views' bar both take these actions; one shared derivation, no second table.
  3. The header's action row is New, Import (the app's non-selection actions) and the ⋯ (console shell: the page-header star and the sidebar pin become one "Pin to sidebar" action in the page overflow menu, landing in the Pinned section at the top; the app sidebar's bottom Favorites group goes #12427). Nothing in HotCRM's metadata changes: the demo's "Add to Campaign" is the instance this is measured on.

Acceptance

  • ObjectView test: an app declaring one selection-scoped and one plain list_toolbar action renders only the plain one in the header; with one row selected the selection bar lists the view's bulkActions and then the selection-scoped action, in that order; the action's gate (objectui#12082's four layers) is applied to the selected rows, not skipped.
  • ListView test: a non-grid view (kanban) shows the same bar with the same roster on selection.
  • Browser reading by the landing seat at 1440 on the HotCRM Leads list: the header reads New · Import · ⋯; selecting two rows floats a bar with Change Owner · Delete · Add to Campaign (whatever the demo's bulkActions declare, then the toolbar action); clearing the selection hides it.
  • pnpm test from the repo root green on the affected files; a changeset (minor) for each published package touched.

Out of scope

  • New bulk actions or any HotCRM metadata; the bar's look beyond placement (it is the existing BulkActionBar); a spec key for the scope (the existing one is read).

Attribution: written by the domain:ui seat in session session_019YFzJp4o8Ck4NsT1toC4UP (account os-bill) on the maintainer's words 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
    and removed on Oct 15, 2026
  2. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (priority:p1; a child of objectui#12444, filed on the maintainer's 「同意开卡」)
    Session: session_01JZnppgHRp4sREcouhkQYDB
    Account: sunhaolin
    Branch: claude/issue-12462-selection-toolbar-actions
    Worktree: objectui-issue-12462
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/app-shell/src/views/ObjectView.tsx, limited to the list_toolbar actions build (localizedToolbarActions, about :2022–:2070 at a8e5b73) and their header render (:3964–:3972) and the hand-off to the list; packages/plugin-grid/src/ObjectGrid.tsx and components/BulkActionBar.tsx (the selection bar's roster); packages/plugin-list/src/ListView.tsx (the non-grid bulk bar, about :1902–:1957); the shared bulk-action derivation (resolveBulkActions and its home) if the roster is computed there; the tests beside each; one .changeset/12462-*.md (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui names no path-derived mandate)
    Clause-②: yes
    Responsibility: n/a — not a defect card
    Thread-read: none
    Serial constraints cleared: read at objectui main cf430cd10e.

    • Same files, different regions, dispatchable in parallel per the card: seat 2's objectui#12440 (ListView.tsx toolbar row, ObjectView.tsx ViewTabBar mounts). Also this seat's objectui#12418 (ObjectView.tsx sort, filter and reorder handlers). Branch from origin/main, merge origin/main once more before opening the PR, and the later lander merges main again with a merge commit, never a rebase.

    • Disjoint: this seat's objectui#12461 (AppHeader.tsx) and objectui#12427 (PR fix(components): the record header star draws only where the host wires a toggle (objectui#12427) #12465); seat 2's objectui#12429 and objectui#12442.

    • Why Clause-②: yes: the route is not known yet. Handing selection-scoped toolbar actions to the grid's and the list's selection bars may add a host prop to ObjectGrid / ListView (published packages), which widens their accepted props. When in doubt, yes. The review is this seat's own, at TIER_DEFAULT.

    • ⛔ No new spec key: the card says the selection scope "is read, not minted". If no declared key carries it, the dev stops and reports.

    • Landing: the card's acceptance includes a browser reading at 1440 on the HotCRM Leads list. The seat takes it before landing.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
    "issue": 12462,
    "status": "needs_decision",
    "branch": "claude/issue-12462-selection-toolbar-actions",
    "pr": null,
    "session": "session_01JZnppgHRp4sREcouhkQYDB — the dispatching seat's session (subagent run)",
    "premise_still_valid": false,
    "summary": "Stopped before building, per the dispatch's A1 stop rule. No action-level key in @objectstack/spec 18.0.0-next.1 declares 'acts on a selection': I read ActionSchema's full key list off the installed schema. The one key that did, bulkEnabled, was retired in spec 17. Its tombstone says the multi-select toolbar 'is driven by the LIST VIEW's bulkActions / bulkActionDefs, never by this flag'. execution (perRecord or aggregate) is the dispatch contract of the action's body, and the spec says it 'changes no dispatch by itself'. The 'selection' member near the objectql.ts line the card cites is ObjectGridSlotKey, the grid's row-selection mode, not an action key. objectui's UIActionSchema has no such key either. The card's problem is also partly false at runtime. HotCRM's create_campaign declares locations: ['list_toolbar', 'list_item'] and no execution. Its default Leads view all_leads already wires bulkActions: ['create_campaign'], and a comment in HotCRM's own source calls the header button a deliberate single-record entry point. A probe through the real ObjectView and ObjectGrid with that declaration showed the selection bar already carrying Add to Campaign with 2 rows ticked, while the header also draws it at 0 and 2 rows. So the only live gap is the header duplicate, and how to remove it is a metadata-semantics decision (open_questions 1). The card's kanban acceptance has no surface to test against (open_questions 2). The landing browser reading on all_leads would show Add to Campaign alone in the bar: the demo wires no Change Owner or Delete bulk action, and adding them is HotCRM metadata, which is out of scope. Two corrections to A2: resolveBulkActions lives in packages/plugin-grid/src/resolveBulkActions.ts, not in packages/types/src/objectql.ts (that range is the BulkActionDef interface). ListView's non-grid bar renders bare bulkActions names through onBulkAction, not through the promotion path. I pushed the empty branch as the claim marker, at base cf430cd with zero commits. No PR, and the worktree is removed.",
    "tests": "No code was written, so no suite is owed. I ran one runtime probe as a temporary file and deleted it before any commit (the worktree was clean at base cf430cd and git diff HEAD was empty). The probe copied the HotCRM fixture from ObjectView.toolbarScriptSelection-12331.test.tsx (create_campaign: locations list_toolbar + list_item, field-backed param) and gave its all view HotCRM's all_leads wiring, bulkActions: ['create_campaign']. It mounted through the real ObjectView and ObjectGrid and wrote its reading to a file: {header0: 1, bar0: 0, header2: 1, inBar: 1, barIds: ['bulk-action-create_campaign']}. Command: bash /home/user/objectstack/scripts/pm/os-verify-lock.sh -c 'cd /home/user/objectui-issue-12462 && pnpm exec vitest run packages/app-shell/src/views/ObjectView.probe-12462.test.tsx -t PROBE'. Result: VERDICT command-exit 0, Tests 1 passed | 6 skipped (7). The passed count is 1, not 0, so the probe ran. No ablation: no behaviour changed.",
    "gates": "None run: there is no diff (the branch head equals base cf430cd). NOT MEASURED: every objectui gate family. Reason: needs_decision before building. There is no PR, so there are no check-runs to cite.",
    "mcp_calls": "5 — mcp__github__issue_read x4 (12462 get, 12462 get_comments, 12444 get, 12462 get_comments read-back of this report) and mcp__github__get_file_contents x1 (objectstack-ai/hotcrm, refused: repository not configured for this session). No MCP write tool.",
    "api_writes": "1 — POST /repos//issues/12462/comments (this os-dev-report), via scripts/pm/post-stamped.mjs on the fleet-write relay. Separately, one git push of the empty branch claude/issue-12462-selection-toolbar-actions (not a REST write). No PR, label or body writes.",
    "files_changed": [],
    "line_budget": "n/a — no diff",
    "deviations": [
    "I read HotCRM's source (src/sales/actions/lead.actions.ts and src/sales/views/lead.view.ts on main) over raw.githubusercontent.com as an unauthenticated public read, because MCP refused objectstack-ai/hotcrm as not configured for this session. It was a read only; nothing was written there."
    ],
    "open_questions": [
    {
    "question": "Which declaration takes a selection-bar action out of the list page header? There is no action-level selection-scope key. The spec's only declaration of selection scope is the view's bulkActions / bulkActionDefs. On HotCRM's all_leads the bar already carries Add to Campaign, so what remains is the duplicate in the header.",
    "options": [
    "A — view-scoped: read an existing declaration and mint nothing. The header's list_toolbar build drops an action that the ACTIVE view names in bulkActions or in a bulkActionDefs entry. The bar is untouched because it already carries the action. Variant A-narrow drops it only when the action is also record-scoped (core's isRecordScopedAction: a list_item or record_* placement), so an object-level list_toolbar action keeps its zero-row header entry. Cost: ObjectView only, in two places: the localizedToolbarActions build and the ?runAction= deep-link arming, which arms only actions the header draws. No host prop, no export, no spec change, no HotCRM change. Effort S. Need: measured once, HotCRM create_campaign on all_leads. objectstack examples/app-showcase has zero list_toolbar actions that a view also wires as a bulk action. Long-term: removes a platform-wide duplicate (one action drawn twice on one page) and uses the spec's own placement home. The spec already describes list_toolbar as 'header/toolbar of a list view (bulk actions, New, export)'. AI-error: adds an implicit precedence rule (a declared list_toolbar placement leaves the header on views that wire it as a bulk action). This is softened because the action still renders in the list toolbar's selection mode. Also, a record-scoped action's header entry only ever runs on exactly one row, which the bar's per-record fan-out already covers. Header content then varies by view: HotCRM's my_leads and hot_leads keep the button. EnvironmentListToolbar receives the same list, but only a sys_environment view that wires a bulk action would reach it, and I measured none. Startup: the smallest platform change, with no new surface.",
    "B — action-scoped via execution (perRecord or aggregate): an action declaring it is drawn in the bar after the view's bulkActions and not in the header. Cost: a roster helper beside resolveBulkActions in plugin-grid plus a host prop on ObjectGrid and on ListView (a Clause-② surface). Effort M. Need: HotCRM create_campaign does not declare execution, so the measured instance does not move without a HotCRM metadata change, which the card forbids. Long-term and AI-error: contradicts spec text. execution 'changes no dispatch by itself', and the bulkEnabled tombstone says the bar is driven by the view 'never by this flag'. This option brings back the retired bulkEnabled under a dispatch-contract key: an AI that declares execution for lint reasons would silently move the action out of the header. Startup: widens two published props for a key that means something else. Not recommended.",
    "C — spec change: an action-level selection declaration in @objectstack/spec, either by reversing the spec-17 bulkEnabled retirement or by a new key, followed by B's renderer work. Cost: an objectstack needs:contract-review plus M in objectui. Effort L. The card names a spec key out of scope. Need: one measured instance, which the view key can already express. Long-term: a second home for one fact (the view's wiring and an action flag), which is exactly the split the retirement removed. AI-error: two places to declare one thing. Startup: capability expansion with no pull. Not recommended.",
    "D — producer side: HotCRM drops 'list_toolbar' from create_campaign's locations. all_leads' bulkActions already puts the action in the bar, and list_item keeps the row entry. Cost: no platform change; effort XS in objectstack-ai/hotcrm. Long-term and AI-error: keeps 'declared means rendered' strict (objectui AGENTS.md #0.1, contract-first); an AI that declares both placements still gets the duplicate, which is what it declared. Need: conflicts with the card's 'Nothing in HotCRM's metadata changes' and with the parent card's framing (平台的基础能力). HotCRM's own comment documents the header button as a deliberate single-record entry point. Startup: zero platform surface."
    ],
    "recommendation": "A-narrow. It is the only option that meets the maintainer's design on the measured instance with no HotCRM, spec, prop or export change. It reads the spec's only declaration of selection scope (the view's bulkActions) rather than minting or overloading a key. The narrowing keeps every case where the header entry does something the bar cannot, such as an object-level action run with zero rows. D is the contract-first alternative if the maintainer prefers that a declared list_toolbar placement is never withdrawn. B contradicts spec text, and C reverses a retirement for one instance. If A or A-narrow is ruled, the card's 'drawn after the view's bulkActions' and its 'one shared derivation for both bars' have nothing left to do, because the action is itself a bulkActions member. The ObjectView pin then becomes: the plain action in the header; the bulk-wired action only in the bar; and as the CONTROL, a view that does not wire it keeps the header button. With A-narrow, a second CONTROL: an object-level list_toolbar action wired as a bulk action keeps its header button."
    },
    {
    "question": "The card's ListView acceptance ('a non-grid view (kanban) shows the same bar with the same roster on selection') has no surface to test against. Should the line be dropped, or should non-grid selection be a separate card?",
    "options": [
    "A — drop that acceptance line from this card. Measured: no non-grid renderer emits a row selection. git grep of onRowSelect across packages//src finds ObjectGrid as the only caller, and plugin-kanban has no selection UI. ListView's non-grid bar dispatches through props.onBulkAction, which has no in-repo supplier (as types/src/zod/objectql.zod.ts's runtime-slot note says). So that bar cannot be reached from a kanban and does nothing in the console. Cost: none; effort 0. Need: no measured pull for kanban multi-select. Long-term: no dead roster code. AI-error: no change. Startup: no capability expansion.",
    "B — a separate card for non-grid selection: row selection for kanban, gallery and calendar; an onBulkAction supplier in the console; and promotion of the non-grid bar through resolveBulkActions (today it renders bare names with no params dialog and no confirm step). Effort L across plugin-kanban, plugin-list and app-shell. Need: none measured. Startup: capability expansion with no pull."
    ],
    "recommendation": "A. The bar this line asks about cannot be reached today, and building kanban selection is capability expansion with no named pull. The non-grid bar's dead state is recorded below as a note, not a card."
    }
    ],
    "out_of_scope_findings": [
    "class: none (dormant, unreachable) · reach: none. No public entry point reaches ListView's non-grid bulk bar, which renders only when currentView is not grid. It needs a selection that no non-grid renderer emits, and its buttons call props.onBulkAction, which nothing in the repo supplies. Evidence: git grep onRowSelect across packages/
    /src (ObjectGrid is the only caller), and objectql.zod.ts's runtime-slot note that onBulkAction and onPageSizeChange 'have no in-repo supplier'. Dedupe words: non-grid bulk bar, onBulkAction supplier, kanban selection, bulk-actions-bar · carrier: none (承接者:无) · noted, not filed"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    待裁:同时接为「批量操作」的列表工具栏动作,如何不再在页头常驻 · domain:ui seat 1 · session_01JZnppgHRp4sREcouhkQYDB · 2026-10-15T10:34Z

    Release: session_01JZnppgHRp4sREcouhkQYDB (account sunhaolin) releases the claim 6189639548, with no work product. The branch claude/issue-12462-selection-toolbar-actions equals main cf430cd10e, and there is no PR. Destination: needs-user-decision in this act, with the assignee cleared. The dev's report is 6190031388.

    一句话问题: 维护者批准的设计是「选中后底部浮出批量操作条:Add to Campaign / Change Owner / Delete;页头不再常驻 Add to Campaign」。实测发现,选中行后底部操作条已经带有 Add to Campaign,剩下的只是页头还常驻同一个按钮。问题在于:用哪一处已有的声明,把它从页头拿掉?

    实测(dev 报告 6190031388,objectui main cf430cd10e、spec 18.0.0-next.1)

    • 本卡前提被证伪: spec 里没有动作级的「作用于选中行」键。
      • 唯一曾经有的 bulkEnabled 已在 spec 17 退役,墓碑原文:选中行工具栏 "is driven by the LIST VIEW's bulkActions / bulkActionDefs, never by this flag"。
      • execution(perRecord / aggregate)是动作体的派发契约,spec 原文说它 "changes no dispatch by itself"。
      • 卡里引用的 'selection' 是表格的行选择模式,不是动作的键。
    • HotCRM 的现状:
      • create_campaign 声明 locations: ['list_toolbar', 'list_item'];
      • 默认视图 all_leads 已把它接进 bulkActions;
      • 真实 ObjectView + ObjectGrid 探针读数:未选中时页头 1 个、操作条 0 个;选中 2 行时页头 1 个、操作条 1 个(bulk-action-create_campaign);
      • HotCRM 源码里有一段注释,称页头按钮是有意保留的单条记录入口。
    • spec 对 list_toolbar 的描述(ui/action.zod.ts:639):"header/toolbar of a list view (bulk actions, "New", export)"。
    • 演示里没有接 Change Owner / Delete 批量动作;补它们属于 HotCRM 元数据,不在本卡范围。

    Governing text

    • 维护者设计原话(卡正文引):「选中后底部浮出批量操作条:Add to Campaign / Change Owner / Delete;页头不再常驻 Add to Campaign」,以及「同意开卡」。
    • spec 的 bulkEnabled 墓碑:选中行工具栏由视图的 bulkActions 驱动。
    • objectui AGENTS.md #0.1:声明即渲染,不在消费端默默改写。

    选项 × 代价 × 工作量

    选项 做什么 客户可感知后果 工作量
    A-narrow 按视图隐藏 页头构建时,跳过「当前视图已在 bulkActions / bulkActionDefs 里接了、且是记录级(有 list_item / record_* 位置)」的 list_toolbar 动作。?runAction= 深链同步处理 Leads 页头变为 New · Import · ⋯,选中行后操作条照常出 Add to Campaign;没接成批量动作的视图(如 HotCRM 的 my_leads)照旧在页头显示 app-shell ObjectView 两处 · S · 无先例
    B 借用 execution 声明了 execution 的动作进操作条、离开页头 HotCRM 没声明 execution,演示不会变,除非改 HotCRM;而且与 spec 原文冲突 plugin-grid + plugin-list 加宿主属性 · M
    C 改 spec 恢复 bulkEnabled 或新增一个动作级键,再做 B 的渲染 同一件事出现两处声明,正是 spec 17 退役它所消除的 spec 契约复核 + ui · L
    D 改生产方 HotCRM 从 create_campaign 的 locations 里去掉 list_toolbar 平台零改动;但与卡里「HotCRM 元数据不动」和 HotCRM 自己的注释冲突 objectstack-ai/hotcrm · XS(本会话无该仓权限)

    业务含义直译:

    • A = 平台定一条规矩:「已经放进选中行操作条的动作,页头就不再重复摆」;
    • B = 借一个本来管别的事的开关,顺手决定摆放位置;
    • C = 再造一个新开关;
    • D = 让写元数据的人自己别往页头放。

    四维分析(业务立场)

    • 项目长远合理性(决定字母):
      • A-narrow 只读 spec 唯一的「选中行」声明(视图 bulkActions),与 spec 对 list_toolbar 「含批量动作」的描述一致,不新增也不改写任何键,而且对所有应用统一生效。
      • D 守住「声明即渲染」最严格的读法,但同一个重复会在每个照此接线的应用里反复出现,要逐个应用去改元数据。
      • B、C 都与 spec 现有文本相冲突。
    • 实际业务需求: 实测拉动只有 HotCRM 的 Add to Campaign 这一例。objectstack 自带的示例应用里,没有「同时放在 list_toolbar、又被视图接成批量动作」的动作。
    • 防 AI 犯错:
      • A 引入一条隐式优先规则:同时声明两处,页头就会让位。代价是 AI 声明了 list_toolbar 却看不到页头按钮;好在动作仍在操作条里,不会消失。
      • D 让「写了什么就显示什么」保持字面成立。
      • B 最差:AI 为了别的原因声明 execution,按钮会被悄悄挪走。
    • 创业阶段不扩散: A 不新增任何面;D 平台零改动;B 新增两个已发布属性;C 扩 spec。

    推荐:A-narrow。 它是唯一一个在不改 HotCRM、不改 spec、不加属性的前提下,就能在实测场景上兑现维护者设计的方案,而且读的是 spec 现成的声明。限定「记录级」则保留了对象级动作在零选中时从页头运行的能力。
    回退:D。 如果维护者认为「声明了 list_toolbar 就必须出现在页头」不可破例,就改 HotCRM 的元数据,平台不动。
    置信缺口: 平台里只有这一个实测实例;其他第三方应用是否也这样接线,本席看不到。

    自检:只看①选 A-narrow;②③④ 是否翻转:否(③ 的隐式规则是代价,不改终态)。

    本席已自裁的子问题(不升级)

    卡里「看板视图选中后显示同一操作条」这条验收撤下。实测:

    • 非表格视图都不产出行选择,onRowSelect 只有 ObjectGrid 调用;
    • ListView 的非表格批量条通过 props.onBulkAction 派发,而仓内没有任何提供方;
    • 这条操作条在控制台里走不到。

    另做看板多选,属于无拉动的能力扩张,不立卡。

    裁后执行

    • 选 A-narrow: 本席重派 S 级 dev,只改 ObjectView 的页头构建和 ?runAction= 深链两处。钉子是:

      • 普通动作留在页头;
      • 接成批量的记录级动作只出现在操作条;
      • CONTROL:没把它接成批量动作的视图,页头仍有该按钮;
      • CONTROL:对象级动作即使接成批量,也保留在页头。

      落地前,本席在 HotCRM Leads 1440 宽度上做浏览器读数。

    • 选 D: 本卡以 not planned 关闭,并注明由 HotCRM 侧修改。本会话没有 hotcrm 仓的权限,需要维护者或有权限的席位立卡。

    • 选 B 或 C: 先按 spec 车道流程立 spec 卡。

    os-decision-facets

    • ① 项目长远合理性:A-narrow 只读既有声明、对所有应用一致、无新面;D 最严守声明即渲染但需逐应用改;B/C 与 spec 文本冲突。
    • ② 实际业务拉动:仅 HotCRM 的 Add to Campaign 一例实测;官方示例零例。
    • ③ 防 AI 犯错:A 引入一条隐式让位规则(动作仍在操作条,不消失);D 字面一致;B 会被无关声明悄悄挪走。
    • ④ 创业阶段不扩散:A 与 D 均零新增面;B 新增两个已发布属性;C 扩 spec。
      Prior rulings read: bulkEnabled retirement (spec 17 tombstone); list_toolbar placement describe (ui/action.zod.ts:639); objectui#12444 parent; thread: 6190031388

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    席位意见(增量)· director seat (summon #42, session_01AKVFxqmRLqktJspVXwPE9A), read as batch #350 item 2, 2026-10-15T11:17Z

    On the domain:ui seat 1 presentation 6190071466 (A-narrow recommended, D the fallback) after the dev's premise_still_valid: false report 6190031388.

    Premises re-run in this act, spec at objectstack origin/main c5dd7fce30, objectui origin/main cf430cd10e, hotcrm origin/main 63db83e2da:

    • list_toolbar is described as 「header/toolbar of a list view (bulk actions, "New", export)」 (packages/spec/src/ui/action.zod.ts:639). The bulkEnabled tombstone (:1541-1545): the multi-select toolbar 「is driven by the LIST VIEW's bulkActions / bulkActionDefs, never by this flag」. execution (:1218) 「changes no dispatch by itself」. So the spec's one declaration of selection scope is the view's wiring, as the dev read.
    • objectui: the header's list_toolbar build is localizedToolbarActions (ObjectView.tsx:2024) drawn through toolbarBar (:2068) or EnvironmentListToolbar (:3966); the selection bar's roster comes from packages/plugin-grid/src/resolveBulkActions.ts (the card's objectql.ts location was the BulkActionDef interface, as the dev corrected); onRowSelect is emitted by ObjectGrid only (:5958), so the non-grid bar is unreachable.
    • HotCRM: create_campaign declares locations: ['list_toolbar', 'list_item'] (src/sales/actions/lead.actions.ts:240) under the comment 「This action runs on a single record — select exactly one row」 (:237); the default view all_leads wires bulkActions: ['create_campaign'] (src/sales/views/lead.view.ts:223). The dev's probe (header 1 / bar 0 at zero rows; header 1 / bar 1 at two rows) was not re-run by this seat.
    • Prior rulings (objectstack corpora, --terms list_toolbar,bulkActions,bulkEnabled,selection,header): 4 hits; ADR-0024 §3, ADR-0029 D9.3, ADR-0037 §4 are term matches; none rules the header's roster; thread: the seat's presentation only. Governing text: the maintainer's design words quoted on the card; the bulkEnabled tombstone; the list_toolbar describe; objectui AGENTS.md #0.1.

    Facets: ① agree — A-narrow reads the spec's only selection declaration and makes one rule for every app; one addition to the reading: the selection bar IS the list toolbar in its selection mode, so a list_toolbar action the active view wires as bulk is still rendered at its declared location, in the mode where it can act; the header's permanent entry is the duplicate, not the declared placement · ② agree (one measured instance, HotCRM's Add to Campaign; the design is the maintainer's own) · ③ agree, with the reading above: A-narrow withdraws no declared placement, it removes a duplicate; B would let an unrelated key move a button; D keeps the duplicate for every app that wires the same way · ④ agree (A-narrow and D add no surface; B widens two published props; C widens the spec).

    Recommend A-narrow; fallback D. 口径 for the executor, binding with the letter:

    1. ObjectView's header build (localizedToolbarActions) and the ?runAction= deep-link arming skip a list_toolbar action that the ACTIVE view names in bulkActions or a bulkActionDefs entry AND that is record-scoped (core's isRecordScopedAction: a list_item or record_* placement). An object-level action keeps its zero-row header entry. No host prop, no export, no spec change, no HotCRM change: Clause-②: no.
    2. Pins: the plain action stays in the header; the bulk-wired record-scoped action appears only in the selection bar; CONTROL: a view that does not wire it keeps the header button (HotCRM's my_leads); CONTROL: an object-level action wired as bulk keeps the header button.
    3. The seat's self-ruled sub-question is confirmed: the kanban acceptance line is dropped (no non-grid renderer emits a selection; the non-grid bar has no in-repo supplier), and no card is opened for kanban selection.
    4. The browser reading at 1440 on the HotCRM Leads list before landing, as the seat said: header New · Import · ⋯; two rows selected float the bar with Add to Campaign (the demo wires no other bulk action; adding any is HotCRM metadata, out of scope).
    5. A memo to the repo:hotcrm seat: the comment at lead.actions.ts:237 describes the header button as the single-record entry; after A-narrow on all_leads that entry is the row's list_item placement and the bar with one row selected; the comment, not the metadata, goes stale.

    Workload: A-narrow = domain:ui · 1 PR · S (ObjectView.tsx two places plus pins; no size precedent re-read). D = hotcrm · XS. B = M with a Clause-② widening. C = L.

    自检:只看①选 A-narrow;②③④ 是否翻转:否。

  6. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #350 item 2 · letter A-narrow · maintainer 「A-narrow (推荐)」 2026-10-15T11:26Z

    Director seat, summon #42, session_01AKVFxqmRLqktJspVXwPE9A, on the domain:ui seat 1 presentation 6190071466, the dev's readings 6190031388 and this seat's delta 6190798176; the maintainer's answer takes the recommendation (batch #350 presented in this seat's session, answered with the option 「A-narrow (推荐)」).

    What the ruling binds (letter A-narrow):

    1. In ObjectView, the header's list_toolbar build (localizedToolbarActions) and the ?runAction= deep-link arming skip an action that the ACTIVE view names in bulkActions or a bulkActionDefs entry AND that is record-scoped (core's isRecordScopedAction: a list_item or record_* placement). An object-level action keeps its zero-row header entry. The selection bar is untouched: it already carries the action through the view's wiring.
    2. The reading that binds the pins: the selection bar is the list toolbar in its selection mode, so a list_toolbar action the view wires as bulk is still rendered at its declared location; what leaves is the header's duplicate. No host prop, no export, no spec key, no HotCRM metadata change: Clause-②: no.
    3. Pins: the plain list_toolbar action stays in the header; the bulk-wired record-scoped action appears only in the selection bar; CONTROL: a view that does not wire it keeps the header button; CONTROL: an object-level action wired as bulk keeps the header button.
    4. The seat's self-ruled sub-question is confirmed: the kanban acceptance line is dropped, and no card is opened for non-grid selection (onRowSelect is ObjectGrid's alone; the non-grid bar has no in-repo supplier).
    5. Before landing, the seat's browser reading at 1440 on the HotCRM Leads list: header New · Import · ⋯; two rows selected float the bar with Add to Campaign; clearing the selection hides it.
    6. Not taken: D (each app edits its own metadata, and HotCRM's comment calls the header entry deliberate), B (execution would move a button for a key that means something else), C (a second declaration of one fact).
    7. A memo goes to the repo:hotcrm seat: the comment at src/sales/actions/lead.actions.ts:237 (the header button as the single-record entry) goes stale after this lands; the metadata does not change.

    Workload of the letter: domain:ui · 1 PR · S (ObjectView.tsx two places plus the pins).

    Execution: needs-user-decision removed and pm:queue added in this act for domain:ui (priority:p1, area:records stand); no assignee (the seat released at 6190071466); the body's first line carries Ruled:. The seat re-dispatches an S dev from origin/main (the empty branch claude/issue-12462-selection-toolbar-actions is the claim marker).

  7. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1, re-dispatch on the ruling 6190946913 (letter A-narrow; the earlier claim 6189639548 ended in the dev's premise_still_valid: false report 6190031388, and the seat released the card at 6190071466)
    Session: session_01JZnppgHRp4sREcouhkQYDB
    Account: sunhaolin
    Branch: claude/issue-12462-selection-toolbar-actions
    Worktree: objectui-issue-12462
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/app-shell/src/views/ObjectView.tsx, limited to the list_toolbar actions build (localizedToolbarActions, :2024) and the ?runAction= arming fed by it (useOfferedNavRunAction, toolbarActionsWithDeepLink, :2029–:2063 on main d9d9d98684), plus whatever reads the active view's bulkActions / bulkActionDefs there; the tests beside it; one .changeset/12462-*.md. The selection bar (plugin-grid's resolveBulkActions / BulkActionBar, ListView's non-grid bar), HotCRM's metadata and @objectstack/spec are outside the surface, by the ruling (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui names no path-derived mandate)
    Clause-②: no
    Responsibility: n/a — not a defect card
    Thread-read: 6190946913
    Serial constraints cleared: read at objectui main d9d9d98684. The work below is the same file in different regions, so it can run in parallel. Branch from origin/main, merge origin/main once more before opening the PR, and whoever lands later merges main again with a merge commit, never a rebase.

    • This seat's objectui#12418, PR feat(app-shell): a list's sort, column layout, filters and tab order are the user's own, not the shared view's (objectui#12418) #12471, now in the merge queue: ObjectView.tsx's sort, filter and reorder handlers, and the header's ⋯ menu.

    • Seat 2's objectui#12440: the ViewTabBar mount rows.

    • Seat 2's objectui#12463: the page root and the list wrappers' height chain.

    • The ruling binds: the header build and the deep-link arming skip an action that the ACTIVE view wires as bulk (bulkActions or a bulkActionDefs entry) AND that is record-scoped (core's isRecordScopedAction). An object-level action keeps its header entry. The selection bar is untouched. No host prop, no export, no spec key, no HotCRM metadata change.

    • Landing: the ruling's item 5 asks for a browser reading at 1440 on the HotCRM Leads list. The seat takes it before landing, and sends the ruling's item 7 memo to the repo:hotcrm seat after the merge.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
    "issue": 12462,
    "status": "done",
    "branch": "claude/issue-12462-selection-toolbar-actions",
    "pr": "#12494",
    "session": "session_01JZnppgHRp4sREcouhkQYDB — the dispatching seat's session (subagent run)",
    "premise_still_valid": true,
    "summary": "Implemented ruling A-narrow in ObjectView only. A new memo, headerToolbarActions, sits after activeView. It drops a list_toolbar action that the active view wires as bulk (any non-empty bulkActions name except delete, or the name of any renderable bulkActionDefs entry, mirroring ObjectGrid's fold) AND that @object-ui/core's isRecordScopedAction calls record-scoped. The deep-link arming (useOfferedNavRunAction), the action:bar node and EnvironmentListToolbar now read that list. No export, prop, spec key or HotCRM change (Clause-②: no). A ?runAction=create_campaign link on a view that wires the action is not armed: nothing runs and the link stays in the URL, as for any name the header does not draw. On a view that does not wire it, the link arms as before. Mechanism readings: A1 held (activeView is resolved after the memos, so the arming and the bar node moved below it, with no new prop or export). A2 held for isRecordScopedAction (on the core entry through export-star). resolveBulkActions is NOT exported from plugin-grid, and ObjectView loads plugin-grid only lazily, so the two view keys are read directly and agreement is pinned. A3: EnvironmentListToolbar receives the same filtered list, consistent by construction; whether any sys_environment view wires bulk is NOT MEASURED, because its metadata is in the cloud repo, which this session cannot reach. A4 is replayed in pin 1.",
    "tests": "Final head 8752968 (after merging origin/main d4bc81e, which carries PR #12471, PR #12479 and PR #12473). All of the following ran through os-verify-lock.sh. (1) turbo run build --filter=@object-ui/app-shell^... --concurrency=2: 28 successful, 28 total, build-exit=0. (2) pnpm --filter @object-ui/app-shell type-check, which echoes tsc --noEmit && tsc -p tsconfig.test.json: exit 0, 0 error TS. tsconfig.test.json --listFilesOnly lists the new pin file. (3) From the repo root, pnpm exec vitest run --maxWorkers=2 over 91 files: every app-shell ObjectView* suite; every app-shell test naming runAction, list_toolbar or bulkActions; the plugin-grid resolveBulkActions, BulkActionBar and BulkActionBar.regionName.zh-12145 suites as CONTROL; and the one WRITE_SITES or AFFORDANCE_GRANTS census, packages/plugin-form/src/affordanceGrantMap-12082.test.tsx. Result: Test Files 91 passed (91), Tests 970 passed (970), VERDICT command-exit 0. The new pin file ObjectView.bulkWiredHeaderDuplicate-12462.test.tsx gives Tests 6 passed (6) on HEAD. Ablation via objectstack scripts/ablation-replace.mjs: the anchor if (wired.size === 0) return localizedToolbarActions; became if (wired.size >= 0) ..., with mutation landed (anchor 1 -> 0, blob changed). Result: Tests 3 failed | 3 passed (6). Red: the bulkActions pin and the bulkActionDefs pin (expected button to be null), and the deep-link not-armed pin (expected div role=dialog to be null). Green: both header CONTROLs and the deep-link CONTROL. Restored: blob == HEAD, git diff HEAD empty. The ablation ran on c608ef1 and again on the final head 8752968 with the same split. No dist leg: the subject is imported from source (relative ObjectView import, and the vitest alias maps @object-ui/* to src). Expected direction: turns red, and that is what was observed.",
    "gates": "Local subset on 8752968. Exit codes were captured to files before any pipe. check-control-bytes 0; check-new-cross-file-line-citations 0 (VERDICT 0 new citation(s)); check-changeset-presence 0; check-changeset-no-major 0; check-changeset-overwrite 0; check-vi-mock-specifiers 0; check-vi-mock-inherit 0; check-vi-mock-override-shape 0; check-test-path-roots 0 (the counts of the last four rose by one test file, so the new pin was scanned). eslint on the two changed .ts/.tsx files: exit 0, 0 errors, 197 warnings, against 196 for the base ObjectView.tsx alone. The +1 is the filter's (a: any); removing it measured TS7006. No dispatch-gates --ran reconciliation: that tool refuses a sister repo, so the list above was derived by hand from objectui package.json check:* scripts and the lint, repo-gates, changeset-guard and performance-budget workflows. CI-owned and not re-run locally: pnpm lint, the full test shards, Bundle Analysis (performance-budget), Build & E2E. PR #12494 check-runs at report time (36 total): success — Changeset Bump Policy, Changeset Claim Re-read, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report, Line Citation Gate, Build Docs, label; skipped — Test (coverage), Test (coverage shard), dependabot; in_progress — Lint, Type Check, Test (shard 1/16 … 16/16), Test (dist pins), Build & E2E, Bundle Analysis, Repo Gates, Governed Surface Queue Guard, Spec Main Shape Gate (known red on main, objectui#12464, advisory), Live E2E (informational).",
    "mcp_calls": "5 — mcp__github__issue_read x3 (12462 get, 12462 get_comments, 12462 get_comments read-back of this report) and mcp__github__pull_request_read x2 (12494 get, 12494 get_check_runs). No MCP write tool.",
    "api_writes": "2 strokes via the fleet-write relay (each a POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]). (1) pr_create: POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos//issues/12494/assignees [sunhaolin]; read-back 10586 bytes sent = stored, identical. (2) This os-dev-report: POST /repos//issues/12462/comments via scripts/pm/post-stamped.mjs. No label, body PATCH or assignee write on the card. Separately, 6 git pushes of the branch (fast-forward probe, then 5 pushes), all fast-forward, none forced.",
    "files_changed": [
    "packages/app-shell/src/views/ObjectView.tsx",
    "packages/app-shell/src/views/ObjectView.bulkWiredHeaderDuplicate-12462.test.tsx",
    ".changeset/12462-bulk-wired-header-duplicate.md"
    ],
    "line_budget": "n/a (no governed or skills surface). git diff --numstat d4bc81e 8752968: ObjectView.tsx +113 / -50, most of the -50 being the arming and bar block moved below activeView; the pin file +281; the changeset +19.",
    "deviations": [
    "Start base was caac3b2, not the dispatch's d9d9d98: PR #12471 (objectui#12418) landed between dispatch and start. A local branch of the claim name already existed at cf430cd, from the prior run, so git worktree add -b refused. I attached the worktree to that branch and fast-forwarded it to origin/main (plain fast-forward push, no force).",
    "origin/main moved three more times during the run: c950345 (objectui#12463), 7a5f4c1 (objectui#12450) and d4bc81e (objectui#12440 and objectui#12464). Each was merged with a merge commit carrying the trailer pair. The first merge commit's message was amended before its push to add the pair. #12440 edits ObjectView.tsx in other regions, and git auto-merged it. All local gates and the ablation were re-run on the final head.",
    "A lint-tidy commit (94469d1) dropped the filter's : any and broke type-check with TS7006. The union run on 6cf0ad5 caught it, and de8795a reverted it. That region of the final ObjectView.tsx equals the first implementation commit.",
    "Changeset bump is minor; the dispatch allowed patch or minor.",
    "Two lock calls did not reach the lock: the first build (queued behind another worktree) and one later build+typecheck that returned 99. Both resumed on the same OS_VERIFY_LOCK_SLOT; neither was a measured failure."
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "class: none (a boundary of the ruled rule, not a defect) · reach: none filed. The header reads the view's wiring, not ListView's internal visualization state. On a kanban or calendar visualization, or with selection.type none, of a view that wires a record-scoped action as bulk, the header does not draw the action and no selection bar exists. The header entry could not run there anyway on the paths that consult isRecordScopedAction: with zero ticked rows it refuses ("select a row first"), as pinned by the ObjectView.toolbarScriptSelection-12331 suite. Ruling item 4 confirms no card for non-grid selection. Dedupe words: non-grid selection, kanban bulk bar, header bulk-wired action · carrier: none (承接者:无) · noted in PR Acceptance notes, not filed",
    "class: none (a boundary of the ruling's literal text) · reach: none filed. Under the ruling, a bulkActionDefs entry that names the action counts even without execution aggregate. resolveBulkActions keeps such an entry's documented callout meaning (no actionDef attached), so its bar button does not run the action while the header still yields. No measured author of that shape. Dedupe words: bulkActionDefs callout, aggregate resolve · carrier: none (承接者:无) · noted in PR Acceptance notes, not filed"
    ]
    }


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 1 · session_01JZnppgHRp4sREcouhkQYDB · 2026-10-15T14:01Z

    • Merged: PR feat(app-shell): the list header leaves a bulk-wired, record-scoped list_toolbar action to the selection bar (objectui#12462) #12494 landed through the merge queue as main d35cfbf3b1 at 2026-10-15T13:58Z. It is an ancestor of origin/main; its first parent is 03cf7b2c26.
    • Verified by content: all 3 files the PR changes are blob-identical on d35cfbf3b1 to the accepted head 8752968ff1. Their hunks are line-identical as well.
    • Mis-close scan: the PR body's only closing keyword is Fixes objectstack-ai/objectui#12462.
    • What now holds (ruling 6190946913, letter A-narrow):
      • The list page header no longer draws a duplicate of a list_toolbar action that the active view wires as bulk AND that is record-scoped. The selection bar carries it, and the ?runAction= arming follows the header.
      • An object-level action keeps its header entry, and so does a view that does not wire it.
      • The HotCRM Leads reading at 1440 (ACCEPT 6193567628) confirms all of it.
    • The chain: claim 6191304452 (the re-dispatch on the ruling), dev report on the card, ACCEPT 6193567628. The earlier claim 6189639548 ended in the premise_still_valid: false report 6190031388, which the decision analysis 6190071466 took to the director.
    • The ruling's item 7, done: the memo to the repo:hotcrm seat is [PM seat] repo:hotcrm — 🟢 huangyiirene hotcrm#1353 comment 6193911772. It covers the stale source comment at src/sales/actions/lead.actions.ts:233–:238. No metadata changes.
    • Carried on: the parent objectui#12444's design acceptance stays with the maintainer, after the merge.
    • Label: pm:dispatched is removed in the same act as this note. The assignee stays, as the record of who drove it.

    Generated by Claude Code

  10. added a commit that references this issue on Oct 23, 2026
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