Repository navigation
fix(spec)!: an action:button refuses confirmText beside a non-empty array params, as the two action doors do - #29869
Conversation
…ty array params, as the action doors do The predicate and refusal move to ui/action-confirm-beside-params.ts so component.zod.ts reads the same ones without publishing them. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufziDSrjZDDBZDrXMGAQ
…ion:button pair refusal Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufziDSrjZDDBZDrXMGAQ
…te slot refuses a locale map for its own reason Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufziDSrjZDDBZDrXMGAQ
📓 Docs Drift CheckThis PR changes 1 package(s): 16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 9 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 3fb90bc253bd04a93b642aecd72244baec7f523d && git checkout 3fb90bc253bd04a93b642aecd72244baec7f523d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4b71a82b76531059ad45ba224899aaadaa219ebc bb8c665baa276edfac440fcd1630700e19c8e2e8 && git checkout -B drift-repro 4b71a82b76531059ad45ba224899aaadaa219ebc && git merge --no-ff bb8c665baa276edfac440fcd1630700e19c8e2e8
node scripts/docs-audit/affected-docs.mjs --json 4b71a82b76531059ad45ba224899aaadaa219ebc
|
|
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K Contract-review-claim: session_01WGufzfgvGexKn9vLHsy89K · head bb8c665 · 2026-11-03T08:53Z |
|
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K Contract reviewServed-tier: Inputs read: card #29844 body and all four comments (ruling 6597621758 letter A, the seat's claims 6597681847 and 6598028624, the os-dev-report 6599345445); PR #29869 body, its six-file list, and the net diff of ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS |
|
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K 维护者速读
席位意见:裁决范围逐条落地,收窄档契约复审 PASS,全量 dogfood 在本 head 0 失败;同意落地。复审确认 席位核对 — ACCEPT · domain:spec#3 · head
|
|
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K Cross-reference: the two doors that the PR #29869 review (record 6599507683) found outside this card's ruling, Posted 2026-11-04T06:26Z. |
domain:spec#3 · os-support-ai · session_01WGufziDSrjZDDBZDrXMGAQ
Requested by Yibo Wang · project thread
Fixes #29844
Ruling-ref: 6597621758
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — no
skills/**file teaches anaction:buttoncarryingconfirmTextbeside an arrayparams. The one rule line about the pair (skills/objectstack-ui/rules/actions.mdL22, "A registered action is refused it beside a non-emptyparams(put the question ondescription)") stays true and names the remedy this refusal prescribes; theskills/objectuipage-builder eval that asks for aconfirmTextbutton (evals/page-builder.jsonid 4) is param-less, so it stays correct. The generatedcontent/docs/references/ui/component.mdxrow that renders the button'sconfirmTextdescribe is regenerated here.BREAKING accept-set narrowing at
ActionButtonPropsSchema(@objectstack/spec), served where a page'saction:buttonpropertiesare judged: the component-props gate (component-props-invalid,@objectstack/lint) at every CLI command and at the runtimepagesave door.维护者速读
action:button同时写confirmText和非空数组params时,现在和已注册动作、内联动作一样被拒收,提示把这句话挪到description、删掉confirmText。action:button带这对组合;不带params、params: []、对象形params(静态值,不弹参数框)都照常通过。已存页面照常加载,下次保存才被拒。回滚即 revert 本 PR。packages/spec/src/ui/component.zod.ts(ActionButtonPropsSchema链尾新增的.refine),packages/spec/src/ui/action-confirm-beside-params.ts(判据与提示从action.zod.ts原样移入),packages/spec/src/ui/action-confirm-params-guard.test.ts末尾新的 describe 组。pnpm --filter @objectstack/spec exec vitest run src/ui/action-confirm-params-guard.test.ts,预期 21 个测试全过,其中按钮组 5 条(1 条拒收、4 条对照)。What changes
declaresConfirmBesideParams(data)(confirmText != nullandparamsa non-empty ARRAY) andCONFIRM_BESIDE_PARAMS_REFUSAL(path['confirmText']) move, byte for byte, from module level inpackages/spec/src/ui/action.zod.tsinto a sibling module,packages/spec/src/ui/action-confirm-beside-params.ts, whichaction.zod.tsandcomponent.zod.tsboth import. The move is socomponent.zod.tscan read them without exporting them fromaction.zod.ts, whichui/index.tsre-exports withexport *and would publish (theaction-on-success-executor.ts/action-declarative-update.tspattern; the new module is not re-exported).ActionSchemaandInlineActionSchemaread them unchanged.Array.isArray(params) && params.length > 0read refuses a non-empty array and leaves an object-formparams(anaction:button's static values) andparams: []alone, so no narrower predicate was needed.ActionButtonPropsSchemagains one.refine((data) => !declaresConfirmBesideParams(data), CONFIRM_BESIDE_PARAMS_REFUSAL)at the end of its chain.action:iconand theaction:group/action:menumember rows are not touched (see Acceptance notes).confirmTextdescribe now appends one sentence: refused beside a non-empty arrayparams, correct with noparams, besideparams: []and beside an objectparams. Only theaction:buttonrow's describe changes; the sharedACTION_ROW_COPY_DESCRIBE.confirmTextthataction:iconand the group/menu members also render is untouched, since those rows do not refuse the pair.content/docs/references/ui/component.mdxis regenerated from it (check:generated --fix, which proved onlycheck:docsstale).ActionSchema'sconfirmTextfield, the#7428refinement comment inActionSchema's chain, and the guard test's file header..changeset/29844-action-button-confirm-beside-params-refused.md:@objectstack/specminor, both declaration lines, the ADR-0087 disposition marker, the narrowing banner, before / now / unchanged / who, and the remedy.Before / after on the census case
There is no shipped carrier, so the census case is the card's own body:
origin/mainbb2c04e3;component.zod.tsis byte-identical at06513bc4, the src the probe read):success: true.success: false, one issue at['confirmText'],code: 'custom', and its message equals bothActionSchema's andInlineActionSchema's issue for their pair (asserted withtoBe).validateComponentProps(@objectstack/lintsrc against this head's specdist), on a page whoseaction:buttoncarries the body above: one finding,component-props-invalid, severityerror, atpages[0].regions[0].components[0].properties.confirmText, message opening "confirmText: An action that declaresconfirmTextbeside a non-emptyparamsshows the user TWO dialogs for". The remedy (description, noconfirmText), the param-less button,params: []and an objectparams: 0 findings each.ADR-0087 disposition
The changeset body carries the ADR-0087 marker comment with the disposition
adr-0087: not-required (no-migration-prescription), as #29829's changeset does. Nodocs/adr/**path is touched. No key is added, removed or renamed; the census found 0 shipped carriers; and the one rewrite there could be (moveconfirmTextintodescription) needs the author to read the sentence, so the refusal names it and nothing converts it.Census at the base
At
origin/mainbb2c04e3: every file underexamples/,packages/,ui/,skills/,apps/,content/,docs/,templatesthat namesaction:button(206 files,CHANGELOG.mdexcluded), eachaction:buttonoccurrence read with a 45-line window cut at the next componenttype, kept when the window holdsconfirmTextand a non-emptyparams: [. 3 windows, each read by hand:packages/lint/src/validate-action-copy-templates.test.tsL125–126: a test fixtureaction:buttonwithconfirmTextbesideparams: [{ name: 'tier', … }]. It feedsvalidateStackExpressions(the copy-template rule), which does not parseActionButtonPropsSchema, so it is unaffected; the lint suite below runs it green.skills/objectui/rules/protocol.mdL266: a false positive; the window spans prose aboutvariant: "danger"and a registered action'sparamsexample further down.Cloud fixtures are not in this repository and were not read. Disposition: 0 shipped carriers, 1 test fixture outside this door.
Tests
action-confirm-params-guard.test.tsgains the block "confirmText+ non-empty ARRAYparamsis refused on theaction:buttonrow too": the refusal (path,code, message equal toActionSchema's andInlineActionSchema's) and four accept controls:descriptionbeside an arrayparams,confirmTextwith noparams,confirmTextbesideparams: [],confirmTextbeside an objectparams. The header names the third door.confirmTextis a template slot that refuses a locale map for its own reason (invalid_union, measured), so such a test would pass with the refusal removed. The first ablation run found exactly that and the case was dropped.Readings (under os-verify-lock)
d77dce5b):vitest run src/ui/action-confirm-params-guard.test.ts src/ui/inline-action.test.ts src/ui/component.test.ts src/ui/component-action-element-rows-20371.test.ts:Test Files 4 passed (4),Tests 458 passed (458).pnpm --filter @objectstack/spec test && pnpm --filter @objectstack/spec typecheck(headbb8c665b):Test Files 806 passed | 1 skipped (807),Tests 24050 passed | 1 skipped | 1 todo (24052); VERDICT command-exit 0.scripts/ablation-replace.mjs(headbb8c665b; anchor hit 1 → 0, blobc9c80b8e→f1896925): the button refine's predicate replaced bytrue || …;action-confirm-params-guard.test.ts→Tests 1 failed | 20 passed (21)(the button refusal test); restored, blob equals HEAD,git diff HEADempty. Nodistin that path (the test imports./component.zod).validateComponentPropsagainst this head's specdist, five cases, results above.node ui/scripts/check-action-forward-parity.mjs: exit 0 (action:button owes 31, forwards 28; no key moved).ActionButtonPropsSchema:cd ui && vitest run packages/components/src/__tests__/spec-symbol-action-renderer-props-11073.test.ts packages/types/src/__tests__/imported-defaults-8317.test.ts:Test Files 2 passed (2),Tests 278 passed (278).runner-variant.tsandpublic-blocks.zod.tsread the schema's type, which a.refinedoes not change.bb8c665b, after the dogfood closure build):pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/validate-action-copy-templates.test.ts src/validate-component-props-list-view-22444.test.ts src/validate-component-props-row-on-success.test.ts src/validate-component-props-url-target.test.ts src/validate-component-props.test.ts:Test Files 5 passed (5),Tests 215 passed (215); VERDICT command-exit 0. The copy-template rule needs no change: this refusal adds no copy key.Full dogfood battery (narrowing arm): on head
bb8c665b, one locked command:pnpm --workspace-concurrency=2 --filter '@objectstack/dogfood^...' build && pnpm --filter @objectstack/dogfood test(noOS_TEST_SHARD, no path):Test Files 407 passed | 3 skipped (410),Tests 3359 passed | 28 skipped (3387), 0 failures; VERDICT command-exit 0 (33m58s held).Acceptance notes
authorable-surface: no key added or removed,check:authorable-surfaceexit 0.dropped-refinements.baseline.json: unchanged;ui/ActionButtonPropsalready lists the root site""(itssuperRefines), and the build accepted the new refine at that site with no ledger move. Generated reference pages:content/docs/references/ui/component.mdx(one row).api-surface/: unchanged (no new export). Lint consumers:validate-component-propsjudges the row throughComponentPropsMapand needed no change.action:icon(ActionIconPropsSchema) and theaction:group/action:menumember rows declareconfirmTextand an array-or-objectparamstoo, and their renderers forward both to the same runner (action-icon.tsxL155/L199,action-group.tsxL351/L380,action-menu.tsxL274/L312). At the lint door the pair passes on all three (0 findings, measured on this head). Ruling 6597621758 namesActionButtonPropsSchemaonly, so they are left for the seat.Verification
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackatbb8c665b: 102 derived families. Run serially: 89 exit 0 on the first pass; 13 answered exit 3 (PREREQUISITE NOT MET: adistolder than src, or the teardown self-test's pinned fixture outside the shallow clone), and after the closure build andgit fetch --unshallow origin mainall 13 reran exit 0.--ranreconciliation:102 derived famil(ies) accounted for — 102 run, 0 NOT-MEASURED.git merge-treeagainstorigin/mainis clean, so this head is not merged with it. CI's check-runs on this PR are the verdict for every family CI runs.Generated by Claude Code