fix(spec): title every authorable row property on dashboard.widgets and dashboard.globalFilters - #18505
Conversation
…nd globalFilters
Studio renders a `type: 'repeater'` field as a table whose column headers read
`items.properties[k].title ?? k` off the JSON Schema derived by
`z.toJSONSchema(...)`. With no `title` the fallback arm runs in every locale,
English included, so the maker saw raw machine keys inside an otherwise
translated panel. This is a missing authoring label in the contract, not a
translation gap: the bundle overlay only ever REPLACES a title already present,
so the English default has to live on the schema.
Adds `.meta({ title })` to the 17 authorable row properties of
`dashboard.widgets[]` and the 10 of `dashboard.globalFilters[]`, beside the
existing `.describe()` rather than in place of it, following the mechanism
already landed on `dashboard.header.actions` and the skill/report/page carriers.
The shrink-only ledger in `repeater-item-titles.test.ts` is EXACT in both
directions, so both paid entries are deleted from it rather than left behind.
No `retiredKey()` tombstone is titled: the five on this row declare their keys
unwritable, and an authoring label would advertise them as writable. The
sibling control in `dashboard.test.ts` used `widgets[].id` as its untitled
example, which this change titles; it is re-pointed onto a tombstone, which
makes the control pin the tombstone rule instead of merely borrowing a
coincidence.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KB5PFtxuy1x3dcR5gxudx6
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 136 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 7e1c708d8b560533019311acc7d473ff0e5d232b && git checkout 7e1c708d8b560533019311acc7d473ff0e5d232b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin fed4a15ab5b50633553a939e4a9793e5a17ac530 82838f4fac598d4374617a4b05a9435aeb138b07 && git checkout -B drift-repro fed4a15ab5b50633553a939e4a9793e5a17ac530 && git merge --no-ff 82838f4fac598d4374617a4b05a9435aeb138b07
node scripts/docs-audit/affected-docs.mjs --json fed4a15ab5b50633553a939e4a9793e5a17ac530
|
Contract reviewServed-tier: ① Derived judgmentsPopulation re-derived through the platform's own predicate (
② Semver level
The sharper question, answered on what ships: ③ Boundary flags
Implemented-by: VERDICT: PASS Seat provenance (PM note, not part of the record)
Generated by Claude Code |
Landing status — pre-check complete, held on one unavailable routePre-check, all three conditions, read after the last state write:
What is holding it. ⛔ No backup channel is being spent on this: the bare Generated by Claude Code |
Fixes #17505
Clause-②: yes — no authorable key moves, but every titled row property gains a
titlenode in the emitted JSON Schema, a published artifact. Changeset isminoraccordingly (AGENTS.mdPost-Task Checklist step 3:yestakes at leastminor). Theneeds:contract-reviewcarrier is the seat's and is already on the card; this PR neither hangs nor strips it.What this does
Studio renders a
type: 'repeater'field as a table whose column headers readitems.properties[k].title ?? koff the schema fromz.toJSONSchema(...). With notitlethe fallback arm runs in every locale, English included, so the maker sawrequiresService,filterBindingsandoptionsFrominside an otherwise translated panel.resolveMetadataFormSchemaTitlesonly ever REPLACES a title already present, so the English default has to live on the schema.This applies the mechanism ruled in #16458 and already landed on
dashboard.header.actions(PR #17227) and on theai/skill,ui/reportandui/pagecarriers (PR #17500). No new contract decision.The row-property set, re-derived on today's tree
The card's
17 + 10 = 27is a 2026-09-10 reading, so it was re-derived through the platform's own predicate (z.toJSONSchemaovergetMetadataTypeSchema,io: 'input') rather than by regexing source — using the derivation helpers copied byte-identical out ofrepeater-item-titles.test.tsitself.dashboard:widgetsdashboard:globalFiltersIt is still 27, and the card is NOT stale.
#17616removedstageOrderfrom widget types that do not read it, butstageOrderlives insideoptions— one level BELOW the repeater's row properties, whereoptionsitself is the single row property. A refusal at that depth cannot move this count.The emitted schema, before and after
Read as Studio reads it — the property node first, never the
$reftarget, because a.meta({ title })on a schema zod hoists into$defsis emitted as a sibling of the$ref.widgets.items.properties.id.title=undefinedwidgets.items.properties.id.title="Widget ID", in bothio: 'input'andio: 'output'globalFilters.items.properties.targetWidgets.title="Target Widgets"What must NOT get a title, and what was checked
retiredKey()tombstones on this row —actionUrl,actionType,actionIcon,responsive,aria— are untouched and still emittitle: undefinedin both io modes. A tombstone declares a key unwritable; an authoring label would advertise it as writable. They are spec:retiredKey()tombstones are emitted into a repeater row'sitems.properties, so the served schema offers six columns the parse door refuses #17502's subject and are not addressed here.field:options6,object:fields.options6,view:columns14,view:sort2,view:tabs9 still untitled — so no shared item schema leaked a title sideways.columnsandheader.showTitlestill emit notitle, which is what keeps the two absence controls inpackages/rest/src/meta-types-schema-titles.test.tsvalid (3/3 green).One control had to be re-pointed, not deleted
dashboard.test.tsusedwidgets[].idas its "a sibling with no authored title has none" control — proving the emitter invents no titles. This change titles that property, so the control was re-pointed onto a tombstone (widgets[].actionUrl) and given a lit leg (widgets[].idis now'Widget ID'). It now pins the tombstone rule instead of borrowing a coincidence, which is strictly stronger than what it replaced.The ledger is paid, not weakened
repeater-item-titles.test.tsis exact in both directions, so a paid debt left in the set is as red as an unpaid one. Bothdashboard:*entries are deleted; five remain. Verified by name in the verbose run:dashboard:widgetsanddashboard:globalFiltersnow execute without the(ledger: still owed titles)arm, while the five survivors keep it.Verification
pnpm --filter @objectstack/spec buildcommand-exit 0)pnpm --filter @objectstack/spec testpnpm --filter @objectstack/spec typecheckpnpm --filter @objectstack/spec check:generatedcheck:authorable-surfaceincludedpackages/restmeta-types-schema-titles.test.tspackages/platform-objectstranslationscheck:authorable-surfacegreen is the load-bearing one for the declared fence:packages/spec/authorable-surface.base.jsondid not move and was not touched, so the in-flight anchor-only PR that owns it is unaffected.Acceptance notes
dashboard.form.tsdeliberately does not enumerate thewidgets/globalFilterschildren. That enumeration is the i18n-catalog half (os i18n extractemitsREPEATER.PROPERTYkeys from the form's declaredlabels) and belongs to [Decision] Repeater row-property localisation: 604 catalog leaves across four locales — who authors the translated copy? #17508, a decision card. The spec-side pin that holds a formlabelequal to a schematitleis scoped toheader.actionsonly, so nothing here reds without it.retiredKey()tombstones ... (5 ondashboard.widgets)". On today's tree the two carriers in this card hold exactly 5, all onwidgets, andglobalFiltersholds 0 — so the sixth is not on either carrier of this card. 承接者: spec:retiredKey()tombstones are emitted into a repeater row'sitems.properties, so the served schema offers six columns the parse door refuses #17502, whose subject is the tombstone set.Generated by Claude Code