Skip to content

feat(types)!: SidebarSchema declares what the sidebar node draws; nine unread keys retire, variant takes the registration's enum (objectui#11465) - #11485

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-11465-sidebar-declared-surface
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-11465-sidebar-declared-surface

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11465
Clause-②: yes

Dev run for objectui#11465 under the domain:spec seat's claim (comment 5955764801), session https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC. It executes triage's key-by-key direction (comment 5954161531): retire the nine keys the sidebar node never draws, give variant the renderer's enum, and move the types README example to children composition. collapsible was settled by PR objectui#11463 and is untouched.

Note on spelling: GitHub strips tag-shaped text from bodies, so generic parameters and placeholders below are written as words.

The accept-set change, key by key

Key Both faces before After
title string refused by name: compose a heading text node at the start of children
nav a NavLink array refused by name: navigation is app metadata (the app's navigation, objectui#11441), or compose items as children
content a node or a node list refused by name: move the node(s) into children
footer a node or a node list refused by name: the last entries of children
position left or right refused by name: delete the key
defaultCollapsed boolean refused by name: delete the key (the sidebar provider owns the open state)
collapsed boolean refused by name: delete the key
width string or number refused by name: delete the key; the in-flow form takes a className width utility such as w-72
collapsedWidth string or number refused by name: delete the key (the node never collapses to a narrower width)
variant default, bordered, floating sidebar, floating, inset (the registration's enum); default and bordered refused with invalid_value and a message that says to write sidebar
  • TypeScript face (packages/types/src/navigation.ts): each retired key is a ?: never tombstone with a RETIRED docblock, so tsc refuses it by name. variant is the three-value union with @default 'sidebar'.
  • zod face (packages/types/src/zod/navigation.zod.ts): each retired key is a retirementTombstone built by a local retiredSidebarKey(key, why, instead), whose message reads RETIRED (objectui#11465, ADR-0049) — KEY on sidebar had no reader: WHY Instead: INSTEAD. variant is z.enum(['sidebar', 'floating', 'inset'], { error }).
  • Widening: variant: 'sidebar' and variant: 'inset' were refused (invalid_value at variant) on both faces before this change, measured; both validate now. Narrowing: the nine keys, and variant: 'default' / 'bordered', which validated on both faces before, measured.
  • The renderer is unchanged. Its registration already offered sidebar / floating / inset, and shadcn's Sidebar reads variant off the spread.

Zone 2 readings, measured before building

H1 — which keys the renderer reads after PR objectui#11463. SidebarNode reads schema.children and destructures collapsible; everything else goes through its rest spread onto shadcn's Sidebar, which destructures side, variant, collapsible, className, children and spreads the remainder onto the panel element. A one-off runtime probe (real SchemaRenderer, real registry, bare host at desktop width, both collapsible forms, one key at a time against its absence; not committed) read:

  • title: 'ZZTITLE' landed as an HTML title attribute on the panel element, a native hover tooltip, never a heading;
  • nav, content, footer landed as attributes whose value is the string [object Object];
  • position, width landed as inert attributes; collapsedWidth as a lowercase collapsedwidth attribute, with React's unknown-prop warning;
  • defaultCollapsed, collapsed: markup byte-identical to absence, React warned and dropped them;
  • controls in the same probe: side: 'right' moved the panel to the right edge, and className: 'w-72' replaced the width class, so the probe could see a change when one happened.

No key on the retire list is read, so all nine retire. ⚠️ title is the one whose spread had a user-visible effect (the tooltip). I read that as the rest-spread DOM leak, the same channel that writes nav="[object Object]", not as the node honouring "Sidebar title"; it is flagged in the report for the contract review.

H2 — the retirement convention. Followed the in-repo precedent: retirementTombstone() on the zod face plus ?: never on the TypeScript face (objectui#11434 PR 1, objectui#10387). For the two retired enum values the precedent is an enum with a named error (object-data-table's drillDown.target, objectui#10685). ⛔ No bare deletion: BaseSchema's index signature would type it any, and .passthrough() would let it parse green.

H3 — does variant reach shadcn as authored. Yes, through the real SchemaRenderer. On the collapsible form, sidebar draws exactly what an absent variant draws; floating and inset each draw differently (padded panel; floating's inner panel takes the rounded, bordered, shadowed classes through data-variant). default and bordered changed only data-variant; their classes were sidebar's. On the in-flow form (collapsible: false) every value, the three included, draws byte-identical markup: shadcn's not-collapsible branch ignores variant. The renderer is not changed. The behaviour is stated in the describe text, the TypeScript doc and the changeset, and pinned (below).

H4 — corpus authors of a retired key or value. Swept every tracked file for an authored sidebar node (type: 'sidebar', the JSON spelling, and ui:sidebar) across examples/**, content/docs/**, every README, every package's tests and fixtures, the designer palettes, and the objectstack checkout. Each hit re-judged:

Document Authored Disposition
packages/types/README.md, "4. Composable" nav rewritten to children composition (collapsible: false, a text and a button child): the ruling
packages/types/examples/dashboard.ts nav (three links) the three links become three ghost button children; collapsible: true kept as authored
content/docs/components/basic/sidebar.mdx and the four components-basic-sidebar catalog documents collapsible, className, children no change
content/docs/components/navigation/header-bar.mdx a bare sidebar placeholder no change
tests (sidebar-node-provider-10859, the app-shell one-provider pin, widget-dom-leak-sweep, container-declaration-ratchet, the cli ratchet) no retired key no change
objectstack its type: 'sidebar' hits are app navigation shapes, not this node no change

No document anywhere authors variant on a sidebar node.

Pins

  • packages/types/src/__tests__/sidebar-declared-surface-11465.test.ts
    • type level (tsc -p tsconfig.test.json): each retired key reads as undefined on both faces; variant is exactly the three values on both faces; collapsible keeps its boolean (non-vacuity); a @ts-expect-error per retired key and per retired value.
    • runtime: each retired key, for every former arm, is refused at its own path on the strict face, the tolerant face and the mirror; the message starts with the RETIRED prefix and carries Instead: and its prescription; lit controls (the same node without the key parses everywhere; an undeclared key stays green on the tolerant face).
    • variant: the three validate on both faces; default and bordered are refused at variant with invalid_value.
    • README leg: the sidebar literal of the README's "4. Composable" fence is extracted and evaluated, never retyped, and validates on both faces; control: the example the README taught before is refused at nav.
  • packages/components/src/renderers/__tests__/sidebar-variant-registration-11465.test.tsx: reads the live registration's variant enum; every value it offers validates on both faces, and the two retired values are refused; through the real SchemaRenderer, the offered values draw pairwise-different markup on the collapsible form, and identical markup on the in-flow form.
  • The README snippet type-checks: pnpm check:doc-snippets compiles the fence against the built dist.

Ledger moves:

  • zod-mirror-parity.test.ts: the arm-count non-vacuity control moves from SidebarSchema.content (now a one-arm tombstone) to SidebarSchema.children, the channel the node reads. The NavLinkSchema row leaves RECURSION_BREAKING_MIRRORS: its no-dead-entries case went red naming it, because SidebarSchema.nav was the only registered slot that reached it. NavLink and NavLinkSchema stay exported.
  • scripts/markdown-test-inputs.mjs: the new test is adjudicated as a reader of packages/types/README.md (its --audit named it as unadjudicated).

Gates — all at c95b819e

Gate Verdict
pnpm --filter @object-ui/types run type-check (all three legs; the new test and examples/dashboard.ts confirmed in the programs with --listFilesOnly) exit 0
pnpm --filter @object-ui/components run type-check (the new test confirmed in the program) exit 0
pnpm exec vitest run packages/types/ 334 files, 8814 tests passed
consumer radius: the two new pins, sidebar-node-provider-10859, container-declaration-ratchet, schema-catalog sidebar-node-docs-10859 / safe-validate-corpus-6318 / component-fixture-declared-keys / catalog-gallery-render, the cli ratchet, the layout one-provider pin, markdown-test-inputs.test.ts 10 files, 733 tests passed
pnpm check:doc-snippets (closure built with its own --build-filter) 776 of 776 blocks judged, 0 failed
pnpm check:doc-examples, check:doc-types, check:doc-fences, check:readme-exports exit 0 each
check-changeset-no-major, -presence, -fixed, -overwrite; check:changeset-claims (report-only: 18 pending changesets name a touched file; each read, none is falsified by this diff) exit 0 each
check:new-line-citations (0 new), check:control-bytes exit 0 each
check:component-surface-parity (report-only) the nine ui:sidebar interface-extra-key rows leave the census; read-of-tombstoned-key stays 0
check:handler-key-reads, check-prompt-component-keys, check-test-path-roots, check-type-check-coverage, check:unreferenced-sources, check:published-dist, check-doc-expression-carriage, check-lint-coverage, markdown-test-inputs --audit exit 0 each
check:sdui-registration-pins NOT MEASURED: exit 2, no console build to weigh; this diff moves no registration input, so it is not owed here

Lint, narrowed and declared. eslint --no-inline-config --format json over the seven changed lintable files: 7 files, 0 errors (2 warnings, both on unchanged zod-mirror-parity.test.ts lines whose inline disable --no-inline-config ignores). The **/*.{ts,tsx} block of eslint.config.js covers six of them and the .mjs is covered too; the config sets no parserOptions.project or projectService, so linting is not type-aware and this diff cannot move a verdict in an untouched file. The repo-wide pnpm lint and the full test farm are CI's.

Ablation and reverse verification

Each leg ran through objectstack's scripts/ablation-replace.mjs in wrap mode on the committed tree: the mutation proven on disk (anchor count 1 to 0, blob changed), the restore proven (blob equals HEAD, git diff HEAD empty). vitest resolves @object-ui/types to source through the root alias, so the vitest legs need no rebuild.

  1. zod variant enum back to default / bordered / floating: 7 failed (four in the types pin, three in the components pin).
  2. zod title accepting a string again: 2 failed (the refusal-path and message cases for title).
  3. TypeScript title?: string again: tsc -p tsconfig.test.json red with TS2344 (the Equal row), TS2578 (the unused @ts-expect-error) and TS2322 in zod-mirror-parity.test.ts's drift ledger.
  4. The README's old nav line put back into the fence: pnpm check:doc-snippets judged 1 block failed, TS2322 at the README's nav line (a NavLink-shaped array is not assignable to undefined), which proves the gate read the rebuilt .d.ts. The first attempt was a no-op (my anchor was a prefix of its replacement, and the tool refused to count it as landed); it was re-run with a non-prefix anchor.

Acceptance notes (observations, not filed)

  • side: the registration offers side (left / right) and shadcn honours it, but SidebarSchema does not declare it, so the strict face refuses side: 'right' (unrecognized_keys) while the render honours it. check:component-surface-parity already reports it (ui:sidebar.side, input-outside-keyset). The position tombstone deliberately does not point at side.
  • variant on the in-flow form draws nothing (H3); not changed, by the ruling.
  • NavLink / NavLinkSchema stay exported but no longer type any declared member; whether they retire too is an open question in the report, outside the claimed file surface.
  • The rest spread: SidebarNode forwards every unread key onto the panel element, which is how [object Object] attributes arose. After this change the validator refuses the nine; the renderer is untouched (⛔ no consumer-side narrowing).
  • content/docs/components/basic/sidebar.mdx does not document variant; its sentence on what the renderer reads is not made false by this change, and it is outside the claimed surface.
  • Overlap: main was merged at 2c0ddf226 (PR objectui#11477 edits other blocks of the types README and the imports of examples/dashboard.ts); this diff's hunks there are the sidebar literals only.

Generated by Claude Code

claude added 3 commits October 2, 2026 15:52
…e unread keys retire, variant takes the registration's enum (objectui#11465)

Retire title, nav, content, footer, position, defaultCollapsed, collapsed,
width and collapsedWidth on both faces: a ?: never tombstone on the
TypeScript face, a retirementTombstone refused by name with the reason and
what to write instead on the zod face. variant becomes sidebar / floating /
inset, the sidebar registration's own enum; default and bordered are
refused with no alias window.

The types README's composition example and examples/dashboard.ts author
the node through children. zod-mirror-parity: the arm-count non-vacuity
control moves from SidebarSchema.content to SidebarSchema.children, and
the NavLinkSchema recursion row leaves the ledger (nothing registered
reaches it once nav is a tombstone).

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
…of the types README

markdown-test-inputs' audit found the new test reads packages/types/README.md;
record it so a README-only pull request runs the shard that reads it.

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation package: types package: components tests labels Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 18 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/10387-header-bar-unread-keys-retired.md

  • names packages/types/examples/dashboard.ts → packages/types/examples/dashboard.ts — edited by this change

    The in-tree examples that used these keys (packages/types/examples/dashboard.ts, the @object-ui/types README and the ObjectOS integration guide) now use crumbs and actions.

.changeset/11355-small-p1-sites-r2.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ObjectChartSchema.isAnimationActive?: boolean is declared on the TypeScript face. Code sets it false for a render with no entrance animation: DashboardRenderer and DashboardGridLayout on the object-chart nodes they build, and DatasetWidget and DatasetReportRenderer on the nodes they hand the chart registration. ChartRenderer honours it. The zod mirror declares no member for it, so it is not an authoring key; zod-mirror-parity.test.ts files it as runtime-only.

.changeset/5903-objectgantt-declared-keys.md

  • names packages/types/src/__tests__/zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⚠️ The JUSTIFICATION for that parity clause was retired (objectui#9743); the clause itself stands and did not move. As first written it credited the zod-mirror-parity ratchet with a zero-drift reading for this pair — but at that time the ratchet measured three directions and was structurally blind to the MIRRORED-but-undeclared one (objectui#9711), so a zero from it recorded that it had not looked in that direction, not that nothing was there. objectui#9725 landed the fourth direction, and it covers this pair BY NAME: packages/types/src/__tests__/zod-mirror-parity.test.ts registers objectql.zod.ts#ObjectGanttSchema in both its mirror map and its declaration map; assertionMirroredUndeclaredMatchesLedger requires every registered pair's mirrored-but-undeclared key set to equal that pair's MirroredUndeclared ledger entry — never for a pair the ledger does not name — and assertionNoVacuousMirroredUndeclaredMeasurement refuses a measurement that has degenerated to any. ⛔ Read this pair's verdict off that reconciliation, which re-derives it on every run, rather than off any figure written here; when this paragraph was authored, on 2026-09-18, it required no MirroredUndeclared entry for the pair.

.changeset/5928-classname-style-props-rename.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Where the non-pair is recorded now. zod-mirror-parity.test.ts keys its existing EXCLUSIONS entry — the mechanism that accounts for every exported const with no TypeScript declaration to mirror, each with its stated reason — to ClassNameStylePropsSchema. Named for its own two keys, the const leaves no like-named declaration for a name-derived pairing to reach for.

.changeset/6051-gantt-flat-config-declared-keys.md

  • names packages/types/src/__tests__/zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⚠️ The JUSTIFICATION for that parity clause was retired (objectui#9743) — the same retirement objectui#5903's entry carries, for the same clause and the same reason; the clause itself stands and did not move. As first written it credited the zod-mirror-parity ratchet with a zero-drift reading for this pair, at a time when that ratchet measured three directions and was structurally blind to the MIRRORED-but-undeclared one (objectui#9711), so a zero from it recorded that it had not looked in that direction. objectui#9725 landed the fourth direction, and it covers this pair BY NAME: packages/types/src/__tests__/zod-mirror-parity.test.ts registers objectql.zod.ts#ObjectGanttSchema in both its mirror map and its declaration map; assertionMirroredUndeclaredMatchesLedger reconciles every registered pair's mirrored-but-undeclared key set against that pair's MirroredUndeclared ledger entry — never for a pair the ledger does not name — and assertionNoVacuousMirroredUndeclaredMeasurement refuses a measurement that has degenerated to any. ⛔ Read this pair's verdict off that reconciliation, which re-derives it on every run, rather than off any figure written here; when this paragraph was authored, on 2026-09-18, it required no MirroredUndeclared entry for the pair.

.changeset/6150-undeclared-but-consumed-keys.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⭐ AMENDED, and the amendment ships in this same release. The TreeViewSchema slice (604476d97) gave the key a zod arm after all — a NAMED REFUSAL (handlerKeyRefusal(key, 'runtime-slot', label)), never a shape — because "no mirror entry" is not neutral under BaseSchema.passthrough(): it meant an authored { "type": "tree-view", "onNodeClick": { "action": "toast" } } parsed GREEN, survived the parse, and reached a call site that expects a function. ⇒ the three clauses this bullet used to carry are no longer true of the code shipping beside it. The key is now a MEMBER of TreeViewSchema.shape and an authored value is refused BY NAME at path onNodeClick; it has LEFT zod-mirror-parity.test.ts's RuntimeOnlyDeclared for that file's KnownDrift; and it is no longer "the first pair to sit there without also sitting in UnmirroredDeclared" — draining it emptied that difference, so RuntimeOnlyDeclared is now a SUBSET of UnmirroredDeclared and the union of the two equals UnmirroredDeclared itself. ⛔ objectui#6152's ruling is untouched by any of this: what the key still does not have, and never will, is a z.function() shape — no serialized document could satisfy one.

.changeset/6175-column-state-persistence.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Nothing is retired. Both spellings remain declared on DataTableSchema; onColumnReorder stays declared and stays unwired, exactly as the RuntimeOnlyDeclared ledger in zod-mirror-parity.test.ts records it. Which of the two survives is a declared-surface ruling that stays open and is deliberately not settled here.

.changeset/6349-types-internal-name-collisions-batch-1.md

  • names navigation.ts → packages/types/src/navigation.ts — edited by this change

    BreadcrumbItem / BreadcrumbSchema — re-pointed, because one copy was stale. Both were declared in data-display.ts and in navigation.ts. The data-display pair was not a second dialect but a strict SUBSET: no key declared differently on either side, and missing BreadcrumbItem.icon / onClick / siblings and BreadcrumbSchema.maxItems. Everything that reads a breadcrumb was already on the navigation declaration — registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems included), the ui:breadcrumb renderer consumes it, and the component's own documentation page documents icon and maxItems. data-display.ts now re-exports the one authority.

  • names zod/navigation.zod.ts → packages/types/src/zod/navigation.zod.ts — edited by this change

    BreadcrumbItem / BreadcrumbSchema — re-pointed, because one copy was stale. Both were declared in data-display.ts and in navigation.ts. The data-display pair was not a second dialect but a strict SUBSET: no key declared differently on either side, and missing BreadcrumbItem.icon / onClick / siblings and BreadcrumbSchema.maxItems. Everything that reads a breadcrumb was already on the navigation declaration — registry.ts maps the 'breadcrumb' component type to it, src/index.ts re-exports it under the bare names, zod/navigation.zod.ts mirrors it (icon, onClick, siblings, maxItems included), the ui:breadcrumb renderer consumes it, and the component's own documentation page documents icon and maxItems. data-display.ts now re-exports the one authority.

.changeset/6639-objectgrid-title-mirrored.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The gain is the typed refusal: the mirror's .passthrough() base was already admitting any title unexamined, and it now enforces the declared string. zod-mirror-parity.test.ts's UnmirroredDeclared ledger records the key as worked off — the ledger's first shrink by repair (97 + 1 mirrored + 23 reclassified is what the seeded "121" now means).

.changeset/6646-breadcrumb-separator-max-items.md

  • names packages/types/src/navigation.ts → packages/types/src/navigation.ts — edited by this change

    BreadcrumbSchema has declared both since it shipped (packages/types/src/navigation.ts, mirrored in zod/navigation.zod.ts), and separator is additionally advertised to authors on the component's own documentation page. The renderer contained zero occurrences of either name: it always emitted the bare BreadcrumbSeparator and it never collapsed. That made separator the sharper of the two — an author who read the page, wrote "separator": "/" and saw a chevron got feedback identical to having misspelled the key, with nothing to tell the two apart.

  • names zod/navigation.zod.ts → packages/types/src/zod/navigation.zod.ts — edited by this change

    BreadcrumbSchema has declared both since it shipped (packages/types/src/navigation.ts, mirrored in zod/navigation.zod.ts), and separator is additionally advertised to authors on the component's own documentation page. The renderer contained zero occurrences of either name: it always emitted the bare BreadcrumbSeparator and it never collapsed. That made separator the sharper of the two — an author who read the page, wrote "separator": "/" and saw a chevron got feedback identical to having misspelled the key, with nothing to tell the two apart.

.changeset/7344-handler-string-any-mirrors.md

.changeset/7654-floating-chatbot-trigger-icon-tombstone.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Every other tombstone in this package pairs ?: never with a retirementTombstone() refusal on the Zod twin. There is no twin here to carry one: FloatingChatbotConfig has no Zod mirror at all, and floatingConfig sits in the UnmirroredDeclared ledger (zod-mirror-parity.test.ts, complex.zod.ts#ChatbotSchema). BaseSchema is .passthrough(), so the whole floatingConfig object rides through unvalidated — before this change and after it. Minting a mirror to host a refusal would be the declared-but-UNMIRRORED axis (objectui#6152), a different defect: a key can be mirrored and inert, or unmirrored and live, and fixing one says nothing about the other. This change does not widen into it.

.changeset/7804-tree-view-handler-slot.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The pair moves from zod-mirror-parity.test.ts's RuntimeOnlyDeclared to its KnownDrift, which empties the former of the one entry the latter did not also hold — so the two unmirrored ledgers are now in a containment relation, and the cross-ledger figure that recorded their difference states the containment instead.

.changeset/7917-export-breadcrumb-object-tree-zod-schemas.md

  • names navigation.zod.ts → packages/types/src/zod/navigation.zod.ts — edited by this change

    AnyComponentSchema declares 107 node component types. 105 of them could be named on the ./zod barrel — ButtonSchema.safeParse(node), which is what a designer, a form builder or a targeted test needs. The arms declaring type: 'breadcrumb' (navigation.zod.ts) and type: 'object-tree' (objectql.zod.ts) could not: both were already export const in their own module, but index.zod.ts — the package's only zod entry point — did not re-export them, so the schemas existed, were maintained, and were applied by the union while no consumer could name them.

.changeset/8338-retire-toast-action.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The parity ledgers drain with it, every figure re-derived by zod-mirror-parity.test.ts's own AST and mirror instruments rather than stepped by hand: KnownDrift 42 entries / 64 keys → 41 / 63 (the entry's whole content, so the entry went too — the ledger's first loss by RETIRING a key rather than by moving either face toward the other), and WiderThanDeclared 23 / 36 / 47 arms, split 6 / 30 / 0 / 11 → 22 / 35 / 45, split 6 / 29 / 0 / 10. The pair itself stays registered, so EXPECTED_MIRROR_PAIRS does not move.

.changeset/8572-chatbot-body-retired.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Why this key and not another. It was the ONE place in this vocabulary where body did not mean "what goes inside this component": zod-mirror-parity.test.ts carried the pair under KnownDrift as "two different meanings of one key", and the same collision was the whole reason chatbot was the single arm of the component union whose output was not assignable to SchemaNode. Both ledger rows move with this change, and the two pins that recorded the old state are INVERTED rather than deleted (see below).

.changeset/9511-record-id-is-a-string.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⭐ The declaration alone would not have been enough, and this is the reusable part. A TypeScript declaration does not run at parse time. The hand-written zod mirror is the only face in this repository that can refuse an authored number, so narrowing the declaration without the mirror would have shipped declared !== enforced on a published surface — and zod-mirror-parity.test.ts would have stayed GREEN through it, because that instrument asserts a mirror accepts everything its declaration declares and a mirror left WIDER passes. Both faces moved together for that reason.

.changeset/object-view-unmirrored-keys-7779.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Who is NOT affected: every correctly typed document, and every document that never wrote these keys — absent stays valid on all nine. No renderer changed. The parity ledger (zod-mirror-parity.test.ts) records the move: UnmirroredDeclared 14 entries / 96 keys to 14 / 87, the ObjectViewSchema entry re-derived into the SPEC-DERIVED half because the mirror now references the spec in code.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 2c0ddf226 (merge-base with origin/main): 8 file(s) changed outside .changeset/, read against 2026 pending declaration(s) that publish a body (2655 pending in total). · run

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3550.7 KB 3574.6 KB
Main entry chunk (gzip) 150.4 KB 350 KB
Entry file index-Cjyv0H-d.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.88KB 6.25KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 569.61KB 136.76KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 230.46KB 63.94KB
fields (index.js) 261.25KB 66.11KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.49KB 9.23KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.36KB 11.18KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 83.53KB 22.84KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 141.23KB 38.00KB
plugin-designer (index.js) 231.28KB 48.76KB
plugin-detail (index.js) 245.27KB 64.47KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.73KB 44.50KB
plugin-gantt (index.js) 179.14KB 45.06KB
plugin-grid (index.js) 232.66KB 63.77KB
plugin-kanban (index.js) 49.51KB 15.54KB
plugin-list (index.js) 116.60KB 28.96KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.07KB 2.39KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 21.59KB 7.71KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c95b819e9e022db2a9cb2acdca55f6678ef2a795
Local-runs: none

Inputs: card #11465 (body and its three comments — triage ruling 5954161531, the claim 5955764801, the os-dev-report 5956474485); PR #11485 (body, the 9-file list, the net diff against main at 2c0ddf226); the head's 42 check-runs, read twice; and two files at the head for context only (navigation.zod.ts's import block, sidebar.tsx with its registration). Nothing built, run or re-run.

① Derived judgments

Public surface the diff touches: SidebarSchema on both faces of @object-ui/types (src/navigation.ts, src/zod/navigation.zod.ts) and the types README. No export is added or removed. @object-ui/components gains one test and no source. scripts/markdown-test-inputs.mjs gains one ledger row.

  1. Nine keys narrow to absence — title, nav, content, footer, position, defaultCollapsed, collapsed, width, collapsedWidth: ?: never with a RETIRED docblock on the TypeScript face; retirementTombstone (through the local retiredSidebarKey) on the zod face, each refusal naming the key, the card, ADR-0049, the reason and an Instead: prescription. Right. These are exactly the nine triage ruled retired. The renderer at the head destructures collapsible, renders schema.children and spreads the rest onto shadcn's Sidebar; its registration inputs are collapsible, side, variant and the children slot. None of the nine is read. It is the precedent form (objectui#10387, objectui#11434), not a bare deletion, which BaseSchema's index signature and .passthrough() would turn into any and a green parse. retirementTombstone is already imported in navigation.zod.ts (line 20 at the head), so the zod face compiles as written.
  2. variant goes from 'default' | 'bordered' | 'floating' to 'sidebar' | 'floating' | 'inset' on both faces, @default 'sidebar'; 'default' and 'bordered' are refused with invalid_value and a message that says to write 'sidebar'; no alias window. Right. The registration at the head offers enum: ['sidebar', 'floating', 'inset'] with defaultProps.variant: 'sidebar'. The widening (sidebar, inset validate where both faces refused them) and the narrowing (default, bordered validated before) are both named in the PR body and the changeset. The H3 caveat — shadcn ignores variant on the in-flow form, collapsible: false — is written on the TS doc, the zod describe and the changeset, and pinned; it does not make the declaration false on the collapsible form the key is for.
  3. collapsible and the onCollapsedChange tombstone are untouched. Right — PR objectui#11463's and objectui#6124's.
  4. Renderer sidebar.tsx untouched. Right. Triage's ⛔ no narrowing at the consumer holds; variant already reached shadcn through the spread, so no read had to follow.
  5. Types README "4. Composable" sidebar literal becomes collapsible: false plus a text and a ghost button child; examples/dashboard.ts's three nav links become three ghost button children, collapsible: true kept. Right — these were the measured producers of nav (the card's named reach), and the README hunk is the sidebar literal only, leaving PR objectui#11477's blocks alone.
  6. zod-mirror-parity.test.ts: NavLinkSchema leaves RECURSION_BREAKING_MIRRORS (its one registered carrier, SidebarSchema.nav, is now a tombstone, so the no-dead-entries case named it) and the arm-count non-vacuity control moves from content (now one arm) to children (still single-or-list, two arms). Right — both moves are forced by the retirement, the control still exercises a two-arm key, and the count prose above the list was corrected in step.
  7. scripts/markdown-test-inputs.mjs: the new types test is adjudicated as a reader of packages/types/README.md. Right — the audit's own precondition for a README-reading test; a ledger row naming only the new test and its input, not a gate change.
  8. NavLink / NavLinkSchema stay exported. Right for this claim — the ruling did not name them (③ Q1).
  9. Pins the ruling asked for, all present: each retired key refused at its own path on the strict face, the tolerant face and the mirror, for every former arm, with the RETIRED prefix and its prescription; type-level Equal rows and one @ts-expect-error per retired key and per retired value, with collapsible as the non-vacuity neighbour; every variant value the LIVE registration offers validates on both faces and the two retired ones are refused (the components test reads ComponentRegistry.getMeta('sidebar'), so a registration that grows a value the schema refuses turns red); the README literal is extracted, not retyped, and validates on both faces, with the former nav example as the refused control; check:doc-snippets compiles the fence against the built dist.

Nothing in the diff is judged wrong.

② Semver level

.changeset/11465-sidebar-declared-surface.md: '@object-ui/types': minor, titled feat(types)!:, with a BREAKING (authoring) paragraph, the widening and narrowing of variant, a one-line migration per key and a before/after fence. That is what the diff publishes: one package's authoring surface narrows (nine members to never, two enum values gone) and widens (two enum values gained). @object-ui/components publishes nothing (test only) and correctly carries no changeset. The level is this repository's own scoring of a breaking authoring change — Changeset Bump Policy, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report and Changeset Claim Re-read are all green on the head.

Clause-②: yes — present and identical on the PR body, the claim 5955764801 and the changeset, and correct: the accept set moves in both directions.

③ Boundary flags

Dev deviations (os-dev-report 5956474485):

  • scripts/markdown-test-inputs.mjs outside the claimed surface — answered: accepted, as ① item 7.
  • One test file in packages/components — answered: accepted. The claim's surface lists "Tests" with no package bound, the pin against the live registration can only be written where the registry is importable, and sidebar.tsx is untouched.
  • Two zod-mirror-parity rows — answered: accepted. The claim allowed that file "only if a ledger row moves", and both moves are measured, as ① item 6.
  • main merged, not rebased, at 2c0ddf226; the toolchain in scratch; model-free trailers — process, no bearing on the contract. What is judged here is the net diff against main.

open_questions:

Triage's own lines: ⛔ no narrowing at the consumer (renderer untouched) — held; ⛔ no grace window (default / bordered refused at once) — held; serial after PR objectui#11463 (the base 2c0ddf226 carries it; the renderer read here is the settled one) — held.

Out-of-scope findings (side undeclared yet honoured; the rest-spread DOM leak; variant inert on the in-flow form; sidebar.mdx silent on variant) are correctly left out of this diff: the first has a family carrier (check:component-surface-parity, the objectui#4631 census), the rest are stated in the PR's acceptance notes, and the position tombstone correctly does not point authors at the undeclared side.

Check-runs on the head, read at 2026-10-02T16:18:40Z and again at 16:22:25Z (42 runs): at the second read 31 success, 3 skipped (dependabot and two coverage matrix placeholders), 8 in_progress — Type Check, Spec Main Shape Gate, Test (shard 1/8), 3/8, 5/8, 6/8, 7/8, 8/8 — and none red. Green already: Build & E2E, Doc Snippet Type Check (which compiles the README fence against the rebuilt .d.ts, so the TypeScript face builds with the tombstones), Test (dist pins), Lint, shards 2 and 4, and the five changeset gates. The pending Type Check and shards bear on the new pins (the Equal rows, the @ts-expect-error directives, the runtime refusals), not on the shape of the contract judged in ① and ②; the dev's report measured all of them green at this head. The adopting seat reads them to conclusion before merging; a red one re-opens this record.

Implemented-by: claude/issue-11465-sidebar-declared-surface
Reviewed-by: session_01XvhGmGAP79ZB8swnkapxPC

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 2, 2026 16:29
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit ca3de72 Oct 2, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11465-sidebar-declared-surface branch October 2, 2026 16:44
os-tesla pushed a commit that referenced this pull request Oct 2, 2026
Brings in objectui#11485, #11486 and #11482; the parity ledger merged
without a conflict (#11485 moved rows in other blocks).

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
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 package: components package: types tests

Projects

None yet

2 participants