You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Oct 26, 2026. It is now read-only.
Repository navigation
Commit 8e90779
Browse filesBrowse the repository at this point in the historyBrowse files
feat(core,react,app-shell): action confirmText, successMessage and errorMessage read the template envelope and fill {{ }} holes (objectui#13367) (#13409)
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuT1VQkBzUTyDsEUih9AXFHq)_Fixes#13367
The renderer half of objectstack-ai/objectstack#24814 (ruling A): an
action's `confirmText`, `successMessage` and `errorMessage` read the
`template` envelope and fill `{{ }}` holes, before the spec types the
three slots as `template` slots. Claim: #13367 comment `6369803045`.
## What changes
- **`ActionRunner`** (`@object-ui/core`) reads each key as a string or
`{ dialect?: 'template', source? }`:
- `confirmText`: `{{ }}` holes over `record` (the record the action runs
against; on a list, the clicked row, the same rule as
`templateEvaluatorFor`) and `params` (the action's static params object,
without the stashed row; the collected values do not exist yet when the
confirmation shows);
- `successMessage`: `{{ }}` holes over `result` (the payload
`${result.*}` reads) and `record`;
- `errorMessage`: no root is declared yet, so the text shows as written.
- **The engine** is `@objectstack/formula`'s `templateEngine`, the one
the flow notifications render with and the one #13183's ruling names. It
is exported at this repo's `.objectstack-sha` pin and already a
dependency of `@object-ui/core`. No engine and no hole grammar is
written here.
- **Transition:** a sentence with no `{{ }}` hole keeps today's reading
(`${…}` through `ExpressionEvaluator` for `confirmText`, `${result.*}`
for `successMessage`), so the platform objects' `${result.email}`
sentences keep working until the spec card converts them.
- **One dialect per sentence:** a sentence with a `{{ }}` hole goes
through the template engine only. A record value that happens to contain
`${…}` is never evaluated after it was filled into a hole.
- **No object reaches a React child:** an envelope with no string
`source` gives `''` to the confirm handler (the declared confirmation
still gates the run), the default text to the success toast, and the
action's own error to the error toast.
- **`useActionTextLocalizer`** (`@object-ui/react`) kept the three keys
as `I18nLabel` maps and collapsed them with `pickLocalized`, which turns
`{ dialect: 'template', source }` into its first string value: the word
`template`. It now hands the envelope on, and a bundle entry replaces
its `source` (i18next leaves a missing `{{ }}` variable in place,
measured on the installed version).
- **Flow actions** (`@object-ui/app-shell`): `flowSuccessCopy` read the
invoking action's `successMessage` only as a string, so an envelope fell
to the flow's own copy. It now reads it through `actionCopySource`.
`FlowRunner`'s `actionSuccessMessage` takes the same two shapes (the
type widening made app-shell's type-check red at the two launch sites
otherwise).
- `@object-ui/core` exports the reader: `ActionCopyTemplate`,
`isActionCopyEnvelope`, `actionCopySource`, `hasTemplateHoles`,
`fillTemplateHoles`.
Clause-②: yes (the claim said `no`; `ActionDef`'s three keys widen and
core gains five exports, so this declares the widening)
Metadata-protocol: none
## Tests
- New
`packages/core/src/actions/__tests__/ActionRunner.copyTemplate-13367.test.ts`
(28): a plain string and an envelope per key; each root (`record` from
the header and from a row, `params` without `_rowRecord`, `result`); a
formatter; an undeclared root prints empty; `${…}` still filled for
`confirmText` and `successMessage`; one dialect per sentence for both; a
refused source shown as written; an envelope with no source for each
key; `outcomeMessages` still outranks; an envelope counts as declared
copy for a navigation (objectui#12755); the reader helpers.
- New
`packages/react/src/hooks/__tests__/useActionTextLocalizer.templateEnvelope-13367.test.tsx`
(5): nameless and named actions keep the envelope, a bundle entry
replaces `source`, a sourceless envelope passes through, strings and
maps unchanged.
- `packages/app-shell/src/utils/__tests__/flowResponse.test.ts`: 3 new
cases for the envelope on a flow action.
- Ablation (on `f25bc24`): the localizer's `collapseCopy` put back to
`pickLocalized`, and the success rung put back to string-only: 9 failed
(5 runner, 4 localizer); restored with `git checkout HEAD -- packages`,
`git diff --quiet HEAD` clean.
- `pnpm exec vitest run --maxWorkers=2 packages/core/ packages/react/` →
331 files, 5566 passed. `packages/components/src/renderers/action/
packages/app-shell/src/hooks/__tests__/ packages/app-shell/src/utils/
packages/plugin-grid/` → 391 files, 3995 passed. After the app-shell
commit: app-shell hooks, utils and flow views → 141 files, 1427 passed.
After merging `origin/main` (`889d028`): core actions, react hooks,
flowResponse and the objectui#13379 navigator pins → 84 files, 1097
passed.
- `NODE_OPTIONS=--max-old-space-size=8192 pnpm exec turbo run type-check
--filter=@object-ui/app-shell --filter=@object-ui/console` → 37/37. A
first run over app-shell, console, components and the plugin packages
went red on the two launch sites above, which the second commit fixes.
- eslint on the touched files: 0 errors. Gates exit 0:
`check:new-line-citations`, `check:control-bytes`, `check:spec-symbols`,
`check:changeset-claims`, `check:pending-changeset-literals`,
`check:unreferenced-sources`, `check:test-path-roots`,
`check:action-forward-parity`, `check:handler-key-reads`,
`check:phantom-deps`, `check:self-import`, `check:esm-specifiers`,
`check:component-surface-parity`, `check:vi-mock-specifiers`,
`check:comment-mask-corpus`, `scripts/check-changeset-presence.mjs`.
`check:readme-exports` needs every package built and was left to CI.
## Order
This PR → objectstack's `.objectui-sha` moves past it →
objectstack-ai/objectstack#24814 types the slots (it carries
`Blocked-by: #13367`).
## Serial
objectui#13395 (objectui#13384) also edits `ActionRunner.ts`, in other
hunks (a refusal inserted just above the confirm read, and the
`RUNNER_TEXT` docblock, whose last sentence this PR rewords). Whichever
lands second merges main.
## Acceptance notes
- `{{ }}` formatters render in `en-US`: the runner has no language (its
docblocks say so), so `{{ amount | currency }}` does not follow the
user's locale yet. Noted, not filed.
- `FlowRunner`'s terminal toast after a paused flow shows the action's
copy as written: neither `{{ }}` nor `${result.*}` is filled there,
before or after this PR. Noted, not filed.
- The Studio action inspector and preview (`ActionDefaultInspector`,
`ActionPreview`) read the three keys as `I18nLabel` maps, so an envelope
would show there as `template`. Authoring surface, outside the runner;
noted, not filed.
- #13183 (bulk `confirmText` fill step, `baozhoutao`) can call the same
`fillTemplateHoles` over `{ count, … }`; this PR does not touch
plugin-grid.
- [x] Adds no product-specific widget, dialog, hook or string (see
AGENTS.md › Product-neutral renderer)
---
_Generated by [Claude
Code](https://claude.ai/code/session_0159TkE3rFbrKwMs3mWhnniW)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_0159TkE3rFbrKwMs3mWhnniW
---------
Co-authored-by: Claude <noreply@anthropic.com>
An action's `confirmText`, `successMessage` and `errorMessage` read the `template` envelope and fill `{{ }}` holes (objectui#13367).
8
+
9
+
`@objectstack/spec` is making these three keys `template` slots (objectstack#24814), so a parsed action serves `{ dialect: 'template', source }` where it served a string. Before this change the success toast skipped the envelope and showed the default text, and the error toast showed the generic failure. `ActionRunner` now reads a string or the envelope for all three, and fills `{{ path }}` / `{{ path | formatter }}` holes with `@objectstack/formula`'s `templateEngine`, the engine the flow notifications render with:
10
+
11
+
-`confirmText`: `record` (the record the action runs against, the clicked row on a list) and `params` (the action's static params);
12
+
-`successMessage`: `result` (the payload `${result.*}` reads) and `record`;
13
+
-`errorMessage`: no hole is filled; the text shows as written.
14
+
15
+
A sentence with no `{{ }}` hole keeps today's `${…}` reading, so the platform's own `${result.email}`-style success sentences still work until the spec converts them. A sentence is read in one dialect only: a value filled into a `{{ }}` hole is never read again as `${…}`. An envelope with no text never reaches the dialog or a toast as an object; a `confirmText` envelope with no text still asks for confirmation.
16
+
17
+
`useActionTextLocalizer` keeps the envelope: it used to read it as a per-locale map and hand the runner the word `template`. A bundle entry replaces the envelope's `source`. On a flow action the console reads the envelope too, so the action's own success copy still outranks the flow's (`flowSuccessCopy`). `@object-ui/core` exports the reader it uses: `ActionCopyTemplate`, `isActionCopyEnvelope`, `actionCopySource`, `hasTemplateHoles` and `fillTemplateHoles`.
0 commit comments