Repository navigation
feat(spec): InlineActionSchema picks description, so an inline action's param dialog can say what it does - #29369
Conversation
…on's param dialog can say what it does An inline action (`element:button`'s `properties.action`) was refused `description` as an unknown key, although the button's renderer already forwards it to the runner and the console's param dialog draws it under its title. The pick now takes ActionSchema's own field (the `template` slot with the inline locale-map arm). The docblock records the one remaining window: the inline copy localizer does not yet collapse a locale-map `description`, so it reaches the dialog unresolved until the renderer half lands. It also corrects the `bodyExtra` paragraph, whose forward-list gap has closed. Claude-Session: https://claude.ai/code/session_01ADHkmT9dnPvF2SeYafuvmH Co-authored-by: Claude <noreply@anthropic.com>
…t it is
The action-copy pass judged an inline `properties.action` on
`confirmText`, `successMessage` and `params[].helpText` only, because
`InlineActionSchema` did not pick `description`. Now that it does, an
inline sentence with a `${…}` token, a hole over an unbound root or a
blank text would publish from a page while the same text on a registered
action is refused at `objectstack validate`, `compile` and the publish
gate. INLINE_KEYS gains `description`; the inline pass already takes the
locale map.
Claude-Session: https://claude.ai/code/session_01ADHkmT9dnPvF2SeYafuvmH
Co-authored-by: Claude <noreply@anthropic.com>
…rface; changeset Claude-Session: https://claude.ai/code/session_01ADHkmT9dnPvF2SeYafuvmH Co-authored-by: Claude <noreply@anthropic.com>
…ot the does-not-widen block Claude-Session: https://claude.ai/code/session_01ADHkmT9dnPvF2SeYafuvmH Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 10 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 d955821c288e547441f7b1171c322cfe03a7e42c && git checkout d955821c288e547441f7b1171c322cfe03a7e42c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b5e7ba55521394c6a0156fc26c26d336bed0f2bf ab80f700cd15a49694ed96485b167b09c0598615 && git checkout -B drift-repro b5e7ba55521394c6a0156fc26c26d336bed0f2bf && git merge --no-ff ab80f700cd15a49694ed96485b167b09c0598615
node scripts/docs-audit/affected-docs.mjs --json b5e7ba55521394c6a0156fc26c26d336bed0f2bf
|
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract-review-claim: session_01ADHkmSduNGC2SZXszyrNut · head 7b59669 · 2026-11-02T13:24Z The lane seat reviews this head itself at |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract reviewServed-tier: Inputs read: card #29248 (body, triage 6575512138, claim 6576402352, os-dev-report 6578161293), PR #29369 (body, 10-file list, net diff against merge base ① Derived judgments
② Semver level
③ Boundary flagsDev report 6578161293:
④ Design judgment
Must-fix: none. Should-fix 1 ( Should-fix 2 (carried to #29249, not this diff): the ui half must add
Check-runs on the head at review time — Implemented-by: VERDICT: PASS |
…ine pick lacks `description` Comment only. The inline pick carries `description` on this branch, so the remedy the refusal prescribes has a slot on an inline action too; whether the inline door should also refuse the pair stays a separate question, and no behaviour changes. Claude-Session: https://claude.ai/code/session_01ADHkmT9dnPvF2SeYafuvmH Co-authored-by: Claude <noreply@anthropic.com>
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Contract reviewServed-tier: This is a DELTA record for the push after the PASS record 6578506913 (head Implemented-by: VERDICT: PASS |
|
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut Head-sha: 维护者速读
席位意见:可落地。契约复审在 7b59669 判 PASS(6578506913),在现 head 的 delta 判 PASS(6578630622),CI 全绿。 席位核对 — ACCEPT ·
|
Requested by Steve · project thread
Fixes #29248
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: none — no
skills/**file and no hand-writtencontent/docspage lists the inline action's key set; the only lists are the generatedcontent/docs/references/ui/{action,component}.mdxtables, regenerated here.维护者速读
element:button的properties.action)现在可以写description,即参数对话框标题下那行说明;同时os validate/ 发布门对它的检查与已注册动作一致。icon、variant、body、cancelText、submitText)仍被拒。已知缺口:控制台尚未把内联的多语言 map 折成当前语言,等 UI 半张卡落地。回滚即 revert 本 PR。packages/spec/src/ui/action.zod.ts(InlineActionSchema的 pick 与上方注释)、packages/lint/src/validate-action-copy-templates.ts(INLINE_KEYS)、packages/spec/src/ui/inline-action.test.ts(新用例与对照组)。pnpm --filter @objectstack/spec exec vitest run src/ui/inline-action.test.ts,预期全部通过,含「descriptionsays what the action does」一组 10 条。What changes
packages/spec/src/ui/action.zod.ts:InlineActionSchema's.pick({…})gainsdescription: true. It isActionSchema's own field (thetemplateslot with the inline locale-map arm), so the describe text, the parse and the refusals are shared, not restated. The derivedInlineAction/InlineActionParsedtypes follow from the pick.element:buttonrenderer's forward list (ui/packages/components/src/renderers/basic/elements.tsx) already passesaction.descriptionto the runner, and the console's param-collection handler (ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx) draws it under the dialog title. It also states the one window that remains: the inline copy localizer (ui/packages/react/src/hooks/useActionTextLocalizer.ts) collapses onlyconfirmText,successMessageanderrorMessage(itsDECLARED_COPY_KEYS), so an inline locale map ondescriptionreaches the dialog unresolved until ui: the action text localizer resolvesdescription, so an inline action's param dialog shows it in the user's language (cloud#3281 ruling A, half 2 of 2) #29249 lands. Spec accepts ahead of the renderer, never the reverse.bodyExtraparagraph said the forward list "does not yet includebodyExtra". Measured false on main27c1c555(elements.tsxforwardsbodyExtra: action.bodyExtra), so it now says that window closed. Comment only.packages/lint/src/validate-action-copy-templates.ts(own commit; the seat added it to the claim's file surface):INLINE_KEYSgainsdescription, and the two comments that said the inline pick has nodescriptionare corrected. Without this, an inlinedescriptionwith a${…}token, a hole over an unbound root or a blank text parses (the slot keeps its blank arm, as on a registered action) and would passobjectstack validate,compileand the publish gate, while the same text on a registered action is an error there. The describe text that renders into the InlineAction reference table says those doors name it, so leaving lint alone would ship declared ≠ enforced.content/docs/references/ui/action.mdxandcontent/docs/references/ui/component.mdx(onedescriptionrow each in the InlineAction tables),packages/spec/authorable-surface/ui.json(ui/InlineAction:description)..changeset/29248-inline-action-description.md:@objectstack/specand@objectstack/lint, minor.ui/**(ui: the action text localizer resolvesdescription, so an inline action's param dialog shows it in the user's language (cloud#3281 ruling A, half 2 of 2) #29249) and cloud.cancelTextandsubmitTextare not picked (see Acceptance notes).Tests
Forced edits (they pinned the old pick):
packages/spec/src/ui/inline-action.test.tsINLINE_FIELDSgains'description'("admits exactly the documented field set"), and the "admits only fields ActionSchema itself declares" body gains adescriptionso the round-trip covers it.packages/spec/src/ui/action-description.test.ts"an INLINE action still refuses it" pinned the refusal this card removes. It now asserts the inline parse keeps the description as its template envelope; the file header no longer lists "widen the INLINE surface" among what the key's change must not do.New:
inline-action.test.ts"InlineActionSchema —descriptionsays what the action does" (10 cases): a plain string parses and is kept as thetemplateenvelope; a locale map is kept verbatim; nodescriptionstill parses and adds none; a non-string value is refused inline at the same path with the same code as onActionSchema;ElementButtonPropsSchemacarries it to parse output; CONTROL:icon,variant,body,cancelText,submitTextare each stillunrecognized_keys, naming exactly that key.inline-action-type.test.ts: two bodies typed throughInlineActioncarrydescription(locale map, plain string), compiled by the package typecheck and parsed by the door.packages/lint/src/validate-action-copy-templates.test.ts: an inlineproperties.actiondescriptionis judged like a registered one (a${…}token, a${…}inside a locale-map value atdescription.zh-CN, a blank sentence and a{{ result.* }}hole are each one error; a bound{{ record.* }}hole and a clean locale map draw nothing); and the page publish gate answers an inline${…}description with one error and no advisory.Acceptance notes
27c1c555with the spec dist built from it: an inlinedescriptionwas REFUSED, not stripped.InlineActionSchema.safeParse({ type: 'api', target: 'invite_user', description: 'Sends an e-mail invitation.' })answeredunrecognized_keyswithkeys: ["description"], for the plain string and the locale map alike. So nothing an author wrote was silently lost; a page that declared it failed validation. On the head the same calls parse and keep it (string →{ dialect: 'template', source }, map verbatim).submitTextandcancelText(useConsoleActionRuntime.tsx). Neither is picked byInlineActionSchemanor forwarded byelement:button, so an inline action cannot set the dialog's button labels. Nothing is declared that is not read, so this is no defect; noted for whoever widens the inline dialog copy next.bodyprints "Did you meanbody→mode?": the suggestion list is computed fromActionSchema's full shape, andmodeis not on the inline pick either, so following it draws a second refusal. Loud, not silent; noted only.ui/scripts/check-action-forward-parity.mjsreads the inline owed set from the built spec:element:buttonnow owes 13 (was 12) and still forwards all of them, exit 0.Verification
Final head
7b596699. The full suites ran at89985489; the only commit after it moves one spec test into its owndescribe(test file only), so the three touched spec test files, the spec typecheck and all gates were re-run on7b596699.Spec, lint and ui consumers
pnpm --filter @objectstack/spec testat89985489: Test Files 790 passed (790), Tests 23392 passed, 1 todo. At7b596699:inline-action.test.ts,action-description.test.ts,inline-action-type.test.ts3/3 files, 60/60 tests.pnpm --filter @objectstack/spec typecheckexit 0 at89985489and at7b596699(it compilesinline-action-type.test.ts, so the twodescriptionbodies typed asInlineActionare judged by tsc).pnpm --filter @objectstack/lint testat89985489: Test Files 189 passed (189), Tests 7827 passed, 5 skipped.pnpm --filter @objectstack/lint typecheckexit 0.ui/against the rebuilt spec dist:elementButtonActionMembers-8071,action-bodyShape-forward,action-onSuccess-forward,action-outcomeMessages-forward-11344,action-undeclared-keys-8648,block-config,block-config-i18n,PageBlockInspector.i18n: 8/8 files, 126 tests passed. (The first run ofPageBlockInspector.i18ncould not resolve@objectstack/lintbefore lint's own dist was built; re-run after the build, 22/22.) The full ui suites are left to CI's Test UI.node ui/scripts/check-action-forward-parity.mjsexit 0:element:button owes 13, forwards 18(was 12 on main);check-component-surface-parity.mjsreport-only, exit 0.packages/metadata*: no fixture pins the inline key set (git grepforInlineActionand the pick's key list outside spec, lint and ui finds none), so nothing to run there.Reverse verification
Both through
scripts/ablation-replace.mjs(anchor hit 1 → 0, blob restored to HEAD,git diff HEADempty after each):description: true,from the pick →inline-action.test.ts,action-description.test.ts,inline-action-type.test.ts: 8 failed, 52 passed. Red: the field-set pin, the plain-string, locale-map, same-refusal andelement:buttoncases, the flipped inline pin, and both typed bodies' runtime half. The five CONTROL rows and "still parses without one" stay green, as they should.'description'from lint'sINLINE_KEYS→validate-action-copy-templates.test.ts: 2 failed, 23 passed (exactly the two new inline-description cases).Gates and generated artifacts
pnpm --filter @objectstack/spec build+check:generatedat89985489: 1 of 14 stale (check:docs), regenerated with--fixand committed; then 14/14 up to date. At7b596699, after a rebuild:check:generatedexit 0, no tracked file moved.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 103 derived, the same list at both heads. On7b596699: 103 run, every exit 0,--ranreconciliation "103 run, 0 NOT-MEASURED (a DERIVED zero)". Seven dist-reading spec gates first answered exit 3 (PREREQUISITE NOT MET: the dist predated the moved test file) and were re-run after the spec rebuild, exit 0 each. On89985489the same 103 also all exited 0.check-changeset-no-major --eventon this body: exit 0, LEVEL AXIS readsyes (widening)+added, no package gradedpatch.check-partof-closing-keywordon this body: exit 0.check:skill-frame-freshnessexit 0.check:dev-prereqsexit 1: 35ui/workspace packages have nodist/in this container (onlypackages/**was built); the spec dist is fresh. A local state reading, not a finding about this diff.!in the title, no(narrowing)arm).Generated by Claude Code