Repository navigation
fix(spec)!: a single-brace token in a translated flow screen title is refused, like the description beside it (#22627) - #22673
Conversation
…lot judge (#22627) The engine picks a translated screen title in the run's locale and renders it through renderTextSlot, like the description and the refusing end message beside it, but only those two were judged: a single-brace {name} in a translated heading parsed and was drawn as literal text. The three engine-picked template leaves now share one leaf, flowTextSlotTranslation, which runs textSlotTemplateRefusal; an empty string stays accepted. Claude-Session: https://claude.ai/code/session_016njDy8ozy9B9Ns5Y8kAWEK Co-authored-by: Claude <noreply@anthropic.com>
…tle translation The narrowing's changeset carries its FROM -> TO, so the ADR-0087 gate refuses a not-required disposition; the D3 semantic entry translation-flow-screen-title-text-slot-refused records it, and the registry is regenerated. Claude-Session: https://claude.ai/code/session_016njDy8ozy9B9Ns5Y8kAWEK Co-authored-by: Claude <noreply@anthropic.com>
…d-refinements ledger The text-slot judge on a translated screen title reaches the runtime and not the published JSON Schema, like its description and refusal-message siblings; the eight schemas that carry the flows face name the site. Claude-Session: https://claude.ai/code/session_016njDy8ozy9B9Ns5Y8kAWEK Co-authored-by: Claude <noreply@anthropic.com>
…reen-title-text-slot-judge
📓 Docs Drift Check3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 139 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 967cf2fadf12ff4d3fa0f43c1854701a99cffb4c && git checkout 967cf2fadf12ff4d3fa0f43c1854701a99cffb4c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 243dd3c6256673dc1a56f9419be03c397129f825 9c3733f39b37bf9805ef4558c87474da9b01d316 && git checkout -B drift-repro 243dd3c6256673dc1a56f9419be03c397129f825 && git merge --no-ff 9c3733f39b37bf9805ef4558c87474da9b01d316
node scripts/docs-audit/affected-docs.mjs --json 243dd3c6256673dc1a56f9419be03c397129f825 |
…reen-title-text-slot-judge # Conflicts: # packages/spec/dropped-refinements.baseline.json
Contract reviewServed-tier: PR #22673 for card #22627, branch ① Derived judgments
② Semver level
③ Boundary flagsOpen questions: none in either report. Every flag answered below; nothing escalates. Report 1 (
Report 1, deviations.
Report 2 (
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #22627
Clause-②: no (narrowing)
What changed
The flow engine picks three translated templates in the run's locale and renders each through
renderTextSlot, the{{ }}dialect: a screen's heading (title), its body text (description), and a refusingendnode'smessage(maintainer ruling A on #22507, implemented by PR #22626 atd25b187203). Thedescriptionandmessagetranslation leaves ran the one text-slot judge,textSlotTemplateRefusal. Theflows.FLOW.screens.NODE_ID.titleleaf was a barez.string(). Sotitle: 'Welcome, {name}'parsed, and the heading showedWelcome, {name}.packages/spec/src/system/translation.zod.ts. One package-internal leaf factory,flowTextSlotTranslation, runs the judge.title,descriptionandrefusals.NODE_ID.messageall use it, so one judge covers the three by construction, and the two spelled-out copies of thesuperRefineare gone. It is a factory, so each slot keeps its own schema node as before. No new export. The flows docblock's⚠️paragraph that recorded the gap now says both screen keys are judged, and thetitleleaf's comment says so too. The.describe()strings are unchanged: the heading's already called it a{{ }}template.translation.test.ts, beside thedescriptionpins.{name}in a translated title, at the key's path, throughTranslationDataSchemaandTranslationItemSchema. The message starts withTEXT_SLOT_TEMPLATE_REFUSALand names{{ name }}.Welcome, {{ record.name }}and''are accepted.FLOW_SCREEN_COPY_KEYS, plus the refusal message, refuses{name}. A floor stops an emptied key list from passing.translation-flow-screen-title-text-slot-refused(protocol 18) in its own file, plus the regeneratedmigrations/registry.ts. PR feat(spec,service-automation)!: refuse the date macros in flow value slots with their CEL string form (#19939 pass 3) #22635's18.flow-text-slot-single-brace-refused.tsis not touched.packages/spec/dropped-refinements.baseline.json. The judge reaches the runtime and not the published JSON Schema, like its two siblings. The 8 schemas that carry the flows face now name the newflows.valueType.screens.valueType.titlesite, anddroppedRefinementSitesgoes from 696 to 704. The ledger is hand-edited, and the build printed the corrected entries..changeset/22627-flow-screen-title-translation-text-slot.md:@objectstack/spec: minor. It is BREAKING (an accept-set narrowing) under the launch-window convention, and it carries the FROM → TO.No other generated artifact moved:
check:generatedreported all 15 artifacts up to date against the rebuiltdist, both before and after the merge.The dispatch's premises, measured
main. At86da194919,titlewasz.string().optional(),descriptionran the judge in asuperRefine, and the⚠️paragraph recorded the gap.screensgroup, the only four underexamples/**andpackages/**:examples/app-crmcrm.translation.ts,examples/app-showcasetranslations/index.ts, andexamples/app-todoja-JP.ts/zh-CN.ts. It judged everyflows.F.screens.N.titlestring withtextSlotTemplateRefusal: 8 judged, 0 refused. The positive control, the same walker overmessagesstrings, judged 48 and refused 2. Both arecrm.activity.due_today({count}, en and zh-CN). No fixture or bundle flips.textSlotTemplateRefusal('')answersundefined, measured. It is pinned fortitlehere, beside the existingdescriptionandmessagepins.check-adr-0087-registration.mjs --base origin/mainrefusednot-required (no-migration-prescription)because the changeset carries a FROM → TO table (evidencefrom-to-label). A breaking narrowing owes the upgrader that rewrite, so the disposition isregistered, now green:[BREAKING+clause-②-narrowing] registered translation-flow-screen-title-text-slot-refused (new here).flow-text-slot-template.ts(head3c97f71067) rewrites the date-macro remedy and the module docblock. It does not touchTEXT_SLOT_TEMPLATE_REFUSALor the plain-path "Write X as Y" remedy, and the pins rely on nothing else.@objectstack/specdist. Results are in the tests table.Ablation (expected direction: red; observed: red)
node scripts/ablation-replace.mjsran on the committed fix (d3723c736). It replaced the anchortitle: flowTextSlotTranslation().optional().describe(withtitle: z.string().optional().describe(. The anchor count went from 1 to 0, and the blob from62144f762132to14fb1e3e3d4f. Thenvitest run src/system/translation.test.tsran: 2 failed | 150 passed. The failures are exactly the two title pins, the refusal pin and the family pin. The accept-control stayed green, as it should. Restore check: blob62144f762132equals HEAD, andgit diff HEADis empty. The test imports./translation.zodfrom source, so nodistrebuild was involved.Tests and gates
The head is
327c0cc2fb: the fix plus a merge oforigin/mainat5fb1746611, done withscripts/pm/os-regen-merge.sh. That merge brought in PR #22635's judge edits. The regeneratedregistry.tscame out byte-identical,check:generatedreported all 15 artifacts current, and the branch delta againstorigin/mainis exactly this PR's 6 files.Before the merge (
cf08a125a): a full consumer sweep against a rebuiltdist.@objectstack/specvitest run --project local(the package'spnpm test)@objectstack/specvitest run --project repo(not part ofpnpm test; run anyway)@objectstack/specpnpm typechecktranslation.test.tsis in thetsconfig.test.jsonprogram, with no debt entry)@objectstack/lintvitest run, the whole package, includingvalidate-translation-references.test.ts@objectstack/service-automationvitest run, the whole package, includingscreen-copy-translation.test.ts@objectstack/clivitest run --project unit i18n: every unit-tier file whose path names i18n, includingi18n-flow-screen-coverage,i18n-flow-refusal-coverageandi18n-extract@objectstack/downstream-contractvitest runAfter the merge (
327c0cc2fb).@objectstack/specvitest run --project local, thenpnpm typecheck@objectstack/lintvitest run src/validate-translation-references@objectstack/service-automationscreen-copy-translation.test.ts+builtin/text-slot-template.test.ts@objectstack/clivitest run --project unit i18n@objectstack/downstream-contractvitest runOther checks.
i18n-extract-check-dry-run,-check-jsonand-companion-orphan.327c0cc2fbwith the judge as it now stands: 8 screen headings judged, 0 refused. The control refused 2 of 48 messages.node scripts/pm/dispatch-gates.mjs --commands(no paths) derived the same 92 commands before and after the merge. All 92 were run at327c0cc2fb, and all 92 exited 0.--ranreconciliation:92 derived, 92 run, 0 NOT-MEASURED, 0 UNRUN, a derived zero with every exit code recorded.cf08a125a, had three PREREQUISITE NOT MET refusals (exit 3), which measure nothing: a shallow clone missingcheck-plugin-teardown-shape's pinned fixture commit, and unbuilt packages forcheck:dual-build-cjs-loadsandcheck:i18n-walk-parity. All three were re-run green once their inputs existed.check-adr-0087-registration,check-changeset-no-major,check-closing-keyword-parity,check-spec-docblock-symbol-anchorsanddocs-audit/check-affected-docs.check:api-surface,check:authorable-surface,check:docs,check:liveness,check:strictness-ledger,check:export-originsandcheck:objectui-pin-citations.check:i18n-walk-parity,check:doc-authoring,check:issue-citationsandcheck:spec-parsed-alias.Acceptance notes
descriptionhave no key in theflowstranslation face — the zh-CN intake wizard translates its labels and placeholders and keeps its options and body text in English (17.7.0) #22507 changeset still says the opposite, in the same release..changeset/22507-screen-copy-engine-pick-spec.md(PR feat(i18n): the engine translates a screen's title and description in the run's locale #22626, unreleased) ends with "A translated screentitleis not judged by the text-slot rule yet". This PR's changeset carries the repo's usual sentence for that case ("An earlier entry in this release says …; that describes the face before this change"). The other file was outside this card's file surface, so it was not edited. If deleting that last bullet reads better in the compiled CHANGELOG, it is a one-line follow-up.AutomationEngine.renderFlowTextSlotinpackages/services/service-automation/src/engine.tssays the single brace is refused "for the keys that judge covers". That is still true, and the judge now covers all three keys, so the hedge could go. Not edited (outside the surface).config.title, the heading falls back tonode.label, which is plain text and never judged on the source. Its translation is still judged, because the engine renders whatever it picks throughrenderTextSlot. The docblock says so.content/docs/ui/translations.mdxflows bullets already say a translation keeps the holes of the text it translates, and nothing there is made false. The generatedreferences/system/translation.mdxdid not move.Generated by Claude Code