Repository navigation
feat(formula): label(record.<field>) renders a select value in the reader's locale - #26755
Conversation
…ader's option label A CEL macro that reads the field name off its argument and answers from a pure-data EvalContext.optionLabels map the caller resolves in the reader's locale. Added to the advertised catalog. Claude-Session: https://claude.ai/code/session_01WGufzfay61tE4MHpZNiV4t Co-authored-by: Claude <noreply@anthropic.com>
The engine pins the option labels a formula's label(record.<field>) reads for each read and write it serves: the object's declared options with the i18n bundle laid over them by translateObject, in ExecutionContext.locale (the request's Accept-Language). Stored expressions get the declared labels. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfay61tE4MHpZNiV4t
…t-Language; changeset Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfay61tE4MHpZNiV4t
…d bound Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfay61tE4MHpZNiV4t
📓 Docs Drift CheckThis PR changes 2 package(s): 26 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 10 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 25 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 37d5eb6037c6db34d5546c8cb6ff68412747c005 && git checkout 37d5eb6037c6db34d5546c8cb6ff68412747c005
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a485b5a3e28d84550e8a33bbb5cfc2d52c82ff9e 8ffe3ae67856cc968eb7ae61788f1b2f6611cac3 && git checkout -B drift-repro a485b5a3e28d84550e8a33bbb5cfc2d52c82ff9e && git merge --no-ff 8ffe3ae67856cc968eb7ae61788f1b2f6611cac3
node scripts/docs-audit/affected-docs.mjs --json a485b5a3e28d84550e8a33bbb5cfc2d52c82ff9e
|
Contract reviewServed-tier: Inputs read: card #26608 (body, triage 6440185938, claim 6442789358, amendment 6442958772, os-dev-report 6444975664), PR #26755 body and file list, the net diff ① Derived judgmentsEach accept-set or public-surface change the diff implies, named right or wrong against the tree:
② Semver levelChangeset ③ Boundary flags
④ Design judgment
Attempts to break it, with the outcome of each:
Findings
Check-runs on Implemented-by: VERDICT: PASS |
…he skill row says formula fields only Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzfay61tE4MHpZNiV4t
席位核对 · ACCEPT ·
|
Requested by Yibo Wang · project thread
Fixes #26608
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: #26760
维护者速读
label(record.<field>),把 select 字段的存储值渲染为读者语言的选项标签;objectql 在读和写的响应里按请求的Accept-Language给公式备好标签。'v' + string(record.version_no) + ' · ' + label(record.kind)对 zh-CN 读者是v2 · 清稿,对 en-US 读者是v2 · Clean。v2 · clean,保存提示和记录标题对所有语言的读者都显示英文值(A record name composed of select option labels cannot render in the reader's locale — no option-label function in CEL, and the save toast and related lists show the raw or stored string #26608)。label(的公式才会构建标签表,其余读路径只多一次源码扫描。没有新的翻译键,也没有第二条翻译路径(复用translateObject,与GET /meta/object同一函数)。i18n 服务缺失或抛错时退回声明的标签,读不会失败。回滚即 revert 本 PR。packages/objectql/src/formula-option-labels.ts,然后packages/formula/src/stdlib.ts里的bindOptionLabel。pnpm --filter @objectstack/objectql exec vitest run src/engine-formula-option-label.test.ts,以及packages/qa/dogfood下的test/formula-option-label-locale.dogfood.test.ts。skills/**(Tier H),落地需要你的同意。What changes
@objectstack/formulalabel(record.<field>)joins the CEL stdlib andCEL_STDLIB_FUNCTIONS. It is a macro: the type check accepts exactlyrecord.<field>and refuses a literal, a path through a lookup, an optional select,previous.<field>and any computed argument, with a message that names the one shape.EvalContext.optionLabels(new, typeEvalOptionLabels: field → stored value → label) is resolved by the caller in the reader's locale before evaluation, so the stdlib holds no translator or resolver. It is not mounted as a CEL variable.'', and a value no option declares reads as itself, as the select control shows it. A field with no options (on every row, empty or not), a list value (multi-select), and a context with nooptionLabelsfail loudly.validate.tsare updated (77 registered names, 42 callable bare, 30 fromregisterStdLib, 38 advertised). The receiver-only count also picks uphasCapability, which was already registered on main and missing from the prose.@objectstack/objectqlformula-option-labels.tsbuilds the map for one read: the object's fields that declareoptions, with the i18n service's bundle laid over them bytranslateObjectfrom@objectstack/spec/system. The bundle and the locale options (fallbackChain,defaultLocale) are built as the REST metadata route builds them, so the label a formula prints is the label the select control shows the same reader:objects.<o>.fields.<f>.options.<v>, thenpicklists.<p>.options.<v>, then the declared label. No key is spelled in this module.applyFormulaPlanpasses the map toExpressionEngine.evaluate. The map is built only when some formula in the plan callslabel(.ExecutionContext.locale(the request'sAccept-Language):find,findOne, the insert and update responses (hydrateWriteFormulas), andserveFormulaField.expandRelatedRecordsalready runs afindwith the read's context, so expanded lookups follow.computeStoredExpressions), the synchronousevaluateFormulaField, any read with no locale, and an engine with no i18n service or one whosegetTranslationsthrows.setI18nServiceaccepts the optionalgetTranslations,getFallbackLocaleandgetDefaultLocaleit now reads.II18nServicealready carries all three.Docs and skill
skills/objectstack-formula/SKILL.mdgains an "Options" row forlabel(record.f)(+116 tokens, 5627 of 5997). The row says it answers only in formula fields and faults in a predicate.content/docs/data-modeling/formulas.mdxgains the stdlib row and a cookbook entry, "Record name from an option label".Skills readings
skills/objectstack-formula/SKILL.mdlinesskills/objectstack-formula/SKILL.mdtokens (ceiling 5997)SKILL.md, linesSKILL.md, tokens (ceil(bytes / 4))evals/cel-predicates-formulas.jsontokens (ceiling 1046)Readers (measured)
recordSaveMessage, app-shell)Accept-LanguagegetRecordDisplayName)GET /data/:object/:id→findOnefindexpand→findwith the same contextSEARCH_VIRTUAL_TYPES)text+expression)evaluateFormulaField(sync, e.g. a hook's record-title accessor)No
ui/**file changes: the console renders the name the server serves. The console sends its display language asAccept-Languageon every call.Tests
packages/formula/src/option-label.test.ts(new): zh-CN and en-US maps, per-field maps, empty and undeclared values, the run-time faults, the compile-time refusals, andinferExpressionTypereturningtext.packages/formula/src/cel-engine.test.ts:labeljoins the drift probe.packages/objectql/src/engine-formula-option-label.test.ts(new): the insert response in zh-CN and en-US, the update response,findOneandfindper locale, a base-language tag (zh), no locale, a stored expression keeping the declared label under a zh-CN writer, and no i18n service or a throwing one.packages/qa/dogfood/test/formula-option-label-locale.dogfood.test.ts(new, real boot): POST in zh-CN and en-US, GET by id in both, PATCH in zh-CN, the list read in zh-CN, and the name matching the option labelGET /meta/object/:nameserves in the same locale.Acceptance notes
label()in a validation rule, predicate or flow condition has no option labels and faults loudly instead of comparing against a label; the docs say to compare the stored value there. No publish-time refusal oflabel()outside a formula is built in this PR.cel-predicates-formulas.jsonis at its ceiling (1046 of 1046), and a compact case measures about 130 tokens.Verification
All runs on
f780ce78, in a cloud worktree, underscripts/pm/os-verify-lock.sh, unless a section names8ffe3ae6.Unit tests:
@objectstack/formulaand@objectstack/objectql@objectstack/formula:option-label.test.ts,cel-engine.test.tsandskill-catalog-sync.test.tspass (94 tests), andcel-stdlib-drift.test.tspasses. The catalog counts invalidate.tswere measured on the built environment (77 / 42 / 35 / 30 / 38).@objectstack/objectql:engine-formula-option-label.test.tspasses 7/7; with the otherengine-formula*files, 8 files and 135 tests pass.Dogfood on a real boot
@objectstack/dogfood:formula-option-label-locale.dogfood.test.tspasses 5/5.Typecheck
typecheckpasses for@objectstack/formulaand@objectstack/objectql(includingcheck:test-typecheck).tsc --noEmitpasses for@objectstack/dogfoodwithNODE_OPTIONS=--max-old-space-size=8192; at the default heap it runs out of memory locally, before reporting anything.Derived gates: 102 commands, 101 measured green, 1 not measured
node scripts/pm/dispatch-gates.mjs --commandsderives 102 commands; 99 exit 0 on the first run.check:objectql-double-limitfound that the new test'sfinddouble ignored the caller'slimit. Fixed inf780ce78, and it now reports OK.spec check:skill-examplesexited 3 because the client SDK was not built. After building@objectstack/client-reactand its dependencies it passes: 266 examples type-check.check:dual-build-cjs-loadsexits 3 because it needs a whole-repo build; nothing was measured. That is left to CI.--ranwith exit codes: 102 derived families accounted for, 101 run, 1 NOT-MEASURED (the recorded exit 3).Contract-review should-fixes on
8ffe3ae6optionLabelOfchecks the field's options before the empty-value answer;option-label.test.tspins a null text field faulting.@objectstack/formula:option-label,cel-engine,skill-catalog-syncandcel-stdlib-driftpass (4 files, 98 tests);typecheckpasses.@objectstack/objectql:engine-formula-option-label.test.tspasses 7/7 against the rebuilt formula package.spec check:skill-docs,check:skill-identifier-liveness,check:skill-compatibility,check:skill-frame-sync,check:skill-top-level-keys,check:nul-bytes,check-skills-token-ratchet,spec check:skill-examples.Skills token ratchet
check-skills-token-ratchetexits 0. The readings are in the Skills readings table above.🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzfay61tE4MHpZNiV4t