Skip to content

fix(spec,app-shell): zh-CN view panel reads Chinese on every row of its collapsed sections - #30356

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-nq73a8
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-nq73a8

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Fixes #30267

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:中文控制台下,Studio 视图属性面板里「表格选项 / 分页 / 终端用户控制 / 导航与共享」四个折叠区块展开后的行名与说明全部变成中文(实测英文行 70 → 9,剩下 9 个是「允许的可视化」里的类型选项名 Grid / Kanban 等);英文控制台文字不变。
  • 为什么改:这些区块的子项在 spec 表单里没有逐项声明,翻译提取器只认已声明的子项,所以它们从来没有翻译键,任何语言都显示英文;Studio on zh-CN still mixes languages and terms: view properties panel, flow nodes, publish sheet labels, and 仪表板 beside 仪表盘 #30039 浏览器验收时发现。
  • 风险与回滚:只改表单布局、提示文字与翻译目录,不动数据与接口;面板不再显示两个早已退役的键(导航 view、共享 type),已存的值保存时原样保留。回滚即还原本 PR。
  • 先看:packages/spec/src/ui/view.form.ts(逐项声明子字段与提示)、packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts(中文译文)、ui/packages/app-shell/src/views/metadata-admin/metadata-form-zh-cn.ts(控制台中文副本)。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/metadata-form-i18n.test.ts,预期全部通过,其中 objectstack#30267 一组断言四个区块下每一行的名称与说明都含中文、英文对照组不含中文。
  • 需要你做的:无,席位落地。

What changes

viewForm declared selection, pagination, userFilters, appearance, userActions, navigation and sharing as type: 'composite' with no children. The panel (SchemaForm) then derives the child rows from the JSON Schema, and os i18n extract walks only DECLARED children, so none of those rows had a metadataForms.view.fields['PARENT.CHILD'] key. A zh-CN Studio read 70 English rows across the four collapsed sections.

  • packages/spec/src/ui/view.form.ts spells out every live child of the seven composites (plus hints on addRecord's two existing children), and gives the rows that had no hint one. Every declared hint is byte-equal to the schema .describe() text the panel already showed, so an English console reads the same text. userFilters.fields is enumerated as a repeater with widget: 'grid' and required: true on field, which keeps the inline table it rendered as before (the auto-detected master-detail table), now with translatable column heads.
  • packages/spec/src/ui/view.zod.ts: UserFilterFieldSchema's six properties carry .meta({ title }) (Field / Label / Type / Options / Show Count / Default Values, the strings the panel already printed). The repeater-title guard requires it for any enumerated repeater.
  • Retired keys no longer offered: the schema-derived list rendered the navigation.view and sharing.type tombstones as editable rows; the declared lists omit them, as addRecord already did for its two tombstones. A stored value is not dropped by a panel save: CompositeField writes { ...obj, [child]: v } (SchemaForm.tsx, the composite onChange), so a key the form does not render is carried through unchanged, exactly as before.
  • Catalogs (packages/platform-objects/src/apps/translations/*.metadata-forms.generated.ts): regenerated with node scripts/check-i18n-bundles.mjs --write; every new leaf is translated in zh-CN, ja-JP and es-ES (the catalog-wide echo ledgers hold all three translated locales at zero en echoes). The provenance companions lose the rows of leaves that are now translated, as the regenerator writes them.
  • Console overlay (ui/packages/app-shell/src/views/metadata-admin/metadata-form-zh-cn.ts): copies the platform's zh-CN leaves for every new row, leaf for leaf.

Tests

  • packages/spec/src/ui/view-form-composite-children.test.ts (new): each composite's declared children EQUAL the schema's live keys (tombstones excluded), so a key added to a block later cannot silently drop out of the panel; each declared hint equals its schema description; the repeater's row labels equal the item titles; no retired key is offered.
  • packages/platform-objects/src/apps/translations/view-composite-children.test.ts (new): population derived from the form; every declared child is a key in all four catalogs, the en leaf is the form's text, zh-CN never echoes en.
  • ui/.../metadata-form-i18n.test.ts: new objectstack#30267 group, every row label and hint under the four sections contains CJK in zh-CN, a sample per composite, the two retired rows gone, and an en control that reads the spec English with no CJK.
  • object-lifecycle-panel-echo-decisions.test.ts: positive-control count 701 → 731 (thirty new row labels, authored in all three locales).
  • metadata-form-zod-reconciliation.test.ts: three omit rows for view / userFilters (tabs, showAllRecords, allowAddTab). The walk resolves the block through the view union to the page-shaped UserFiltersSchema; the list arm the form lays out is ObjectUserFiltersSchema, which refuses those keys.

Ablation (each through scripts/ablation-replace.mjs, mutation proven on disk, restored to HEAD):

Mutation Pin that went red
delete the overlay row 'userActions.editInline' ui: "every row label and every hint under the four sections is Chinese", "a sample of each composite's children" (2 failed / 22)
delete the refresh declaration from userActions spec: "userActions: declares exactly the schema's live keys" (1 failed / 20)
zh-CN pagination.pageSize label back to Page Size platform-objects: "zh-CN carries its own text for every leaf" (1 failed / 7)

Acceptance notes

  • Live, example CRM, zh-CN, /studio/com.example.crm/interfaces?surface=view:crm_lead.all, four sections expanded, English lines counted per section: 表格选项 5 → 0, 分页 5 → 0, 终端用户控制 44 → 9, 导航与共享 16 → 0 (70 → 9). The 9 left are the appearance.allowedVisualizations option chips (Grid, Kanban, Gallery, Calendar, Timeline, Gantt, Map, Chart, Tree): enum values the widget prints raw. The metadata-forms catalog has no channel for a form field's option labels (the extractor emits label / helpText / placeholder), so they are reported, not fixed here. en-US after: the same English rows as before, minus the two tombstone rows; the userFilters.fields table still shows Field* Label Type Options Show Count Default Values.
  • File surface beyond the claim's list: view.zod.ts (six titles), the two ledger tests above, and the ja-JP / es-ES catalogs (required by the echo ledgers).
  • Out of scope, unchanged: the authored-label vs translated-rail mismatch noted in fix(app-shell): zh-CN view properties panel reads the empty filter prompt and the description hint in Chinese #30265.

Verification

Commands and readings (final head)

Head f5483a169f (branch merged with origin/main c1240a3 first; git merge-tree against main 8c9f48f is clean).

  • Full build npx turbo run build --filter='!@objectstack/docs' under os-verify-lock: VERDICT command-exit 0 (13m50s).
  • pnpm --filter @objectstack/spec test: exit 0 (an earlier run on this branch failed only metadata-form-zod-reconciliation → fixed by the three ledger rows).
  • pnpm --filter @objectstack/spec typecheck: exit 0 · pnpm --filter @objectstack/spec check:generated: exit 0, all 14 artifacts up to date.
  • pnpm --filter @objectstack/platform-objects test / typecheck: exit 0 / exit 0.
  • pnpm --dir ui exec vitest run packages/app-shell/src/views/metadata-admin/metadata-form-i18n.test.ts: exit 0 (22 tests) · pnpm --dir ui --filter @object-ui/app-shell type-check: exit 0 · eslint on the touched ui and spec/platform-objects files: exit 0.
  • node scripts/check-i18n-bundles.mjs: all 11 bundle sets in sync · check:i18n-stale-fill: OK.
  • dispatch-gates --ran: 84 derived, 84 run, 0 NOT-MEASURED. check-plugin-teardown-shape --self-test first exited 3 (shallow clone could not read its pinned fixture commit); after git fetch origin 621a4876… it exits 0.
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dev-prereqs.mjs --self-test :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm --filter @objectstack/spec run check:api-surface :: exit 0
pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0
pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0
pnpm --filter @objectstack/spec run check:docs :: exit 0
pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm --filter @objectstack/spec run check:empty-state :: exit 0
pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0
pnpm --filter @objectstack/spec run check:export-origins :: exit 0
pnpm --filter @objectstack/spec run check:exported-any :: exit 0
pnpm --filter @objectstack/spec run check:liveness :: exit 0
pnpm --filter @objectstack/spec run check:llms-txt :: exit 0
pnpm --filter @objectstack/spec run check:skill-refs :: exit 0
pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0
pnpm --filter @objectstack/spec run check:variant-docs :: exit 0
pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:i18n :: exit 0
pnpm check:i18n-stale-fill :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:merge-driver :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:pm-prior-rulings :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:spec-docblock-symbol-anchors :: exit 0
pnpm check:spec-parsed-alias :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGufzjGQVFPCizmQreyKkv

…get catalog keys

The view form declared selection, pagination, userFilters, appearance,
userActions, navigation and sharing as composites with no children, so
the i18n extractor emitted no key for any of their rows. Each child is
now declared with the hint the panel already showed (its schema
description), userFilters.fields is enumerated as a repeater with
titled row properties, and the retired navigation.view / sharing.type
tombstones are no longer offered.

Claude-Session: https://claude.ai/code/session_01WGufzjGQVFPCizmQreyKkv
Co-authored-by: Claude <noreply@anthropic.com>
Regenerated the metadata-forms catalogs for the newly declared view
children and translated every new zh-CN leaf. ja-JP and es-ES carry
their own text for the six userFilters.fields row properties (the
repeater-row guard refuses an en echo there); their other new leaves
are en fills, as the extractor leaves them.

Claude-Session: https://claude.ai/code/session_01WGufzjGQVFPCizmQreyKkv
Co-authored-by: Claude <noreply@anthropic.com>
…llapsed sections

The console overlay copies the platform's zh-CN leaves for every newly
declared view composite child, and the metadata-forms catalogs carry
ja-JP and es-ES text for them too (the catalog-wide echo ledger reads
zero in every translated locale). Pins: the overlay group in
metadata-form-i18n.test.ts and a catalog test deriving its population
from the form; the round-10 positive control moves 701 -> 731.

Claude-Session: https://claude.ai/code/session_01WGufzjGQVFPCizmQreyKkv
Co-authored-by: Claude <noreply@anthropic.com>
Before it was enumerated, userFilters.fields rendered as the panel's
auto-detected master-detail table with a required marker on Field.
The declared repeater now asks for the grid layout and marks the
required row property, so the panel keeps that look with translated
column heads.

Claude-Session: https://claude.ai/code/session_01WGufzjGQVFPCizmQreyKkv
Co-authored-by: Claude <noreply@anthropic.com>
…not offer

The reconciliation walk resolves view.userFilters to the page-shaped
UserFiltersSchema; the list arm the view form lays out refuses tabs,
showAllRecords and allowAddTab, so they get omit rows.

Claude-Session: https://claude.ai/code/session_01WGufzjGQVFPCizmQreyKkv
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation protocol:system protocol:ui tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Nov 5, 2026
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/platform-objects, @objectstack/spec, touching 30 documentable anchor(s).

17 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 1013ef11617b578c6525e50300f74f2e0b072b3c.

⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 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; 103 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 — 170 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 1013ef11617b578c6525e50300f74f2e0b072b3c → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 39cf8e9e9420870b402c0cc6127db6f6336693ac — the merge of head fa3694dfa37337f8d125d75bcdde5a9d4c7bbcdd into base 1013ef11617b578c6525e50300f74f2e0b072b3c, 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 39cf8e9e9420870b402c0cc6127db6f6336693ac && git checkout 39cf8e9e9420870b402c0cc6127db6f6336693ac
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1013ef11617b578c6525e50300f74f2e0b072b3c fa3694dfa37337f8d125d75bcdde5a9d4c7bbcdd && git checkout -B drift-repro 1013ef11617b578c6525e50300f74f2e0b072b3c && git merge --no-ff fa3694dfa37337f8d125d75bcdde5a9d4c7bbcdd

node scripts/docs-audit/affected-docs.mjs --json 1013ef11617b578c6525e50300f74f2e0b072b3c

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 1013ef11617b578c6525e50300f74f2e0b072b3c → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-support-ai · session_01WGufzfE4V9fzZgJLrLDmMC

维护者速读(席位终稿)

席位核对 · ACCEPT · head f5483a169f · 2026-11-05T07:25Z

  1. Shape: draft, base main, Fixes #30267. One changeset: @objectstack/spec, @objectstack/platform-objects, @object-ui/app-shell patch, Clause-②: no / Metadata-protocol: none. 13 files, +1024/−74 (four generated catalogs ~580 of it). check-governed-merges --pr 30356: NOT governed. git merge-tree vs main 8c9f48f8 clean.
  2. Read by the seat: route (a) of the Step 1 seam report: view.form.ts declares the seven composites' children with labels/hints equal to the current English screen text; the extractor emits metadataForms.view.fields['<parent>.<child>']; catalogs regenerated via check-i18n-bundles --write; zh rows in the platform bundle, copied by the console overlay. UserFilterFieldSchema gains six .meta({ title }) (English column heads, unchanged on screen). Retired navigation.view / sharing.type are no longer offered; the seat's condition is met: a stored value survives a panel save (the composite editor spreads the block). Shot 30267-after-zh-CN-panel.png reads Chinese through all four sections.
  3. Accepted deviations: file surface beyond the Claim: view.zod.ts (six .meta titles), metadata-form-zod-reconciliation.test.ts (three omit rows), object-lifecycle-panel-echo-decisions.test.ts (ledger 701 → 731), and the ja-JP / es-ES catalogs (translated, not left as en fills). Claim surface amended here.
  4. Evidence: spec + ui pins green; dispatch-gates 84/84 exit 0 with --ran zero NOT-MEASURED. Live zh-CN probe on the example CRM: English rows 70 → 9. CI on this head running at review time; auto-merge waits on it.
  5. Out of scope: the nine allowedVisualizations option chips (Grid / Kanban / …) stay English: the metadata-forms catalog has no channel for a field's option labels; filed separately. Serial with draft PR feat(spec): an action param can name the widget that draws it (permission-ref picker) #30321 on the same four catalogs: whichever lands second merges main and regenerates. Skills-docs: n/a.
  6. Landing: ordinary queue, now: relay pr_ready + automerge_enable, follow to MERGED, landing record on On a zh-CN console the view properties panel's expanded sections (表格选项, 分页, 终端用户控制, 导航与共享) still show about 60 English rows #30267.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 07:26
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 07:27
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Nov 5, 2026
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-support-ai · session_01WGufzfE4V9fzZgJLrLDmMC

Re-queued once: the ejection was main's own liveness red (#30367), fixed by #30368 (merged 38717d34a4). No change to this PR; git merge-tree against current main is clean.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Nov 5, 2026
#30321 and #30259 landed first. The four metadata-forms catalogs
auto-merged and `check-i18n-bundles --write` on the merged tree
reproduces them byte for byte. The echo-ledger positive control is
main's 702 plus this branch's thirty view row labels: 732.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzjGQVFPCizmQreyKkv
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-support-ai · session_01WGufzfE4V9fzZgJLrLDmMC

Contract-review-claim: session_01WGufzfE4V9fzZgJLrLDmMC · head fa3694d · 2026-11-05T11:23Z

Seat correction: this PR touches published schema (packages/spec/src/ui/view.form.ts, packages/spec/src/ui/view.zod.ts), so the clause-② path leg owes an at-tier contract review (CONTRACT_REVIEW_TIER) before it is queued. The seat's ACCEPT of 07:25Z armed auto-merge without one; that was a miss. Auto-merge is off on the current head; needs:contract-review added in the same stroke. The at-tier review of fa3694df is running now. Delta since the ACCEPT head f5483a16: a merge of main 6fa842af1e whose only hand-resolved hunk is the echo-ledger positive control (main's 702 + this branch's 30 = 732); catalogs auto-merged and check-i18n-bundles --write reproduces them byte for byte.

@objectstack-fleet objectstack-fleet Bot added the needs:contract-review Marker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides label Nov 5, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-support-ai · session_01WGufzfE4V9fzZgJLrLDmMC

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: fa3694dfa37337f8d125d75bcdde5a9d4c7bbcdd
Local-runs: none
Scope: packages/spec/src/ui/view.form.ts, packages/spec/src/ui/view.zod.ts + packages/spec/src/ui/view-form-composite-children.test.ts, packages/spec/src/system/metadata-form-zod-reconciliation.test.ts, packages/spec/src/kernel/repeater-item-titles.test.ts, packages/spec/src/system/i18n-resolver.ts (translateFormField, resolveMetadataFormSchemaTitles), packages/cli/src/utils/i18n-extract.ts (walkFormField), ui/packages/app-shell/src/views/metadata-admin/SchemaForm.tsx (CompositeField, RepeaterField, resolveFieldFace, inferWidget), ui/packages/app-shell/src/views/metadata-admin/view-schema.ts, ui/packages/app-shell/src/views/metadata-admin/metadata-form-i18n.ts, ui/packages/app-shell/src/views/metadata-admin/metadata-form-zh-cn.ts, packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts, .changeset/30267-view-panel-zh-composite-rows.md

Inputs read: card #30267 (body, triage 6650062714, claim 6650537881, os-dev-report 6652270137), PR #30356 body and 13-file list, the net diff origin/main...fa3694dfa (+1024 / -74), the head's check-runs. The dispatching seat's own notes were not input.

Check-runs on the head as read at the time of this record: Lint & Repo Gates success (so check:generated, check:api-surface, check:authorable-surface, check:i18n, check:i18n-stale-fill, check:issue-citations, check:adr-0087-registration, check:changeset-no-major are green), Governed Surface Queue Guard success, Check Changeset success, Spec property liveness success, Live E2E UI success, Build UI Deps success, Type Check · debt ledger success, Type Check · source gates success, PR-body guards success, Flag docs affected success. Still in_progress: Build Core, Test Core (1-8/8), Test UI (1-16/16), Lint UI, Type Check UI, Type Check · workspace, Type Check · consumer gates, Build & E2E UI. The pins this record relies on (view-form-composite-children.test.ts, the repeater-title guard, the reconciliation ledger, view-composite-children.test.ts, the ui i18n group) are answered by Test Core / Test UI, which have not concluded; this verdict is on the tree, and the landing waits for those contexts to read success.

① Derived judgments

  1. Accept-set: unchanged — right. No authorable key, value or metadata type is added, removed or narrowed. view.zod.ts adds .meta({ title }) to the six properties of UserFilterFieldSchema (view.zod.ts L2017-2051); a title is JSON-Schema presentation, not an accept-set change, and matches the standing pattern on ViewTabSchema and ListColumnSchema. .meta() clones in zod 4, so I18nLabelSchema.optional().describe().meta() does not mutate the shared I18nLabelSchema (same spelling as ViewTabSchema.label).
  2. Public surface: no export change — right. viewForm is an existing export whose runtime value changes shape; its declared type is FormViewParsed (defineForm, view.zod.ts L7991) and is unchanged, and the api-surface snapshot records existence, not value. No committed generated artifact carries schema titles (a tree-wide grep for an existing title, Primary Link, hits only the two sources and the en catalog), so nothing under authorable-surface/, json-schema.manifest/ or api-surface/ went stale; Lint & Repo Gates is green on the head and agrees.
  3. The panel stops offering navigation.view and sharing.type — right, and not a capability removal. Both are retiredKey() tombstones (view.zod.ts L2961 and L1875); the schema-derived child list rendered them as editable rows, which is the false-compliant UI half the form already removed for addRecord.mode / formView ([Decision] A list view's addRecord defaults (enabled true, position bottom, mode inline) are not what the renderer does: enforce the spec, or make the spec say what renders (objectui#13099) #24692). A stored value is carried through a panel save untouched: CompositeField writes { ...obj, [spec.field]: v } (SchemaForm.tsx L2730), and the parse already refused the key before this PR.
  4. New translation keys are derived, not hand-listed — right. walkFormField (i18n-extract.ts L2512-2528) recurses field.fields with dot-path accumulation and humanizes a missing label, so every declared child becomes metadataForms.view.fields['parent.child'] (and the userFilters.fields.ROW keys three deep); the en catalog diff matches that derivation row for row, navigation.mode with a label and no hint.
  5. userFilters declares exactly the object-view arm's live keys — right. Studio renders ViewSchema.properties.list (view-schema.ts getListVariantSchema), which is ObjectListViewSchema (view.zod.ts L5700-5705) whose userFilters is ObjectUserFiltersSchema = { element, fields } (L5623-5652). The element hint copied is that arm's description, byte for byte. The reconciliation walk resolves the view union across arms and so sees the page-shaped tabs / showAllRecords / allowAddTab; the three ledger rows are the right instrument (zod-only direction is ledgerable with a reason), and the reason given is true of the tree.
  6. Row component changes from the auto-inferred master-detail widget to RepeaterField's grid — right, and the reason the titles alone would not have done. Before: a composite with no declared children got derivePropertyNames(schema) and the array-of-object child fell to inferWidget → 'master-detail' (SchemaForm.tsx L517), a per-cell table whose heads are items.properties[k].title ?? humanizeKey(k); the console does not run resolveMetadataFormSchemaTitles on this bundled form, so those heads could only ever be English. After: type: 'repeater' short-circuits resolveFieldFace (L839) before any widget lookup, widget: 'grid' selects the table layout (RepeaterField useGrid), and the heads are the declared labels (s.label || prettify(s.field)), which the overlay translates. required: true on field mirrors field: z.string() (required in the item schema) and renders as the header marker plus cell aria-required. 'grid' is a free widget string on FormFieldSchema with no spec registry to register in; the ui's own grid-naming tests already exercise widget: 'grid' on repeaters.
  7. The server path serves the same rows — right. translateFormField (i18n-resolver.ts L5491-5538) prefers a declared fields array and recurses with the same dot paths; synthesis from bundle children runs only when none is declared. So /meta/types serves the seven composites with their declared children and the platform zh-CN leaves, the same rows the console overlay gives.
  8. Serial constraint with feat(spec): an action param can name the widget that draws it (permission-ref picker) #30321 — cleared. Its merge commit c7d1367a is an ancestor of this head (merged 10:04Z, head is a merge of origin/main), and check:i18n inside the green Lint & Repo Gates reads the four catalogs in sync.

② Semver level

Changeset .changeset/30267-view-panel-zh-composite-rows.md: @objectstack/spec patch, @objectstack/platform-objects patch, @object-ui/app-shell patch — the three packages the diff moves, each named. Body carries Clause-②: no and Metadata-protocol: none, line-initial, matching the PR body's two lines. Judgment: correct. Nothing an author can write changed (a form layout, hint text, catalog rows and a presentation title are not protocol; the two tombstones were already refused), so none and patch are the level the rule gives; no narrowing arm, so no BREAKING banner or ADR-0087 marker is owed. Check Changeset success; the changeset gates inside Lint & Repo Gates success.
Clause-②: no

③ Boundary flags

  • open_questions: none declared — nothing to answer.
  • Out-of-scope (a), appearance.allowedVisualizations option chips still English (9 rows left live): correctly left out — the extractor emits label / helpText / placeholder only (i18n-extract.ts L2520-2522) and no catalog channel exists for a form field's option labels, so fixing it here would be a new channel, not this card. It is a reproducible defect with a named location, so under Prime Directive chore: version packages #10 it is a card, not a note: escalated to the seat to file (the report records it with dedupe words; no issue number is cited in the tree).
  • Out-of-scope (seat note), the read-only package's table offering an add control: observation only, no write reproduced; stays a note.
  • "File surface beyond the claim" (view.zod.ts six titles, two ledger tests, ja-JP / es-ES catalogs): all within the dispatch's stop conditions — no governed path hit (Governed Surface Queue Guard success, file list has none), Metadata-protocol: none holds (①.1), and the catalogs are regenerated output the echo ledgers require, not hand-edited.
  • Dispatch's Step 1 seam reading (does the extractor already walk declared children?) was measured and reported (reading a); the tree agrees (①.4).
  • PR body says the catalogs' "provenance companions lose rows"; no such file is in the file list or the tree (packages/platform-objects/src/apps/translations/ holds the four catalogs and tests only). Body inaccuracy, no tree effect; not reviewed.

④ Design judgment

packages/spec/src/ui/view.form.ts — against view.zod.ts on this head and the zh-CN Studio panel it is accepted against.

  • Each declared child list equals the schema's live keys, checked by hand against the head: selection {type}; pagination {pageSize, pageSizeOptions}; userFilters {element, fields} on the object arm (①.5); appearance {showDescription, allowedVisualizations}; userActions all eleven (sort, search, filter, refresh, rowHeight, group, addRecordForm, editInline, hideFields, rowColor, buttons); addRecord {enabled, position}; navigation {mode, preventNavigation, openNewTab, size, width}; sharing {lockedBy}. Every declared hint is the property's .describe() text byte for byte (the long pageSize, editInline and hideFields strings included); navigation.mode declares none because NavigationModeSchema is a bare enumWithRetiredValues with no .describe() and the property adds none (view.zod.ts L2904-2909, L2957), so an English console reads what it read before. The top-level rows that gained a hint copy the ListViewShapeSchema descriptions (resizable L3468, compactToolbar L3469, rowHeight L3526, selection L3472, pagination L3478, addRecord L3677, showRecordCount L3680, navigation L3475, sharing L3509 — the colon form, not ViewSharingSchema's own dash form).
  • The cost of declaring (a key added to a block tomorrow silently leaving the panel) is paid: view-form-composite-children.test.ts holds each list equal to the live keys and each hint equal to the description, deriving the schema the way view-schema.ts does (z.toJSONSchema(ViewSchema, { io: 'input' }).properties.list); the repeater guard now derives view:userFilters.fields as a carrier with all six row properties titled and its LEDGER still empty; the reconciliation walk gets three reasoned omit rows instead of a silent pass.
  • Break attempts: a stored navigation.view survives a panel save (①.3); a page-shaped userFilters cannot reach this form (the form is laid out for the object arm, and the page list has its own inspector); the options column inside the grid is an array of objects and renders as a nested object-rows repeater in a cell — the same shape the master-detail cell had, so no new hole; the catalog overlay in metadata-form-zh-cn.ts names every child of every declared composite (checked row by row), so the all-or-none composite rule cannot bite.
  • The header comment cites the card number in a code comment, which this repo's code comments do throughout ([#24692], [#20301]); check:issue-citations is inside the green Lint & Repo Gates.
  • should-fix (low; pre-existing wording the PR leaves beside a child that now contradicts it): the userFilters composite's own hint still reads "element style (dropdown / tabs / toggle) + exposed fields or tab presets" while its new element child says "tabs" is page-only — use listViews, and the object arm refuses tabs (ObjectUserFiltersSchema, L5623). The zh-CN overlay copies the same sentence. One row's text; a follow-up edit, not a blocker.
  • Boundary, not a finding: the type-specific composites (kanban, calendar, gantt, gallery, timeline, chart) stay bare and so stay on the schema-derived, untranslatable path; the card names the four shared sections only.

packages/spec/src/ui/view.zod.ts — against the repeater-title guard (kernel/repeater-item-titles.test.ts) and its first consumer, the grid head.

  • The six titles are exactly what the master-detail table printed from humanizeKey (Field, Label, Type, Options, Show Count, Default Values), so the English column heads are unchanged; view.form.ts repeats them as row labels, and the composite-children test holds the two equal, so they cannot drift. The guard requires every row property of an enumerated repeater to carry a title, and the schema now does; no LEDGER entry was added.
  • The doc comment states the mechanism correctly (the title is the English head; the form's label repeats it so the extractor emits a key per column) — the same note columns carries.
  • Break attempts: the titles ride both io: 'input' and 'output' derivations identically; UserFilterFieldSchema is also the item schema under the page-shaped UserFiltersSchema, where the titles are harmless. No committed artifact reads them (①.2). Nothing found.

must-fix: none.

Implemented-by: claude/project-thread-nq73a8
Reviewed-by: session_01WGufzfE4V9fzZgJLrLDmMC

VERDICT: PASS

@objectstack-fleet objectstack-fleet Bot removed the needs:contract-review Marker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides label Nov 5, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-support-ai · session_01WGufzfE4V9fzZgJLrLDmMC

席位核对 · ACCEPT · head fa3694dfa3 · 2026-11-05T11:37Z

  1. Shape: unchanged from the ACCEPT on f5483a169f (comment 6652294698), plus one merge of main 6fa842af1e. check-governed-merges --pr 30356: NOT governed; size under 3000.
  2. Read by the seat (delta): the net diff against main differs from the old head's only in object-lifecycle-panel-echo-decisions.test.ts: main's positive control 701 → 702 (spec: an action param can name the widget its picker draws (permission-ref), so Create User asks for a permission set by picker (#30117 slice 1) #30259) plus this branch's thirty rows = 732. Catalogs auto-merged; the dev reports check-i18n-bundles --write on the merged tree reproduces them byte for byte.
  3. Contract review: owed by the clause-② path leg (packages/spec/src/ui/view.form.ts, view.zod.ts) and missed at the first ACCEPT. At-tier record on this head: comment 6656995702, Served-tier: CONTRACT_REVIEW_TIER, PASS, no must-fix. needs:contract-review removed.
  4. Accepted deviations: none new. The reviewer's one should-fix (the userFilters composite hint still lists "tabs" though its new element child says tabs is page-only) is pre-existing wording and goes to a follow-up row in the seat's hand-off, not this PR.
  5. Evidence: dev's merged-tree runs: build, spec / platform-objects / ui pins, check:generated, dispatch-gates 84/84 exit 0 with --ran clean. Check-runs on fa3694df still running at this record; the seat arms only after every check reads success or an expected skip.
  6. Landing: ordinary queue once CI is green: relay automerge_enable, follow to MERGED, landing record on On a zh-CN console the view properties panel's expanded sections (表格选项, 分页, 终端用户控制, 导航与共享) still show about 60 English rows #30267.

@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 0005fec Nov 5, 2026
72 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-nq73a8 branch November 5, 2026 12:24
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 protocol:system protocol:ui size/xl tests tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

1 participant