Skip to content

feat(spec): retire the metric sub-caption — the widget translation subCaption key and translateDashboard's options.description overlay - #21342

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-21257-retire-widget-subcaption
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-21257-retire-widget-subcaption

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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's description key. No options.description comeback, and no new widget-level key.

What changes

  1. The translation member is retired. In packages/spec/src/system/translation.zod.ts, subCaption on the widget translation node is now a retiredKey() tombstone. Its prescription is: delete the entry; translate the card subtitle through the widget's description entry; run os migrate meta --from 17. The node is spread into the per-app bundle entry, the platform bundle entry and the translation item, and all three refuse the key.
  2. The overlay is removed. translateDashboard no longer writes options.description, and it carries options through by reference. The attr union of lookupWidgetAttr drops 'subCaption', and the WidgetLike.options and translateDashboard docblocks say so.
  3. The census asserts nothing writes the key. scripts/check-widget-option-census.mjs: NON_DECLARED_MEMBERS is empty. The description row 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 new ledger parameter on judge. A new battery pins description coming back into the census as UNDECLARED-AND-UNLEDGERED, and the self-test floor goes from 16 to 17. The gate's own WITNESS GONE text prescribes taking the key out of the census, so @objectstack/sdui-parser's CONSUMED_WIDGET_OPTION_KEYS drops description in the same change. An authored options.description now draws the unconsumed-widget-option warning, which is a warning and fails nothing.
  4. Docblocks in ui/dashboard.zod.ts, sdui-parser/src/dashboard-widget-options.ts and platform-objects/.../source-hash.ts now state the retired state and point at widget.description.

Retirement kit, per .claude/skills/spec-property-retirement:

  • D2 conversion. translation-widget-sub-caption-removed (protocol 18, MAJOR_18_CONVERSIONS order 55, retiredAfter: '17.6.0'). It strips the key from bundle entries and bare items, and is retired from the load path. Stored translation rows replay it through applyConversionsToStoredItem.
  • D3 entry. The semantic entry translation-widget-sub-caption-retired and its STEP18_RATIONALE fragment (order 60). The registry region was regenerated with gen:migration-registry.
  • Liveness. The translation.json dashboards row has new evidence and a new note, and verifiedAt is 2026-10-02. The member sits below the ledger's walk boundary, inside the widgets record, so it has no row of its own.
  • Generated. content/docs/references/system/translation.mdx was regenerated by check:generated --fix, which judged only check:docs stale. Nothing was hand-edited.
  • Docs. content/docs/ui/translations.mdx is corrected at the table row, the callout and the packaged-strings table.
  • Changeset. .changeset/21257-widget-sub-caption-retired.md bumps @objectstack/spec, @objectstack/sdui-parser and @objectstack/lint by minor. It has a **BREAKING** banner, a FROM-to-TO table and the ADR-0087 marker registered translation-widget-sub-caption-removed, translation-widget-sub-caption-retired.
  • No RETIRED_KEYS_BY_MAJOR row. The key sits under two records, and the nested-key resolver deliberately does not traverse additionalProperties. The component-copy submitLabel retirement (commit d173125) made the same choice.

The subtitle alias: refused, not repointed

The old table carried subtitle: 'subCaption'. With subCaption tombstoned, that row is the shape the alias-integrity audit refuses by name. Measured by putting the row back in: alias-integrity.test.ts went red with "subtitle -> subCaption — subCaption is a tombstone; it accepts nothing".

Every precedent in the tree for an alias whose target was retired moves the alias into guidance with a refusal: chart accessibility / ariaProps, postgres passwd / pwd, permission restore / purge, app home / homepage / landingpage, and component-copy submit. None of them repoints the alias.

Repointing it at description would also silently change what the word is taken to mean. The alias existed because a subtitle on a metric widget meant the caption under the number. So subtitle is now a guidance entry that names both readings: card-header copy belongs under description; 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: subtitle was never accepted.

Measurements

  • Producers (objectstack main 393ae878d3, re-read at 1371dc980c).
    • git grep subCaption over examples/, packages/platform-objects and apps/ returns 0 hits. Control: widget translation entries exist in the showcase and platform bundles (widgets: appears 4 times in examples/app-showcase/src/system/translations/index.ts and once in each platform-objects locale file).
    • Authored widget options blocks in the 6 *.dashboard.ts sources under examples/ and packages/platform-objects: 0. The two options: hits are global-filter option arrays. Control: widget.description is authored, for example on pipeline.dashboard.ts.
    • app-multi-package has no dashboards.
    • So: 0 producers. This is not a stop.
  • objectui pin check (.objectui-sha 31971ff1e28f). The pinned objectui still reads subCaption and options.description: useObjectLabel.widgetSubCaption, widgetSubCaption.ts, DatasetWidget, DashboardRenderer, and its own sdui-parser census. That is objectui#11389's reader half. It imports no symbol this PR removes: git grep at the pin finds no import of WidgetLike, translateDashboard or lookupWidgetAttr. Control: the pin does import from @objectstack/spec elsewhere, for example spec-symbol-parity.test.ts. No exported symbol is removed here, and the bundle-shaped subCaption fixtures at the pin are untyped. So there is no build break, and no sibling fix rides this landing.
  • Lockstep window. objectui's CONSUMED_WIDGET_OPTION_KEYS at the pin still lists description, 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-lockstep compares 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 is 3f1c6d9c4d with origin/main 1371dc980c merged)

  • pnpm --filter @objectstack/spec build, then check:generated. 1 of 15 artifacts was stale (check:docs), and --fix regenerated only that one.
  • Spec tests:
    • vitest run --project local (at 3f1c6d9c4d): 597 files, 17487 passed, 1 todo.
    • vitest run --project repo: 48 files, 849 passed.
    • pnpm --filter @objectstack/spec typecheck: exit 0.
  • Other package tests:
    • @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.
  • Derived gates. node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 137 commands at 8438e24995. All 137 ran with recorded exit codes, and --ran reconciles: 136 run, 1 NOT-MEASURED, 0 UNRUN.
    • The NOT-MEASURED one is check:pm-dispatch-gates. Two foreground runs were killed at the container cap (580s and 560s) before reaching a verdict; CI runs it.
    • Five gates first exited 3 (PREREQUISITE NOT MET) and passed after the CLI and client-react closures were built: check:i18n, check:i18n-walk-parity, check:skill-examples, check:lean-entry-closure, check:dual-build-cjs-loads.
  • Reverse verification (cross-package type). I planted subCaption on widget_total_users in packages/platform-objects/src/apps/translations/en.ts, which is typed TranslationData through spec's rebuilt dist. tsc --noEmit went red: en.ts(210,69), error TS2322, a string not assignable to the '[REMOVED] Key retired: …': never mark. The file was restored, the blob equals HEAD, and git diff HEAD is empty. My first attempt was a no-op: ablation-replace refused because the replacement contained the anchor. It wrote nothing.
  • Ablations, one-shot, each restored to the HEAD blob.
    • Re-adding an options.description write in translateDashboard turned the 3 new resolver pins red.
    • Swapping the tombstone for z.string().optional() turned 3 translation pins red. The subtitle pin stays green because it is independent of the tombstone.
    • Restoring the subtitle -> subCaption alias turned alias-integrity red, as quoted above.
  • eslint, a declared narrowing. I ran eslint --no-inline-config --format json on the 13 changed .ts / .mjs files: 13 files linted, 0 errors, 0 warnings, and none ignored, because an ignored file would have shown as a warning. eslint.config.mjs states that type-aware linting is never enabled (no parserOptions.project), so this diff cannot move a verdict on an untouched file. The repo-wide pnpm lint belongs to CI.

Acceptance notes

  • Two example comments still describe the dataset-bound read set as "plus the description sub-caption": examples/app-crm/src/dashboards/pipeline.dashboard.ts:41 and examples/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.
  • DashboardWidgetOptionsSchema stays passthrough, as ruled when the bag was opened, so the spec still parses an authored options.description. What now names it is the unconsumed-widget-option warning at os validate, os build and os lint. This is an observation, not a finding: 0 producers were measured.
  • The card body asked for Clause-②: yes (narrowing). The measured arm is no (narrowing), which matches the claim: no surface widens. The subtitle change alters a refusal message only, and the new conversion and semantic entries are registry rows, not an accept-set or export widening.
  • #20274 regenerates liveness/state-counts* and content/docs/references/**. This PR changes no state count; the dashboards row stays live. It does change references/system/translation.mdx. If the two collide, the second to land regenerates through scripts/pm/os-regen-merge.sh.

Generated by Claude Code

claude added 3 commits October 2, 2026 04:02
…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>
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/platform-objects, @objectstack/sdui-parser, @objectstack/spec, touching 19 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/platform-objects/src/apps/translations/source-hash.ts, packages/spec/liveness/translation.json, packages/spec/src/ui/dashboard.zod.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/ui/translations.mdx (via options.description (literal, a string literal in summary), subCaption (literal, a string literal in appTranslationDataShape; a string literal in apply; a string literal in lookupWidgetAttr; a string literal in summary; a string literal in translateDashboard), widget.description (literal, a string literal in summary))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-5.mdx (via retiredAfter (symbol, a field of const object translationWidgetSubCaptionRemoved), retiredFromLoadPath (symbol, a field of const object translationWidgetSubCaptionRemoved))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/platform-objects/src/apps/translations/source-hash.ts, packages/spec/liveness/translation.json, packages/spec/src/ui/dashboard.zod.ts) — pages documenting those are invisible to this run
  • 9 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 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; 97 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 — 139 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 222ecc27f9050857d4620e7010d8b85f9d35e378 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 43a1eb0a64f4660d4463415133f26e49e69678d3 — the merge of head 8438e24995588dc07df1152be7ec21e70bbb40ec into base 222ecc27f9050857d4620e7010d8b85f9d35e378, 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 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

⚠️ 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 222ecc27f9050857d4620e7010d8b85f9d35e378 → 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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 8438e24995588dc07df1152be7ec21e70bbb40ec
Local-runs: none

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 origin/main... at this head, merge-base 1371dc980c); the head's check-runs; .claude/skills/spec-property-retirement/SKILL.md; scripts/pm/clause2-line.mjs, scripts/check-changeset-no-major.mjs, scripts/check-adr-0087-registration.mjs, scripts/check-widget-option-census.mjs; objectui read-only at the .objectui-sha pin 31971ff1e28f. Nothing was built, run or re-run locally.

Check-runs on this head (read 2026-10-02T05:59Z): 35 check-runs, all completed: 33 success, 2 skipped by the workflow filter (Console Pin Gate, because the diff moves none of the console paths, which are the pin, the console build scripts and the spec entry layout, not spec content; Packed-tarball smoke (opt-in)), 0 failure, 0 still running. The gate families this review leans on, and where they ran: Check Changeset runs check-changeset-no-major.mjs, check-empty-changeset.mjs and check-adr-0087-registration.mjs --base MERGE_BASE (success); Lint & Repo Gates runs check:widget-option-census, check:sdui-lockstep, check:migration-registry, check:generated and check:pm-dispatch-gates (success); Spec property liveness (success); the four Type Check jobs, Build Core, Dogfood Verify CLI, Dogfood Regression Gate 1-3 and Temporal Conformance (success); Test Core 1-6 (success). Governed Surface Queue Guard is green and the file list touches no governed surface, so this record is owed by the Clause-② rule (non-test packages/spec/src/**), not by Prime Directive #14.

① Derived judgments

Every accept-set and public-surface change the diff implies, named and judged.

  1. subCaption on the widget translation node becomes a retiredKey() tombstone (packages/spec/src/system/translation.zod.ts). An accept-set narrowing on all three faces the node is spread into: TranslationDataSchema (the per-app bundle entry, appTranslationDataShape() as strictObject's second argument), PlatformTranslationDataSchema and TranslationItemSchema (both spread the shape). The new pin walks the three faces and asserts the one issue path ends in subCaption. The prescription follows the five house conventions: the backticked qualified key first; was removed in @objectstack/spec 17.7.0 (ADR-0049 enforce-or-remove), the spelling 18 tombstones on main use, with 17.7.0 the release this minor cuts from the 17.6.0 label; the why-inert clause; the imperative fix naming the widget's description entry; and the verbatim os migrate meta --from 17 sentence. No issue numbers in the string. Right. This executes the ruling's first limb exactly: the sub-caption is retired at the spec, one authored description (widget.description) remains.

  2. The subtitle alias leaves aliases and becomes a guidance entry; it is not repointed at description. strictObject consults aliases and guidance only on the unrecognized_keys path, so subtitle was never accepted and is not accepted now: a refusal-message change, not an accept-set change. An alias whose target is a tombstone is the exact shape alias-integrity.test.ts refuses (is a tombstone; it accepts nothing), so removal was forced; the only question was where to send the author. The cited precedents hold on main (ui/chart.zod.ts accessibility / ariaProps guidance after aria was retired; ui/app.zod.ts home / homepage / landingpage guidance), and none repoints. A repoint at description would have sent an author who meant the caption under a metric's value into the card-header subtitle slot, a different rendered position, without saying so; the guidance names both readings and lets the author choose. Right, and the tighter of the two transitions, which is the ruled direction.

  3. translateDashboard no longer writes options.description; lookupWidgetAttr's attr union drops 'subCaption' (i18n-resolver.ts). The deleted block was the key's only writer in either repository (0 authored producers, measured on the card, in the ruling and in the PR with a control). options is carried through by reference (next = { ...w }), pinned by the rewritten resolver tests: the residue case (a stored subCaption entry reaches neither options.description nor widget.description) and the no-bag case. lookupWidgetAttr is module-private; WidgetLike keeps its type and gains a docblock. Right. There is no comeback through options.description: the overlay is gone, DashboardWidgetOptionsSchema still declares no description, and the census reds if the key returns without a writer.

  4. The census asserts that nothing writes an undeclared key (scripts/check-widget-option-census.mjs): NON_DECLARED_MEMBERS is empty; the row checks keep running against a fixture ledger through a new ledger parameter on judge; a new battery pins description returning as UNDECLARED-AND-UNLEDGERED; the floor moves from 16 to 17 with the roster. Right, and it is the gate's own WITNESS GONE prescription applied: a ledger row's evidence file must be in this repo and must hold the witness in code, so the row could not have stayed once the writer left.

  5. @objectstack/sdui-parser CONSUMED_WIDGET_OPTION_KEYS drops description. A published package, so this is a behaviour change at a door: validateDashboardWidgetOptions (sdui-parser, imported by @objectstack/lint) now emits the unconsumed-widget-option warning on an authored widget options.description at os validate, os build and os lint, and the printed accepted set loses description. Who sees it and when: an objectstack app author who writes options.description on a dashboard widget, from the release that ships this PR; severity warning, so no exit code moves; 0 such authors measured in the four example apps and the platform bundles. Truthfulness at the pin: objectui 31971ff1e28f still renders an authored options.description as the metric sub-caption (widgetSubCaption.ts limb 1 reads widget.options.description at line 116; DatasetWidget and DashboardRenderer's inline arms consume it), so for that author the words reaches no renderer run one card ahead of the pinned renderer until objectui#11389 (already pm:blocked on spec(i18n,dashboard): retire the metric sub-caption — the subCaption widget translation member and translateDashboard's options.description overlay (ruling C on objectui#11389; reverses #5428 item 4) #21257) drops the read. The window errs in the safe direction: a warning on a key with no declared home and no writer, which objectui's own strict authoring face already refuses by name (its schema reference at the pin, objectui#11228); it is documented at the array and in the lockstep test; and check:sdui-lockstep compares grammar, codes and the containment predicate, not this array. Right, forced by item 4, stated truthfully in the changeset; the window closes with objectui#11389.

  6. The migration kit. D2 translationWidgetSubCaptionRemoved (conversions/registry.ts): toMajor: 18, retiredFromLoadPath: true, retiredAfter: '17.6.0' (the package label, as the census pin requires; the precedent series is 17.4.0 and 17.5.0 for the 17.5.0 and 17.6.0 tombstones), a stripKeys walk over both authored shapes exactly as translationComponentSubmitLabelRemoved walks its key, copy-on-write, a fixture with expectedNotices: 2 (one per stripped key instance) and the emptied widget entry left in place. MAJOR_18_CONVERSIONS order 55: main holds 1 to 54, so 55 is the next free order, inserted at its identifier's sort position (after translationPerAppSettingsRemoved, before tursoConfigTimeoutToTimeoutMs); no collision. The D3 semantic entry translation-widget-sub-caption-retired is its own file under entries/semantic/, with non-empty reason and acceptanceCriteria, and the generated registry region matches it; the STEP18_RATIONALE fragment takes order 60, where main's highest is 59 and the header rule is highest plus one with ties rendering by id, inserted at its id's sort position. Right. No RETIRED_KEYS_BY_MAJOR row: the key sits under two records (dashboards, widgets), nested-authorable-keys.ts deliberately never descends additionalProperties, and authorable-surface/ on main has no subCaption row, so gate (b) never sees this tombstone and a row would have hit (b3), a NESTED key this build does not emit. The component-copy submitLabel precedent has no row either; the only submitLabel row on main is ui/ElementFormProps:submitLabel, a different def. Right. spec-changes.json and docs/protocol-upgrade-guide.md carry no protocol-18 conversion on main, so no regeneration was owed there.

  7. Liveness and the generated reference. liveness/translation.json's dashboards row stays live, its evidence narrowed to lookupWidgetAttr on .title / .description, with a dated note and verifiedAt 2026-10-02; no row is added or removed, so the README counts are unchanged, and Spec property liveness is green. content/docs/references/system/translation.mdx changes exactly the three faces' widgets type line (subCaption?: string gone), consistent with a never member. Right.

  8. The objectui pin, both halves verified at 31971ff1e28f. (a) It still reads subCaption (packages/i18n/src/useObjectLabel.ts:458, plugin-dashboard/src/DashboardRenderer.tsx:808, DatasetWidget.tsx, widgetSubCaption.ts) and options.description (limb 1), and its own sdui-parser census still lists description: the reader half, objectui#11389's scope. (b) It imports no symbol this PR removes: git grep at the pin for WidgetLike, translateDashboard, lookupWidgetAttr and DashboardLike finds docblock mentions only; the one non-test spec translation type import (metadata-form-i18n.ts, TranslationBundle) authors no subCaption; the four test fixtures that carry subCaption are untyped or cast to DashboardComponentSchema. No export is removed. So no sibling fix rides this landing, and Console Pin Gate skipped is the console filter working (spec content is not one of its paths), not a missing verdict.

  9. Docblocks and docs (ui/dashboard.zod.ts, sdui-parser/src/dashboard-widget-options.ts, platform-objects/.../source-hash.ts, content/docs/ui/translations.mdx) state the retired state and point at widget.description; read sentence by sentence against the diff, consistent. The branch tree's remaining subCaption mentions outside the kit are explanatory comments only.

② Semver level

  • Arm. Clause-②: no (narrowing) on the PR body and in the changeset. By clause2-line.mjs, yes (narrowing) asserts a diff that widens one surface and narrows another; no (narrowing) is a truthful no to the widening question plus breaking. Nothing here widens: no new export (lookupWidgetAttr is private, WidgetLike's type is unchanged), no key newly accepted (subtitle was never accepted; the guidance row is a message), no accept set relaxed; the D2 and D3 rows are registry data every protocol-18 retirement adds, reached through os migrate meta and the spec-changes projection, not a published accept set or export. So no (narrowing) is right, and the card body's yes (narrowing) (copied from the ruling's execution-parameter line) misstated the measurement, not the decision. The dispatch seat's ACCEPT should carry the corrected arm back to the card; nothing to file.
  • Gates. check-changeset-no-major.mjs reads no (narrowing) as a carried axis owing at least one minor and refuses major in the launch window: three packages at minor, no major. check-adr-0087-registration.mjs reads two breaking signals (the **BREAKING** banner and the narrowing arm) and the marker registered translation-widget-sub-caption-removed, translation-widget-sub-caption-retired: both ids resolve at HEAD in conversions/registry.ts and migrations/registry.ts, and both are new in this diff. Both gates' real runs sit in the Check Changeset job: success.
  • Level per package. @objectstack/spec minor: an accept-set narrowing plus a resolver output change, breaking, shipped minor under the launch-window convention with the banner as carrier. Right. @objectstack/sdui-parser minor: an exported constant's value and a warning's reach change at a published door. Right. @objectstack/lint minor: its own source is untouched (one test pin moves), but the door its users hold, os lint, changes behaviour through the census it imports, and the fixed group versions in lockstep regardless; naming it puts the change in the CHANGELOG where that user reads. Right, as a changelog carrier.
  • Prose. The changeset states what ships: the retired key and its three faces, the removed overlay, the FROM / TO table with both the subCaption and the subtitle row, the one-line fix, the tsc and parse channels, the new warning named as a warning that fails nothing, zero measured producers, and the kit (tombstone, guidance, conversion with retiredAfter, D3, the census member). Stored translation items converted when read is true of a load-path-retired conversion replayed through applyConversionsToStoredItem. Truthful.

③ Boundary flags

The dev's three out_of_scope_findings, then the four deviations, then the NOT MEASURED gate; open_questions is empty and none was found.

  1. Two example comments (examples/app-crm/src/dashboards/pipeline.dashboard.ts:41, examples/app-todo/src/dashboards/task.dashboard.ts:22, "plus the description sub-caption"). Present on this head, comment-only, outside the claimed single-writer surface. True of the pinned renderer today, false the moment objectui#11389 lands; not a metadata-authoring trap, because an author who follows them into options.description meets the new warning by name. Answer: accepted as noted, and escalated to the dispatch seat for a carrier: no card carries them (the dev names none, and objectui#11389 cannot edit this repo); the natural carrier is the .objectui-sha pin bump that follows objectui#11389. Not filed here.
  2. objectui's own census member, its leg-3 census test and content/docs/plugins/plugin-dashboard.mdx:337 / :369 at the pin. objectui#11389's ruled scope is the three reads; its own leg-3 test ("if this read disappears, description needs re-triage") is the forcing function that pulls the census member out with the read, so no new card is owed for the array. Answer: accepted; the two doc lines are objectui#11389's to catch in its own tree, and the dispatch seat should name them in that card's unlock comment. Not filed here.
  3. DashboardWidgetOptionsSchema stays passthrough. Ruling C did not reopen the open-bag ruling, and a never-declared key in a passthrough bag has no tombstone slot and no unrecognized_keys path for a guidance row; the lint warning is the one channel the ruled design offers, the same one every other unconsumed options key gets. 0 producers; an observation, not a finding. Answer: accepted.
  4. Deviation: surface beyond the claim's literal list (sdui-parser CONSUMED_WIDGET_OPTION_KEYS, its lockstep test, the lint test). Forced by scope item 3 through the census gate; the file itself was in the claim for its docblock; No other open PR may claim the same single-writer path is green. Answer: accepted; the changeset names the two extra packages.
  5. Deviation: model-free commit trailers and PR footer. The repo's pre-push rule outranks the harness reminder; not a contract matter. Accepted.
  6. Deviation: zero label writes. No label was ordered and skip-changeset does not apply. Accepted.
  7. Deviation: no RETIRED_KEYS_BY_MAJOR row. Judged in ① item 6: right, and the only choice that does not red gate (b3). Accepted.
  8. NOT MEASURED locally: check:pm-dispatch-gates (two runs killed at the container cap). Its CI home is the Lint & Repo Gates job (the PM dispatch-gates self-test step), which concluded success on this head at 05:59Z, so the gate is answered by CI rather than by the local run. Accepted.

Implemented-by: claude/issue-21257-retire-widget-subcaption
Reviewed-by: session_01UtnxvdiN376GF3sgXwAw4d

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 2, 2026 06:03
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 2, 2026 06:03
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 99e1912 Oct 2, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21257-retire-widget-subcaption branch October 2, 2026 06:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants