Skip to content

fix(platform-objects): decide the object field-editor panel en-echoes per leaf, and derive the panel pin - #19418

Merged
huangyiirene merged 3 commits into
mainfrom
claude/issue-19403-object-field-editor-en-echoes
Sep 20, 2026
Merged

huangyiirene merged 3 commits into
mainfrom
claude/issue-19403-object-field-editor-en-echoes

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

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 editorobject.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.ts bundle and compare .label leaves against the en bundle. Population fixed here: the .label leaves of the metadata-form catalogs (the seat's population, 538 leaves), plus the sibling helpText leaf 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:

reading result
en string leaves in the metadata-form catalogs 893
of which .label leaves 538
⭐ POSITIVE CONTROL — labels genuinely translated 459 (zh-CN) · 443 (ja-JP) · 443 (es-ES)
labels echoing the en source, per locale 79 · 95 · 95
label keys echoing in ALL THREE locales 79 ⇒ 237 leaves

⭐ 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:

  • Same-string precedent, authored in all three localesplaceholder (action.fields.params.placeholder.label reads 占位文本 / プレースホルダー / Marcador de posición), deleteBehavior and expression and summaryOperations (all three authored on the sibling field panel, label and helpText), summaryOperations.object / .field (nine and seven other paths), visibleWhen (two other paths).
  • Decided AGAINST a same-string precedentfields.rows.label. report.fields.rows.label is authored as 行 / 行 / Filas, but that leaf names a pivot axis and this one is a COUNT of text rows (its own helpText says so), so zh-CN and ja-JP depart to 行数 while es-ES keeps Filas, which carries both senses. This is the leaf that shows the judgement is per leaf and not per string.
  • Machine tokens kept English inside translated proselookup in lookupFilters (the authored fields.reference.helpText keeps lookup and tree in all three locales), count in summaryOperations.field.helpText, CEL, and the autonumber literals "INV-{0000}" / {YYYY}/{MM}/{DD} / {field_name}.
  • A distinction the source draws, preservedsummaryOperations.function.label is rendered as the plain word (函数 / 関数 / Función), deliberately not as "aggregation function", because the source says Function at the label and Aggregation function at the helpText one line below.
  • Panel-internal control for the prose leaves: each carried an entry in this package's per-locale source-hashes.generated.ts provenance 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.ts is the #19355 shape: one row per leaf, verdict per locale, reason, and the en source pinned EQUAL to the live bundle so rewording the English reds the row instead of leaving a decision standing over text nobody judged. An echo verdict must carry its own per-locale departure reason.

Two additions, both because this round would otherwise ship a check that cannot fail:

  1. The echo-reason rule is a predicate with a dark control. Every verdict here is translate, so asserting the rule over the ledger alone evaluates nothing — the suite feeds it a synthetic echo row and asserts it is refused, then asserts it is accepted once a departure reason is added.
  2. The panel population is DERIVED, which is the structural half the card asks for. repeater-row-properties.test.ts already refuses an en-echo inside its population, and it filters on spec.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 for objectForm (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 the object.fields['fields.*'] leaves out of the en catalog 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 the merge=os-regen gap on *.metadata-forms.generated.ts (git check-attr merge reads unspecified on 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:i18n green 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 in zh-CN:

  • before: label: "占位文本", x1 · after: x0, label: "Placeholder", x1 → x2, blob 8f480a6bbe306bd6a8c0b277
  • suite under the mutation: 2 failed | 9 passed — the enumerated verdict (zh-CN object.fields.fields.placeholder.label reads its en source again) AND the derived panel pin, independently
  • restored: blob back to 8f480a6bbe30 == HEAD, git diff HEAD empty, working tree clean
  • suite restored: 11 passed

Verification

  • pnpm --filter @objectstack/platform-objects test — 44 files, 610 passed
  • pnpm --filter @objectstack/platform-objects typecheck — green (its tsconfig.test.json program reaches the new file; --listFiles confirms it, and it contributes no errors)
  • pnpm check:i18ngreen, 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, at da144a4d
  • Gate families derived with scripts/pm/dispatch-gates.mjs and reconciled with --ran: 59 derived · 58 run green · 0 unrun · 1 NOT MEASUREDpnpm check:dual-build-cjs-loads exits 3 (PREREQUISITE NOT MET: it reads built output for every package in the repo). That prerequisite is a whole-repo build, which is CI's Build Core, and this diff adds no package and changes no export.

Changeset, measured

@objectstack/platform-objects is private: false with files: ["dist", …], and src/metadata-translations/index.ts imports all three translated bundles. Probed on the built output: a new leaf (Dominio de valores, and 占位文本 in its esbuild-escaped form) is present in dist/metadata-translations/index.js, dist/index.js and dist/plugin.js, with a pre-existing translated leaf as the positive control and an invented string as the negative control. ⇒ the diff publishes, skip-changeset would be wrong, and a patch changeset is included.

⚠️ Worth one line for whoever probes this dist next: the first probe searched for the literal CJK and found nothing, including the positive control — tsup emits non-ASCII as \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)

family keys
page.fields.interfaceConfig.* (+ page.sections.interface) 15
object.fields.enable.* (+ validations) — the object capabilities panel 9
field.fields.* — the standalone field panel (several keys now have a precedent from this round) 9
dataset.fields.* + dataset.sections.* + dataset.label 12
action.fields.* 6
hook.fields.* 5
metadata-type display names with no panel: seed · mapping · api · doc · book · capability 6
report.fields.* + report.sections.dataset_binding 3

Acceptance notes

Noted, not filed:

  • report.sections.* (label and description) — the population the card records as having no home. Still uncounted by any .label census and unseen by any pin. Successor: the report family row above, if the seat dispatches it.
  • The object form declares deleteBehavior twice under its field editor, with mutually exclusive visibleWhen predicates (data.type == 'lookup' and data.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.lookupFilters declares no type (it carries widget: 'json'). Legal — FormFieldBaseSchema.type is .optional() with "auto-infers widget if omitted". No finding.
  • The nine bundle sets other than this one still do not opt into the provenance table (--source-hashes), so their generated leaves stay legacy-trusted. Documented in the extract config; not this card's.

Generated by Claude Code

… 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>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Sep 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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): node scripts/docs-audit/affected-docs.mjs --json 5a5e710fb9b5bd973d44f9a5d3466d94ddaf4fa2packageMentionDocs.

Which tree this was computed on

This run read content/docs from 96702c7dd142f2d5195dcc67b4f08f6cedaddf10 — the merge of head da144a4d7322aa6808f31a69e58a6f2eaee5d934 into base 5a5e710fb9b5bd973d44f9a5d3466d94ddaf4fa2, 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 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants