Repository navigation
feat(spec): retire the metric sub-caption — the widget translation subCaption key and translateDashboard's options.description overlay - #21342
Conversation
…tion and the options.description overlay Ruling C on objectui#11389 (batch 264 item 5) retires the metric sub-caption at both ends, objectstack first. A widget keeps one authored description, widget.description. - translation.zod.ts: subCaption becomes a retiredKey() tombstone on the widget translation node; the subtitle -> subCaption alias moves to guidance and is refused naming both readings, not repointed at description. - i18n-resolver.ts: translateDashboard no longer overlays options.description; lookupWidgetAttr's attr union drops subCaption. - D2 conversion translation-widget-sub-caption-removed (order 55) and D3 semantic entry translation-widget-sub-caption-retired (rationale order 60). - check-widget-option-census: NON_DECLARED_MEMBERS is empty; the self-test drives the row checks through a fixture ledger. - sdui-parser: CONSUMED_WIDGET_OPTION_KEYS drops description. - Docblocks, liveness note, docs page and changeset follow. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
…d widget subCaption Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
…tire-widget-subcaption
📓 Docs Drift CheckThis PR changes 3 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 139 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 43a1eb0a64f4660d4463415133f26e49e69678d3 && git checkout 43a1eb0a64f4660d4463415133f26e49e69678d3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 222ecc27f9050857d4620e7010d8b85f9d35e378 8438e24995588dc07df1152be7ec21e70bbb40ec && git checkout -B drift-repro 222ecc27f9050857d4620e7010d8b85f9d35e378 && git merge --no-ff 8438e24995588dc07df1152be7ec21e70bbb40ec
node scripts/docs-audit/affected-docs.mjs --json 222ecc27f9050857d4620e7010d8b85f9d35e378
|
Contract reviewServed-tier: Rendered 2026-10-02T06:00Z by the dispatch seat's isolated reviewer subagent, adversarially briefed. Inputs and nothing else: card #21257 (body, claim 5945241790, dev report 5946221077); the ruling record on objectstack-ai/objectui#11389 (comment 5942430353, read-only); PR #21342 (body, 17-file list, net diff Check-runs on this head (read 2026-10-02T05:59Z): 35 check-runs, all completed: 33 success, 2 skipped by the workflow filter ( ① Derived judgmentsEvery accept-set and public-surface change the diff implies, named and judged.
② Semver level
③ Boundary flagsThe dev's three
Implemented-by: VERDICT: PASS |
Fixes #21257
Clause-②: no (narrowing)
Retires the metric sub-caption at both ends, objectstack first. This executes ruling C on objectstack-ai/objectui#11389 (batch #264 item 5, maintainer 「同意264」), which reverses #5428 item 4. A dashboard widget keeps one authored description,
widget.description. It renders as the card-header subtitle and is translated by the widget node'sdescriptionkey. Nooptions.descriptioncomeback, and no new widget-level key.What changes
packages/spec/src/system/translation.zod.ts,subCaptionon the widget translation node is now aretiredKey()tombstone. Its prescription is: delete the entry; translate the card subtitle through the widget'sdescriptionentry; runos migrate meta --from 17. The node is spread into the per-app bundle entry, the platform bundle entry and thetranslationitem, and all three refuse the key.translateDashboardno longer writesoptions.description, and it carriesoptionsthrough by reference. Theattrunion oflookupWidgetAttrdrops'subCaption', and theWidgetLike.optionsandtranslateDashboarddocblocks say so.scripts/check-widget-option-census.mjs:NON_DECLARED_MEMBERSis empty. Thedescriptionrow left together with its witness and its self-test cases. The row checks (stale row, witness gone, witness only in prose) still run against a fixture ledger, through a newledgerparameter onjudge. A new battery pinsdescriptioncoming back into the census asUNDECLARED-AND-UNLEDGERED, and the self-test floor goes from 16 to 17. The gate's ownWITNESS GONEtext prescribes taking the key out of the census, so@objectstack/sdui-parser'sCONSUMED_WIDGET_OPTION_KEYSdropsdescriptionin the same change. An authoredoptions.descriptionnow draws theunconsumed-widget-optionwarning, which is a warning and fails nothing.ui/dashboard.zod.ts,sdui-parser/src/dashboard-widget-options.tsandplatform-objects/.../source-hash.tsnow state the retired state and point atwidget.description.Retirement kit, per
.claude/skills/spec-property-retirement:translation-widget-sub-caption-removed(protocol 18,MAJOR_18_CONVERSIONSorder 55,retiredAfter: '17.6.0'). It strips the key from bundle entries and bare items, and is retired from the load path. Storedtranslationrows replay it throughapplyConversionsToStoredItem.translation-widget-sub-caption-retiredand itsSTEP18_RATIONALEfragment (order 60). The registry region was regenerated withgen:migration-registry.translation.jsondashboardsrow has new evidence and a new note, andverifiedAtis 2026-10-02. The member sits below the ledger's walk boundary, inside thewidgetsrecord, so it has no row of its own.content/docs/references/system/translation.mdxwas regenerated bycheck:generated --fix, which judged onlycheck:docsstale. Nothing was hand-edited.content/docs/ui/translations.mdxis corrected at the table row, the callout and the packaged-strings table..changeset/21257-widget-sub-caption-retired.mdbumps@objectstack/spec,@objectstack/sdui-parserand@objectstack/lintbyminor. It has a**BREAKING**banner, a FROM-to-TO table and the ADR-0087 markerregistered translation-widget-sub-caption-removed, translation-widget-sub-caption-retired.RETIRED_KEYS_BY_MAJORrow. The key sits under two records, and the nested-key resolver deliberately does not traverseadditionalProperties. The component-copysubmitLabelretirement (commit d173125) made the same choice.The
subtitlealias: refused, not repointedThe old table carried
subtitle: 'subCaption'. WithsubCaptiontombstoned, that row is the shape the alias-integrity audit refuses by name. Measured by putting the row back in:alias-integrity.test.tswent red with "subtitle->subCaption—subCaptionis a tombstone; it accepts nothing".Every precedent in the tree for an alias whose target was retired moves the alias into
guidancewith a refusal: chartaccessibility/ariaProps, postgrespasswd/pwd, permissionrestore/purge, apphome/homepage/landingpage, and component-copysubmit. None of them repoints the alias.Repointing it at
descriptionwould also silently change what the word is taken to mean. The alias existed because asubtitleon a metric widget meant the caption under the number. Sosubtitleis now aguidanceentry that names both readings: card-header copy belongs underdescription; a caption under the value has nowhere to render, so delete it. This keeps the four-axis 「过渡也从紧」 rule: no rename window and no silent repoint. The change is a refusal message, not an accept-set change:subtitlewas never accepted.Measurements
main393ae878d3, re-read at1371dc980c).git grep subCaptionoverexamples/,packages/platform-objectsandapps/returns 0 hits. Control: widget translation entries exist in the showcase and platform bundles (widgets:appears 4 times inexamples/app-showcase/src/system/translations/index.tsand once in eachplatform-objectslocale file).optionsblocks in the 6*.dashboard.tssources underexamples/andpackages/platform-objects: 0. The twooptions:hits are global-filter option arrays. Control:widget.descriptionis authored, for example onpipeline.dashboard.ts.app-multi-packagehas no dashboards..objectui-sha31971ff1e28f). The pinned objectui still readssubCaptionandoptions.description:useObjectLabel.widgetSubCaption,widgetSubCaption.ts,DatasetWidget,DashboardRenderer, and its ownsdui-parsercensus. That is objectui#11389's reader half. It imports no symbol this PR removes:git grepat the pin finds no import ofWidgetLike,translateDashboardorlookupWidgetAttr. Control: the pin does import from@objectstack/specelsewhere, for examplespec-symbol-parity.test.ts. No exported symbol is removed here, and the bundle-shapedsubCaptionfixtures at the pin are untyped. So there is no build break, and no sibling fix rides this landing.CONSUMED_WIDGET_OPTION_KEYSat the pin still listsdescription, and objectui#11389 drops it with the reader. Until then this copy is one member short. That errs in the safe direction: this copy warns on a key that objectui's strict authoring face already refuses.check:sdui-lockstepcompares grammar, codes and the containment predicate, not this array, and it is green. The window is documented at the array.Verification (final head
8438e24995, which is3f1c6d9c4dwithorigin/main1371dc980cmerged)pnpm --filter @objectstack/spec build, thencheck:generated. 1 of 15 artifacts was stale (check:docs), and--fixregenerated only that one.vitest run --project local(at3f1c6d9c4d): 597 files, 17487 passed, 1 todo.vitest run --project repo: 48 files, 849 passed.pnpm --filter @objectstack/spec typecheck: exit 0.@objectstack/sdui-parser: test 218 passed; typecheck exit 0.@objectstack/lint: test 5575 passed after the merge; typecheck exit 0.@objectstack/platform-objects(comment-only change): test 948 passed; typecheck exit 0.node scripts/check-widget-option-census.mjs --self-test: 17 cases pass. The real run is green with 5 declared keys in 5 members and 0 non-declared.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 137 commands at8438e24995. All 137 ran with recorded exit codes, and--ranreconciles: 136 run, 1 NOT-MEASURED, 0 UNRUN.check:pm-dispatch-gates. Two foreground runs were killed at the container cap (580s and 560s) before reaching a verdict; CI runs it.check:i18n,check:i18n-walk-parity,check:skill-examples,check:lean-entry-closure,check:dual-build-cjs-loads.subCaptiononwidget_total_usersinpackages/platform-objects/src/apps/translations/en.ts, which is typedTranslationDatathrough spec's rebuiltdist.tsc --noEmitwent red:en.ts(210,69), error TS2322, astringnot assignable to the'[REMOVED] Key retired: …': nevermark. The file was restored, the blob equals HEAD, andgit diff HEADis empty. My first attempt was a no-op:ablation-replacerefused because the replacement contained the anchor. It wrote nothing.options.descriptionwrite intranslateDashboardturned the 3 new resolver pins red.z.string().optional()turned 3 translation pins red. Thesubtitlepin stays green because it is independent of the tombstone.subtitle -> subCaptionalias turned alias-integrity red, as quoted above.eslint --no-inline-config --format jsonon the 13 changed.ts/.mjsfiles: 13 files linted, 0 errors, 0 warnings, and none ignored, because an ignored file would have shown as a warning.eslint.config.mjsstates that type-aware linting is never enabled (noparserOptions.project), so this diff cannot move a verdict on an untouched file. The repo-widepnpm lintbelongs to CI.Acceptance notes
descriptionsub-caption":examples/app-crm/src/dashboards/pipeline.dashboard.ts:41andexamples/app-todo/src/dashboards/task.dashboard.ts:22. That is true of objectui's renderer at the pin, and false once objectui#11389 lands. These are comments only and outside the claimed file surface, so they are not edited here. No carrier is named.DashboardWidgetOptionsSchemastayspassthrough, as ruled when the bag was opened, so the spec still parses an authoredoptions.description. What now names it is theunconsumed-widget-optionwarning atos validate,os buildandos lint. This is an observation, not a finding: 0 producers were measured.Clause-②: yes (narrowing). The measured arm isno (narrowing), which matches the claim: no surface widens. Thesubtitlechange alters a refusal message only, and the new conversion and semantic entries are registry rows, not an accept-set or export widening.#20274regeneratesliveness/state-counts*andcontent/docs/references/**. This PR changes no state count; thedashboardsrow stayslive. It does changereferences/system/translation.mdx. If the two collide, the second to land regenerates throughscripts/pm/os-regen-merge.sh.Generated by Claude Code