Skip to content

fix(cli): os i18n extract seeds a field help entry from its description when inlineHelpText is empty - #29280

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-eps5bg
Nov 2, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-eps5bg

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread
Fixes #29221

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:os i18n extract 为字段 help 条目取默认语言原文时,改为取 help、inlineHelpText、description 中第一个真正写了内容的值;空字符串视为没写,与 console 帮助行 inlineHelpText || description 的读法一致。
  • 为什么改:原来的 ?? 链停在 inlineHelpText: '' 上,这类字段的 help 条目没有原文,console 实际显示的 description 从未进入翻译包。
  • 风险与回滚:只影响 cli 抽取器给 help 条目选原文;非空 inlineHelpText 仍优先于 description,help 仍最优先,全空字段照旧。仓库内没有任何非测试源码写 inlineHelpText: '' 或 help: '',已提交的翻译包不变。回滚即撤掉本 PR。
  • 先看:packages/cli/src/utils/i18n-extract.ts 中 collectExpectedEntries 的字段 help 那一段;packages/cli/test/i18n-extract-inline-help-text.test.ts 新增的 describe。
  • 两分钟验证:pnpm --filter @objectstack/cli exec vitest run --project unit test/i18n-extract-inline-help-text.test.ts,应看到 15 条全过。
  • 需要你做的:无,席位落地。(卡片的 Edge 2 按派单只做读数、不改代码,见 Acceptance notes。)

What changes

collectExpectedEntries (packages/cli/src/utils/i18n-extract.ts) seeds a field's objects.OBJECT.fields.FIELD.help entry from the first authored link of help, inlineHelpText, description, read through asAuthoredLabel, the one "authored" test the file already uses (a non-empty string, or a non-empty inline locale map). Before, it read field.help ?? field.inlineHelpText ?? field.description, which stops at an empty string.

  • inlineHelpText: '' or help: '' beside a description now scaffolds the description, which is the line the console draws (fieldHelpText in @object-ui/fields: inlineHelpText || description).
  • Unchanged: help wins over both; a non-empty inlineHelpText wins over description; a field whose links are all empty gets the same seed-less entry as before.
  • Whitespace-only text stays authored. That matches the console's || (a single space is truthy, so it is the line drawn) and the resolver's non-empty check in PR fix(spec): serve a field's translated help on its authored inlineHelpText #29217. Skipping it here would make the extractor disagree with both.
  • Changeset: .changeset/29221-i18n-extract-field-help-empty-link.md, a patch for @objectstack/cli.

The os i18n check coverage gate reads the same entries, so a field with inlineHelpText: '' and a description no longer reports its default locale as missing help.

Tests

New describe in packages/cli/test/i18n-extract-inline-help-text.test.ts (9 tests, beside the existing order pins):

  • inlineHelpText: '' with description: 'B' seeds help = 'B'; help: '' likewise; help: '' with an inlineHelpText seeds the inlineHelpText;
  • a non-empty inlineHelpText still wins over description; help still wins over both (with and without an empty inlineHelpText);
  • whitespace-only inlineHelpText stays the source, and the restated console reader draws it too;
  • every link empty still emits the seed-less entry;
  • for five field shapes that author no help, the seed equals the line the console draws (inlineHelpText || description, restated);
  • computeI18nCoverage counts the default locale as covered when the seed is the description behind an empty inlineHelpText.

No forced edits to existing tests.

Acceptance notes

  • One rule, every site (Edge 1, measured). The field help entry is the only place in the extractor that derives help text through a fallback chain. The other help keys each read one key with no fallback: flow screen fields' inlineHelpText (the screen field schema has no description), form section entries' helpText, action params' helpText, bulk params' help, metadata-form fields' helpText. Each already goes through asAuthoredLabel or pushOptional, so an empty string authors no entry there. i18n-coverage.ts has no derivation of its own.
  • Committed bundles (measured). No non-test source in the repo authors inlineHelpText: '' or help: '' (git grep hits only CHANGELOG prose and the @object-ui/fields README/docblock). check:i18n (the nine generated bundle packages) and check:i18n-coverage are in the gate run below; nothing was regenerated.
  • Edge 2 (reading, no code). A field authoring both a non-empty inlineHelpText and a different description gets one help entry, seeded from the inlineHelpText, and the resolver serves its translation on description too. Readers of a field's description besides the console's help line, read on this head:
    • Studio designer surfaces: the field inspector's description box (ObjectFieldInspector), the form canvas preview (ObjectFormCanvas), the data-model designer's row tooltip (DataModelDesigner.fieldTooltip), the property editor (plugin-designer PropertyEditor), and MetadataService.toFieldPayload, which writes description back on save.
    • RecordDetailPanel copies description onto each detail field; no renderer in plugin-detail draws a field's description (grep on this head).
    • The runtime's action summary for the MCP tool list (summarizeActionParams) uses a backing field's description as a param's help when the param has no helpText. It reads the registry's object, not a localized serve (traced to the MCP domain's declarations loop; not traced end to end).
    • A separate description entry from the extractor would need a bundle key that FieldTranslationSchema does not declare, which is a packages/spec change. The fix that needs no new key is in the resolver (translateObject): serve the help entry on description only when the field authors no non-empty inlineHelpText. That is spec seat 1's surface and reverses what the i18n: a field's translated help never shows when the field authors inlineHelpText — the server serves fields.FIELD.help on description and the console draws inlineHelpText first #29153 ruling kept, so it is reported to the seat, not done here.

Verification

Head 3298a9c9, the merge of origin/main at 01252a45. Heavy runs went through os-verify-lock.sh.

cli package: unit tests, typecheck, lint
  • pnpm --workspace-concurrency=2 --filter '@objectstack/cli^...' build: VERDICT command-exit 0.
  • pnpm --filter @objectstack/cli build && pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 && pnpm --filter @objectstack/cli typecheck on a6ea00b0 (the commit before the merge): Test Files 372 passed (372), Tests 5259 passed (5259); typecheck and check:test-typecheck OK. VERDICT command-exit 0. The integration tier is CI's (no integration-tier file or spawn entry touched).
  • After merging origin/main (it touched no cli file; it touched packages/spec and service-analytics, both rebuilt): the two extractor files, Test Files 2 passed (2), Tests 39 passed (39). VERDICT command-exit 0.
  • eslint --no-inline-config on the two touched cli files (the root pnpm lint command, scoped): exit 0, no output.
Ablation: the old chain turns the new pins red

scripts/ablation-replace.mjs put field.help ?? field.inlineHelpText ?? field.description back over the new chain (anchor 1 to 0, blob 21178839abf6 to 1ccf82f67d58), ran the test file through the lock and restored (git diff HEAD empty after). The test imports the extractor from src, so no build was involved.

  • Tests 5 failed, 10 passed (15). The five: inlineHelpText: '' beside a description, help: '' beside a description, help: '' beside an inlineHelpText (each expected undefined to be 'B' or 'I'), the console-agreement matrix (expected undefined to be 'B'), and the coverage case (expected [ { severity: 'error', … } ] to deeply equal []).
  • The pins that stay green under the ablation are the preservation pins (help wins, non-empty inlineHelpText wins, whitespace stays, all-empty is seed-less): they hold before and after, as intended.
dispatch-gates
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on 3298a9c9: 61 commands (the same list as on a6ea00b0, before the merge). Each ran with stdin closed; 58 exited 0 on the first pass.
  • Three exited 3 (prerequisite, not a finding) and pass on a rerun: check-plugin-teardown-shape.mjs --self-test after fetching its pinned fixture commit; check:dual-build-cjs-loads and check:i18n-coverage after turbo run build --filter='./packages/**' --filter='./examples/**' (VERDICT command-exit 0). check:i18n-coverage: "OK (14 config(s), 426 baselined untranslated string(s), none new)"; check:i18n exit 0. The working tree stayed clean: nothing was regenerated.
  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE (each line carrying its exit code): "61 derived famil(ies) accounted for — 61 run, 0 NOT-MEASURED (a DERIVED zero — all 61 recorded an exit code and none of them is 3)". Exit 0.
  • Not measured locally: the CI-only families and scheduled jobs --commands does not list. Those are CI's readings.

Generated by Claude Code

The extractor scaffolded objects.O.fields.F.help from
`help ?? inlineHelpText ?? description`, which stops at an empty
string. The console draws `inlineHelpText || description`, so for a
field with `inlineHelpText: ''` the drawn description was never offered
for translation. Each link now counts only when it carries authored
text (asAuthoredLabel), the rule the rest of the file already uses.

Claude-Session: https://claude.ai/code/session_01ADHkmT5Q35nWM5fCTsoR5H
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tests tooling labels Nov 2, 2026
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 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
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 101 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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 — 35 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 8a8a03d3533c76de53a43ce745064877cbded7a8 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 8a8a03d3533c76de53a43ce745064877cbded7a8

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1

维护者速读

  • 改了什么:os i18n extract 给字段 help 词条取种子时,取第一个「有内容」的来源(help → inlineHelpText → description),空字符串视为没写,与控制台显示规则一致。
  • 为什么改:字段写了 inlineHelpText: '' 和 description 时,控制台显示 description,但抽取器停在空串上,这段说明从未进入翻译包。
  • 风险与回滚:优先级不变;只纯空白仍算已写;已提交的语言包 0 处变化。回滚即撤销本 PR。
  • 先看:packages/cli/src/utils/i18n-extract.ts 约 L1499 的 asAuthoredLabel 链。
  • 两分钟验证:pnpm --filter @objectstack/cli exec vitest run test/i18n-extract-inline-help-text.test.ts。
  • 需要你做的:无,席位落地。

席位意见:与 diff 相符,只改抽取器(卡的 Edge 1)。Edge 2(解析器把 help 词条也用在 description 上)按派单只做读数,转 spec 车道,不在本 PR。

席位核对

ACCEPT · domain:server seat 5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1 · 2026-11-02T10:57Z · head 3298a9c9 · #29221

  1. Shape: draft against main, Fixes #29221 (after the harness attribution block), no other card number beside a closing keyword; patch changeset for @objectstack/cli; Clause-②: no, Metadata-protocol: none. Not governed, +94 / -1, 3 files. No packages/spec/**.
  2. Against the order: the help seed takes the first authored link through the file's one "authored" test (asAuthoredLabel), so '' reads as absent as in the console's inlineHelpText || description; precedence help > inlineHelpText > description unchanged; dev measured it as the extractor's only help fallback chain. Read in the diff.
  3. Accepted deviations: none beyond the attribution block's position.
  4. Gates: dev reports cli 5259 tests green, typecheck and scoped eslint clean, check:i18n / check:i18n-coverage green with 0 committed bundles changed, 61/61 derived gates exit 0 (0 NOT-MEASURED); one ablation turns 5 of 15 pins red, restored. Check-runs on this head were still running at ACCEPT; the seat readies it only after Lint & Repo Gates and TypeScript Type Check read success and no check is red.
  5. Out of scope: Edge 2 (the resolver serves the help entry on description too, so a field authoring both texts shows the inlineHelpText translation where description is read) → handed to the domain:spec lane, which holds translateObject and the i18n: a field's translated help never shows when the field authors inlineHelpText — the server serves fields.FIELD.help on description and the console draws inlineHelpText first #29153 ruling.
  6. Landing: pr_ready + automerge_enable through the relay once CI is green.

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

Projects

None yet

1 participant