Upstream half of objectui#8218 (Studio 「界面」 property panel de-developerization). Filed from that card after measuring where the fix actually belongs; the rendering-side half stayed in objectui.
Not on any release path — objectui#8218 is explicitly decoupled from the cloud#1955 v1 launch, and so is this.
What the maker sees
Studio's interface property panel renders dashboardForm + the DashboardSchema JSONSchema through the console's generic SchemaForm. The 「页眉 → 操作按钮」 array renders as a table whose column headers are label / actionUrl / actionType / icon — raw machine keys, sitting inside an otherwise fully Chinese panel.
Three separable upstream gaps, measured
1. The item property schemas carry no title — so the keys leak in en-US too
DashboardHeaderAction's fields carry .describe(...) but no title, so z.toJSONSchema(DashboardSchema, { io: 'input' }) emits, verbatim:
"label": { "description": "Action button label", "type": "string" },
"actionUrl": { "type": "string", "description": "URL or target for the action" },
"actionType": { "description": "Type of action", "type": "string", "enum": ["script","url","modal","flow","api","form"] },
"icon": { "description": "Icon identifier for the action button", "type": "string" }
The console's table header is items.properties[k].title ?? k. With no title on any of the four, that fallback is the arm that ALWAYS runs — for every locale, English included. So this is not primarily a translation gap: it is a missing authoring label in the contract.
Same shape applies to every repeater item schema, not only this one; header.actions is just where it was caught.
2. There is no localization channel that can reach those headers at all
The only localization path for these forms is resolveMetadataFormLabels (packages/spec/src/system/i18n-resolver.ts), which decorates the FormView field specs — label / helpText / placeholder on FormFieldSpec nodes, synthesizing composite/repeater children from bundle entries.
The array table headers come from a DIFFERENT object: the JSONSchema, which no overlay touches. So the fix the objectui card originally guessed at — adding dashboard.fields['header.actions[].label'] to the generated catalogs — would land on a FormFieldSpec the console's master-detail widget never reads (its props do not even include fieldSpec). Necessary-but-not-sufficient at best, and today not even reachable.
Deciding this needs a spec-side call on which of these the item-level name is:
- A. the JSONSchema's
title, authored on the Zod item schema, with the resolver learning to overlay titles onto a derived JSONSchema; or
- B. a
FormFieldSpec.fields enumeration that the repeater widget is taught to prefer over the schema-derived columns.
A is the smaller change and fixes the English surface immediately; B keeps all authoring copy in one object but needs the console change too. Recommend A, with B available later if item columns ever need per-column widgets.
3. zh-CN.metadata-forms.generated.ts is stale for this very form
packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts, dashboard.fields:
- it names
refreshIntervalSeconds, but the spec's field is refreshInterval — that entry can never match;
- it carries no
header.showTitle / header.showDescription / header.actions entries at all, so the composite's children are unlocalized in the platform's own catalog. objectui currently ships a private overlay of its own for exactly those three, which is why the panel reads 显示标题 / 显示描述 / 操作按钮 today despite the catalog being silent.
4. Related: columns documents a default it does not declare
DashboardSchema.columns emits { description: "Number of grid columns (default 12)", type: "integer", minimum: 1, maximum: 24 } — no default key. The 12 exists only as prose plus a renderer-side fallback.
objectui#8218 made numeric fields gray schema.default in as a placeholder, so an empty box reads "using the default" rather than "unknown". For columns / gap / refreshInterval that placeholder is empty, because the contract declares no default to show. Declaring .default(12) on columns (and auditing sibling props whose describe text names a default) makes the documented value machine-readable rather than prose. A number invented on the renderer side would be a second source of truth, so it was deliberately not hard-coded there.
Acceptance
z.toJSONSchema(DashboardSchema).properties.header.properties.actions.items.properties.label.title is a human string, and the same holds for the other three;
- a locale bundle can name an item-level property, by whichever of A / B is chosen, with the mechanism written down;
zh-CN (and the sibling generated catalogs) name refreshInterval, not refreshIntervalSeconds, and carry the header.* children;
columns declares the default its own description advertises.
Back-link: objectui#8218.
Upstream half of objectui#8218 (Studio 「界面」 property panel de-developerization). Filed from that card after measuring where the fix actually belongs; the rendering-side half stayed in objectui.
Not on any release path — objectui#8218 is explicitly decoupled from the cloud#1955 v1 launch, and so is this.
What the maker sees
Studio's interface property panel renders
dashboardForm+ theDashboardSchemaJSONSchema through the console's genericSchemaForm. The 「页眉 → 操作按钮」 array renders as a table whose column headers arelabel/actionUrl/actionType/icon— raw machine keys, sitting inside an otherwise fully Chinese panel.Three separable upstream gaps, measured
1. The item property schemas carry no
title— so the keys leak inen-UStooDashboardHeaderAction's fields carry.describe(...)but no title, soz.toJSONSchema(DashboardSchema, { io: 'input' })emits, verbatim:The console's table header is
items.properties[k].title ?? k. With notitleon any of the four, that fallback is the arm that ALWAYS runs — for every locale, English included. So this is not primarily a translation gap: it is a missing authoring label in the contract.Same shape applies to every repeater item schema, not only this one;
header.actionsis just where it was caught.2. There is no localization channel that can reach those headers at all
The only localization path for these forms is
resolveMetadataFormLabels(packages/spec/src/system/i18n-resolver.ts), which decorates the FormView field specs —label/helpText/placeholderonFormFieldSpecnodes, synthesizing composite/repeater children from bundle entries.The array table headers come from a DIFFERENT object: the JSONSchema, which no overlay touches. So the fix the objectui card originally guessed at — adding
dashboard.fields['header.actions[].label']to the generated catalogs — would land on aFormFieldSpecthe console's master-detail widget never reads (its props do not even includefieldSpec). Necessary-but-not-sufficient at best, and today not even reachable.Deciding this needs a spec-side call on which of these the item-level name is:
title, authored on the Zod item schema, with the resolver learning to overlay titles onto a derived JSONSchema; orFormFieldSpec.fieldsenumeration that the repeater widget is taught to prefer over the schema-derived columns.A is the smaller change and fixes the English surface immediately; B keeps all authoring copy in one object but needs the console change too. Recommend A, with B available later if item columns ever need per-column widgets.
3.
zh-CN.metadata-forms.generated.tsis stale for this very formpackages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts,dashboard.fields:refreshIntervalSeconds, but the spec's field isrefreshInterval— that entry can never match;header.showTitle/header.showDescription/header.actionsentries at all, so the composite's children are unlocalized in the platform's own catalog. objectui currently ships a private overlay of its own for exactly those three, which is why the panel reads 显示标题 / 显示描述 / 操作按钮 today despite the catalog being silent.4. Related:
columnsdocuments a default it does not declareDashboardSchema.columnsemits{ description: "Number of grid columns (default 12)", type: "integer", minimum: 1, maximum: 24 }— nodefaultkey. The 12 exists only as prose plus a renderer-side fallback.objectui#8218 made numeric fields gray
schema.defaultin as a placeholder, so an empty box reads "using the default" rather than "unknown". Forcolumns/gap/refreshIntervalthat placeholder is empty, because the contract declares no default to show. Declaring.default(12)oncolumns(and auditing sibling props whose describe text names a default) makes the documented value machine-readable rather than prose. A number invented on the renderer side would be a second source of truth, so it was deliberately not hard-coded there.Acceptance
z.toJSONSchema(DashboardSchema).properties.header.properties.actions.items.properties.label.titleis a human string, and the same holds for the other three;zh-CN(and the sibling generated catalogs) namerefreshInterval, notrefreshIntervalSeconds, and carry theheader.*children;columnsdeclares the default its own description advertises.Back-link: objectui#8218.