Repository navigation
Commit 384bb7b
feat(spec,lint): an action's confirmText, successMessage and errorMessage are template slots over the roots the console runner binds (#25637)
Fixes #24814
Clause-②: yes (widening)
Metadata-protocol: semantic
Ruling-ref: 6365736632
An action's person-facing copy (`confirmText`, `successMessage`,
`errorMessage`) is now an ADR-0032 `template` slot 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-sha`
`2958f17e9c6a`), 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
- **The 9 row slots** (`action:button`, `action:icon`, the
`action:group` / `action:menu` member, × `confirmText` /
`successMessage` / `errorMessage`, `ui/component.zod.ts`) and
**`ActionSchema.confirmText` / `successMessage`** (`ui/action.zod.ts`,
which `InlineActionSchema` picks) are `template` slots. A bare string is
shorthand for `{ dialect: 'template', source }`. The envelope is
accepted, and a blank string still parses. `ActionSchema` keeps its
inline locale map arm.
- **Roots, measured at the runner** (objectui `ActionRunner` at
`2958f17e9c6a`, `confirmMessage` / `composeSuccessMessage` /
`handlePostExecution`), and declared in `@objectstack/spec/ui`:
- `confirmText`: `record`, plus `params` only beside an object of static
values (`actionConfirmTemplateRoots`). The runner binds `params` to
`action.params` minus `_rowRecord`. An input list binds nothing, because
its values are collected after the confirmation.
- `successMessage`: `result` (the handler's payload) and `record`
(`ACTION_SUCCESS_TEMPLATE_ROOTS`).
- `errorMessage`: none (`ACTION_ERROR_TEMPLATE_ROOTS`). The runner shows
an authored `errorMessage` as written. Re-measured at the pin: it fills
no hole, as the earlier report said.
- **The judge** (`automation/flow-text-slot-template.ts`
`closedScopeTemplateRefusal`) reads a `${…}` token with its `$`. `Delete
${record.name}?` is prescribed `Delete {{ record.name }}?`, no longer
`Delete ${{ 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).
- **The doors**: a new pass,
`packages/lint/src/validate-action-copy-templates.ts`
`actionCopyTemplateIssues`, runs inside `validateStackExpressions`. That
is `objectstack validate`, `compile` and the publish gate. It covers
registered actions (top-level, and an object's own, deduplicated), the
page action rows and a page component's inline `properties.action`, and
judges each locale of an inline map on its own. `page` joins the rule's
`runtimeTypes`, and on a page write this pass runs alone. **Every
finding is a `warning`**, so it shows as an advisory at the gate and
never as a 422.
- **The platform's sentences, converted**: the 4 `invite_user` sentences
the ruling names, and the 9 `sys_approval_request` decision toasts (see
Deviations). They move from `${result.*}` to `{{ result.* }}` in the
source and in every shipped locale bundle. The en bundles were
regenerated with `check-i18n-bundles --write`.
- **i18n**: `ActionLike.confirmText` / `successMessage` in
`system/i18n-resolver.ts` widen to the envelope. `resolveActionConfirm`
/ `resolveActionSuccess` answer the authored value as written when no
translation applies. `os i18n extract` seeds an envelope like the string
(`actionCopy`, through `templateSlotSource`).
- **Expression-surface ledger**: the census
(`packages/qa/dogfood/test/expression-conformance.test.ts`) scans only
`*.zod.ts` files. So the template input is built at each declaring
`.zod.ts` file, 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 `.ts`
module, and the census never saw them. That is why the construction
lives where it does.
## Measured
- **Renderer, re-verified at the pin** (`.objectui-sha` `2958f17e9c6a`,
objectui `8e90779dcb` merged in): `actionCopySource` /
`fillTemplateHoles` (`packages/core/src/actions/actionCopyTemplate.ts`)
read the envelope for all three keys. `useActionTextLocalizer` hands the
envelope on, and `flowSuccessCopy` reads it.
- **Producers** (authored `confirmText` / `successMessage` /
`errorMessage` in `examples/**` + `packages/**`, tests and generated
bundles excluded): at `e649c2d91e`, 13 carried `${…}` (4
platform-objects, 9 plugin-approvals) and 0 carried `{{`. On this
branch, 0 carry `${…}` and 13 carry `{{`. In `examples/**`, 0 either
way.
- **Downstream shape check** (the unlock's instruction):
`OBJECTUI_ROOT=../objectui bash scripts/build-console.sh`, with objectui
at `2958f17e9c6a` and this branch's spec built (pre-merge head
`3c1eb1e0`). The console's `tsc && vite build` and app-shell's `tsc`
completed. 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 #25612's `hideEmpty` describe).
## Tests
- `packages/spec`: new `src/ui/action-copy-template.test.ts`, plus new
cases in `src/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), and
`ui/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 the `export-origins`
artifact), and all 3 are fixed. After the restructure, `src/ui/`,
`src/system/` and the judge: 211/211 files, 7614 tests.
- `packages/lint`: new `src/validate-action-copy-templates.test.ts`
(13). It drives the real `runRuntimeAuthoringRules` on a `page` write
and an `action` write, and asserts one advisory and zero errors.
Re-pinned: the meta-test's import-specifier artefact (`templates`) and
`runtime-gate.page-visibility-predicate-writes.test.ts`
(`validateStackExpressions` now dispatches on `page`). 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 through
`templateSlotSource`).
- `packages/cli` unit tier: 327/327 files, 4812 tests.
- `packages/metadata-protocol` `protocol.runtime-authoring-gate.test.ts`
(197), `packages/rest` `meta-view-container-i18n.test.ts` (11),
`packages/runtime` `action-execution-destructive.test.ts` (63),
`services/service-i18n` `authored-translations.test.ts` (16): all pass.
- `packages/qa/dogfood` `expression-conformance.test.ts`: 8/8.
`member-transfer-ownership-step-down.dogfood.test.ts` now reads the
served copy through `templateSlotSource`. NOT MEASURED locally: it boots
the showcase. CI's battery runs it.
## Gates
All runs are at head `e875a564` (the merged head). The derivation is
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 119 families, against merge base
`481ac6211`. 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-loads` and
`check:i18n-coverage` (unbuilt packages). All 4 exited 0 after `turbo
run build` (77/77) and the fetch. `--ran` with `cmd :: exit N` reports:
"119 derived famil(ies) accounted for — 119 run, 0 NOT-MEASURED (a
DERIVED zero)".
Also run: `pnpm --filter @objectstack/spec check:generated` reports "All
14 generated artifacts are up to date". `typecheck` exited 0 on spec,
lint, cli, platform-objects, plugin-approvals and dogfood. The
local-only `check:dev-prereqs` and `check:skill-frame-freshness` both
exited 0. The full lint battery and the repo-wide `pnpm lint` are CI's.
One ablation: `isDollarToken` was forced to `return false` through
`scripts/ablation-replace.mjs`. The anchor went 1 to 0, and the blob
went `e12c4385` to `38c36986`. `flow-text-slot-template.test.ts` then
had 4 failed and 41 passed. The restore left the blob back at `e12c4385`
and `git diff HEAD` empty.
## Deviations
- **plugin-approvals, 9 sentences, beyond the ruling's "4".** The
ruling's count came from the earlier report, which missed
`packages/plugins/plugin-approvals/src/sys-approval-request.object.ts`
(9 `Approved "${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
#25612 overlaps, on `component.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.
- **Translated-locale values edited by hand** (zh-CN / ja-JP / es-ES
`*.objects.generated.ts`, `successMessage` leaves only). AGENTS.md
allows this. None of those leaves has a source-hash entry.
- `packages/cli/src/commands/explain.ts`: `confirmText`'s row reads
`template (i18n)`, with the hole spelled out.
## Acceptance notes
- `ActionSchema.errorMessage` and `outcomeMessages` are untouched by the
ruling. `outcomeMessages` still reads `${result.*}` (the runner fills it
that way). They are noted, not filed.
- The Studio action inspector and preview (objectui
`ActionDefaultInspector` / `ActionPreview`) read these keys as
`I18nLabel` maps and would show an envelope as `template`. This was
noted on objectui #13409, and it is an authoring surface outside the
runner.
- The bulk row in the expression-surface ledger still says no evaluator.
objectui #13183 owns that.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01DcqhBfbrsH8ACDsMQ62Hzq)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f1ddace commit 384bb7b
46 files changed
Lines changed: 1209 additions & 179 deletions
File tree
- .changeset
- content/docs/references
- data
- kernel
- ui
- packages
- cli/src
- commands
- utils
- lint/src
- platform-objects/src
- apps/translations
- identity
- plugins/plugin-approvals/src
- translations
- qa/dogfood/test
- spec
- api-surface
- export-origins
- src
- automation
- system
- ui
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
492 | 492 | | |
493 | 493 | | |
494 | 494 | | |
495 | | - | |
496 | | - | |
| 495 | + | |
| 496 | + | |
497 | 497 | | |
498 | 498 | | |
499 | 499 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
332 | 332 | | |
333 | 333 | | |
334 | 334 | | |
335 | | - | |
336 | | - | |
| 335 | + | |
| 336 | + | |
337 | 337 | | |
338 | 338 | | |
339 | 339 | | |
| |||
0 commit comments