Skip to content

fix(app-shell): ActionParamDialog honours the spec carryOver param declaration — carried params render read-only and submit verbatim (objectui#6246) - #10271

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-6246-action-param-carryover
Sep 24, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-6246-action-param-carryover

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #6246
Clause-②: yes

UI half of the objectstack#11753 ruling (maintainer, recommendation A, 「同意」). The spec half, ActionParamSchema.carryOver, landed in objectstack#12614. This PR makes ActionParamDialog honour it: a param that declares carryOver renders as a collapsed read-only summary. It is seeded from the row, submitted verbatim, and has no field widget at all, so there is nothing to edit (the control is not just styled as read-only).

Premise, measured before any edit

  • The key is installable here. This worktree installs @objectstack/spec@17.4.0. grep -rl carryOver node_modules/@objectstack/spec/dist hits dist/ui/index.{js,mjs,d.ts,d.mts} among others, which meets the Restart-when: on the card. The declared shape is carryOver: z.ZodOptional(z.ZodBoolean), a plain optional boolean. A runtime probe against the installed ActionParamSchema accepts { field, defaultFromRow: true, carryOver: true } and keeps carryOver: true in the parsed output. It refuses carryOver: true without defaultFromRow at path carryOver, and it refuses a string value.
  • There is a live producer. clone_permission_set on SysPermissionSet (in @objectstack/plugin-security) declares carryOver: true on object_permissions, field_permissions, system_permissions, row_level_security and tab_permissions, both on objectstack main and in the published @objectstack/plugin-security@17.4.0 tarball (its dist/index.mjs carries field: "row_level_security", defaultFromRow: true, carryOver: true). description is deliberately left editable by the producer. So the Clone dialog now shows three ordinary inputs (display name, API name, description) plus five read-only summaries. The card body says "two ordinary text inputs". This renderer follows the declaration and does not keep a list of names.
  • Why the key never reached the dialog. resolveActionParam() builds its output key by key, so carryOver was dropped before the dialog ever saw the param. ActionParamDialog.tsx read it 0 times. The dialog reads @object-ui/core's ActionParamDef (the resolved shape), not the spec type and not @object-ui/types. @object-ui/types' ActionParam already derives from z.input of the spec schema, so it gained carryOver with no edit.

What changes

  • @object-ui/core: ActionParamDef.carryOver?: boolean, documented against the spec key.
  • @object-ui/app-shell:
    • resolveActionParams.ts: the RawActionParam mirror declares carryOver, and all three output branches (inline, field-backed, unresolved field) copy it.
    • ActionParamDialog.tsx: a carry-over param returns before paramToField(), so no widget is built for it. It renders as a Shadcn Collapsible, closed by default, with a trigger line (label, lock icon, actionDialog.carryOverHint). When expanded, it shows the value verbatim in a pre. The value lives only in the dialog's seeded values. serializeParamValues exempts carry-over params from the upload id reduction, so "verbatim" holds on every field type.
    • metadata-admin/previews/ActionPreview.tsx: the designer's dialog mock says it draws the widget the dialog will render. After this change a carry-over param has no such widget, so the mock draws a read-only "carried over" line instead of the field's input. Without this, the mock would have been made false by this PR.
    • README.md: the ActionParamDialog section documents the key.
  • @object-ui/i18n: one key, actionDialog.carryOverHint, in all ten packs.
  • .changeset/6246-action-param-carry-over.md: app-shell, core and i18n all minor, the same class as the objectui#10129 precedent, which also added a key to ActionParamDef and to the i18n packs.

Pins: ActionParamDialog.carryOver-6246.test.tsx

The fixture is the Clone action's params transcribed from SysPermissionSet. @objectstack/plugin-security is not a dependency of this repo, so the fixture is parsed through the installed ActionParamSchema, and a refusal control proves that parse can fail.

  • Leg A (resolver): every carried facet resolves with carryOver: true and the row value as its seed. Control: the undeclared params resolve without it. The inline and unresolved-field branches carry it too.
  • Leg B (what Clone renders): each facet is a param-carry-over-FACET block with zero input / textarea / select / contenteditable elements and no textbox. No control anywhere is labelled by the facet. The block is collapsed (aria-expanded="false", value not in the DOM). Dialog-wide, the editable controls by identity are exactly description, label and name. Expanding shows the value and still has nothing editable in it. Control: the ordinary params of the same dialog are an INPUT, an INPUT and a TEXTAREA seeded from the row.
  • Leg C (what Clone submits): Confirm sends each facet toBe the row string. The typed label and name go through, description is seeded, and admin_scope is absent. A carried upload value is not reduced to its id. Control: an ordinary upload param still is.
  • Leg D (designer): the ActionPreview mock draws the read-only line for a carry-over param and no textarea, and the ordinary param beside it still draws its text box.

Red before the fix, at the base plus the test file only: Tests 6 failed | 4 passed (10). Legs A and B are red and the upload leg is red. The submit leg was already green, which is expected: an untouched seeded textarea was always sent verbatim. That leg is a guarantee pin, not the gap.

Ablations go through ablation-replace.mjs. The anchor has to hit, the blob hash has to move, and each restore is proven by blob == HEAD plus an empty git diff HEAD:

mutation red green
dialog ignores carryOver (if (false && rawParam.carryOver)) leg B read-only, leg B expand (2) all controls, leg A, leg C (8)
serializeParamValues ignores it (line deleted) the carried-upload pin (1) 9 others
resolver drops it on all 3 branches (3 hits deleted) leg A x2, leg B x2 (4) controls, fixture parse, leg C (6)
designer mock ignores it leg D (1) 10 others

Reverse type check: planting carryOverX: true beside carryOver: true in an ActionParamDef literal of the test turns tsc -p packages/app-shell/tsconfig.test.json red: TS2561 ... 'carryOverX' does not exist in type 'ActionParamDef'. Did you mean to write 'carryOver'?. That program lists the test file (1 hit in --listFiles, 4604 files) and resolves core through the rebuilt packages/core/dist/actions/ActionRunner.d.ts. The tree was restored and proven clean.

Send-side contract: objectstack#11703's pin 6 (packaged-permission-set-lock.test.ts in @objectstack/plugin-security) is untouched. It lives in objectstack, reads only objectstack's action definition, and this diff changes no objectstack file. It was not re-run here.

Local verification (test union after the final commit)

  • pnpm exec vitest run packages/core/ packages/i18n/ --maxWorkers=2 at c11f8d5d: Test Files 232 passed (232), Tests 4541 passed (4541).
  • pnpm exec vitest run packages/app-shell/ --maxWorkers=2 at c11f8d5d: Test Files 754 passed (754), Tests 7461 passed | 1 skipped (7462).
  • New pin plus the ActionParamDialog / resolver / paramToField / paramValueShape / useConsoleActionRuntime neighbours: 20 files, 338 tests green. The metadata-admin/previews/ directory plus the ActionDesigner param tests: 61 files, 702 tests green.
  • turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/core --filter=@object-ui/i18n --concurrency=2: Tasks: 32 successful, 32 total (dependency closure built first). Re-run at c11f8d5d after the last source edit: pnpm --filter @object-ui/app-shell type-check (tsc --noEmit && tsc -p tsconfig.test.json) exit 0.
  • check:control-bytes, check:new-line-citations (0 new), check:i18n-keys, check:i18n-drift (1 key added, 0 en values changed), check:i18n-dead-keys, check:changeset-claims, check:pending-changeset-literals, check:designer-field-key-parity, check:action-forward-parity, check:unreferenced-sources, check:handler-key-reads, check:test-path-roots, node scripts/check-changeset-presence.mjs: all exit 0 at c11f8d5d.
  • Lint was narrowed to the changed files, and the narrowing is measured:
    • The population comes from eslint.config.js, whose TS block is files: ['**/*.{ts,tsx}']. The two changed .md files are outside it.
    • --format json reports 15 files with 0 errors, the same counts as the base on the files compared. With --no-inline-config, ActionPreview.tsx shows 2 errors at both the base and this head. These are the two react-hooks/static-components lines that the file already disables inline.
    • No untouched file's verdict can change: the config uses tseslint.configs.recommended with no parserOptions.project or projectService, so linting is not type-aware, and no rule in eslint-rules/ reads the disk.
  • NOT MEASURED: check:readme-exports, check:doc-examples and check:doc-snippets all refused as PREREQUISITE NOT MET, because the whole workspace is unbuilt in this worktree (their own "run the build first" refusal). The README addition is a JSON fence with no import binding. Their verdict belongs to CI.

Scope notes

  • The claim's file surface names the dialog, its tests, "the param type it reads" and one changeset. This PR also touches these files:
    • resolveActionParams.ts, the RawActionParam mirror plus the pass-through. Without it the declaration cannot reach the dialog.
    • the ten locale packs, for the one new key.
    • the app-shell README, per AGENTS.md Add automated testing infrastructure and CI/CD workflows #2.
    • ActionPreview.tsx, which this change would otherwise have made false.
  • None of these files is in any open PR's file list, read at 2026-09-24T14:35Z.
  • Out of scope, noted and not filed: the action designer's param inspector does not offer a carryOver toggle. An author has to write the key in the JSON source. Nobody has picked this up.

Generated by Claude Code

…declaration

A param that declares `@objectstack/spec`'s `ActionParamSchema.carryOver`
now renders as a collapsed read-only summary with no field widget, and its
row-seeded value is submitted verbatim.

- `resolveActionParam()` builds its output key by key and never copied
  `carryOver`, so the declaration never reached the dialog: every JSON
  permission facet of the permission-set Clone action was offered as a
  prefilled, editable textarea. The key now rides all three branches
  (inline, field-backed, unresolved field) onto `ActionParamDef.carryOver`.
- `ActionParamDialog` returns before `paramToField()` for a carry-over
  param, so no input exists for it; the value lives only in the dialog's
  seeded `values`.
- `serializeParamValues` exempts carry-over params from the upload id
  reduction, so "verbatim" holds on every field type.
- One new console key, `actionDialog.carryOverHint`, in all ten packs.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
…ad-only

`ActionPreview`'s dialog mock states that it draws the widget
`ActionParamDialog` will render. A param that declares `carryOver` now
gets no widget in the dialog, so the mock answers before
`runtimeWidgetFor` and draws a read-only "carried over" line instead of
the declared field's input.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 6 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/6329-actionparam-one-authority.md

  • names ActionPreview.tsx → packages/app-shell/src/views/metadata-admin/previews/ActionPreview.tsx — edited by this change

    ActionParam has one authority again (objectui#6329). The name was declared three times, not twice as the card counted: @object-ui/types publishes it — derived from the spec's ActionParamSchema input, with its own parity suite — and ActionDefaultInspector.tsx and ActionPreview.tsx each carried a module-local interface of the same name. app-shell already read the published one elsewhere (utils/resolveActionParams.test.ts), so both locals were shadows. They are deleted, not reconciled against each other, under the 2026-08-25 family ruling 甲A1.

.changeset/6473-metadatatypeactions-param-dialog-close.md

  • names ActionParamDialog.tsx → packages/app-shell/src/views/ActionParamDialog.tsx — edited by this change

    The pre-reset paramState.resolve?.(null) is dropped as well, on an enumeration rather than on "resolving twice is a no-op": onOpenChange is reachable from exactly three places, all inside ActionParamDialog, and every one settles the promise before asking for the close — handleSubmit, handleCancel, and the Radix root handler that delegates to handleCancel (the single route Escape, an overlay click and the header close button all take). All four routes are driven in the new test, with a census over ActionParamDialog.tsx so a later call site that skipped the settle is red there instead of leaving a promise pending forever.

.changeset/6661-app-launcher-nav-menu-renderers.md

  • names en.ts → packages/i18n/src/locales/en.ts — edited by this change

    Three new strings — the launcher's and the menu's accessible names, and the menu's empty state — are declared under console.nav in en.ts and its nine sibling packs. An inline defaultValue alone is not a fix: it renders English at one call site and leaves the string untranslatable everywhere (objectui#3517).

.changeset/8318-jsdoc-default-tag-corrections.md

  • names core/src/actions/ActionRunner.ts → packages/core/src/actions/ActionRunner.ts — edited by this change

    • CRUDDialogSchema.size / .closeOnOutsideClick / .closeOnEscape / .showClose — there is no register('crud-dialog' anywhere, so no node of that type ever reaches a renderer. Recorded once on the interface. Per key, the name census differs: two spellings occur nowhere outside the declaration and its zod twin, and showClose's one other occurrence (renderers/overlay/drawer.tsx:38) belongs to DrawerSchema. - ActionSchema.level — type: 'action' is not a rendered node type, and core/src/actions/ActionRunner.ts, which is what makes method / chainMode / reload / close live, does not read level. - CardSchema.variant — card IS registered, twice, and neither registration reads it: the ui route forwards the key to ui/card.tsx, which spreads onto a div and mentions variant nowhere, and the page route forwards only its designer props. - PageNodeSchema.isDefault — page IS registered, and PageRenderer neither reads the key nor forwards it: the wrapper element gets toDomProps(props), an allow-list that does not carry it.

.changeset/action-keys-warning-text-5642.md

  • names ActionRunner.ts → packages/core/src/actions/ActionRunner.ts — edited by this change

    Both halves of the message's tail had outlived the change they described. It told the author the key was warned about rather than rejected because ActionDef "still carries [key: string]: any" — objectstack#4075 step 3 deleted that index signature, and actionKeys.pin.test.ts pins the deletion in the opposite direction ({ ActionDef: false, ActionContext: true }), while actionDef-closed-surface.test.ts pins that tsc now rejects exactly such a key at the construction site. And it prescribed promoting the key to an explicit field on ActionDef "(packages/core/src/actions/actionKeys.ts)" — that file holds the INVENTORY (ACTION_DEF_KEYS); the interface is in ActionRunner.ts. The wrong pointer had teeth: an author who followed it edited the inventory alone, which is precisely the half-change the pin test reddens on, since it re-derives the inventory from the interface's AST.

.changeset/retire-components-action-param-dialog-5685.md

  • names src/views/ActionParamDialog.tsx → packages/app-shell/src/views/ActionParamDialog.tsx — edited by this change

    • import { ActionParamDialog } from '@object-ui/components' — no drop-in replacement is published. The surviving implementation is @object-ui/app-shell's ActionParamDialog (src/views/ActionParamDialog.tsx), rendered by app-shell's action runtime (useConsoleActionRuntime, RecordDetailView) rather than exported standalone. A host that needs its own param form builds on @object-ui/fields' shared field widgets (resolveFormWidgetType / getLazyFieldWidget, ADR-0059) — the same seam the surviving dialog renders through.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 5f00ff491 (merge-base with origin/main): 16 file(s) changed outside .changeset/, read against 1285 pending declaration(s) that publish a body (1854 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3037.7 KB 3104.5 KB
Main entry chunk (gzip) 147.8 KB 350 KB
Entry file index-C3O3neby.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 (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 546.88KB 130.96KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 222.47KB 61.83KB
fields (index.js) 253.40KB 63.99KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 33.36KB 10.88KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.02KB 11.00KB
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) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 51.44KB 14.62KB
plugin-charts (index.js) 71.82KB 20.13KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.00KB 35.19KB
plugin-designer (index.js) 215.98KB 44.34KB
plugin-detail (index.js) 257.69KB 67.12KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 144.65KB 37.10KB
plugin-gantt (index.js) 167.99KB 41.37KB
plugin-grid (index.js) 215.46KB 58.88KB
plugin-kanban (index.js) 48.83KB 15.21KB
plugin-list (index.js) 113.90KB 28.11KB
plugin-map (index.js) 21.74KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.68KB 8.95KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.18KB 21.05KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
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) 106.60KB 35.16KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.38KB 1.98KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.74KB 2.54KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 15.71KB 5.30KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 0.20KB 0.18KB
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) 4.74KB 2.25KB
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 (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c11f8d5d

① Derived judgments

  • ActionParamDef.carryOver?: boolean is exactly the spec's z.boolean().optional() (objectstack PR #12614), with no alias reader and no dialect. @object-ui/types' ActionParam already derives from the spec's z.input.
  • The resolver copies carryOver: param.carryOver verbatim on all three branches. There is no ?? and no inheritance from the field, so nothing can flip it.
  • Fail direction holds.
    • The carryOver branch returns before paramToField(), so no widget is ever built.
    • The only focusable element is the collapsible trigger, and onChange is never wired.
    • serializeParamValues skips the upload-id reduction for carried values.
    • The submitted value is the same reference as the row seed.
    • With no row value to seed from, the param is omitted, and the server fills the facet with its empty default. That is fail-closed, never wider.
  • Controls: ordinary params stay on the unchanged widget path; the editable ids are exactly label, name and description.
  • ActionPreview.tsx is truthful and minimal.
  • i18n: the one key is present in all 10 packs, with no English leaking into non-English packs.
  • The 11 pins assert what they claim, and each absence has a lit control beside it, including the refusal control on the installed ActionParamSchema.
  • Surface widening: resolveActionParams.ts, the 10 locales, the README and ActionPreview.tsx are each required, not gratuitous.

② Semver level

  • minor on @object-ui/app-shell, @object-ui/core and @object-ui/i18n is RIGHT; the precedent is 10129-action-param-field-backed-picker.md.
  • No sentence is false. Two are over-general and non-blocking:
    • the designer preview draws 「the same read-only line」: it draws a different static line;
    • a hand edit 「produced」 a wider clone: that states the hazard as unconditional.

③ Boundary flags

  • The Scope notes name every extra file.
  • Fixes #6246 is defensible.
  • Clause-②: yes is on line 2, and the widening is named.
  • Check-runs on c11f8d5d: 43 terminal, 40 success and 3 skipped by design.
  • Governance (i), reported, not decided: the ruling lists description among the carried facets. The producer (clone_permission_set, plugin-security 17.4.0) declares carryOver on five facets and leaves description editable. This renderer keeps no name list, so the remaining gap is one producer declaration.
  • Governance (ii), reported, not decided: objectstack#11753 comment 5536466144 (2026-09-04) re-aims the parent to ADR-0131 C9. It reads 「the fix is not a new contract key on the old one」 and 「not a flag that makes the textareas read-only」, and the parent is pm:blocked + target:v18. The objectui card was unlocked on 2026-09-24 (5805914175, maintainer 「其他同意」) without citing that comment. This PR adds no spec key; it consumes the one already published in 17.4.0. Whether that is within the re-aimed parent's intent is a maintainer question.

Implemented-by: claude/issue-6246-action-param-carryover
Reviewed-by: session_01BA3nKVUwKQJf8DBxrSVtNC

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 24, 2026 15:20
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit 06b82b8 Sep 24, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-6246-action-param-carryover branch September 24, 2026 15:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant