Repository navigation
fix(spec)!: an inline action refuses confirmText beside a non-empty params, as a registered action does - #29829
Conversation
… params, as ActionSchema does An inline page action runs through the registered action's runner, so the pair opens a confirm dialog and then the param dialog for one decision. Both doors now share one predicate and one refusal; the remedy is the inline pick's description. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfHVbsfKWji5VLrLbG
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfHVbsfKWji5VLrLbG
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfHVbsfKWji5VLrLbG
📓 Docs Drift CheckThis PR changes 1 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 5 release-owned page(s) also name something this change touched. These are read-only:
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 d8d242457431d817d58b445fbe0e63cc2f1f17cd && git checkout d8d242457431d817d58b445fbe0e63cc2f1f17cd
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b0ef25f524b870433424b8ad797ef3801374f745 656657595b803b3c9ee684e5ece3b8a42addc4f9 && git checkout -B drift-repro b0ef25f524b870433424b8ad797ef3801374f745 && git merge --no-ff 656657595b803b3c9ee684e5ece3b8a42addc4f9
node scripts/docs-audit/affected-docs.mjs --json b0ef25f524b870433424b8ad797ef3801374f745
|
|
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K Contract-review-claim: session_01WGufzfgvGexKn9vLHsy89K · head 6566575 · 2026-11-03T06:34Z |
|
domain:spec#3 · os-support-ai · session_01WGufzfgvGexKn9vLHsy89K Contract reviewServed-tier: Inputs read: card #29383 body and all nine comments (triage 6580249013, claim 6580616231, measurement report 6580676225, decision card 6580700185, hand-off 6589420791, ruling 6595808462 letter A, seat claims 6595853086 and 6595887076, os-dev-report 6597115604); PR #29829 body, its seven-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 The third door found by contract review 6597260383 ( |
domain:spec#3 · os-support-ai · session_01WGufzfHVbsfKWji5VLrLbG
Requested by Yibo Wang · project thread
Fixes #29383
Ruling-ref: 6595808462
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — no
skills/**file teaches an inline action carryingconfirmTextbesideparams. 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; the generatedcontent/docs/references/**tables that render theconfirmTextdescribe are regenerated here.BREAKING accept-set narrowing at
InlineActionSchema(@objectstack/spec), served where a page'selement:buttonproperties.actionis judged: the component-props gate (component-props-invalid,@objectstack/lint) at every CLI command and at the runtimepagesave door.维护者速读
element:button的properties.action)同时写confirmText和非空params时,现在和已注册动作一样被拒收,提示把这句话挪到description、删掉confirmText。description已能写在内联动作上,作者有地方挪这句话。params或params: []的confirmText照常通过,批量动作不受影响。已存页面照常加载,下次保存才被拒。回滚即 revert 本 PR。packages/spec/src/ui/action.zod.ts(新的共用判定declaresConfirmBesideParams与CONFIRM_BESIDE_PARAMS_REFUSAL,InlineActionSchema链上新增的那条.refine),packages/spec/src/ui/action-confirm-params-guard.test.ts末尾新的 describe 组,.changeset/29383-inline-action-confirm-beside-params-refused.md。pnpm --filter @objectstack/spec exec vitest run src/ui/action-confirm-params-guard.test.ts,预期 16 个测试全过,其中内联组 5 条(2 条拒收、3 条对照)。What changes
packages/spec/src/ui/action.zod.tslifts theActionSchemapair rule out of its refine body into a module-level predicate,declaresConfirmBesideParams(data)(confirmText != nullandparamsa non-empty array, unchanged), and a module-level refusal,CONFIRM_BESIDE_PARAMS_REFUSAL(the existing message, byte for byte, at path['confirmText']).ActionSchema's refinement now readsreturn !declaresConfirmBesideParams(data)with that refusal;InlineActionSchemagains one.refinewith the same two, between its target-required rule and its url-target rule. No second copy of either.InlineActionSchemacarries the same refusal on its own chain, by ruling record 6595808462 letter A, and why (same runner, so the same two dialogs; its pick carriesdescription, so the remedy has a slot).confirmTextdescribe dropped its "On a registered action," qualifier ("pairing this with a non-emptyparamsis refused …"), since both doors that render this describe now refuse the pair; a view'sbulkActionDefsdeclares its ownconfirmText. The NB comment above the field says so.content/docs/references/{data/object,kernel/metadata-plugin,ui/action,ui/component}.mdxare regenerated from it (check:generated --fix, which proved onlycheck:docsstale)..changeset/29383-inline-action-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 at the inline door, on the census case
The census case is the one inline carrier the census found, the pin in
action-confirm-params-guard.test.ts:origin/mainb3d14150):success: true, pinned by the test "InlineActionSchema is out of reach too — it picks fields, not this refine chain".success: false, one issue at['confirmText'],code: 'custom', and its message equalsActionSchema's own issue for the registered pair (asserted withtoBe). Withdescriptionin place ofconfirmText, with noparams, and withparams: [], the same action parses.validateComponentProps,@objectstack/lintsrc against this head's specdist), on a page whoseelement:buttoncarries{ type: 'api', target: '/api/v1/approvals/reject', params: [reason], confirmText: 'Reject this request?' }: one finding,component-props-invalid, severityerror, atpages[0].regions[0].components[0].properties.action.confirmText, message opening "action.confirmText: An action that declaresconfirmTextbeside a non-emptyparamsshows the user TWO dialogs for one decision". The remedy body (description, noconfirmText), the param-less body and theparams: []body: no finding.defineStackdoes not judge it, before or after: a component'spropertiesis an open bag that the stack parse does not dispatch (measured: an inlinecancelText, an inline url action with notarget, and the pair all passdefineStackon this head). The changeset names the props gate as the door, notdefineStack.ADR-0087 disposition
The changeset body carries the ADR-0087 marker comment with the disposition
adr-0087: not-required (no-migration-prescription), the form the repo's narrowing changesets use (223 of the.changeset/*.mdmarkers onorigin/mainarenot-required (no-migration-prescription); the closest precedents are26413-now-anchor-extra-key-refused.mdand26462-diff-missing-version-refused.md). Nodocs/adr/**path is touched. The reasoning: 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, re-run at the base
At
origin/mainb3d14150(report 6580676225 counted ata6441636): every file underexamples/,packages/**,ui/**,skills/**,templates,apps/carryingconfirmText(excludingCHANGELOG.md), eachconfirmTextline read with a ±25-line window that holds an inline marker (element:button,InlineAction,properties:,action: {) and a non-emptyparams: [. 16 windows in 5 files, each read by hand:packages/spec/src/ui/action-confirm-params-guard.test.ts— the inline pin above (the one carrier; flipped here) andActionSchema/BulkActionDefSchemafixtures.packages/lint/src/validate-action-copy-templates.test.tsL111–137 — registered actions andaction:buttonrows, not inline actions.packages/spec/src/ui/action-cancel-text.test.tsL29,action-copy-template.test.tsL158,action-row-update.test.tsL291–295 — registered actions, a roots helper, and a registered action.The files changed between
a6441636andb3d14150that carry both keys are registered-action metadata (platform-objectsidentity objects,plugin-approvals), generated translation bundles, and ui/spec tests none of which parse an inline action with the pair. Cloud fixtures are not in this repository and were not read. Disposition unchanged: 0 shipped carriers, 1 spec test pin.Tests
action-confirm-params-guard.test.ts: the "out of reach" inline pin is removed from the boundary block (renamed "the pair guard is scoped by SCHEMA BOUNDARY — the bulk def keeps the pair") and replaced by a new block, "confirmText+ non-emptyparamsis refused on InlineActionSchema too": the refusal (path,code, message equal toActionSchema's), the locale-map form refused, and three accept controls:descriptionwith noconfirmText,confirmTextwith noparams,confirmTextwithparams: []. The file header names the inline door.Readings (head `65665759`, under os-verify-lock)
vitest run src/ui/action-confirm-params-guard.test.ts src/ui/inline-action.test.ts src/ui/action-cancel-text.test.ts src/ui/action-row-update.test.ts src/ui/component.test.ts:Test Files 5 passed (5),Tests 459 passed (459).pnpm --filter @objectstack/spec test && pnpm --filter @objectstack/spec typecheck:Test Files 804 passed (804),Tests 23884 passed | 1 todo (23885); VERDICT command-exit 0.scripts/ablation-replace.mjs(anchor hit 1 → 0, blobc1a9cf11→2b9084b3): the inline refine's predicate replaced bytrue || …;action-confirm-params-guard.test.ts→Tests 2 failed | 14 passed (16)(the two refusal tests); restored, blob equals HEAD,git diff HEADempty. Nodistin that path (the test imports./action.zod).pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 src/validate-action-copy-templates.test.ts src/validate-component-props.test.ts src/validate-component-props-url-target.test.ts src/validate-component-props-row-on-success.test.ts(after the dogfood closure build):Test Files 4 passed (4),Tests 207 passed (207); VERDICT command-exit 0. The copy-template rule (validate-action-copy-templates, itsINLINE_KEYS) needs no change: it lists copy keys, and this refusal adds none.node ui/scripts/check-action-forward-parity.mjs: exit 0 (element:button owes 13, forwards 18; no spec key moved, so nothing new is owed).Full dogfood battery (narrowing arm), on head
65665759, one locked command:pnpm --workspace-concurrency=2 --filter '@objectstack/dogfood^...' build && pnpm --filter @objectstack/dogfood test(noOS_TEST_SHARD, no path):Test Files 404 passed | 3 skipped (407),Tests 3326 passed | 28 skipped (3354), 0 failures; VERDICT command-exit 0 (44m19s held).Acceptance notes
content/docs/protocol/objectui/actions.mdxL444 (hand-written) still says "On a registered action, pairing it with a non-emptyparamsis refused". It stays true but no longer names the inline door; outside this claim's file surface, so left for the docs owner.skills/objectstack-ui/rules/actions.mdL22 has the same "a registered action" phrasing. True, and it names the remedy; a one-word widening ("A registered or inline action") would be askills/**(Tier H, token-ratcheted) edit, kept out of this Tier S PR.INLINE_KEYS(validate-action-copy-templates.ts) lists copy keys, not pair rules, so it does not move.Verification
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 102 families on this tree (7 paths vs merge baseb3d14150).102 derived gates: 102 exit 0
65665759: 100 exit 0, 2 exit 3 (built-output prerequisites: speccheck:skill-examplesneeds the client SDKdist,check:dual-build-cjs-loadsneeds every package'sdist).check-plugin-teardown-shape --self-testneeded its pinned fixture commit on the shallow clone (git fetch origin 621a4876); fetched before the pass, exit 0.pnpm turbo run build --concurrency=2(Tasks: 118 successful, 118 total, VERDICT command-exit 0), the 2 were rerun: both exit 0 (283 prose examples type-check across 3 surface(s); the CJS load probe at floors 90/58/520/1).check:generated,check:authorable-surface,check:api-surface,check:docs,check:liveness: exit 0. No authorable-surface, api-surface, liveness or dropped-refinements row moves: the refusal adds no key and no export, andInlineActionSchemaalready carried a dropped refinement at every position it is reached from.dispatch-gates --ran: "102 derived famil(ies) accounted for — 102 run, 0 NOT-MEASURED".origin/maingained 19 commits after the base (b0ef25f5at open); none touchesaction.zod.tsor the guard test, and two regenerate other lines ofcontent/docs/references/data/object.mdx. A localmerge-treeof head andorigin/mainis clean; not merged here, so the dogfood reading stays pinned to this head.check-changeset-no-majorwith this body as the event: exit 0 ("LEVEL AXIS: this PR declares clause-②no (narrowing)and metadata-protocolnarrowed, and no package whose published source … it moves is gradedpatch").check-closing-keyword-parity: the only closing target isFixes #29383.CI families are read from this PR's check-runs.
Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzfHVbsfKWji5VLrLbG