Repository navigation
fix(app-shell): ActionParamDialog honours the spec carryOver param declaration — carried params render read-only and submit verbatim (objectui#6246) - #10271
Conversation
…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
…re, i18n) 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
… note Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
|
changeset-claim-re-read
|
✅ 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: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
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 makesActionParamDialoghonour it: a param that declarescarryOverrenders 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
@objectstack/spec@17.4.0.grep -rl carryOver node_modules/@objectstack/spec/disthitsdist/ui/index.{js,mjs,d.ts,d.mts}among others, which meets theRestart-when:on the card. The declared shape iscarryOver: z.ZodOptional(z.ZodBoolean), a plain optional boolean. A runtime probe against the installedActionParamSchemaaccepts{ field, defaultFromRow: true, carryOver: true }and keepscarryOver: truein the parsed output. It refusescarryOver: truewithoutdefaultFromRowat pathcarryOver, and it refuses a string value.clone_permission_setonSysPermissionSet(in@objectstack/plugin-security) declarescarryOver: trueonobject_permissions,field_permissions,system_permissions,row_level_securityandtab_permissions, both on objectstackmainand in the published@objectstack/plugin-security@17.4.0tarball (itsdist/index.mjscarriesfield: "row_level_security", defaultFromRow: true, carryOver: true).descriptionis 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.resolveActionParam()builds its output key by key, socarryOverwas dropped before the dialog ever saw the param.ActionParamDialog.tsxread it 0 times. The dialog reads@object-ui/core'sActionParamDef(the resolved shape), not the spec type and not@object-ui/types.@object-ui/types'ActionParamalready derives fromz.inputof the spec schema, so it gainedcarryOverwith no edit.What changes
@object-ui/core:ActionParamDef.carryOver?: boolean, documented against the spec key.@object-ui/app-shell:resolveActionParams.ts: theRawActionParammirror declarescarryOver, and all three output branches (inline, field-backed, unresolved field) copy it.ActionParamDialog.tsx: a carry-over param returns beforeparamToField(), so no widget is built for it. It renders as a ShadcnCollapsible, closed by default, with a trigger line (label, lock icon,actionDialog.carryOverHint). When expanded, it shows the value verbatim in apre. The value lives only in the dialog's seededvalues.serializeParamValuesexempts 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 allminor, the same class as the objectui#10129 precedent, which also added a key toActionParamDefand to the i18n packs.Pins:
ActionParamDialog.carryOver-6246.test.tsxThe fixture is the Clone action's
paramstranscribed fromSysPermissionSet.@objectstack/plugin-securityis not a dependency of this repo, so the fixture is parsed through the installedActionParamSchema, and a refusal control proves that parse can fail.carryOver: trueand the row value as its seed. Control: the undeclared params resolve without it. The inline and unresolved-field branches carry it too.param-carry-over-FACETblock with zeroinput/textarea/select/contenteditableelements 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 exactlydescription,labelandname. Expanding shows the value and still has nothing editable in it. Control: the ordinary params of the same dialog are anINPUT, anINPUTand aTEXTAREAseeded from the row.toBethe row string. The typed label and name go through,descriptionis seeded, andadmin_scopeis absent. A carried upload value is not reduced to its id. Control: an ordinary upload param still is.ActionPreviewmock draws the read-only line for a carry-over param and notextarea, 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 byblob == HEADplus an emptygit diff HEAD:carryOver(if (false && rawParam.carryOver))serializeParamValuesignores it (line deleted)Reverse type check: planting
carryOverX: truebesidecarryOver: truein anActionParamDefliteral of the test turnstsc -p packages/app-shell/tsconfig.test.jsonred: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 rebuiltpackages/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.tsin@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=2atc11f8d5d:Test Files 232 passed (232),Tests 4541 passed (4541).pnpm exec vitest run packages/app-shell/ --maxWorkers=2atc11f8d5d:Test Files 754 passed (754),Tests 7461 passed | 1 skipped (7462).ActionParamDialog/ resolver /paramToField/paramValueShape/useConsoleActionRuntimeneighbours: 20 files, 338 tests green. Themetadata-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 atc11f8d5dafter 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 atc11f8d5d.eslint.config.js, whose TS block isfiles: ['**/*.{ts,tsx}']. The two changed.mdfiles are outside it.--format jsonreports 15 files with 0 errors, the same counts as the base on the files compared. With--no-inline-config,ActionPreview.tsxshows 2 errors at both the base and this head. These are the tworeact-hooks/static-componentslines that the file already disables inline.tseslint.configs.recommendedwith noparserOptions.projectorprojectService, so linting is not type-aware, and no rule ineslint-rules/reads the disk.check:readme-exports,check:doc-examplesandcheck:doc-snippetsall 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
resolveActionParams.ts, theRawActionParammirror plus the pass-through. Without it the declaration cannot reach the dialog.ActionPreview.tsx, which this change would otherwise have made false.carryOvertoggle. An author has to write the key in the JSON source. Nobody has picked this up.Generated by Claude Code