Skip to content

fix(formula,core,react): a template formatter locale rides a reserved context channel, never a root - #26282

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-vqfv9q
Oct 26, 2026
Merged

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Fixes #26232

Clause-②: yes (narrowing)
Metadata-protocol: none

What changed

The template dialect's formatters used to read their locale from ctx.extra.locale, the same bag every caller passes the template's author-visible roots in. A root named locale (a bulk param, a flow variable) therefore set the formatter locale of the whole sentence, and no caller passed the viewer's locale, so every viewer read en-US.

  • @objectstack/formula: EvalContext.locale?: string is a reserved channel, like timezone: not bound as a variable, never set by a root. templateEngine.evaluate reads its formatter locale from it alone (en-US when unset or empty). The undeclared (ctx as { locale }).locale read it already had becomes the declared, only rung; the extra.locale rung is gone.
  • @object-ui/core: fillTemplateHoles(source, roots, locale?) passes locale on that channel, apart from the roots. ActionRunner.setDisplayLocale(read) installs where the runner reads the viewer's locale, read per fill; confirmText and successMessage format in it, and a callContext dispatch (scopedTo) keeps it.
  • @object-ui/react: ActionProvider and useActionRunner install useDisplayLocale() beside the translator (useActionRunnerDisplayLocale, same latest-ref pattern), so there is no second copy of the locale resolution and a locale switch reaches an already-built runner.
  • @objectstack/service-automation: comment only. renderTextSlot's doc said a flow variable named locale was also the formatters' locale; under the ruling it is a value like any other, so the line now says that.
  • scripts/sdui-manifest.sources/{core,react}.json re-recorded (node scripts/gen-sdui-manifest-node.mjs); one changeset (patch ×4, BREAKING banner, ADR-0087 not-required (no-migration-prescription)).

Red first (base 9598405ec)

Probe through templateEngine.evaluate on origin/main, source Set {{ amount | number:2 }} on {{ count }} records, due {{ due | date:long }}:

no locale root      : "Set 1,234,567.50 on 1200 records, due May 6, 2031"
root named locale   : "Set 1.234.567,50 on 1200 records, due 6. Mai 2031"    (extra.locale = 'de-DE')
ctx.locale zh-CN    : "Set 1,234,567.50 on 1200 records, due 2031年5月6日"

The root sets the formatter locale; and the console filler passed only { extra: roots }, so no viewer locale was ever reached.

Every caller that passed extra.locale (measured)

git grep for templateEngine, ExpressionEngine.evaluate, fillTemplateHoles, renderTextSlot and formatValue callers at base:

  • ui/packages/core fillTemplateHoles (ActionRunner confirmMessage / composeSuccessMessage, plugin-grid bulkConfirmText): roots only, no locale. Now the runner passes its display locale.
  • service-automation renderTextSlot: flow variables as extra, documented as the locale channel. No engine code sets a flow variable named locale and no example or package flow declares one (git grep of name: 'locale' under examples, apps, packages/qa: 0 hits). Under the ruling it stays a value; flow text formats in en-US.
  • packages/formula/src/template-formatters.test.ts: the one caller that set the locale via extra: { locale: 'en-US' }; now locale: 'en-US'.
  • Registry callers (seed-eval, objectql defaults and formula fields, plugin-approvals, plugin-sharing, automation conditions) pass no locale in extra. formatValue (plugin-email) takes opts.locale and is untouched.

Clause-② from the diff

yes: EvalContext gains an optional member and fillTemplateHoles an optional argument (public surface grows). (narrowing): a caller that set the formatter locale through a root (extra: { locale }, a flow variable or bulk param named locale) now formats in en-US, and its value prints only where a hole names it. In-repo producers of that: 0 outside the one test above. A template that names no locale root and is evaluated with no locale renders exactly as before (pinned).

Tests

  • packages/formula/src/template-engine.test.ts (new block): the context locale formats; a root named locale prints and does not format (alone, and beside a context locale); no or empty locale is en-US. formula package: 53 files / 1427 tests pass; typecheck exit 0.
  • service-automation/src/builtin/text-slot-template.test.ts: a flow variable named locale prints and formats in en-US. Package: 207 files / 2699 tests pass; typecheck exit 0.
  • ui/packages/core/.../ActionRunner.copyTemplateLocale-26232.test.ts: fillTemplateHoles root vs argument; the runner's confirmText with and without a display locale, read per fill, kept across a callContext; successMessage. Core: 213 files / 4097 pass, 27 skipped; type-check exit 0.
  • ui/packages/react/.../ActionProvider.copyTemplateLocale-26232.test.tsx: real I18nProvider + LocalizationProvider + ActionProvider / useActionRunner: tenant locale reaches the toast and the confirm; with no tenant locale the UI language does, and a language switch reaches the same runner. React: 120 files / 1483 pass; type-check exit 0.
  • plugin-grid bulk suites (the other fillTemplateHoles caller): 27 files / 264 pass.
  • Expected strings are computed with Intl in each test (no ICU drift between the container's Node and CI's).

Ablation (head 7f9627df, via scripts/ablation-replace.mjs, formula tests import ./template-engine from src): put back the extra.locale rung ahead of ctx.locale → a root named locale is a value a hole prints, not the formatter locale FAILS (1 failed / 10 passed); restored, blob a2e9acd8fba9 == HEAD, git diff HEAD empty.

Gates (local, head 6507217d8)

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 72 commands, every one exit 0 except: check-plugin-teardown-shape --self-test exit 3 on the shallow clone (fetched its control 621a4876, rerun exit 0); check-sdui-manifest exit 1 until the re-record (rerun exit 0); check:dual-build-cjs-loads exit 3 before a full build (rerun after the full build, below). Lint (eslint on the 13 touched TS files, --no-inline-config): 0 errors, 29 warnings, the same set the base versions carry. Full build, check:dual-build-cjs-loads, check:dts-closure and the full dogfood battery (owed by the (narrowing) arm) are in the os-dev report on the card.

Acceptance notes


Generated by Claude Code

…channel, never a root

A template's roots are author-visible values: a bulk action's params, a
flow's variables. The engine read its formatter locale from extra.locale,
so a root named locale set the locale of every formatted value in the
sentence, and no viewer locale was ever passed. EvalContext gains a
reserved locale field the engine reads instead, the console filler takes
the display locale from its caller, and both React runner owners install
useDisplayLocale() on the runner.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbF1LGGBRTWwTfLay1d51s
# Conflicts:
#	scripts/sdui-manifest.sources/core.json
#	scripts/sdui-manifest.sources/react.json
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/formula, @objectstack/service-automation, touching 1 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/services/service-automation/src/builtin/template.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/releases/v17/17-5.mdx (via EvalContext (symbol, a top-level interface))

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/services/service-automation/src/builtin/template.ts) — pages documenting those are invisible to this run
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 14 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 b3ed6c86d46f96d15a2a70eab66ee411fe3b58e9 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 3faff829ae941baf49d25565102a545b0195916d — the merge of head 2d2b09be7683ba4bcd1ab278a1c5f420653baac4 into base b3ed6c86d46f96d15a2a70eab66ee411fe3b58e9, 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 3faff829ae941baf49d25565102a545b0195916d && git checkout 3faff829ae941baf49d25565102a545b0195916d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b3ed6c86d46f96d15a2a70eab66ee411fe3b58e9 2d2b09be7683ba4bcd1ab278a1c5f420653baac4 && git checkout -B drift-repro b3ed6c86d46f96d15a2a70eab66ee411fe3b58e9 && git merge --no-ff 2d2b09be7683ba4bcd1ab278a1c5f420653baac4

node scripts/docs-audit/affected-docs.mjs --json b3ed6c86d46f96d15a2a70eab66ee411fe3b58e9

⚠️ 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 b3ed6c86d46f96d15a2a70eab66ee411fe3b58e9 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Oct 26, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 2d2b09be7683ba4bcd1ab278a1c5f420653baac4
Local-runs: none
Scope: packages/formula/src/template-engine.ts, packages/formula/src/template-engine.test.ts, packages/formula/src/template-formatters.test.ts, packages/formula/src/types.ts, packages/services/service-automation/src/builtin/template.ts, packages/services/service-automation/src/builtin/text-slot-template.test.ts, scripts/sdui-manifest.sources/core.json, scripts/sdui-manifest.sources/react.json, ui/packages/core/src/actions/ActionRunner.ts, ui/packages/core/src/actions/actionCopyTemplate.ts, ui/packages/core/src/actions/tests/ActionRunner.copyTemplateLocale-26232.test.ts, ui/packages/react/src/context/ActionContext.tsx, ui/packages/react/src/context/actionRunnerTranslator.ts, ui/packages/react/src/context/tests/ActionProvider.copyTemplateLocale-26232.test.tsx, ui/packages/react/src/hooks/useActionRunner.ts + packages/formula/src/stdlib.ts (buildScope), packages/formula/src/index.ts (EvalContext export), ui/packages/i18n/src/useDisplayLocale.ts, ui/packages/plugin-grid/src/components/bulkConfirmText.ts (fillTemplateHoles caller, at the merge base and at origin/main), packages/services/service-automation/src/builtin/notify-node.ts, screen-nodes.ts, http-nodes.ts and src/node-text-rendering.ts (renderTextSlot callers), scripts/check-sdui-manifest.mjs, scripts/check-adr-0087-registration.mjs, .github/workflows/pr-automation.yml (the job that runs it)

Inputs read: card #26232 body and all three comments (triage 6422123580, claim 6423821741, os-dev-report 6425204871); PR #26282 body, its 16-file list, and git diff be531f68..2d2b09be (16 files, 385+/20-, identical to the file list; the merge commit 9e83afb and the re-record 2d2b09b add no hunk beyond the two digest files); the head's check-runs, read 2026-10-26T20:02Z.

① Derived judgments

The narrowing itself — right. At the base, template-engine.ts:276 read the formatter locale as extra.locale, then an undeclared (ctx as { locale }).locale, then en-US. At the head the only rung is the declared EvalContext.locale (types.ts), empty string falling to en-US exactly as the old || chain did. buildScope (stdlib.ts:631, read in full) copies record, previous, input, user/org/env and spreads extra at the scope root; it never binds ctx.locale, so the "reserved channel, not a variable" claim holds and a root named locale is exactly what {{ locale }} prints. Pinned three ways in template-engine.test.ts (context locale formats; root locale prints and does not format, alone and beside a context locale; unset or empty is en-US), and the PR's ablation (restore the extra.locale rung ahead of ctx.locale) reds the right pin. Expected strings are built with Intl in-test, so the pins are ICU-drift safe.

Every caller of the template dialect, measured in the head tree — all accounted for, right. Runtime producers of a template-dialect evaluate at the head are exactly two: renderTextSlot (service-automation template.ts:119, passes { extra: textTemplateScope(variables) }, no locale) and fillTemplateHoles (ui core). Every other .evaluate reaches the engine through the registry with an authored expression, and a multi-line search of all 5,543 non-test .ts/.tsx sources under packages/, ui/ and apps/ for an extra: { … locale … } literal returns zero hits; the single-line forms (extra.locale, extra?.locale) return zero outside the new tests. The one caller that did set the locale through a root was template-formatters.test.ts; it is migrated to locale: 'en-US'. formatValue (template-engine.ts:151) takes opts.locale and is untouched. The PR body's caller census is therefore complete and accurate.

Console wiring — right for the two paths it names. fillTemplateHoles(source, roots, locale?) forwards locale on the context and keeps extra: roots. ActionRunner gains displayLocale: (() … ) | null, setDisplayLocale(read), reads it per fill in composeSuccessMessage (line 1830) and confirmMessage (line 1869), and scopedTo (the callContext one-shot runner, line 2985) copies it beside the translator. Both React owners (ActionProvider, useActionRunner) install useActionRunnerDisplayLocale(), which wraps useDisplayLocale() (ui/packages/i18n/src/useDisplayLocale.ts:64, returns locale || language || 'en', the one resolution) in the same latest-ref pattern as the translator; @object-ui/i18n already exports it (index.ts:162). Pinned at the runner (7 tests, incl. per-fill read and callContext) and through real I18nProvider + LocalizationProvider (4 tests, incl. a language switch reaching an already-built runner). The ruling's "no second copy of the locale resolution" is honoured.

The third fillTemplateHoles caller — the card's headline symptom — is deliberately not wired, and the reason has since moved. ui/packages/plugin-grid/src/components/bulkConfirmText.ts at the merge base passes { count } only (step 1 of #25942), so at the base no bulk param was a root at all. #26217 (the step-2 PR that binds the params' display values as roots via bulkConfirmTemplateScope) MERGED at 2026-10-26T19:54Z — after this head (19:30Z) and not an ancestor of it. On origin/main today a bulk param named locale therefore does reach extra; this PR's engine change closes that hijack the moment it lands, with no plugin-grid edit, which is the card's first symptom. The card's second symptom (the bulk confirm sentence formats in en-US for every viewer) stays open: bulkConfirmText still passes no third argument on origin/main. The PR named #26217 as the carrier; #26217 landed without it, so that carrier no longer exists — see ③.

Public surface the diff implies, each named right. Widening: EvalContext.locale? (exported from @objectstack/formula index.ts:173), fillTemplateHoles optional third argument and ActionRunner.setDisplayLocale (both @object-ui/core), useActionRunnerDisplayLocale (@object-ui/react). Narrowing: extra.locale is no longer read. Nothing removed or renamed. The sdui per-package digests (scripts/sdui-manifest.sources/{core,react}.json) move because ui/packages/{core,react}/src moved; manifestSha256 is unchanged, matching an unchanged sdui.manifest.json (not in the diff), and the source counts (111 / 70) are unchanged, consistent with the two new files being tests outside the index.

Metadata-protocol: none — right. Nothing a metadata author writes is added, refused, renamed or re-shaped: confirmText, successMessage, params, flow variables and the hole grammar are untouched. The only "meaning" that changed is which input the engine's formatters take their locale from, and that rung was never an authored-protocol promise: ADR-0032 names locale threading as an open catalog item, packages/spec/src/automation/flow-text-slot-template.ts and io-node-config.zod.ts say nothing of a locale variable, and content/docs never documents it. The one place that did state it was a runtime JSDoc in service-automation template.ts, which the diff corrects. Triage 6422123580 reads the old rung as a hijack bug, not a protocol; none is the honest line and semantic would over-claim.

② Semver level

The changeset .changeset/26232-template-locale-reserved-channel.md bumps @objectstack/formula, @objectstack/service-automation, @object-ui/core, @object-ui/react at patch, carries both line-initial declarations (Clause-②: yes (narrowing), Metadata-protocol: none), a **BREAKING** (a narrowing) banner with the what-to-do, and exactly one ADR-0087 marker, not-required (no-migration-prescription). This is the shape AGENTS.md §3 prescribes for a non-protocol breaking change ("ships patch and keeps its BREAKING banner and ADR-0087 disposition"); a minor is owed only for a Metadata-protocol kind, which is none here. The four packages are the four the diff moves (service-automation moves by a doc-comment hunk only; a patch there is the conservative reading and the gate took it). The disposition's facts hold: no spec key, authorable spelling or stored shape moves, nothing for objectstack migrate meta to rewrite, every bumped package publishes, no registry id is named — and the gate that reads the arm and the marker against the merge base (check-adr-0087-registration.mjs --base, run inside the Check Changeset job per pr-automation.yml:998) is success on this head, so the changeset's own prose was judged not to carry a migration prescription. The Clause-②: yes (narrowing) line is right: surface grows (the widenings above) and one accepted input (extra.locale) stops being honoured, so it is BREAKING by definition and the dogfood battery it owes ran (Dogfood Regression Gate success). PR body and changeset declarations agree.

③ Boundary flags

The os-dev-report (6425204871) lists open_questions: [] and two out_of_scope_findings; the PR body carries the same two as acceptance notes. Each answered:

  1. Bulk confirm dialog not wired to the display locale; carrier named as fix(plugin-grid): fill a bulk confirmText's param holes with the params' display values #26217 (open draft at report time). Answered with an escalation: fix(plugin-grid): fill a bulk confirmText's param holes with the params' display values #26217 merged at 19:54Z without that argument, so the named carrier is gone and the card's second symptom (bulk confirm formats in en-US for every viewer) is now ownerless. Not a defect of this PR — its File surface was the engine channel and the console filler, and this PR's engine change does close the hijack half on today's main — but the seat owes a follow-up card (one argument: useDisplayLocale() into fillTemplateHoles's third parameter in bulkConfirmText/BulkActionDialog), filed with the dedupe words the report gives, before 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 is read as fully closed. Because fix(plugin-grid): fill a bulk confirmText's param holes with the params' display values #26217 is not in this head, the merge queue will build this PR against a main whose bulkConfirmText.ts moved; no path of this diff is touched by fix(plugin-grid): fill a bulk confirmText's param holes with the params' display values #26217, so no textual conflict is expected.
  2. Flow text slots (notify/screen/end/http via renderTextSlot) format in en-US; no flow-level locale channel. Answered, accepted as out of scope: the card's "Who acts" is the evaluate context and the console filler; flow-level locale is a separate design (notify documents one text for every recipient, and io-node-config.zod.ts resolves the e-mail locale per recipient after fan-out). The corrected JSDoc in template.ts states the current behaviour truthfully.
  3. Dev flag: "CI: 0 check-runs at report time; NOT MEASURED: every CI family". Answered by the head's check-runs at 2026-10-26T20:02Z: 23 success (incl. Check Changeset, Dogfood Regression Gate, Governed Surface Queue Guard, Build UI Deps, every Lint · family, Type Check · source gates, the four claim/single-writer guards), 6 skipped opt-in or matrix placeholders, 0 failures; still in_progress: Build Core, Build & E2E UI, Lint UI, Test Core (1–8/8), Test UI (1–16/16), Type Check UI, Type Check · workspace, Type Check · consumer gates, Type Check · debt ledger. The dev's local run of the same families (formula 1427, service-automation 2699, core 4097, react 1483 tests; 80 dispatch-gate commands exit 0; full build; dogfood battery) is consistent with everything CI has finished. The verdict below stands on the families that have concluded; the queue guard will hold the PR on any family that turns red.
  4. Report head vs PR head. The report and PR-body gate runs name 6507217d8; the head is 2d2b09be (a merge of origin/main plus the digest re-record). The net diff against the merge base is unchanged in substance (16 files, same hunks), so the report's measurements carry.
  5. No packages/spec/** touched; EvalContext lives in packages/formula/src/types.ts and is not a spec type (the only EvalContext under packages/spec/src is a local interface in a test), so the claim's stop-condition was not triggered. Right.

Implemented-by: claude/project-thread-vqfv9q
Reviewed-by: session_0186vfjHHMsRXC9EUT9hTFMo

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:server seat 2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo · 2026-10-26T20:22Z · head 2d2b09be7683ba4bcd1ab278a1c5f420653baac4 · #26232

The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」.

  1. Shape: a draft against main with Fixes #26232. Clause-②: yes (narrowing) and Metadata-protocol: none are line-initial. The changesets are four patch entries (formula, service-automation, @object-ui/core, @object-ui/react) with a BREAKING banner and the ADR-0087 disposition not-required (no-migration-prescription). check-governed-merges --pr 26282: NOT governed, 405 changed lines.
  2. Premise and review: triage 6422123580 and the claim 6423821741 asked for a locale slot on the evaluate context, apart from the author-visible roots. The narrowing face is owed, so the contract review is the record of record: comment 6426083828 on this head, at CONTRACT_REVIEW_TIER, read-only, PASS. It found three things. The engine reads only EvalContext.locale, empty still meaning en-US, and no scope binds it, so a root named locale is now only a printable value. A census of every non-test source finds no producer that set the locale through a root, and the one test that did was migrated. The console's runners install useDisplayLocale() through ActionRunner.setDisplayLocale, read on each fill, with no second locale resolution. The head is the dev's merge of main (9e83afb99) plus the two sdui-manifest sources regenerated with gen-sdui-manifest-node.mjs, and check-sdui-manifest exits 0.
  3. Gates: at this stamp the head's 62 check-runs read 56 success and 6 skipped (docs-build and opt-in harnesses), with none failing or in progress; this includes the full UI matrix (Test UI 1–16, Build & E2E UI, Lint UI, Type Check UI), Check Changeset (which runs the ADR-0087 registration gate) and the Dogfood Regression Gate the narrowing arm owes. Mergeable, clean. Locally the dev reports 78 of 78 derived gate families run and exit 0 after the merge, reconciled with --ran.
  4. Accepted deviations: the docs-drift bot lists content/docs/releases/v17/17-5.mdx as naming EvalContext. That page is release-owned and read-only, and it is not edited here.
  5. Out of scope: the bulk confirm dialog still formats in en-US. PR fix(plugin-grid): fill a bulk confirmText's param holes with the params' display values #26217 was to carry the one-argument wiring and merged without it. The remainder is filed as 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, blocked by this PR.
  6. Landing: this seat runs pr_ready and then automerge_enable through the relay, confirms MERGED on both readings, and records the landing on 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, which the merge closes.

Generated by Claude Code

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 size/m tests tooling

Projects

None yet

2 participants