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 }}
Repository navigation
action copy templates: a bulk param named locale sets the formatter locale of the whole confirm sentence, because the template engine reads its locale from the same extra roots #26232
Filing gate ②: a seat ruling on an out-of-scope finding. On #25942 the domain:ui seat 2 ruled B: the locale question goes on its own engine card and is not folded into the bulk-confirm fix. ⛔ Not a claim.
The template engine takes the locale its formatters use from ctx.extra.locale (packages/formula/src/template-engine.ts:276). The console's action-copy filler passes the template's roots in that same extra object (ui/packages/core/src/actions/actionCopyTemplate.ts:82, templateEngine.evaluate({ dialect: 'template', source }, { extra: roots })). Since #25942, a bulk action's params are template roots. So a param named locale silently sets the formatter locale of the whole confirm sentence. When there is no such param, formatters render in en-US rather than the viewer's language.
Business reason. A bulk confirm sentence such as "Set the region to {{ region }} on {{ count }} records" formats its numbers and dates in en-US for a Chinese viewer. A param that happens to be called locale (a plausible field on any localization object) changes how every formatted value in the sentence renders.
Who acts. Whoever owns @objectstack/formula's template evaluation context. The likely shape is a dedicated locale slot on the evaluate context, kept apart from the author-visible roots, which the console's filler passes from the display locale. The renderer side is fillTemplateHoles in ui/packages/core. Triage sets domain and route.
Found by. The os-dev report on #25942 (comment 6420654854, step 2 / PR #26217), out_of_scope_findings.
Duplicate check. Searched for "template engine extra locale", "formatter locale hijack" and "fillTemplateHoles locale" in the 40 newest issues. 0 hits.
Claim: PM loop round 3 · card thread
Session: session_0186vfjHHMsRXC9EUT9hTFMo (the seat); the card thread is session_01XbF1LGGBRTWwTfLay1d51s
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-vqfv9q (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#2
Ruling-ref: triage 6422123580 (p3, lane merge domain:engine → domain:server), dispatched under the maintainer's 「p1 p2 处理完了,就可以处理 p3」 (2026-10-26T10:29Z); domain:ui seat 2 ruling B on #25942 — the template engine reads its formatter locale from a reserved channel on the evaluate context, never from an author-visible root (⛔ no second copy of the locale resolution; the console filler passes the display locale it already has)
File surface: packages/formula/src/template-engine.ts and its evaluate context, ui/packages/core/src/actions/actionCopyTemplate.ts (the filler), every other caller that passes extra.locale today, their tests, one changeset. ⛔ No packages/spec/** (if the evaluate context is a published spec type, stop and put it in open_questions with status: needs_decision)
Container & model: M, bug, mode:thread, default model and effort (set at thread open)
Clause-②: yes (provisional: a root named locale that used to set the formatter locale stops doing so; the dev declares from the real diff)
Metadata-protocol: none
Responsibility: graded by triage 6422123580 from the #25942 out-of-scope finding
Thread-read: 6422123580
Premise: holds (read 2026-10-26T18:12Z at origin/main9598405ec9: card open, unassigned, pm:queue, no claim on the thread)
Resume-from: none — fresh start from origin/main
Serial constraints cleared: no open PR touches packages/formula/src/template-engine.ts or actionCopyTemplate.ts known to the seat at this act; the dev re-measures every open PR on those paths before the first edit and before pr_create
{
"issue": 26232,
"status": "done",
"branch": "claude/project-thread-vqfv9q",
"pr": "https://github.com/objectstack-ai/objectstack/pull/26282",
"session": "session_01XbF1LGGBRTWwTfLay1d51s",
"premise_still_valid": true,
"summary": "Premise held at base 9598405ec: a probe through templateEngine.evaluate rendered 'Set 1,234,567.50 ... due May 6, 2031' with no locale root and 'Set 1.234.567,50 ... due 6. Mai 2031' with extra.locale = 'de-DE', and fillTemplateHoles passed only { extra: roots }, so no viewer locale was reached. Fix: EvalContext gains a reserved locale field (not bound as a variable, never set by a root) and the template engine reads its formatter locale from it alone (en-US when unset or empty); the extra.locale rung is gone. Console: fillTemplateHoles(source, roots, locale?) passes it on that channel; ActionRunner.setDisplayLocale(read) is read per fill for confirmText and successMessage and kept across a callContext dispatch; ActionProvider and useActionRunner install useDisplayLocale() (no second locale resolution). Every caller that passed extra.locale was measured: only formula's own template-formatters test (moved to locale), plus service-automation renderTextSlot, whose doc named a flow variable 'locale' as the formatters' locale; under the ruling that variable is now a value (0 in-repo producers), doc line corrected. Clause-② declared from the diff: yes (narrowing) - EvalContext and fillTemplateHoles widen, a root named locale stops setting the formatter. Metadata-protocol: none, patch x4, BREAKING banner, ADR-0087 not-required (no-migration-prescription). The bulk confirm dialog is not wired to the display locale here because open draft #26217 rewrites bulkConfirmText.ts and BulkActionDialog.tsx; the hijack is closed for it by the engine change.",
"tests": "head 6507217d8. formula: pnpm --filter @objectstack/formula test -> Test Files 53 passed, Tests 1427 passed; typecheck exit 0 (check:test-typecheck OK). service-automation (closure built): Test Files 207 passed, Tests 2699 passed; typecheck exit 0. ui core: pnpm --filter @object-ui/core test -> Test Files 213 passed, Tests 4097 passed / 27 skipped; type-check exit 0. ui react: pnpm exec vitest run packages/react/ (from ui/; the package script answers 'vitest: not found') -> Test Files 120 passed, Tests 1483 passed; type-check exit 0. plugin-grid bulk suites: 27 files / 264 passed. New pins: formula template-engine.test.ts 'formatter locale channel' (3), automation text-slot-template.test.ts (1), core ActionRunner.copyTemplateLocale-26232.test.ts (7), react ActionProvider.copyTemplateLocale-26232.test.tsx (4); expected strings computed with Intl in-test (ICU drift safe). Ablation at 7f9627df via scripts/ablation-replace.mjs (formula tests import ./template-engine from src, no dist leg): extra.locale rung restored ahead of ctx.locale -> 'a root named locale is a value a hole prints, not the formatter locale' FAILED (1 failed / 10 passed); restore: blob a2e9acd8fba9 == HEAD, git diff HEAD empty.",
"gates": "local, final head 6507217d8: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -> 80 commands, each run with exit captured before any pipe: 80 x exit 0; --ran reconciliation: '80 derived famil(ies) accounted for - 80 run, 0 NOT-MEASURED'. Along the way: check-plugin-teardown-shape --self-test exit 3 on the shallow clone until its control 621a4876 was fetched; check-sdui-manifest exit 1 until node scripts/gen-sdui-manifest-node.mjs re-recorded core and react (committed); check:dual-build-cjs-loads exit 3 until the full build. Full pnpm build exit 0; check:dual-build-cjs-loads exit 0; check:dts-closure exit 0. Lint (eslint on the 13 touched TS files, --no-inline-config): 0 errors, 29 warnings, the same set the base versions of those files carry. CI: 0 check-runs and 0 workflow runs on 6507217d8 at report time (relay-opened draft); NOT MEASURED: every CI family, reason: no run has started on the PR head.",
"dogfood_battery": "6507217d8 · os-verify-lock: VERDICT command-exit 0 (held 773s) · Test Files 297 passed / 3 skipped (300) · Tests 2527 passed / 18 skipped (2545) · no failures",
"mcp_calls": "0 - no MCP GitHub calls; reads via gh api REST",
"api_writes": "3 - relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, POST /repos/objectstack-ai/objectstack/issues/26282/assignees) and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/26232/comments); git push not counted",
"open_questions": [],
"out_of_scope_findings": [
"carrier: #26217 · the bulk confirm dialog (BulkActionDialog -> bulkConfirmText -> fillTemplateHoles) still formats in en-US for every viewer; after this PR it is one argument, useDisplayLocale() into fillTemplateHoles's third parameter, which #26217 (open draft, rewrites both files) can carry or a Blocked-by #26217 follow-up can; left out here to avoid colliding with it · dedupe words: bulk confirm display locale, fillTemplateHoles locale, BulkActionDialog useDisplayLocale",
"carrier: none · noted, not filed · flow text slots (notify/screen/end/http via renderTextSlot) format in en-US: no flow-level locale reaches the engine now that a variable named locale is a plain value; notify already documents its title/message as one text for every recipient · in PR Acceptance notes"
]
}
2026-10-26T20:50Z · domain:server seat 2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo. PR #26282 merged at 2026-10-26T20:50Z by the merge queue as 14b55970e6 on main (Fixes #26232; ACCEPT 6426471206, contract review 6426083828 PASS). The merge closed this card as completed.
GET /repos/objectstack-ai/objectstack/issues/26232
closed, completed
Landed: the template engine takes its formatter locale only from EvalContext.locale, so a template root named locale (a bulk param, a flow variable, extra.locale) is now a printable value and no longer sets the locale. This is BREAKING (Clause-②: yes (narrowing)). An action's confirmText and successMessage format in the viewer's display locale through ActionRunner.setDisplayLocale.
Activity
objectstack-fleet commentedon Oct 26, 2026
Triage: first grade —
bug·priority:p3·domain:server(lane merge:domain:engine→domain:server) ·area:i18n·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-26T16:57Z⛔ Not a claim, ⛔ not a dispatch.
confirmText: fill the template slot's declared holes —{{count}}first, the action's params second (the renderer half of objectstack#24798 ruling B) #25942). The template engine reads its formatter locale from the sameextrabag the action's params ride, so a param namedlocalehijacks it. Separate the two: read the locale from a reserved channel, not from user roots.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 3 · card thread
Session:
session_0186vfjHHMsRXC9EUT9hTFMo(the seat); the card thread issession_01XbF1LGGBRTWwTfLay1d51sAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-vqfv9q(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#2Ruling-ref: triage 6422123580 (p3, lane merge
domain:engine→domain:server), dispatched under the maintainer's 「p1 p2 处理完了,就可以处理 p3」 (2026-10-26T10:29Z);domain:uiseat 2 ruling B on #25942 — the template engine reads its formatter locale from a reserved channel on the evaluate context, never from an author-visible root (⛔ no second copy of the locale resolution; the console filler passes the display locale it already has)File surface:
packages/formula/src/template-engine.tsand its evaluate context,ui/packages/core/src/actions/actionCopyTemplate.ts(the filler), every other caller that passesextra.localetoday, their tests, one changeset. ⛔ Nopackages/spec/**(if the evaluate context is a published spec type, stop and put it inopen_questionswithstatus: needs_decision)Container & model:
M,bug,mode:thread, default model and effort (set at thread open)Clause-②: yes (provisional: a root named
localethat used to set the formatter locale stops doing so; the dev declares from the real diff)Metadata-protocol: none
Responsibility: graded by triage 6422123580 from the #25942 out-of-scope finding
Thread-read: 6422123580
Premise: holds (read 2026-10-26T18:12Z at
origin/main9598405ec9: card open, unassigned,pm:queue, no claim on the thread)Resume-from: none — fresh start from
origin/mainSerial constraints cleared: no open PR touches
packages/formula/src/template-engine.tsoractionCopyTemplate.tsknown to the seat at this act; the dev re-measures every open PR on those paths before the first edit and beforepr_createGenerated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 26232, "status": "done", "branch": "claude/project-thread-vqfv9q", "pr": "https://github.com/objectstack-ai/objectstack/pull/26282", "session": "session_01XbF1LGGBRTWwTfLay1d51s", "premise_still_valid": true, "summary": "Premise held at base 9598405ec: a probe through templateEngine.evaluate rendered 'Set 1,234,567.50 ... due May 6, 2031' with no locale root and 'Set 1.234.567,50 ... due 6. Mai 2031' with extra.locale = 'de-DE', and fillTemplateHoles passed only { extra: roots }, so no viewer locale was reached. Fix: EvalContext gains a reserved locale field (not bound as a variable, never set by a root) and the template engine reads its formatter locale from it alone (en-US when unset or empty); the extra.locale rung is gone. Console: fillTemplateHoles(source, roots, locale?) passes it on that channel; ActionRunner.setDisplayLocale(read) is read per fill for confirmText and successMessage and kept across a callContext dispatch; ActionProvider and useActionRunner install useDisplayLocale() (no second locale resolution). Every caller that passed extra.locale was measured: only formula's own template-formatters test (moved to locale), plus service-automation renderTextSlot, whose doc named a flow variable 'locale' as the formatters' locale; under the ruling that variable is now a value (0 in-repo producers), doc line corrected. Clause-② declared from the diff: yes (narrowing) - EvalContext and fillTemplateHoles widen, a root named locale stops setting the formatter. Metadata-protocol: none, patch x4, BREAKING banner, ADR-0087 not-required (no-migration-prescription). The bulk confirm dialog is not wired to the display locale here because open draft #26217 rewrites bulkConfirmText.ts and BulkActionDialog.tsx; the hijack is closed for it by the engine change.", "tests": "head 6507217d8. formula: pnpm --filter @objectstack/formula test -> Test Files 53 passed, Tests 1427 passed; typecheck exit 0 (check:test-typecheck OK). service-automation (closure built): Test Files 207 passed, Tests 2699 passed; typecheck exit 0. ui core: pnpm --filter @object-ui/core test -> Test Files 213 passed, Tests 4097 passed / 27 skipped; type-check exit 0. ui react: pnpm exec vitest run packages/react/ (from ui/; the package script answers 'vitest: not found') -> Test Files 120 passed, Tests 1483 passed; type-check exit 0. plugin-grid bulk suites: 27 files / 264 passed. New pins: formula template-engine.test.ts 'formatter locale channel' (3), automation text-slot-template.test.ts (1), core ActionRunner.copyTemplateLocale-26232.test.ts (7), react ActionProvider.copyTemplateLocale-26232.test.tsx (4); expected strings computed with Intl in-test (ICU drift safe). Ablation at 7f9627df via scripts/ablation-replace.mjs (formula tests import ./template-engine from src, no dist leg): extra.locale rung restored ahead of ctx.locale -> 'a root named locale is a value a hole prints, not the formatter locale' FAILED (1 failed / 10 passed); restore: blob a2e9acd8fba9 == HEAD, git diff HEAD empty.", "gates": "local, final head 6507217d8: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -> 80 commands, each run with exit captured before any pipe: 80 x exit 0; --ran reconciliation: '80 derived famil(ies) accounted for - 80 run, 0 NOT-MEASURED'. Along the way: check-plugin-teardown-shape --self-test exit 3 on the shallow clone until its control 621a4876 was fetched; check-sdui-manifest exit 1 until node scripts/gen-sdui-manifest-node.mjs re-recorded core and react (committed); check:dual-build-cjs-loads exit 3 until the full build. Full pnpm build exit 0; check:dual-build-cjs-loads exit 0; check:dts-closure exit 0. Lint (eslint on the 13 touched TS files, --no-inline-config): 0 errors, 29 warnings, the same set the base versions of those files carry. CI: 0 check-runs and 0 workflow runs on 6507217d8 at report time (relay-opened draft); NOT MEASURED: every CI family, reason: no run has started on the PR head.", "dogfood_battery": "6507217d8 · os-verify-lock: VERDICT command-exit 0 (held 773s) · Test Files 297 passed / 3 skipped (300) · Tests 2527 passed / 18 skipped (2545) · no failures", "mcp_calls": "0 - no MCP GitHub calls; reads via gh api REST", "api_writes": "3 - relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, POST /repos/objectstack-ai/objectstack/issues/26282/assignees) and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/26232/comments); git push not counted", "open_questions": [], "out_of_scope_findings": [ "carrier: #26217 · the bulk confirm dialog (BulkActionDialog -> bulkConfirmText -> fillTemplateHoles) still formats in en-US for every viewer; after this PR it is one argument, useDisplayLocale() into fillTemplateHoles's third parameter, which #26217 (open draft, rewrites both files) can carry or a Blocked-by #26217 follow-up can; left out here to avoid colliding with it · dedupe words: bulk confirm display locale, fillTemplateHoles locale, BulkActionDialog useDisplayLocale", "carrier: none · noted, not filed · flow text slots (notify/screen/end/http via renderTextSlot) format in en-US: no flow-level locale reaches the engine now that a variable named locale is a plain value; notify already documents its title/message as one text for every recipient · in PR Acceptance notes" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
2026-10-26T20:23Z ·
domain:serverseat 2 · ACCEPT → PR #26282 comment 6426471206, contract review6426083828PASS (landing through the queue).Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Landed
2026-10-26T20:50Z ·
domain:serverseat 2 ·os-project-manager·session_0186vfjHHMsRXC9EUT9hTFMo. PR #26282 merged at 2026-10-26T20:50Z by the merge queue as14b55970e6onmain(Fixes #26232; ACCEPT6426471206, contract review6426083828PASS). The merge closed this card as completed.git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26282-*'git merge-base --is-ancestor 14b55970e6 origin/mainGET /repos/objectstack-ai/objectstack/issues/26232completedEvalContext.locale, so a template root namedlocale(a bulk param, a flow variable,extra.locale) is now a printable value and no longer sets the locale. This is BREAKING (Clause-②: yes (narrowing)). An action'sconfirmTextandsuccessMessageformat in the viewer's display locale throughActionRunner.setDisplayLocale.en-US. That remainder is plugin-grid: the bulk action confirm sentence still formats its numbers and dates in en-US for every viewer, because BulkActionDialog does not pass the display locale to fillTemplateHoles #26307, which this merge unblocks.Generated by Claude Code