Repository navigation
feat(spec,lint): an action's confirmText, successMessage and errorMessage are template slots over the roots the console runner binds - #25637
Conversation
Claude-Session: https://claude.ai/code/session_01DcqhBfbrsH8ACDsMQ62Hzq Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcqhBfbrsH8ACDsMQ62Hzq Co-authored-by: Claude <noreply@anthropic.com>
…mplate slots; convert the platform's ${result.*} sentences
Claude-Session: https://claude.ai/code/session_01DcqhBfbrsH8ACDsMQ62Hzq
Co-authored-by: Claude <noreply@anthropic.com>
…ing .zod.ts files; classify them in the expression-surface ledger Claude-Session: https://claude.ai/code/session_01DcqhBfbrsH8ACDsMQ62Hzq Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DcqhBfbrsH8ACDsMQ62Hzq Co-authored-by: Claude <noreply@anthropic.com>
…tion-copy-template-slots
Claude-Session: https://claude.ai/code/session_01DcqhBfbrsH8ACDsMQ62Hzq Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 5 package(s): 30 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 151 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 a3f6527f068c586b3c6b716ab1ea0c5004442207 && git checkout a3f6527f068c586b3c6b716ab1ea0c5004442207
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e8de8a7ece08da5875cb50186eed3b6509a367c8 e875a56445ed6ccec00d46a7edaf8d37baaa2529 && git checkout -B drift-repro e8de8a7ece08da5875cb50186eed3b6509a367c8 && git merge --no-ff e875a56445ed6ccec00d46a7edaf8d37baaa2529
node scripts/docs-audit/affected-docs.mjs --json e8de8a7ece08da5875cb50186eed3b6509a367c8
|
席位核对ACCEPT · #24814 ·
Generated by Claude Code |
|
Contract review absent: tier quota wall · The Generated by Claude Code |
|
Claim: cross-account contract review of this PR at head ⛔ Not a claim on the card and no label change now. Under the cross-account review patrol ( Generated by Claude Code |
Contract reviewServed-tier: Cross-account review: implemented under ① Derived judgmentsEvery accept-set and public-surface change the diff implies, each judged:
② Semver level
③ Boundary flagsPR author Check-runs on Dev flags (os-dev-report
Open questions (
Out-of-scope findings, carried as noted: Studio's inspector and preview read the three keys as Landing notes for the owning seat: PR #25626 also edits Review mechanics: the shared checkout's Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #24814
Clause-②: yes (widening)
Metadata-protocol: semantic
Ruling-ref: 6365736632
An action's person-facing copy (
confirmText,successMessage,errorMessage) is now an ADR-0032templateslot over the roots the console's action runner binds. This is ruling A's second half: the console moved first (objectui #13409, in the pinned.objectui-sha2958f17e9c6a), and this PR types the action-copy family. It uses the #24947 form: the blank arm is kept, door findings are warnings, and the judge prescribes{{ }}for a${…}token. The refusal half is its own BREAKING card, to be filed once this lands. Nothing here refuses text that parses today.What changes
action:button,action:icon, theaction:group/action:menumember, ×confirmText/successMessage/errorMessage,ui/component.zod.ts) andActionSchema.confirmText/successMessage(ui/action.zod.ts, whichInlineActionSchemapicks) aretemplateslots. A bare string is shorthand for{ dialect: 'template', source }. The envelope is accepted, and a blank string still parses.ActionSchemakeeps its inline locale map arm.ActionRunnerat2958f17e9c6a,confirmMessage/composeSuccessMessage/handlePostExecution), and declared in@objectstack/spec/ui:confirmText:record, plusparamsonly beside an object of static values (actionConfirmTemplateRoots). The runner bindsparamstoaction.paramsminus_rowRecord. An input list binds nothing, because its values are collected after the confirmation.successMessage:result(the handler's payload) andrecord(ACTION_SUCCESS_TEMPLATE_ROOTS).errorMessage: none (ACTION_ERROR_TEMPLATE_ROOTS). The runner shows an authorederrorMessageas written. Re-measured at the pin: it fills no hole, as the earlier report said.automation/flow-text-slot-template.tsclosedScopeTemplateRefusal) reads a${…}token with its$.Delete ${record.name}?is prescribedDelete {{ record.name }}?, no longerDelete ${{ record.name }}?. A zero-root scope names every hole and token in its text as literal text. The flow text slots' judge (textSlotTemplateRefusal) is unchanged, and the bulk sentence keeps its single-brace wording (pinned).packages/lint/src/validate-action-copy-templates.tsactionCopyTemplateIssues, runs insidevalidateStackExpressions. That isobjectstack validate,compileand the publish gate. It covers registered actions (top-level, and an object's own, deduplicated), the page action rows and a page component's inlineproperties.action, and judges each locale of an inline map on its own.pagejoins the rule'sruntimeTypes, and on a page write this pass runs alone. Every finding is awarning, so it shows as an advisory at the gate and never as a 422.invite_usersentences the ruling names, and the 9sys_approval_requestdecision toasts (see Deviations). They move from${result.*}to{{ result.* }}in the source and in every shipped locale bundle. The en bundles were regenerated withcheck-i18n-bundles --write.ActionLike.confirmText/successMessageinsystem/i18n-resolver.tswiden to the envelope.resolveActionConfirm/resolveActionSuccessanswer the authored value as written when no translation applies.os i18n extractseeds an envelope like the string (actionCopy, throughtemplateSlotSource).packages/qa/dogfood/test/expression-conformance.test.ts) scans only*.zod.tsfiles. So the template input is built at each declaring.zod.tsfile, and all 11 positions are discovered and classified:template-action-confirm-text,template-action-success-message,template-action-error-message. A first cut built them in a plain.tsmodule, and the census never saw them. That is why the construction lives where it does.Measured
.objectui-sha2958f17e9c6a, objectui8e90779dcbmerged in):actionCopySource/fillTemplateHoles(packages/core/src/actions/actionCopyTemplate.ts) read the envelope for all three keys.useActionTextLocalizerhands the envelope on, andflowSuccessCopyreads it.confirmText/successMessage/errorMessageinexamples/**+packages/**, tests and generated bundles excluded): ate649c2d91e, 13 carried${…}(4 platform-objects, 9 plugin-approvals) and 0 carried{{. On this branch, 0 carry${…}and 13 carry{{. Inexamples/**, 0 either way.OBJECTUI_ROOT=../objectui bash scripts/build-console.sh, with objectui at2958f17e9c6aand this branch's spec built (pre-merge head3c1eb1e0). The console'stsc && vite buildand app-shell'stsccompleted. Turbo: 36/36 then 34/34 tasks successful. The canaries passed, including "✓ Console bundle carries THIS tree's @objectstack/spec, and only it". The merge brought no spec type change this check reads (only spec(ui): record:detailshideEmpty: false— in read-only mode an all-empty section renders its heading plus one fold row; describe() says so #25612'shideEmptydescribe).Tests
packages/spec: newsrc/ui/action-copy-template.test.ts, plus new cases insrc/automation/flow-text-slot-template.test.ts(${…}, zero-root, bulk control). Re-pinned:ui/action.test.ts,ui/component-report-items-action-members-typed.pin.test.ts(the copy keys written as envelopes stay byte-identical, and the bare-string normalisation is pinned), andui/component-props-unknown-members.pin.test.ts(the envelopes'ast). Full suite before the restructure: 789 of 793 files passed, 3 failed, 1 skipped (the 3 were the fixtures above plus theexport-originsartifact), and all 3 are fixed. After the restructure,src/ui/,src/system/and the judge: 211/211 files, 7614 tests.packages/lint: newsrc/validate-action-copy-templates.test.ts(13). It drives the realrunRuntimeAuthoringRuleson apagewrite and anactionwrite, and asserts one advisory and zero errors. Re-pinned: the meta-test's import-specifier artefact (templates) andruntime-gate.page-visibility-predicate-writes.test.ts(validateStackExpressionsnow dispatches onpage). Full suite: 159/159 files, 7250 tests.packages/platform-objects: 94/94 files, 1343 tests.packages/plugins/plugin-approvals: 95/95 files, 1244 tests (token pins moved to{{ }}, and the parsed copy is read throughtemplateSlotSource).packages/cliunit tier: 327/327 files, 4812 tests.packages/metadata-protocolprotocol.runtime-authoring-gate.test.ts(197),packages/restmeta-view-container-i18n.test.ts(11),packages/runtimeaction-execution-destructive.test.ts(63),services/service-i18nauthored-translations.test.ts(16): all pass.packages/qa/dogfoodexpression-conformance.test.ts: 8/8.member-transfer-ownership-step-down.dogfood.test.tsnow reads the served copy throughtemplateSlotSource. NOT MEASURED locally: it boots the showcase. CI's battery runs it.Gates
All runs are at head
e875a564(the merged head). The derivation isnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 119 families, against merge base481ac6211. Each was run with its exit code captured before any pipe. 115 exited 0 on the first pass. 4 refused with exit 3 (PREREQUISITE NOT MET, nothing measured):check-plugin-teardown-shape --self-test(a shallow clone; I fetched the pinned fixture commit),spec check:skill-examples,check:dual-build-cjs-loadsandcheck:i18n-coverage(unbuilt packages). All 4 exited 0 afterturbo run build(77/77) and the fetch.--ranwithcmd :: exit Nreports: "119 derived famil(ies) accounted for — 119 run, 0 NOT-MEASURED (a DERIVED zero)".Also run:
pnpm --filter @objectstack/spec check:generatedreports "All 14 generated artifacts are up to date".typecheckexited 0 on spec, lint, cli, platform-objects, plugin-approvals and dogfood. The local-onlycheck:dev-prereqsandcheck:skill-frame-freshnessboth exited 0. The full lint battery and the repo-widepnpm lintare CI's.One ablation:
isDollarTokenwas forced toreturn falsethroughscripts/ablation-replace.mjs. The anchor went 1 to 0, and the blob wente12c4385to38c36986.flow-text-slot-template.test.tsthen had 4 failed and 41 passed. The restore left the blob back ate12c4385andgit diff HEADempty.Deviations
packages/plugins/plugin-approvals/src/sys-approval-request.object.ts(9Approved "${result.request.record_title}"-style toasts). Left alone, the platform would ship 9 sentences its own doors warn on. This conversion meets the four conditions for a bounded in-place fix: the same defect class, a mechanical conversion of the same form, no other open PR touches the file (all open PRs' file lists were read, and only spec(ui): record:detailshideEmpty: false— in read-only mode an all-empty section renders its heading plus one fold row; describe() says so #25612 overlaps, oncomponent.zod.ts), and the same gate family. The claim's file surface needs that file and its translations added. The seat should write that addendum, not me.*.objects.generated.ts,successMessageleaves only). AGENTS.md allows this. None of those leaves has a source-hash entry.packages/cli/src/commands/explain.ts:confirmText's row readstemplate (i18n), with the hole spelled out.Acceptance notes
ActionSchema.errorMessageandoutcomeMessagesare untouched by the ruling.outcomeMessagesstill reads${result.*}(the runner fills it that way). They are noted, not filed.ActionDefaultInspector/ActionPreview) read these keys asI18nLabelmaps and would show an envelope astemplate. This was noted on objectui feat(plugin-security): extend the packaged-permission-set lock to the restore leg of the write-through #13409, and it is an authoring surface outside the runner.status, and make the fold preserve the walk's total #13183 owns that.Generated by Claude Code