Repository navigation
fix(app-shell): a list-toolbar flow launch resolves the row selection by the script launcher's one rule, and the toolbar script launch is pinned through the real list page (objectui#12331) - #12336
Conversation
… by the script launcher's one rule, and the toolbar script launch is pinned through the real list page (objectui#12331) The flow handler carried its own copy of the selection rule. It now calls @object-ui/core's resolveServerActionRecordId, the rule the script handler already uses: one row supplies the record, several rows refuse, and zero rows refuse on a record-scoped action. A new real-render suite pins a record-scoped script action with a param at list_toolbar: 0 and 2 rows show the refusal and send nothing; 1 row sends one request, and its result reaches the success toast. Claude-Session: https://claude.ai/code/session_01CsyMRsFJBKNEMa4oPY1b5z Co-authored-by: Claude <noreply@anthropic.com>
…the flow launcher now runs an aggregate launch it used to refuse Claude-Session: https://claude.ai/code/session_01CsyMRsFJBKNEMa4oPY1b5z Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Seat ACCEPT: objectui PR #12336 at
|
Contract reviewServed-tier: Inputs read: card #12331 (body and its three comments: triage 6148585247, claim 6149709380, os-dev-report 6151207543), PR #12336 (body, 4-file list, net diff against Check-runs on the head. 29 success, 3 skipped (the two coverage entries and dependabot), 1 failure: ① Derived judgmentsThe diff is one source file (
② Semver levelChangeset Clause-②: yes — the dev's re-judgment from the claim's ③ Boundary flagsDev flags (
Premise, escalated. The card measured, at 0 and 2 rows, no request and no toast; the dev's real-render pins on the same chain code (item 11) show the refusal toast and no request. Nothing in this diff changes what the card's user would see at 0 or 2 rows on a script action: the PR pins the console's existing behaviour and folds the flow launcher. Before the card closes as fixed, the seat should have the HotCRM declaration read (the card cites Out-of-scope note carried from the report (cancelling a param dialog may raise a red "Action cancelled by user (params)" toast): not measured, not filed, not this card's. Nothing is filed here. Implemented-by: VERDICT: PASS Read at 2026-10-13T17:44Z. Generated by Claude Code |
… to Campaign declaration, list_item placement and field-backed record-picker param included (objectui#12331) Measured on the PR head: 0 and 2 rows show the refusal and send nothing; 1 row sends one request carrying the row and the picked campaign, and the declared successMessage shows after the picker was used. Claude-Session: https://claude.ai/code/session_01CsyMRsFJBKNEMa4oPY1b5z Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…e deltas the contract review found under-declared A one-row aggregate flow launch now carries only _selectedIds, and a zero-row aggregate launch on a record-scoped flow is no longer refused; both follow the one rule the script launcher already uses. Claude-Session: https://claude.ai/code/session_01CsyMRsFJBKNEMa4oPY1b5z Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Inputs read:
To judge the diff I also read, at base and head:
Nothing was built, run or re-run. This review judges the whole net diff again on this head. Check-runs on the head: 29 success, 3 skipped (the two coverage entries and dependabot) and 1 failure,
① Derived judgmentsThe only source change is
② Semver levelChangeset
Clause-②: yes — the published console's flow launcher now dispatches an aggregate ③ Boundary flagsAddendum flags (6158185596):
Round-1 escalation 1, the api leg: answered on citation.
Round-1 escalation 2, the premise: partly answered. The rest stays escalated.
Implemented-by: VERDICT: PASS Read at 2026-10-14T00:41Z. Generated by Claude Code |
Seat ACCEPT, patch round: objectui PR #12336 at
|
Part of #12331
Clause-②: yes (widening) — the flow launcher now runs an aggregate launch (
params._selectedIdswith several rows ticked) that its own copy of the selection rule used to refuse. It now follows the script launcher's rule. No type, export or prop changes, and@object-ui/core'sresolveServerActionRecordIdis unchanged.What was measured
The card's silent drop does not reproduce on
main(72594f29ec) through the real chain. The newObjectView.toolbarScriptSelection-12331.test.tsxmounts these real pieces:ObjectView, with the grid publishing its checkbox selection into the runner context;action:bar→action:button→ActionRunnerchain;scripthandler;ActionParamDialogandConsoleToaster(sonner is not mocked: the assertions read the toast in the DOM).The action is a
type: 'script'action with one param, launched from the header toolbar. The suite ran on the base tree before any source change here:locationslist_toolbar+record_headerlist_toolbar+record_headerlist_toolbar+record_headerrecordId= that rowlist_toolbaronlylist_toolbaronlyrecordIdHotCRM's own declaration, measured on this PR's head (
fa572b663). The seat readCreateCampaignAction(objectstack-ai/hotcrmmain2f3d898,src/sales/actions/lead.actions.ts), and the suite replays it key for key:name: 'create_campaign',objectName: 'crm_lead',type: 'script';locations: ['list_toolbar', 'list_item'];successMessage: 'Leads added to campaign!',refreshAfter: true;{ field: 'crm_campaign', objectOverride: 'crm_campaign_member', label: 'Campaign', required: true }.The dialog therefore draws a record picker, a lookup to
crm_campaign, answering from a mocked campaign source behind the console'sSchemaRendererProvider. Each case clicks the header toolbar's button, picks "Cloud Migration Webinar" in that picker and confirms:{ recordId: 'lead_1', params: { crm_campaign: 'cmp_webinar' } }So the record-picker path, the
list_itemplacement andrefreshAfterdrop nothing on this chain either: both the refusal and the success toast arrive after the picker was used. The card's no-toast reading does not reproduce here with the exact declaration. Its console was a build pinned at1fb1cedagainst an 18.0.0-next.0 backend. Between that pin andmain(git diff 1fb1ced 72594f29ec), the action path's files are byte-identical exceptObjectGrid.tsx, and its changes there touch no selection code. The files compared wereActionRunner.ts,serverActionHandler.ts,useConsoleActionRuntime.tsx,consoleServerAction.ts,ActionParamDialog.tsx,action-button.tsx,ActionContext.tsxandObjectView.tsx. A re-measure on the next console pin is the seat's to ask for.None of the suspects drops the click on
main:selectedRecords;What makes an action single-record. Spec 18's
ActionSchemahas no single-record key. The console readslocations: a record placement (list_item,record_header,record_moreorrecord_section) makes an action record-scoped (isRecordScopedAction).execution: 'perRecord'names the bulk dispatch contract, and the spec says it changes no dispatch by itself.So an action declared at
list_toolbaralone is object-level, and zero rows dispatches it with no record. That is the control row above. HotCRM's action carrieslist_item, so it is record-scoped and zero rows refuses, which the HotCRM rows confirm.The 1-row result. The answer
{ success: true, data: { count: 0, skipped: 1 } }reaches the runner's existing success toast, through its existing three rungs:outcomeMessages[outcome];successMessage, with${result.*}filled in from the answer;Two cases are pinned:
successMessage: 'Added ${result.count}, skipped ${result.skipped}'shows "Added 0, skipped 1";For the user to see count and skipped, the app writes that copy (HotCRM's half). No toast path was added.
What changed
useConsoleActionRuntimeheld a second, flow-worded copy of the rule the script handler takes from core. It now callsresolveServerActionRecordIdand returns that rule's refusal. A flow launch changes in three ways:recordIdField, as a script launch already reads it. The copy always readid.params._selectedIds) is no longer refused because several rows are ticked. The script launcher was already exempt. This is the Clause-② widening.recordIdfrom the selection. With one row ticked, the trigger carries only_selectedIds, where it used to carry that row'srecordIdbeside them. With zero rows on a record-scoped flow, it is no longer refused, though the grid starts an aggregate run only from a non-empty selection. The contract review found these two deltas under-declared; the changeset now names both.list_toolbar-only control.useConsoleActionRuntime.test.tsx. In the first, the flow and script handlers give the rule's own answer for the same selection. In the second, an aggregate flow launch is not refused.@object-ui/app-shellminor, because of the widening.Verification (head
40efb23a8)turbo run build --filter=@object-ui/app-shell^..., ran through the verify lock atHEAD=40efb23a8 DIRTY=0. Result:Tasks: 28 successful, 28 total,VERDICT command-exit 0. All 28 were turbo cache hits, because no dependency source moved.HEAD=40efb23a8 DIRTY=0:Test Files 151 passed (151),Tests 1380 passed (1380),VERDICT command-exit 0. Atfa572b663it was 151 files and 1377 tests; the three new tests are the HotCRM rows.git grepoverpackages/app-shelltests foruseConsoleActionRuntime,serverActionHandler,ActionParamDialog,selectedRecords,list_toolbar,flowHandlerandresolveServerActionRecordId.RecordDetailViewsuites that exercise its action doors.@object-ui/corewas not touched.pnpm --filter @object-ui/app-shell type-check(tsc --noEmit && tsc -p tsconfig.test.json) ran through the verify lock atHEAD=40efb23a8 DIRTY=0:VERDICT command-exit 0.tsc -p tsconfig.test.json --listFilesOnlylists both changed test files.40efb23a8:check-changeset-presence,check:changeset-claims,check:pending-changeset-literals,check:new-line-citations,check:control-bytes,check-changeset-no-major,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:handler-key-reads,check:action-forward-parityandcheck:action-ref-convention.check-governed-queue-guard --testover the four paths: NOT GOVERNED.1943693e5(40efb23a8adds only changeset prose):eslint --no-inline-config --format jsonover the three changed TS files reports 3 files, 0 errors and 167 warnings. The warnings are the two runtime files' existingno-explicit-anyand hooks warnings; the new suite has none.calculateConfigForFilereports emptyparserOptionsfor all three files, so type-aware linting is off and this diff cannot change a verdict on any untouched file.fa572b663; this round changed only the test file): run through the verify lock, withscripts/ablation-replace.mjsin WRAP mode plus a trap.isRecordScopedActionimport. On disk: the anchors went 1 → 0, the blobs wentfbdf953c3262→8c9d9d826eed→8b6bb9ece4a2, and the flow-copy markers went from 0 to 2.Tests 3 failed | 48 passed (51). Exactly the three new flow pins went red.fbdf953c3262) andgit diff HEADis empty.MODULE_NOT_FOUNDbefore mutating anything, so it is not counted.Not done here: the
apilegThe triage names the
apilaunch as well. It is not changed here.apiHandlerreads no grid selection at all. Running it through the same rule would refuse zero rows for any api action that also mounts on rows. That includes the servedsys_two_factoractionregenerate_backup_codes(locations: ['list_toolbar', 'list_item']), which acts on the current user and needs no row. This is left to the seat as an open question in the report.Acceptance notes
https://claude.ai/code/session_01CsyMRsFJBKNEMa4oPY1b5z.apileg. The two under-declared aggregate deltas are carried into the changeset and into "What changed" above. The HotCRM-declaration rows answer the code half of the premise escalation. The card's remaining gap, toasts the HotCRM pass did not see although this chain raises them, stays on card 12331 (seat record on the PR, re-review 6158279161), so this PR is linked as a part, not as the close.BulkActionDialog.tsx),package.jsonandpnpm-lock.yamlare untouched.Body refreshed at 2026-10-14T00:32Z for the patch round.
Generated by Claude Code