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

fix(core): refuse a single-record action's row count before its dialog, and translate the refusal (objectui#13384) - #13395

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-13384-record-action-row-guard
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-13384-record-action-row-guard

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Closes #13384
Clause-②: yes (widening: ActionRunnerHandler / ServerActionHandler gain optional refuseBeforeInput; ServerActionRecordIdResolution gains optional errorKey; nothing is refused)

What changed

A single-record action launched from a list toolbar with 0 or 2+ rows selected is now refused before any confirmation or param dialog opens. It gets the same toast as before and sends nothing. With exactly one row the dialog behaves as before. The refusal is also translated now: it read English under zh-CN.

  • @object-ui/core ActionRunner: ActionRunnerHandler gains an optional refuseBeforeInput(action, context). execute asks the handler it will dispatch to, after the condition / disabled gates and before confirmText and the param collection. A refusal goes through the usual post-execution (error toast, onFailure), and nothing is dispatched. If the refusal names an errorKey, the runner translates it through setTranslator's translator, with the English as default. This is the same channel the runner's own RUNNER_TEXT uses, so core still takes no i18n dependency.
  • @object-ui/core serverActionHandler:
    • The two refusal strings come from a keyed table (actions.selectExactlyOneRow, actions.selectRowFirst).
    • resolveServerActionRecordId returns the key as errorKey beside the English error.
    • createServerActionHandler attaches refuseBeforeInput, which runs the same resolver the dispatch runs, including an injected resolveRecordId. So a record page, which resolves against its own record, is never refused.
    • It skips the early check when the dialog itself collects a param named recordId.
    • The check at dispatch time stays for callers that invoke the handler directly.
  • @object-ui/app-shell createConsoleServerActionHandler: forwards refuseBeforeInput from core's handler, which it builds with the wrapper's own resolveRecordId. Without the forward the runner never saw the member and the console kept its dialog-first order.
  • @object-ui/i18n: both keys are added to all ten packs. No German low-high quote span is added, so the de-quote-pairing-3876 counts are unchanged.
  • One changeset: core, i18n and app-shell, patch.

Why the runner asks the handler instead of holding the row rule itself: on a record page, a related-list grid publishes selectedRecords: [] into the page's runner context. A runner-side copy of the rule would refuse every record_header action there. Only the handler knows its own resolver.

Tests

Run on head 024092b03:

  • pnpm exec vitest run --maxWorkers=2 over packages/core/src/actions/__tests__/, packages/i18n/, and the 21 app-shell test files that read consoleServerAction, resolveServerActionRecordId or toolbarScriptSelection: 146/146 files, 2113 passed, 13 skipped.
  • New ActionRunner.recordRowGuard-13384 (core):
    • 0 rows and 2 rows: the toast shows, no param dialog and no confirmation open, and nothing is sent.
    • 1 row: the dialog opens and the one request carries that row.
    • Controls: an object-level toolbar action with 0 rows still opens its dialog; an injected record resolver is not refused; a dialog that collects recordId is not judged early.
    • The translator is asked by key, with the English default; an empty answer falls back to English.
  • serverActionHandler.test.ts: pins for errorKey and refuseBeforeInput.
  • consoleServerAction.test.tsx: pins the forward, both alone and through a real ActionRunner. 0 and 2 rows open no dialog, pre-open no tab and send nothing. 1 row opens the dialog.
  • ObjectView.toolbarScriptSelection-12331: its five 0- and 2-row cases now also assert that no dialog opened, beside the toast and the absent request. Every other claim in that suite is unchanged.
  • New recordSelectionRefusal-13384 (i18n): pins the zh-CN text; every other pack defines both keys with its own text.
  • Type-check, NODE_OPTIONS=--max-old-space-size=8192 pnpm exec turbo run type-check --concurrency=2:
    • --filter=@object-ui/core: 3/3 tasks
    • --filter=@object-ui/app-shell: 30/30 tasks, including tsc -p tsconfig.test.json
  • pnpm check:i18n-keys, check:i18n-drift, check:i18n-dead-keys: exit 0. Dead-keys is report-only. It lists the two new keys as needs-review, the same class as their actions.* siblings, which are also reached only through a key table.
  • eslint on the touched files: 0 errors. The warnings are pre-existing no-explicit-any; none is added.
  • check:new-line-citations: 0 new. check-changeset-presence: pass.

Ablation

Each run used ablation-replace with on-disk proof, and was restored to git diff HEAD empty:

Mutation Result
The runner's early refusal disabled 5 core cases red (0 rows, 2 rows, confirmation, two translator cases)
errorKey dropped from the refusal 4 red
The console wrapper's forward deleted 8 red: 3 in consoleServerAction.test.tsx, plus all five 0- and 2-row cases of ObjectView.toolbarScriptSelection-12331

Acceptance notes

  • Out of scope, still open: the console flowHandler in useConsoleActionRuntime.tsx calls resolveServerActionRecordId directly and is not a forwarded handler. A type: 'flow' action with params, launched from a list toolbar with 0 or 2+ rows, still opens its dialog first, and its refusal still reads English. It could read the new errorKey. That file belongs to objectui#13379's claim, so it was not touched here.

  • Out of scope, still open: the console wrapper's own newTabUrl fast-path refusal ("This action runs on a single record — no record id available.") is still a hard-coded English string with no pack key.

  • Adds no product-specific widget, dialog, hook or string (see AGENTS.md › Product-neutral renderer)

Session: https://claude.ai/code/session_01WJozybDmRH8z2c5SD7vJkj

🤖 Generated with Claude Code

https://claude.ai/code/session_01WJozybDmRH8z2c5SD7vJkj

…g, and translate the refusal (objectui#13384)

A single-record script action placed in list_toolbar (HotCRM's "Add to
Campaign") launched with 0 or 2+ rows ran its whole param dialog, then
refused after Confirm, in English under zh-CN.

ActionRunner now asks the handler it will dispatch to for an early refusal
(`refuseBeforeInput`, optional on ActionRunnerHandler) before the
confirmation and the param dialog. createServerActionHandler answers it with
the same record-id resolution the dispatch runs, so a refused launch toasts
and sends nothing before the user types anything; one row behaves as before.

The two refusals now carry pack keys (actions.selectRowFirst,
actions.selectExactlyOneRow) in all ten packs; resolveServerActionRecordId
returns the key as `errorKey`, and the runner translates the early refusal
through its translator by that key.

Claude-Session: https://claude.ai/code/session_01WJozybDmRH8z2c5SD7vJkj
Co-authored-by: Claude <noreply@anthropic.com>
…le server-action wrapper (objectui#13384)

createConsoleServerActionHandler wrapped core's handler in a new function,
so the runner never saw `refuseBeforeInput` and the console kept opening the
param dialog before refusing. The wrapper now forwards it from core's
handler (built with the wrapper's own resolveRecordId), so a single-record
action launched from a list toolbar with 0 or 2+ rows is refused before any
dialog, with no pre-opened tab and no request.

The objectui#12331 list-toolbar suite now asserts that its 0- and 2-row
launches open no dialog, beside the toast and the absent request; the
consoleServerAction suite pins the forward, alone and through a real
ActionRunner.

Claude-Session: https://claude.ai/code/session_01WJozybDmRH8z2c5SD7vJkj
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 024092b03355d856ede501bb55d6dcbda875b900
Local-runs: probe — scripts/check-governed-queue-guard.mjs --test over the committed file list, to read the face (NOT GOVERNED, 19 paths)

Face: the Clause-②: yes declaration alone (no published schema, no governed path), so the lane seat reviews at TIER_DEFAULT. Inputs read: the card objectui#13384, its claim 6368680261 and the seat's amendment widening the file surface, and the PR diff at this head. Check conclusions are taken from this head's check-runs.

① Derived judgments

The diff widens three public types of @object-ui/core and changes no existing member:

  1. ActionRunnerHandler is now the same function type intersected with an optional refuseBeforeInput(action, context). Every existing handler is still assignable.
    • The runner asks it only on the handler it will dispatch to, after the condition and disabled gates and before the confirmation and the param dialog.
    • An answer becomes a refused ActionResult through the usual post-execution path (toast, onFailure). undefined lets the run go on exactly as before.
    • An update-operation action never reaches a registered handler, so it is never asked.
    • Correct.
  2. ServerActionHandler gains the same optional member. createServerActionHandler sets it from the very resolver the dispatch runs, including a host's own resolveRecordId.
    • A record page, whose context carries the record id, is therefore never refused early.
    • A dialog that itself collects recordId is not judged before it runs.
    • The dispatch keeps its own check for callers that invoke the handler directly.
    • Correct.
  3. ServerActionRecordIdResolution gains an optional errorKey. error keeps the exact English text it carried before, so a host that shows error sees no change.
    • The runner translates the early refusal by that key through the translator it already has (setTranslator).
    • Core gains no i18n dependency.
    • Correct.

The rule is asked of the handler rather than held in the runner. That is the right altitude: a related-list grid publishes selectedRecords: [] into a record page's runner, so a row rule in the runner would refuse every record_header action there.

The console wrapper createConsoleServerActionHandler forwards the member. Without the forward the console behaviour would not change; the dev measured both with and without it. The 12331 suite's five 0-row and 2-row cases now assert no dialog, the refusal toast and no request. The other claims in that suite are unchanged.

② Semver level

The changeset declares patch for @object-ui/core, @object-ui/i18n and @object-ui/app-shell, and no major.

③ Boundary flags

  • The file surface was widened by the seat's claim amendment to cover consoleServerAction.ts and the 12331 test. Both are in this diff, and nothing outside the amended surface is touched.
  • useConsoleActionRuntime.tsx belongs to objectui#13379 (PR fix(app-shell): host an app-relative onSuccess.navigate under the current app (objectui#13379) #13394) and is untouched.
    • Its flowHandler still calls resolveServerActionRecordId directly, so a flow action with params launched from a list toolbar still opens its dialog first, with an English refusal.
    • The wrapper's newTabUrl refusal is still English.
    • Both are named in the PR's Acceptance notes as follow-ups. Neither moves a floor: no security or permission boundary, no published contract semantics, no capability trade-off, no gate strength.
  • No open questions from the dev.

Implemented-by: claude/issue-13384-record-action-row-guard
Reviewed-by: session_01WJozybDmRH8z2c5SD7vJkj

VERDICT: PASS

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 77 chunks) 3242.7 KB 3744.1 KB
Main entry chunk (gzip) 81.8 KB 350 KB
Entry file index-DVaQL7UQ.js —
Status PASS —

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

Package Size Gzipped
app-shell (buildIdentity.js) 0.80KB 0.49KB
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.41KB 7.90KB
app-shell (runtime-config.js) 24.52KB 8.48KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 617.54KB 150.58KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 253.04KB 70.68KB
fields (index.js) 296.30KB 75.73KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.33KB 11.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (ServerReadScope.js) 4.03KB 1.82KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.99KB 0.43KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 55.56KB 16.21KB
plugin-charts (index.js) 86.08KB 23.67KB
plugin-chatbot (index.js) 238.47KB 56.30KB
plugin-dashboard (index.js) 147.76KB 40.03KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 267.63KB 71.14KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 186.64KB 48.54KB
plugin-gantt (index.js) 178.46KB 45.13KB
plugin-grid (index.js) 278.37KB 77.46KB
plugin-kanban (index.js) 64.49KB 19.83KB
plugin-list (index.js) 149.89KB 39.29KB
plugin-map (index.js) 27.17KB 9.00KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.31KB 11.88KB
plugin-tree (index.js) 15.27KB 5.40KB
plugin-view (index.js) 94.54KB 24.33KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 129.13KB 42.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.71KB 2.11KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (product-blocks.js) 0.20KB 0.19KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

…the early refusal, not a dialog (objectui#13384)

`?runAction=create_campaign` runs a record-scoped action as the list page
mounts, with no row selected. Since objectui#13384 such a run is refused
for its row count before its param dialog opens, so the control case's
"the param dialog opens" no longer holds. Its claim (the same link arms on
a view that does not wire the action, and is consumed) is now shown by the
refusal toast, with no dialog; the not-armed case also asserts no such
toast. The fixture keeps the same action, so the differential stays.

Claude-Session: https://claude.ai/code/session_01WJozybDmRH8z2c5SD7vJkj
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: TIER_DEFAULT
Head-sha: a297aa733624925adc7077d7413ed8c7f6efca81
Local-runs: probe — git diff 024092b03..a297aa733 --stat (one test file, +18/−2)

Face: the same Clause-②: yes declaration as the record at 024092b03 (comment 6369313613). Since then the head has moved by one test-only commit, so this record carries that review forward to the new head.

① Derived judgments

a297aa733 changes only ObjectView.bulkWiredHeaderDuplicate-12462.test.tsx. CI at 024092b03 had failed on its control case, "the same link arms: the param dialog opens", which runs ?runAction=create_campaign: a record-scoped action launched with no row selected. That is exactly the 0-row launch this PR now refuses before any dialog. The dev measured it: with the console forward removed, the file passes 6/6.

The deep link supplies no record id in any form the resolver misses, so the new behaviour is right and the pin's premise changed. The case keeps its claim, that the link arms and is consumed on a view that does not wire it. It now shows the run by the refusal toast and the absence of a dialog. Its "not armed" twin now also asserts that no refusal toast appears.

The judgments of the earlier record (the three widened types, the forward, the record-page exemption) are unchanged, because no source file moved.

② Semver level

Unchanged: patch for @object-ui/core, @object-ui/i18n and @object-ui/app-shell, no major.

③ Boundary flags

  • The dev reran all of packages/app-shell/src/views/ (1134 files, 10817 passed) and every app-shell test that reads consoleServerAction or resolveServerActionRecordId (11 files, 204 passed). No other test assumed a dialog opens for a record-scoped action with 0 rows.
  • The PR body's Tests section reports 024092b03 and does not mention this update; this record carries it.

Implemented-by: claude/issue-13384-record-action-row-guard
Reviewed-by: session_01WJozybDmRH8z2c5SD7vJkj

VERDICT: PASS

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 77 chunks) 3243.1 KB 3744.1 KB
Main entry chunk (gzip) 81.8 KB 350 KB
Entry file index-B4RHerJR.js —
Status PASS —

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

Package Size Gzipped
app-shell (buildIdentity.js) 0.80KB 0.49KB
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.41KB 7.90KB
app-shell (runtime-config.js) 24.52KB 8.48KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 614.12KB 150.38KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 253.04KB 70.68KB
fields (index.js) 295.17KB 75.63KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.17KB 11.96KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (ServerReadScope.js) 4.03KB 1.82KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.99KB 0.43KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.91KB
plugin-calendar (index.js) 55.41KB 16.19KB
plugin-charts (index.js) 85.95KB 23.65KB
plugin-chatbot (index.js) 237.89KB 56.23KB
plugin-dashboard (index.js) 147.15KB 39.95KB
plugin-designer (index.js) 232.83KB 50.01KB
plugin-detail (index.js) 267.21KB 71.14KB
plugin-editor (index.js) 2.21KB 1.04KB
plugin-form (index.js) 185.46KB 48.48KB
plugin-gantt (index.js) 178.19KB 45.06KB
plugin-grid (index.js) 278.21KB 77.54KB
plugin-kanban (index.js) 64.27KB 19.81KB
plugin-list (index.js) 149.54KB 39.24KB
plugin-map (index.js) 27.11KB 8.99KB
plugin-markdown (index.js) 13.79KB 4.79KB
plugin-report (index.js) 46.42KB 13.06KB
plugin-timeline (index.js) 39.30KB 11.87KB
plugin-tree (index.js) 15.25KB 5.38KB
plugin-view (index.js) 94.26KB 24.27KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 129.13KB 42.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.71KB 2.11KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (product-blocks.js) 0.20KB 0.19KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT — objectui#13384 (p3, S), head a297aa733624925adc7077d7413ed8c7f6efca81, reviewed 2026-10-23T18:43Z.

Seat: domain:ui#1 · Clause-②: yes(合同复核见本 PR 上的两条 ## Contract review:024092b03 一条,a297aa733 一条承接,均为 VERDICT: PASS)

项 读数
形态 19 个文件,+567/−26:@object-ui/core 的 ActionRunner.ts、serverActionHandler.ts 及测试;@object-ui/app-shell 的 consoleServerAction.ts 及测试、ObjectView.toolbarScriptSelection-12331.test.tsx;10 个语言包各 2 个键和一个钉测;一个 changeset(core、i18n、app-shell patch)。都在认领加修正后申报的文件面内。NOT GOVERNED。与当前 main 无冲突(本次读取时 git merge-tree 无冲突)。
改动核对 读了 diff。运行器在条件门和禁用门之后、确认框和参数框之前,问它将要派发的那个处理器有没有提前拒绝(refuseBeforeInput);有就按常规的执行后路径弹出提示、走 onFailure,什么都不发。服务端动作处理器用派发时同一个解析器作答,所以记录页(上下文带记录 id)不会被提前拒绝;参数框自己收集 recordId 的动作不提前判断。控制台的包装函数把这个成员转发出去,否则控制台行为不会变。两句拒绝文案改为语言包键 actions.selectRowFirst、actions.selectExactlyOneRow,英文原文不变,运行器经已有的翻译器按键翻译,core 没有新增 i18n 依赖。
新钉测 core:0 行、2 行不弹参数框和确认框、不发请求;1 行弹框并发一次请求;对象级动作、自定义解析器、自收集 recordId 三个对照;翻译器按键取文。控制台:转发本身和经真实运行器的端到端(不弹框、不预开标签页、不发请求;1 行照常弹框)。12331 套件的 5 个 0 行和 2 行用例改为断言不弹框、有拒绝提示、无请求,其余断言不变。i18n:钉住 zh-CN 文案,其他各包都有自己的译文。反向验证:去掉运行器的提前拒绝,5 项 core 用例变红;去掉 errorKey,4 项变红;去掉控制台转发,8 项变红。
门禁 dev 本地:core actions、packages/i18n/ 和 21 个读这些函数的 app-shell 测试共 146 个文件 2113 项通过;core type-check 3/3、app-shell type-check 30/30(含测试);check:i18n-keys / drift / dead-keys 通过;eslint 0 错误;check:new-line-citations 0 新增。CI 全绿后由席位转 ready、进合并队列。
CI 返修 024092b03 的 CI 在 ObjectView.bulkWiredHeaderDuplicate-12462 的对照用例上变红:深链 ?runAction=create_campaign 在未选行时启动一个单记录动作,正是本卡要在弹框前拒绝的情形。a297aa733 只改这一个测试:原主张(视图未接线时深链照样触发并被消费)不变,改为用拒绝提示且无对话框来证明已运行;「未触发」的对照也加了不出现拒绝提示的断言。dev 重跑 packages/app-shell/src/views/ 全量 1134 个文件 10817 项通过,另 11 个读这些函数的测试 204 项通过。没有改源码。
接受的偏差 提前拒绝也排在 confirmText 之前,不只是参数框之前;原来确认后才被拒的 2 行启动,现在同样在弹框前被拒。都接受。
不在本卡内 PR 的 Acceptance notes 已写,属后续:控制台 useConsoleActionRuntime.tsx 的 flowHandler(objectui#13379 的文件面)直接调用 resolveServerActionRecordId,带参数的流程动作从列表工具栏启动时仍先弹框、英文拒绝;包装函数的 newTabUrl 拒绝仍是英文字面量。
落地条款 Closes #13384:合并后关卡。

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 18:44
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 18:44
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 23, 2026
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Re-queued · domain:ui seat 1 · session_01WJozybDmRH8z2c5SD7vJkj · 2026-10-23T20:38Z

At 19:06Z the merge queue dropped this PR. Its merge-group CI run 40005992766 went red on one job: Test (shard 7/16) was cancelled when it reached the 20-minute job cap (the timing note reads "20m20s = 102% of the 20-minute cap"). The other 15 shards passed in 6 to 9 minutes.

It is not this PR's failure:

No fix exists to port into this PR, so the seat re-enables auto-merge once. That is this failure's one re-run. A second timeout on the same shard is treated as real.


Generated by Claude Code

Merged via the queue into main with commit 7f60e9d Oct 23, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-13384-record-action-row-guard branch October 23, 2026 20:50
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

2 participants