fix(platform-objects): decide the object field-editor panel en-echoes per leaf, and derive the panel pin - #19418
Conversation
… per leaf Translated-locale values only; no keys added or removed. Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE Co-authored-by: Claude <noreply@anthropic.com>
…s, and derive the panel pin Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NcPSwnmJHczmTu6FG7NMjE Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 3 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 96702c7dd142f2d5195dcc67b4f08f6cedaddf10 && git checkout 96702c7dd142f2d5195dcc67b4f08f6cedaddf10
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5a5e710fb9b5bd973d44f9a5d3466d94ddaf4fa2 da144a4d7322aa6808f31a69e58a6f2eaee5d934 && git checkout -B drift-repro 5a5e710fb9b5bd973d44f9a5d3466d94ddaf4fa2 && git merge --no-ff da144a4d7322aa6808f31a69e58a6f2eaee5d934
node scripts/docs-audit/affected-docs.mjs --json 5a5e710fb9b5bd973d44f9a5d3466d94ddaf4fa2 |
Part of #19403
Clause-②: no
What this is
One panel family, decided leaf by leaf. The card's deliverable is a per-leaf judgement with its reason recorded, not 84 translations; every leaf below was decided on its own evidence, and the verdicts with their reasons live in a ledger the suite reads (
object-field-editor-panel-echo-decisions.test.ts), not only in this body.The family: the object field editor —
object.fields['fields.*'], the panel the card samples (placeholder,valueDomain,rows,deleteBehavior,expression…). It is the most trafficked authoring surface in Studio: every field an author creates is typed into it.⛔ Not
Fixes— this is one family of the card's population, and the card outlives this round. Eight families remain (65 label keys), listed at the foot of this body.The reading, re-taken on this base — the card's numbers are NOT inherited
Census instrument: flatten every
*.metadata-forms.generated.tsbundle and compare.labelleaves against theenbundle. Population fixed here: the.labelleaves of the metadata-form catalogs (the seat's population, 538 leaves), plus the siblinghelpTextleaf of every key this round decides — a panel whose field name is Chinese and whose tooltip is English is the same defect half fixed.Measured on worktree base
0862063ba:enstring leaves in the metadata-form catalogs.labelleaveszh-CN) · 443 (ja-JP) · 443 (es-ES)ensource, per locale⭐ The control is what makes the echo count mean something: 459 translated against 79 echoing is a discrimination, not a parser that matches everything.
This is 79 / 237, not the card's 83 / 249, and the difference is fully accounted for: the card's table was taken before PR #19401 landed, which translated exactly four label leaves (
report.fields.dataset/values/rows,dataset.fields.measures). 83 − 4 = 79, and the control moves the same way (455 → 459, 439 → 443, 439 → 443), matching the reading the dispatching seat took after #19401 to the unit. The other count on the card (82 / 230, over 417 declared form-field paths, older base) answers a different question and is not comparable — stated there, confirmed here.After this change: 65 keys / 195 leaves echo in all three locales; control 473 · 457 · 457.
The verdicts — 14 keys × 2 string leaves × 3 locales = 84 decisions
Every one is
translate, and not one of them is "translate because it is English". The echo direction was tested per leaf by asking what this catalog's own authored corpus answers for that term:placeholder(action.fields.params.placeholder.labelreads 占位文本 / プレースホルダー / Marcador de posición),deleteBehaviorandexpressionandsummaryOperations(all three authored on the siblingfieldpanel, label and helpText),summaryOperations.object/.field(nine and seven other paths),visibleWhen(two other paths).fields.rows.label.report.fields.rows.labelis authored as 行 / 行 / Filas, but that leaf names a pivot axis and this one is a COUNT of text rows (its own helpText says so), sozh-CNandja-JPdepart to 行数 whilees-ESkeeps Filas, which carries both senses. This is the leaf that shows the judgement is per leaf and not per string.lookupinlookupFilters(the authoredfields.reference.helpTextkeepslookupandtreein all three locales),countinsummaryOperations.field.helpText,CEL, and the autonumber literals"INV-{0000}"/{YYYY}/{MM}/{DD}/{field_name}.summaryOperations.function.labelis rendered as the plain word (函数 / 関数 / Función), deliberately not as "aggregation function", because the source saysFunctionat the label andAggregation functionat the helpText one line below.source-hashes.generated.tsprovenance table in all three locales, while every authored neighbour on the same panel carried none — which is what an unauthored extractor fill looks like here.Per-leaf reasons for all 28 leaves are in the ledger; this list is the summary, not the record.
The instrument, reused not reinvented
object-field-editor-panel-echo-decisions.test.tsis the #19355 shape: one row per leaf, verdict per locale, reason, and theensource pinned EQUAL to the live bundle so rewording the English reds the row instead of leaving a decision standing over text nobody judged. Anechoverdict must carry its own per-locale departure reason.Two additions, both because this round would otherwise ship a check that cannot fail:
translate, so asserting the rule over the ledger alone evaluates nothing — the suite feeds it a syntheticechorow and asserts it is refused, then asserts it is accepted once a departure reason is added.repeater-row-properties.test.tsalready refuses an en-echo inside its population, and it filters onspec.type === 'repeater'; the object form declares its field editor as{ field: 'fields', type: 'record' }with 35 children, so that derivation yields exactly four row properties forobjectForm(fields.options.*) and none of the 14 keys here — the card's "0 of the keys sit inside it", measured rather than assumed. The new file walks theobject.fields['fields.*']leaves out of theencatalog and refuses any that reads its source without a ledger row saying so, so a re-fill or a field added to the editor tomorrow is red on the day it lands.Regeneration — the silent-loss hazard on this path
Translated-locale values are hand-written; the provenance tables are not. Regenerated with
pnpm i18n:extract(the command this package's extract config documents), never by hand, and no key was added or removed — so themerge=os-regengap on*.metadata-forms.generated.ts(git check-attr mergereadsunspecifiedon these paths) has nothing to lose even in principle. The bundle diff is 84 insertions / 84 deletions, leaf for leaf; the extract's own diff is 84 deletions and zero insertions — the 28 provenance rows per locale that now record these leaves as authored.pnpm check:i18ngreen is the proof that the hand-written values are byte-identical to what a fresh extract writes, across all nine bundle sets.Reverse verification — the new pin can fail
Committed first, then ablated through
scripts/ablation-replace.mjs(anchor must hit; on-disk counts and blob hashes are the tool's own verdict), one decided leaf reverted to its English source inzh-CN:label: "占位文本",x1 · after: x0,label: "Placeholder",x1 → x2, blob8f480a6bbe30→6bd6a8c0b277zh-CN object.fields.fields.placeholder.label reads its en source again) AND the derived panel pin, independently8f480a6bbe30== HEAD,git diff HEADempty, working tree cleanVerification
pnpm --filter @objectstack/platform-objects test— 44 files, 610 passedpnpm --filter @objectstack/platform-objects typecheck— green (itstsconfig.test.jsonprogram reaches the new file;--listFilesconfirms it, and it contributes no errors)pnpm check:i18n— green, 9 packages in sync (prerequisite closure built first; its first run exited 3, PREREQUISITE NOT MET, which is not a reading)pnpm lint— repo-wide, exit 0, atda144a4dscripts/pm/dispatch-gates.mjsand reconciled with--ran: 59 derived · 58 run green · 0 unrun · 1 NOT MEASURED —pnpm check:dual-build-cjs-loadsexits 3 (PREREQUISITE NOT MET: it reads built output for every package in the repo). That prerequisite is a whole-repo build, which is CI'sBuild Core, and this diff adds no package and changes no export.Changeset, measured
@objectstack/platform-objectsisprivate: falsewithfiles: ["dist", …], andsrc/metadata-translations/index.tsimports all three translated bundles. Probed on the built output: a new leaf (Dominio de valores, and 占位文本 in its esbuild-escaped form) is present indist/metadata-translations/index.js,dist/index.jsanddist/plugin.js, with a pre-existing translated leaf as the positive control and an invented string as the negative control. ⇒ the diff publishes,skip-changesetwould be wrong, and apatchchangeset is included.\uXXXX. A probe with no positive control would have read that as "this does not publish".The eight families that remain (65 label keys, all-three-locale echoes, this base)
page.fields.interfaceConfig.*(+page.sections.interface)object.fields.enable.*(+validations) — the object capabilities panelfield.fields.*— the standalone field panel (several keys now have a precedent from this round)dataset.fields.*+dataset.sections.*+dataset.labelaction.fields.*hook.fields.*seed·mapping·api·doc·book·capabilityreport.fields.*+report.sections.dataset_bindingAcceptance notes
Noted, not filed:
report.sections.*(label and description) — the population the card records as having no home. Still uncounted by any.labelcensus and unseen by any pin. Successor: thereportfamily row above, if the seat dispatches it.deleteBehaviortwice under its field editor, with mutually exclusivevisibleWhenpredicates (data.type == 'lookup'anddata.type == 'master_detail') and different option sets. Checked because a duplicate child looked like a defect: it is deliberate, both copies carry the same label and helpText, and the extractor emits one key. No finding.fields.lookupFiltersdeclares notype(it carrieswidget: 'json'). Legal —FormFieldBaseSchema.typeis.optional()with "auto-infers widget if omitted". No finding.--source-hashes), so their generated leaves stay legacy-trusted. Documented in the extract config; not this card's.Generated by Claude Code