Skip to content

feat(spec): InlineActionSchema picks description, so an inline action's param dialog can say what it does - #29369

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-1zb3tp
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-1zb3tp

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread
Fixes #29248

Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: none — no skills/** file and no hand-written content/docs page lists the inline action's key set; the only lists are the generated content/docs/references/ui/{action,component}.mdx tables, regenerated here.

维护者速读

  • 改了什么:页面上内联声明的动作(element:button 的 properties.action)现在可以写 description,即参数对话框标题下那行说明;同时 os validate / 发布门对它的检查与已注册动作一致。
  • 为什么改:您在 cloud#3281 裁定 A,先放宽上游契约。「邀请成员」对话框现在只显示通用提示,说不清邀请会发生什么;没有这个键,云端无从写这句话。
  • 风险与回滚:纯放宽,旧元数据全部照常通过;只多接受一个键,其余未选的键(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,预期全部通过,含「description says what the action does」一组 10 条。
  • 需要你做的:无,席位落地。

What changes

  • packages/spec/src/ui/action.zod.ts: InlineActionSchema's .pick({…}) gains description: true. It is ActionSchema's own field (the template slot with the inline locale-map arm), so the describe text, the parse and the refusals are shared, not restated. The derived InlineAction / InlineActionParsed types follow from the pick.
  • The schema's docblock records why: the element:button renderer's forward list (ui/packages/components/src/renderers/basic/elements.tsx) already passes action.description to 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 only confirmText, successMessage and errorMessage (its DECLARED_COPY_KEYS), so an inline locale map on description reaches the dialog unresolved until ui: the action text localizer resolves description, 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.
  • Same docblock, in place: the bodyExtra paragraph said the forward list "does not yet include bodyExtra". Measured false on main 27c1c555 (elements.tsx forwards bodyExtra: 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_KEYS gains description, and the two comments that said the inline pick has no description are corrected. Without this, an inline description with 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 pass objectstack validate, compile and 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.
  • Regenerated: content/docs/references/ui/action.mdx and content/docs/references/ui/component.mdx (one description row each in the InlineAction tables), packages/spec/authorable-surface/ui.json (ui/InlineAction:description).
  • .changeset/29248-inline-action-description.md: @objectstack/spec and @objectstack/lint, minor.
  • Not touched, per the card: ui/** (ui: the action text localizer resolves description, 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. cancelText and submitText are not picked (see Acceptance notes).

Tests

Forced edits (they pinned the old pick):

  • packages/spec/src/ui/inline-action.test.ts INLINE_FIELDS gains 'description' ("admits exactly the documented field set"), and the "admits only fields ActionSchema itself declares" body gains a description so 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 — description says what the action does" (10 cases): a plain string parses and is kept as the template envelope; a locale map is kept verbatim; no description still parses and adds none; a non-string value is refused inline at the same path with the same code as on ActionSchema; ElementButtonPropsSchema carries it to parse output; CONTROL: icon, variant, body, cancelText, submitText are each still unrecognized_keys, naming exactly that key.
  • inline-action-type.test.ts: two bodies typed through InlineAction carry description (locale map, plain string), compiled by the package typecheck and parsed by the door.
  • packages/lint/src/validate-action-copy-templates.test.ts: an inline properties.action description is judged like a registered one (a ${…} token, a ${…} inside a locale-map value at description.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

  • Premise, measured on main 27c1c555 with the spec dist built from it: an inline description was REFUSED, not stripped. InlineActionSchema.safeParse({ type: 'api', target: 'invite_user', description: 'Sends an e-mail invitation.' }) answered unrecognized_keys with keys: ["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).
  • The console's param dialog also draws submitText and cancelText (useConsoleActionRuntime.tsx). Neither is picked by InlineActionSchema nor forwarded by element: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.
  • The inline refusal of body prints "Did you mean body → mode?": the suggestion list is computed from ActionSchema's full shape, and mode is not on the inline pick either, so following it draws a second refusal. Loud, not silent; noted only.
  • ui/scripts/check-action-forward-parity.mjs reads the inline owed set from the built spec: element:button now owes 13 (was 12) and still forwards all of them, exit 0.

Verification

Final head 7b596699. The full suites ran at 89985489; the only commit after it moves one spec test into its own describe (test file only), so the three touched spec test files, the spec typecheck and all gates were re-run on 7b596699.

Spec, lint and ui consumers
  • pnpm --filter @objectstack/spec test at 89985489: Test Files 790 passed (790), Tests 23392 passed, 1 todo. At 7b596699: inline-action.test.ts, action-description.test.ts, inline-action-type.test.ts 3/3 files, 60/60 tests.
  • pnpm --filter @objectstack/spec typecheck exit 0 at 89985489 and at 7b596699 (it compiles inline-action-type.test.ts, so the two description bodies typed as InlineAction are judged by tsc).
  • pnpm --filter @objectstack/lint test at 89985489: Test Files 189 passed (189), Tests 7827 passed, 5 skipped. pnpm --filter @objectstack/lint typecheck exit 0.
  • ui consumers that parse or forward an inline action, run from 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 of PageBlockInspector.i18n could not resolve @objectstack/lint before 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.mjs exit 0: element:button owes 13, forwards 18 (was 12 on main); check-component-surface-parity.mjs report-only, exit 0.
  • packages/metadata*: no fixture pins the inline key set (git grep for InlineAction and 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 HEAD empty after each):

  • Drop 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 and element:button cases, the flipped inline pin, and both typed bodies' runtime half. The five CONTROL rows and "still parses without one" stay green, as they should.
  • Drop 'description' from lint's INLINE_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:generated at 89985489: 1 of 14 stale (check:docs), regenerated with --fix and committed; then 14/14 up to date. At 7b596699, after a rebuild: check:generated exit 0, no tracked file moved.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 103 derived, the same list at both heads. On 7b596699: 103 run, every exit 0, --ran reconciliation "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. On 89985489 the same 103 also all exited 0.
  • check-changeset-no-major --event on this body: exit 0, LEVEL AXIS reads yes (widening) + added, no package graded patch. check-partof-closing-keyword on this body: exit 0.
  • Local-only: check:skill-frame-freshness exit 0. check:dev-prereqs exit 1: 35 ui/ workspace packages have no dist/ in this container (only packages/** was built); the spec dist is fresh. A local state reading, not a finding about this diff.
  • Dogfood: not owed (no ! in the title, no (narrowing) arm).

Generated by Claude Code

…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>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:ui tests tooling labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @objectstack/spec, touching 4 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/error-catalog.mdx (via helpText (literal, a string literal in INLINE_KEYS))
  • content/docs/automation/flows.mdx (via helpText (literal, a string literal in INLINE_KEYS))
  • content/docs/deployment/troubleshooting.mdx (via ActionSchema (symbol, a top-level const))
  • content/docs/frontend/architecture-overview.mdx (via ActionSchema (symbol, a top-level const))
  • content/docs/frontend/troubleshooting.mdx (via ActionSchema (symbol, a top-level const))
  • content/docs/protocol/kernel/i18n-standard.mdx (via helpText (literal, a string literal in INLINE_KEYS))
  • content/docs/protocol/objectui/actions.mdx (via ActionSchema (symbol, a top-level const), helpText (literal, a string literal in INLINE_KEYS))
  • content/docs/protocol/objectui/layout-dsl.mdx (via helpText (literal, a string literal in INLINE_KEYS))
  • content/docs/ui/translations.mdx (via helpText (literal, a string literal in INLINE_KEYS))
  • content/docs/ui/views.mdx (via helpText (literal, a string literal in INLINE_KEYS))

⛔ 5 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-1.mdx (via ActionSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-3.mdx (via ActionSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-5.mdx (via helpText (literal, a string literal in INLINE_KEYS))
  • content/docs/releases/v17/index.mdx (via ActionSchema (symbol, a top-level const))
  • content/docs/releases/v18/18-2.mdx (via ActionSchema (symbol, a top-level const), helpText (literal, a string literal in INLINE_KEYS))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 101 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json b5e7ba55521394c6a0156fc26c26d336bed0f2bf → packageMentionDocs.

Which tree this was computed on

This run read content/docs from d955821c288e547441f7b1171c322cfe03a7e42c — the merge of head ab80f700cd15a49694ed96485b167b09c0598615 into base b5e7ba55521394c6a0156fc26c26d336bed0f2bf, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs b5e7ba55521394c6a0156fc26c26d336bed0f2bf → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

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 CONTRACT_REVIEW_TIER (triage ruling 6575141496 on #28741 returned contract review to the lane): a widening of a published schema (InlineActionSchema picks description).

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 7b59669918a5f84a79c14fbbc509d1e9e494004e
Local-runs: none
Scope: packages/spec/src/ui/action.zod.ts + packages/lint/src/validate-action-copy-templates.ts, packages/spec/src/ui/action-copy-template.ts, packages/spec/src/stack.zod.ts, ui/packages/components/src/renderers/basic/elements.tsx, ui/packages/app-shell/src/hooks/useConsoleActionRuntime.tsx, ui/packages/react/src/hooks/useActionTextLocalizer.ts, ui/packages/core/src/actions/ActionRunner.ts, ui/packages/i18n/src/useObjectLabel.ts

Inputs read: card #29248 (body, triage 6575512138, claim 6576402352, os-dev-report 6578161293), PR #29369 (body, 10-file list, net diff against merge base 27c1c555), the check-runs on the head. The rest of the diff (changeset, regenerated content/docs/references/ui/{action,component}.mdx, packages/spec/authorable-surface/ui.json, the three spec test files, the lint test file) is the lane seat's ACCEPT at TIER_DEFAULT; it was read to judge the hits file, not reviewed.

① Derived judgments

  • InlineActionSchema picks description: true — RIGHT. It is actionObject()'s own field (actionCopy('description'): the ADR-0032 template slot with the inline locale-map arm), so the describe text, the parse envelope (string → { dialect: 'template', source }), the blank arm and the shape refusals are shared, not restated. The strict object still refuses icon, variant, body, cancelText, submitText (CONTROL rows in inline-action.test.ts). Executes the card's ask and the cloud#3281 ruling-A sequencing (spec half first).
  • Public surface — InlineAction and InlineActionParsed gain the optional key by derivation; no new export, so api-surface/ui.json is correctly untouched. authorable-surface/ui.json gains ui/InlineAction:description (the only authorable-key artifact that moves; authorable-surface.base.json is the merge-base deletion anchor and correctly does not move; authorable-defaults has no row because the key has no default). RIGHT.
  • The pick follows the renderer, not ahead of it — RIGHT for the string arm, read in the live tree: elements.tsx forwards description: action.description to execute(…); ActionRunner.dialogCopyOf reduces an envelope to its text; useConsoleActionRuntime calls actionDescription(objForI18n, action?.name, action?.description), which with no name returns the authored text, then interpolate (dialogCopyFiller) fills {{ record.* }} holes; ActionParamDialog draws state.description under the title. The docblock's account matches the code.
  • The locale-map arm is a declared window — RIGHT as stated, with its cost named here: useActionTextLocalizer's DECLARED_COPY_KEYS is confirmText | successMessage | errorMessage, so an inline { en, "zh-CN" } on description is not collapsed; dialogCopyOf passes a non-envelope through unchanged; actionDescription(…, undefined, map) returns the map; fillTransitionalCopy regex-tests a non-string and hands it to the legacy evaluator (read, not run: the dialog shows a coerced object or its generic line, never the authored sentence). The same gap already exists for a registered action's locale-map description on main, so this PR introduces no new failure class; it widens the audience of an existing one and says so in the docblock, the changeset and the PR body. Card ui: the action text localizer resolves description, so an inline action's param dialog shows it in the user's language (cloud#3281 ruling A, half 2 of 2) #29249 (pm:blocked on this card) is the close.
  • Lint INLINE_KEYS gains description — RIGHT and necessary: judgeAction(…, INLINE_KEYS, true) now judges an inline description (string and each .locale of a map) with actionConfirmTemplateRoots (record, plus params only when static), closedScopeTemplateRefusal, validateExpression('template') and the blank check, each an error at … (element:button) · action description[.locale]. Without it the describe text that renders into the InlineAction reference table ("named and refused at objectstack validate, compile and the publish gate") would be declared and not enforced (Prime Directive 10). A rule and its gate, landed as one change.
  • Forward-parity owed set grows 12 → 13 for element:button — holds by reading: elements.tsx already forwards the key. Note the Lint UI check-run (which carries check:action-forward-parity) was SKIPPED on this head by its paths filter (no ui/** change), so this judgment rests on the read, not on a check-run; the dev's local exit 0 is their account.
  • Cross-reference walk (stack.zod.ts collectInlinePageActions) parses with InlineActionSchema and reads only type/name/target: unaffected. RIGHT that nothing else moves.
  • Regenerated references (action.mdx, component.mdx): one description row each in the InlineAction tables, text identical to ActionSchema's describe — consistent with a shared field.

② Semver level

  • .changeset/29248-inline-action-description.md: @objectstack/spec: minor, @objectstack/lint: minor. RIGHT. Metadata-protocol: added (a new authorable key) takes at least minor on every package the diff moves; lint moves (new refusals reach texts the schema refused on main, so no previously valid document is refused — not a narrowing).
  • Clause-②: yes (widening) — RIGHT: every body InlineActionSchema accepted on main still parses to the same output; the inline surface accepts strictly more. Both declaration lines are line-initial in the PR body and the changeset body; the Check Changeset check-run is success.
  • No skip-changeset, no BREAKING arm, no ADR-0087 disposition owed.

③ Boundary flags

Dev report 6578161293: open_questions: [], six deviations, two out-of-scope findings. Each answered:

  • File surface widened to packages/lint — accepted: the claim 6576402352 names the two lint files in its File surface and the reason; ① above confirms the rule/gate pairing. No escalation.
  • In-place bodyExtra docblock correction — measured true in the live tree (elements.tsx forwards bodyExtra: action.bodyExtra). Comment only. Answered.
  • Forced test edits (INLINE_FIELDS, the ActionSchema round-trip body, the flipped inline pin moved to its own describe) — each pinned the pick this card changes; the "does NOT widen" header now lists two things and points to inline-action.test.ts. Answered.
  • Final-head re-run narrowed to the three touched spec files + typecheck + 103 gates — the last commit is test-only; the head's own check-runs are the gate verdicts (pending, below). Answered.
  • Full ui/ suites left to CI — Test UI (16 shards) in progress at review time. Pending, not a flag.
  • No PR assignee/reviewer — standing order; no action.
  • Out-of-scope: submitText / cancelText not picked and not forwarded — nothing declared that is not read; acceptance-note placement is correct under Prime Directive 10. No card owed by this PR.
  • Out-of-scope: inline refusal of body suggests mode — pre-existing, loud; polish. No card owed by this PR.
  • Escalated by this review (not a dev flag): the stale refinement comment and the follow-up question in ④ should-fix 1.

④ Design judgment

packages/spec/src/ui/action.zod.ts, judged against the ruling it implements (cloud#3281 ruling A, half 1 of 2 per card #29248) and its first consumer (element:button → ActionRunner → the console's param-collection handler, read in the live ui/ tree):

  • The pick is the minimal, derived change the ruling asks for; the shared-field design means the registered and inline doors cannot drift (pinned: same path, same code on a bad shape). Attempts to break it: a non-string value is refused inline as on ActionSchema; confirmText + params on an inline action still parses (as on main; see should-fix 1); a ${…} or blank inline sentence parses (the slot's blank arm, as on a registered action) and is now refused at the doors by the lint half; a locale map parses and reaches the dialog unresolved (the declared window, ①). Nothing silent was found that the docblock does not state.
  • The docblock's "widen when a renderer widens, not before" rule is honoured for the string arm and consciously bent for the map arm with the direction stated ("spec accepts ahead of the renderer, never the reverse"), the same discipline the bodyExtra precedent set and the card's own ordering requires.

Must-fix: none.

Should-fix 1 (action.zod.ts, the confirmText-beside-params refinement comment, around the line "The same holds for {@link InlineActionSchema} … and which does not pick description, so the remedy below has no slot there to move into"): false on this head. The inline pick now carries description, so the remedy slot exists inline too. Correct the comment in place (comment only; the sibling stale bodyExtra paragraph in the same file was corrected by this PR, this one was missed). It also raises a design question this PR correctly does not answer: whether the inline shape should now carry the same two-dialogs refusal — that is a Clause-②: no (narrowing) on its own card, not a rider here.

Should-fix 2 (carried to #29249, not this diff): the ui half must add description to DECLARED_COPY_KEYS in useActionTextLocalizer.ts; until it lands an inline locale-map description reaches the dialog as a non-string (①). Cloud should author the string arm (or wait for #29249) when it adds description to INVITE_ACTION.

packages/lint/src/validate-action-copy-templates.ts (widened in): one array entry plus two corrected comments; the judge, roots and message builders are the registered ones. Paths and severities match the new test's expectations by reading. Holds.

Check-runs on the head at review time — success: Auto Label, Dogfood Regression Gate, Plan Test Core shards, Build UI Deps, Build Docs, Type Check · source gates, Lint · docs & code guards, Lint · scoped (query-options erasure, tooling self-tests, slot lookup & corpus), Lint · PM dispatch-gates, Lint · repo & tooling gates, Lint · CI/release/engine gates, PR-body guards, Governed Surface Queue Guard, Spec property liveness, Check Changeset, Flag docs affected, Check Documentation Links, Check PR Size. in_progress (PENDING, not judged): Test Core (8 shards), Test UI (16 shards), Type Check UI, Type Check · workspace, Type Check · consumer gates, Type Check · debt ledger, Build Core, Build & E2E UI, Live E2E UI, Lint · ESLint. skipped by filter: Lint UI (see ① on forward parity), Temporal Conformance, UI Published Artifacts, Dogfood Verify CLI, packed-tarball smoke, 18.x harness. No check-run has failed. The verdict below is a design verdict; landing still waits for the required set to conclude success.

Implemented-by: claude/project-thread-1zb3tp
Reviewed-by: session_01ADHkmSduNGC2SZXszyrNut

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>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ab80f700cd15a49694ed96485b167b09c0598615
Local-runs: none

This is a DELTA record for the push after the PASS record 6578506913 (head 7b596699). The delta 7b596699...ab80f700 touches one file, packages/spec/src/ui/action.zod.ts, and only the comment above the confirmText-beside-params refinement: it now says the inline pick carries description, so the remedy has a slot inline too, and that whether the inline door should refuse the pair is a separate narrowing question. That is Should-fix 1, done as asked. No code, schema, describe, generated reference or test moved, so every judgment in 6578506913 (①–③, semver minor/minor, Clause-②: yes (widening)) carries to this head unchanged. The narrowing question goes to its own finding card.

Implemented-by: claude/project-thread-1zb3tp
Reviewed-by: session_01ADHkmSduNGC2SZXszyrNut

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut

Head-sha: ab80f700cd15a49694ed96485b167b09c0598615 · read 2026-11-02T14:05Z

维护者速读

  • 改了什么:页面上内联声明的动作(element:button 的 properties.action)现在可以写 description,也就是参数对话框标题下的那行说明。os validate / 发布门对它的检查与已注册动作一致。
  • 为什么改:您在 cloud#3281 裁定 A,先放宽上游契约。「邀请成员」对话框现在只显示通用提示,说不清邀请会发生什么;没有这个键,云端没有地方写这句话。
  • 风险与回滚:纯放宽,旧元数据全部照常通过,只多接受一个键,其余未选的键(icon、variant、body、cancelText、submitText)仍被拒。已知缺口:控制台还没把内联的多语言 map 折成当前语言,等 UI 半张卡 ui: the action text localizer resolves description, so an inline action's param dialog shows it in the user's language (cloud#3281 ruling A, half 2 of 2) #29249。在那之前云端请写纯字符串。回滚就是 revert 本 PR。
  • 先看:packages/spec/src/ui/action.zod.ts(InlineActionSchema 的 pick 和上方注释),packages/lint/src/validate-action-copy-templates.ts(INLINE_KEYS)。
  • 两分钟验证:pnpm --filter @objectstack/spec exec vitest run src/ui/inline-action.test.ts,预期全部通过。
  • 需要你做的:无,席位落地。

席位意见:可落地。契约复审在 7b59669 判 PASS(6578506913),在现 head 的 delta 判 PASS(6578630622),CI 全绿。

席位核对 — ACCEPT · domain:spec#2 · head ab80f700

  1. Shape: draft, base main, Fixes #29248. Clause-②: yes (widening) and Metadata-protocol: added sit at line start in the body and the changeset (@objectstack/spec minor, @objectstack/lint minor). There are 10 files, all inside the claim's surface as amended in 6576402352 (the lint file was added by the seat). No governed path. +208/−32.
  2. Premise measured: on main, InlineActionSchema's pick has no description, so a strict inline action carrying it is refused. The new inline-action.test.ts group parses it on the head, and its CONTROL rows still refuse icon, variant, body, cancelText and submitText. The renderer path (elements.tsx → ActionRunner → useConsoleActionRuntime → ActionParamDialog) already forwards and draws the key; the review read this in the live tree.
  3. Gates: ci-failure.mjs --sha ab80f700… GREEN, 65 check-runs, none failed, none superseded. Skipped by paths filter: Lint UI, Dogfood Verify CLI, UI Published Artifacts, Temporal Conformance, Packed-tarball smoke, 18.x harness, schedule gate. check:action-forward-parity (inside Lint UI) did not run because no ui/** file changed; the review judged forward parity by reading the code.
  4. Accepted deviations: the forced test edits in inline-action.test.ts (INLINE_FIELDS) and action-description.test.ts (the inline-refusal pin this card removes) pinned the old pick and are named in the body. The should-fix-1 comment correction (ab80f700) is comment-only, as verified in the delta record.
  5. Out of scope: the locale-map arm needs DECLARED_COPY_KEYS in useActionTextLocalizer.ts, carried on ui: the action text localizer resolves description, so an inline action's param dialog shows it in the user's language (cloud#3281 ruling A, half 2 of 2) #29249 (note 6578561587). Whether the inline door should refuse confirmText beside params is a narrowing question, filed as spec(ui): an inline action accepts confirmText beside params, the pair ActionSchema refuses as two dialogs; once #29369 gives the inline pick description, decide whether the inline door refuses it too #29383. Skills-docs: none is right: only generated reference tables list the inline key set, and they are regenerated here.
  6. Landing: not Tier H and under 3000 lines, so the seat lands it with pr_ready + auto-merge. After merge, the seat writes a landing note on spec: InlineActionSchema picks description, so an inline action's param dialog can say what the action does (cloud#3281 ruling A, half 1 of 2) #29248, removes pm:dispatched, and lifts ui: the action text localizer resolves description, so an inline action's param dialog shows it in the user's language (cloud#3281 ruling A, half 2 of 2) #29249's Blocked-by.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spec: InlineActionSchema picks description, so an inline action's param dialog can say what the action does (cloud#3281 ruling A, half 1 of 2)

1 participant