Skip to content

feat(types): four zod mirrors declare members their TypeScript twins declared, and pagination retires page (objectui#6152, round 3) - #11229

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-6152-unmirrored-round3
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-6152-unmirrored-round3

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Refs #6152
Clause-②: yes (narrowing). The diff widens and narrows at once. A declared-but-unmirrored key gains its mirror, so @object-ui/types/zod accepts what the TypeScript face already invited. page → currentPage retires one spelling (refused at the key on both faces, which is the narrowing) under the startup no-gradualism rule, and is priced in the changeset. Arm corrected from (widening) per the contract record 5914849079.

Round 3 of objectui#6152, dispatched under claim 5912572626 (the domain:devx seat 2, session session_01TdiauJaVCHuj45EzZGUxHh, on the maintainer's direct order). ⛔ Draft for the director seat's contract review. Do not mark it ready, and do not enable auto-merge. The card stays open: ObjectGridSchema and the spec-derived pairs are not this round's.

What changed

  1. Ruling (a): ObjectFormSchema.open and submitHandler go to RuntimeOnlyDeclared by NAME. The shape pin widens from the on* spelling to that spelling plus a new RuntimeOnlyNamedAllowList, one row per key, each row carrying its reason as a string-literal type. Three pins hold the list: a non-callback key without a row is red, a row whose key has left the ledger (or that names a callback) is red, and a row without a reason is red. Each pin also has a synthetic recognition case. The comparison is unchanged: assertionUnmirroredMatchesLedger still reconciles one measurement against the union of both ledgers. Neither key is mirrored, and neither declaration moved.
  2. Ruling (b): PaginationSchema.currentPage is mirrored, and page retires at once. page becomes ?: never on the interface and retirementTombstone() on the mirror, whose message says to rename it to currentPage. The pagination renderer drops its page fallback.
  3. Mirrored, each key measured READ first: all eight FormSchema layout keys, ReportComponentSchema.conditionalFormatting, and six DetailViewSchema configuration keys. The nested entries (fieldTabs, fieldPanes, sectionGroups, highlightFields, the formatting rules) use module-private schemas that restate their TypeScript element types member for member. One exception is stated in its docblock: sectionGroups[].sections is z.array(z.any()). Binding DetailViewSectionSchema there would import that pair's own KnownDrift rows as a new drifted key, and growing KnownDrift is never the remedy. This is the same exception round 1 took for sections[].fields.
  4. Ledger figures are re-derived by the file's own pins. Two pairs are now runtime-only and nothing else, so the cross-ledger sentence states a difference again. It is now spelled to hold at any difference, and its pin reads all three figures. The containment leg that the objectui#7804 slice added is deleted, not inverted, and the comment next to it says why.
  5. One changeset (@object-ui/types and @object-ui/components, both minor; the break is spelled out). Also, three dated correction notes are appended to pending changesets that this change made false (see Deviations).

Ledger totals, before → after (read by the file's own pins, not by hand)

ledger before (52bf34824) after
UnmirroredDeclared 12 entries / 62 keys: 4 / 16 spec-derived, 8 / 46 local 9 / 44: 3 / 14 spec-derived, 6 / 30 local
RuntimeOnlyDeclared 3 / 7 4 / 9
cross-ledger 3 of the 3 are in UnmirroredDeclared; the union is 12 2 of the 4; the union is 11 = 9 + 2 runtime-only-only
KnownDrift, WiderThanDeclared, MirroredUndeclared 49 / 87 · 3 / 3 / 3 · unchanged unchanged

H1 held: 12 / 62 with the 4 / 16 and 8 / 46 split on main. One correction to the dispatch: ObjectGridSchema carries 11 keys, not 14, because objectui#11068 closed three.

Per-key table

The read census is a type-checker walk over every package's sources. It covers property access, element access, in, destructures, and object-literal producers, all resolved to the interface member through root symbols, plus a separate list of untyped (any) reads. Its controls fired on every pair (for example PaginationSchema.totalPages 1 read, DetailViewSchema.objectName 45). The authored census covers 493 tracked JSON files, 352 Markdown JSON fences, and 3118 TS/TSX files that carry a string-literal type.

pair · key read site or producer action
object-form · open read by ObjectForm (open: schema.open, drawer and modal variants); written only by in-code hosts: AppContent, useActionModal, ObjectManager, FieldDesigner, the byo-backend-console example; 0 documents RuntimeOnlyDeclared, allow-list row + reason
object-form · submitHandler ObjectForm calls await schema.submitHandler(writePayload); supplied only by MasterDetailForm; 0 documents RuntimeOnlyDeclared, allow-list row + reason
pagination · currentPage pagination renderer (schema.currentPage); authored 3 times (two catalog documents, the site playground) mirrored
pagination · page read only as the fallback limb of schema.currentPage or schema.page; authored 0 times retired on both faces; the read is dropped
form · fieldTabs, defaultFieldTab, fieldTabsPosition renderers/form/form.tsx (schema.fieldTabs, schema.defaultFieldTab, schema.fieldTabsPosition) mirrored
form · fieldPanes, fieldPanesOrientation, fieldPanesResizable renderers/form/form.tsx (schema.fieldPanes, schema.fieldPanesOrientation, schema.fieldPanesResizable) mirrored
form · fieldContainerClass, mobileStickyActions renderers/form/form.tsx; also produced by ObjectForm when it builds a form node mirrored
report · conditionalFormatting ReportViewer evaluates the rules per cell on the report a report-viewer node carries mirrored
detail-view · primaryField, summaryFields, autoTabs, sectionGroups, highlightFields DetailView (schema.*) mirrored
detail-view · defaultTab DetailView, through its own (schema as any).defaultTab cast at the one read site mirrored

Stays in the ledger, each with its route written on its entry:

pair · key measured route
report · chartConfig 0 reads, 0 documents narrowing candidate (objectui#6170 family rule; owes the inertness probe) — reported, not narrowed
report · reportType 0 reads; one in-code producer (the spec-report converter) narrowing candidate — reported, not narrowed
detail-view · activities, comments, history read; they carry host-fetched runtime data; 0 documents the round-1 "class (b)" classification question
detail-view · recordNavigation read; its value carries a REQUIRED callback (onNavigate) needs a ruling; ⛔ a callback is not mirrored
detail-view · autoDiscoverRelated 0 reads (typed or untyped), 0 documents narrowing candidate — reported, not narrowed
label · content read as the THIRD limb of text, label, content; authored once (a test) canonical-spelling ruling, ⛔ not a mirror

page census (pagination nodes only)

before after
renderer read sites of page 1 (the fallback limb in renderers/basic/pagination.tsx) 0
corpus documents authoring page 0 (JSON 0, Markdown fences 0, TS object literals 0) 0
documents authoring currentPage (control) 3 3

So no corpus document needed correcting. The data-table's own page key (DataTableSchema.page) is a different member and is untouched.

Verification

Heavy runs went through the shared verify lock. Each exit code was captured before any pipe.

  • @object-ui/types at the final head 1371bca: type-check (tsc --noEmit, tsconfig.examples.json, tsconfig.test.json) exit 0, no diagnostics; vitest run packages/types/ gives 290 files / 6941 tests passed. build exit 0 ("dist completeness: 1 package(s) complete").
  • Consumers. This set is derived from what moved. @object-ui/components owns the pagination and form renderers and the page TypeScript change. @object-ui/plugin-detail and @object-ui/plugin-report own the readers. @object-ui/plugin-form produces two of the form keys. The zod-face validators cover the schema-catalog corpus, app-shell previews and the CLI. Their dependency closure was built first. type-check exit 0 for components, plugin-detail, plugin-report and plugin-form. Tests: components 341 files passed + 1 skipped (four shards); plugin-detail 225 + 1 skipped; plugin-report 24; plugin-form 153; examples/schema-catalog/test/ + app-shell previews/ 122; packages/cli/src/__tests__/ 23; block-config-schema-parity-8216 15 tests passed.
  • Reverse verification. Each prediction was written before its run. ablation-replace did the mutations, and each restore was proven by blob == HEAD and an empty git diff HEAD.
    • A1: the four zod mirrors were restored to base. The new pin file went 53 failed / 6 passed, exactly as predicted.
    • A2: the allow-list row open was renamed. tsc -p tsconfig.test.json exit 2 with exactly two TS2344, at assertionRuntimeOnlyIsCallbackShapedOrNamed and assertionNamedAllowListRowsAreFiled. The comparison stayed green, as predicted: the list is not in the comparison.
    • A3: page: 2 was written onto a PaginationSchema inside components. Its tsc --noEmit exit 2 with one TS2322, so the consumer reads the rebuilt page?: never. A first A3 attempt was refused by the tool before any run: the replacement contained the anchor.
  • Gates, all exit 0: check-changeset-presence, check-changeset-no-major, check-changeset-fixed, check:changeset-claims (report-only; every named paragraph read, see Deviations), check:pending-changeset-literals, check-changeset-overwrite (report-only; case 2, frontmatter unchanged), check:control-bytes, check:new-line-citations (0 new), check:handler-key-reads, check:element-data-source-declaration, check:spec-symbols, check:test-path-roots, check:unreferenced-sources, check-type-check-coverage, and check-governed-queue-guard --test (NOT GOVERNED over the 12 paths).
  • Lint, narrowed and proven. eslint --no-inline-config --format json ran over the 8 touched TS files; the JSON has 8 entries and none of them is an ignore notice. It found 0 errors. The 3 warnings sit on untouched lines. eslint.config.js enables no type-aware linting (empty parserOptions), so this diff cannot move a verdict in an untouched file. The repo-wide pnpm lint belongs to CI.

Deviations (declared)

The claim's file surface does not name three pending changesets. Commits b3c93e0 and 3b77588 append a dated Correction, 2026-09-30 note to each, in the maintainer's append-only shape (numstat 2 0 each; frontmatter and every existing line unchanged):

  • .changeset/6152-object-form-unmirrored-members.md said the routes of open and submitHandler are open;
  • .changeset/6150-undeclared-but-consumed-keys.md and .changeset/7804-tree-view-handler-slot.md said the two unmirrored ledgers are in a containment relation.

Each became false with this change. The seat is asked to widen the claim's file surface to these three paths.

Not this round (the round-4 worklist)

  • data-display.zod.ts#DataTableSchema, 17 keys, cut at the pair boundary by the size guard. Every key is read in renderers/complex/data-table.tsx (mostly by destructure), 15 of them are produced by ObjectGrid's in-code dataTableSchema, and almost no document authors any of them. So they fall under the round-1 "class (b)" question too.
  • complex.zod.ts#ChatbotSchema and #ChatbotFloatingSchema. displayMode's runtime tombstone was deliberately withheld by objectui#7654 (stored designer documents carry it, and chatbot-display-mode-retired.test.ts pins that as a tripwire). floatingConfig needs a FloatingChatbotConfig mirror, with its own tripwire. requestBody is read by all three registrations and is mirrorable.
  • ObjectGridSchema (11) and the spec-derived pairs, excluded by the claim.

Acceptance notes

  • examples/schema-catalog/.../components-basic-pagination/with-item-count.json authors pageSize and totalItems, which neither face declares and nothing reads. The tolerant face keeps them and the strict face refuses them, measured. It is reported, not changed here.
  • .changeset/6639-objectgrid-title-mirrored.md says what "121" "now means". That went stale before this round, and it is not corrected here.
  • The DetailViewSchema mirror's onNavigate docblock calls the nested recordNavigation.onNavigate "authorable where it lives". A function is not authorable; this is prose only, and noted.

Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh


Generated by Claude Code

…re (objectui#6152 round 3)

- ObjectFormSchema.open / submitHandler move to RuntimeOnlyDeclared by NAME,
  through a new RuntimeOnlyNamedAllowList that carries each key's reason; the
  shape pin widens from the on* spelling to that spelling plus the named list.
- PaginationSchema.currentPage is mirrored; page retires on both faces
  (tombstone) and the renderer's page fallback is dropped.
- FormSchema's eight layout keys are mirrored.
- ReportComponentSchema.conditionalFormatting is mirrored.
- DetailViewSchema's six configuration keys are mirrored.

Ledger figures follow in the next commit.

Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh
Co-authored-by: Claude <noreply@anthropic.com>
…(objectui#6152)

UnmirroredDeclared 12 / 62 -> 9 / 44 (3 / 14 spec-derived, 6 / 30 local);
RuntimeOnlyDeclared 3 / 7 -> 4 / 9. Two pairs are now runtime-only and
nothing else, so the cross-ledger sentence states the difference again, in
a spelling that holds at any size, and its pin reads all three figures.

Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh
Co-authored-by: Claude <noreply@anthropic.com>
…etirement (objectui#6152)

Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh
Co-authored-by: Claude <noreply@anthropic.com>
…ing changesets it made false

Both corrections are appended, dated notes; no existing line or frontmatter
is edited (the maintainer's append-only ruling for pending changesets).

Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh
Co-authored-by: Claude <noreply@anthropic.com>
…which round 3 made false (objectui#6152)

An appended, dated note; no existing line or frontmatter is edited.

Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh
Co-authored-by: Claude <noreply@anthropic.com>
…fix a route count (objectui#6152)

Claude-Session: https://claude.ai/code/session_01TdiauJaVCHuj45EzZGUxHh
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 28 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/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/5905-componentinput-inputtype-tombstone.md

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

    The write was measured as a no-op before it was deleted, and re-measured on this branch's base rather than inherited from the card. A structural census over every inputs: array in the repository (211 regions, all tracked TS/TSX/JS sources) scores inputType at exactly ONE authoring site — the plugin-markdown registration — against name 953, type 969, label 966, description 194, enum 119, required 86 and binding 4 in the same pass over the same regions, so the instrument was not blind. The other 192 in-repo inputType hits are a DIFFERENT face: FormField.inputType (zod/form.zod.ts), the text-input renderer's prop, and SchemaBuilder.inputType, none of which sit on a ComponentInput. The publication path is unchanged and was re-confirmed: packages/sdui-parser/src/index.ts forwards exactly seven keys per input — name, type, of, required, enum, binding, description — so an authored inputType could not reach the published sdui.manifest.json even in principle.

.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/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-name-authority-batch-3.md

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

    @object-ui/components — ComboboxOption now IS @object-ui/types' declaration. The component declared its own { value, label }, a strict subset of the ComboboxOption that @object-ui/types declares for ComboboxSchema.options and mirrors in form.zod.ts ({ value, label, disabled? }). The component now re-exports the types declaration (through the @object-ui/types/form subpath — the root barrel does not publish the name), so the name ComboboxOption exported from @object-ui/components gains the optional disabled?: boolean member. Every value that type-checked before still does — nothing narrows and no key changes type; the one thing that moves is keyof ComboboxOption, so a consumer that EXHAUSTS the type (a Record over its keys) will need the new key. Note that the Combobox component itself does not read option.disabled — that member was already declared on the @object-ui/types face and is now visible on this one too; it is recorded as a separate finding, not changed 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/6396-previous-values-dom-leak.md

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

    Scope is the runtime leak only. The declared key stays exactly as declared (packages/types/src/form.ts, packages/types/src/zod/form.zod.ts are untouched): it has a live consumer, so there is nothing here for the enforce-or-remove channel.

.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/6938-checkbox-wrapper-class.md

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

    packages/components/src/renderers/form/checkbox.tsx:36 reads cn("flex items-center space-x-2", schema.wrapperClass) — classes on the wrapper div around the box and its label — and neither the TypeScript interface in packages/types/src/form.ts nor the zod mirror in zod/form.zod.ts declared the key. It compiled through BaseSchema's index signature and parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on FileUploadSchema and FilterBuilderSchema (objectui#6150); the checkbox was left out only because its doc page's schema block is a six-line summary.

.changeset/7113-chart-data-model.md

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

    .extend() with a NEW key still works and preserves the fold and the refinement; .optional(), z.discriminatedUnion, z.toJSONSchema and safeValidateSchema are all unaffected. Nothing in this repository calls the throwing combinators on either const, and the published surface already ships refined mirrors (objectql.zod.ts, complex.zod.ts, form.zod.ts, app.zod.ts), so the class is not new — but it is a real behaviour change on a published export and it belongs in the release note rather than in a reviewer's file.

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

.changeset/7530-predicate-envelope-declared.md

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

    • ExpressionWire (type, main entry) — the TypeScript wire union, in packages/types/src/expression.ts. - ExpressionWireSchema (@object-ui/types/zod) — its runtime twin, hoisted out of zod/form.zod.ts (where it was module-private) into zod/expression.zod.ts and imported by both base.zod.ts and form.zod.ts. One envelope type, reused by reference; no second spelling.
  • names form.zod.ts → packages/types/src/zod/form.zod.ts — edited by this change

    • ExpressionWire (type, main entry) — the TypeScript wire union, in packages/types/src/expression.ts. - ExpressionWireSchema (@object-ui/types/zod) — its runtime twin, hoisted out of zod/form.zod.ts (where it was module-private) into zod/expression.zod.ts and imported by both base.zod.ts and form.zod.ts. One envelope type, reused by reference; no second spelling.

.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/7694-chart-series-chart-type-alias-refusal.md

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

    • Before: series: [{ name: 'revenue', chartType: 'line' }] validated green through @object-ui/types/zod (safeValidateSchema, objectui check / objectui validate, any pipeline that keeps parse()'s output) — and the key was gone from the output, so a consumer of the parse result drew that series in the chart's own family, precisely what the author was overriding. On the TypeScript face the key was merely an excess property on a fresh literal; a widened object carrying it assigned structurally. - After: the same document REFUSES at series[i].chartType (issue code invalid_type) with one message on both channels — the parse-time issue and the .describe() metadata: Unrecognized key(s) on this chart series: \chartType`. Did you mean `chartType` → `type`? …followed by the reason and the remedy. Writetype: 'bar' | 'line' | 'area'. On the TypeScript face ChartDataSeries.chartTypeis a?: nevertombstone, so both the fresh literal and the widened assignment aretsc errors. - **Both written** ({ type: 'bar', chartType: 'line' }) is refused at chartTypealone — the key is not folded ontotypeand no precedence is minted between the two spellings. - **Which documents to scan.** The narrowing does not stop atChartDataSeriesSchema; it reaches every document through the parents that embed it — ChartSchema.series (zod/data-display.zod.ts, z.array(ChartDataSeriesSchema)) and, one level further out, ReportSectionSchema.chart (zod/reports.zod.ts, ChartSchema.optional()). Authors meet it through safeValidateSchema() (zod/index.zod.ts, which parses AnyComponentSchema) and through the CLI's objectui validate command (packages/cli/src/cli.ts). In practice: every chartnode'sseries[], and every report section whose chart` carries one.

.changeset/7722-wrapper-class-five-more.md

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

    Each of renderers/form/switch.tsx, textarea.tsx, date-picker.tsx, select.tsx and renderers/data-display/list.tsx reads schema.wrapperClass onto its wrapper element, and neither the TypeScript interface (form.ts, data-display.ts) nor the zod mirror (zod/form.zod.ts, zod/data-display.zod.ts) declared the key. The reads compiled through BaseSchema's index signature (objectui#5155) and the values parsed through .passthrough(), admitted unexamined. The same key, on the same class of read, is declared on CheckboxSchema (b74a8598d), FileUploadSchema and FilterBuilderSchema (objectui#6150); these five were left out only because their doc pages never listed it.

.changeset/7735-zod-mirrors-stop-authoring-defaults.md

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

    What changed. All 41 .default() call sites under packages/types/src/zod/ are removed — layout.zod.ts 22, crud.zod.ts 11, form.zod.ts 5, views.zod.ts 2, app.zod.ts 1. @object-ui/components reconciles the third face a separate finding found: flex's registration defaultProps.align seeded 'center', the value its own renderer never applies, so a designer-made node laid out differently from a hand-authored one; it now seeds 'start'.

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

    What changed. All 41 .default() call sites under packages/types/src/zod/ are removed — layout.zod.ts 22, crud.zod.ts 11, form.zod.ts 5, views.zod.ts 2, app.zod.ts 1. @object-ui/components reconciles the third face a separate finding found: flex's registration defaultProps.align seeded 'center', the value its own renderer never applies, so a designer-made node laid out differently from a hand-authored one; it now seeds 'start'.

.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/8478-describe-line-addresses.md

.changeset/8478-zod-pins-form-layout.md

.changeset/8499-node-slot-registered-arms.md

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

    • SemanticElementSchema (zod/layout.zod.ts) — the seven HTML sectioning tags renderers/layout/semantic.tsx registers: aside main header nav footer section article. - HtmlElementSchema (zod/layout.zod.ts) — the 37 safe flow/inline tags renderers/basic/html-elements.tsx registers (h1…h6, p, a, ul, img, …), plus the per-tag keys that module forwards to the DOM (href, target, rel, title, src, alt, width, height, dateTime, cite). ⚠️ Dated note, 2026-09-27 — that set has since gained code — objectui#10756. At this change TAGS and this arm both named 37 tags; both now name 38, and the parity pin counts 38. The rest of this entry is kept as the reading of this change. - InputShorthandSchema (zod/form.zod.ts) — email / password, the two aliases renderers/form/input.tsx registers onto the input renderer with inputType pinned. inputType is deliberately NOT declared on this arm: the wrapper spreads its own value last, so an authored one is overwritten. ⚠️ Dated note, 2026-09-28 — inputType is now declared on this arm, as a refusal — objectui#8762. Later in this same release the arm declares inputType on both faces and refuses it by name (?: never on the TypeScript face, a retirementTombstone on the zod mirror, at path inputType), with guidance pointing at { "type": "input", "inputType": "email" }. So "inputType is deliberately NOT declared on this arm" no longer holds; the reason does, since the wrapper still spreads its own value last. The rest of this entry is kept as the reading of this change. - UiCalendarSchema (zod/form.zod.ts) — ui:calendar, the date-picker primitive renderers/form/calendar.tsx registers under exactly that key (skipFallback, because bare calendar belongs to the plugin-calendar view).

.changeset/8516-8556-mirror-partial-record-narrowing.md

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

    | key | mirror was | mirror is | | :-- | :--------- | :-------- | | GridSchema.columns (zod/layout.zod.ts) | z.record(z.string(), z.number()) | a PARTIAL record over the six breakpoints | | ReportComponentSchema.exportConfigs (zod/reports.zod.ts) | z.record(z.string(), ReportExportConfigSchema) | a PARTIAL record over ReportExportFormat |

.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/9067-zod-barrel-named-arms.md

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

    • InputShorthandSchema (zod/form.zod.ts) — the email / password shorthand arm. - UiCalendarSchema (zod/form.zod.ts) — ui:calendar, the date-picker primitive renderers/form/calendar.tsx registers, a different component from the calendar plugin view that owns the bare literal.

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

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

    The three authorable keys, each on BOTH faces. ObjectFormSchema.recordId (objectql.ts + zod/objectql.zod.ts), DetailViewSchema.resourceId (views.ts + zod/views.zod.ts) and DetailSchema.resourceId (crud.ts + zod/crud.zod.ts). ⚠️ The crud pair is DetailSchema, not DetailViewSchema, and it reaches the same renderer — not by symbol but by data flow: plugin-detail registers the 'detail' node type onto DetailView. A read that follows TypeScript symbols alone finds two keys and is incomplete.

  • 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 dded788ad (merge-base with origin/main): 8 file(s) changed outside .changeset/, read against 1815 pending declaration(s) that publish a body (2425 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3576.7 KB 3607.4 KB
Main entry chunk (gzip) 149.6 KB 350 KB
Entry file index-WdfQVRHt.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.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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.06KB 136.05KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 228.91KB 63.54KB
fields (index.js) 261.19KB 66.27KB
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.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 40.88KB 11.46KB
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.01KB 3.93KB
plugin-calendar (index.js) 52.17KB 15.06KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 138.73KB 37.05KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 242.01KB 63.55KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 172.30KB 44.19KB
plugin-gantt (index.js) 172.43KB 42.85KB
plugin-grid (index.js) 230.79KB 63.39KB
plugin-kanban (index.js) 48.44KB 15.11KB
plugin-list (index.js) 116.28KB 28.88KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 32.26KB 9.42KB
plugin-tree (index.js) 11.20KB 3.89KB
plugin-view (index.js) 90.32KB 22.76KB
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) 119.55KB 39.23KB
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.78KB 2.09KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.17KB 2.73KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
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) 22.61KB 7.40KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
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 (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) 4.74KB 2.26KB
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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.82KB 7.15KB
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: 1371bca6a0f1604643e661265b4064479ee541c2
Local-runs: none

PR #11229 (draft, same-repo head, 12 files, +753/-100 against merge base 52bf34824), round 3 of card #6152. Inputs read: the card body and all 27 comments, the PR body, file list and net diff, the PR's two comments, and the 43 check-runs on the head. Check-runs: 0 failures, 0 in progress; Type Check, the eight Test shards, Test (dist pins), Lint, Build & E2E, Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Line Citation Gate and Governed Surface Queue Guard all success; the coverage and dependabot jobs skipped by design. Governed surfaces: none of the 12 paths. Excluded surfaces (ObjectGridSchema, the spec-derived pairs, strict-authoring-face.ts, PR #11207's four files) untouched. Every claim below was read off the diff, the twins at the head, and the check-runs; nothing was built or run locally.

① Derived judgments

  1. FormSchema mirror gains eight layout members (mobileStickyActions, fieldContainerClass, fieldTabs, defaultFieldTab, fieldTabsPosition, fieldPanes, fieldPanesOrientation, fieldPanesResizable). Strict face widens (keys the twin already invited are accepted); tolerant face narrows on wrong-typed values. Re-read at the head: renderers/form/form.tsx reads every one of the eight off schema.*. The two enums match the twin ('top' | 'bottom' | 'left' | 'right', 'horizontal' | 'vertical'); the module-private FormFieldTabEntrySchema and FormFieldPaneEntrySchema restate FormFieldTab and FormFieldPane member for member with matching optionality, visibleWhen as ExpressionWireSchema, which is the ExpressionWire twin by reference. No .default() added (objectui#7735 holds). RIGHT.
  2. DetailViewSchema mirror gains six configuration members (primaryField, summaryFields, autoTabs, defaultTab, sectionGroups, highlightFields). Re-read at the head: DetailView reads five off schema.* and defaultTab through its (schema as any).defaultTab cast, which stays. DetailViewSectionGroupSchema restates SectionGroup member for member with the one stated exception, sections: z.array(z.any()), the round-1 sections[].fields precedent, so KnownDrift does not grow. DetailViewHighlightFieldSchema restates HighlightField; its type reuses DetailViewFieldSchema.shape.type, which carries .optional() as the twin's type?: does. RIGHT.
  3. ReportComponentSchema.conditionalFormatting mirrored, rule shape { field, operator (the five), value: any, backgroundColor?, textColor? } equal to the twin's inline element type; read by ReportViewer (report?.conditionalFormatting) on the report a report-viewer node carries, and the new pin exercises that nested path. RIGHT.
  4. PaginationSchema.currentPage mirrored (z.number().optional(); twin currentPage?: number), read by the pagination renderer. RIGHT.
  5. PaginationSchema.page retired on both faces. page?: never on the interface (a tombstone, not a deletion: BaseSchema's index signature would type-check a deleted member silently, the same shape as the objectui#9256 body / children tombstones on this interface) and retirementTombstone(PAGINATION_PAGE_RETIRED) on the mirror (a tombstone, not a deletion: BaseSchema is .passthrough()), one string feeding the parse message and .describe(), naming currentPage. The renderer read schema.currentPage || schema.page || 1 becomes schema.currentPage || 1. This is a NARROWING of a published accept set (a pagination document carrying page parsed green on the tolerant face and is refused at the key now) and of the TypeScript type. Retiring at once rather than aliasing is right: the read was the fallback limb only; re-checked at the head, currentPage is authored by the two catalog documents and the site playground (the census control) and page by none; the docs page content/docs/components/basic/pagination.mdx never listed page; the ruling chain is the seat's round-1 answer 2 (5894479982: currentPage canonical, page retires by tombstone on both faces) carried by the round-2 landing note (5909347061) and the round-3 dispatch (5912572626), under objectui#6170's family rule and the no-gradualism rule (废弃别名/拼写默认立即退休). DataTableSchema.page is a different member and is untouched (no data-display file in the diff). RIGHT.
  6. ObjectFormSchema.open / submitHandler reclassified into RuntimeOnlyDeclared by NAME through the new RuntimeOnlyNamedAllowList, one row per key with its reason as a string-literal type. No mirror or declaration moved; no published surface changes (safeValidateSchema still keeps an authored value at either key unexamined, the strict face still refuses both). assertionRuntimeOnlyIsCallbackShapedOrNamed (renamed from ...IsCallbackShapedOnly) widens the shape pin by name only; assertionNamedAllowListRowsAreFiled and assertionEveryNamedAllowListRowHasAReason close the two ways the list could become a bucket, each with a synthetic recognition case; assertionUnmirroredMatchesLedger is untouched, so both keys are still measured. This is the seat's round-1 answer 1 (5894479982) under objectui#6182 (5406405611, handlers stay runtime-only). RIGHT.
  7. Ledger figures. UnmirroredDeclared 12 / 62 to 9 / 44 (3 / 14 spec-derived, 6 / 30 local); RuntimeOnlyDeclared 3 / 7 to 4 / 9; the cross-ledger sentence respelled to hold at any difference, its pin reading all three figures; the objectui#7804 containment leg deleted, not inverted, reason beside it. Arithmetic re-derived from the diff: 62 - 16 mirrored - 2 reclassified = 44; 12 - 3 emptied entries = 9; 7 + 2 = 9 and 3 + 1 = 4; runtime-only-only pairs = ObjectFormSchema + FormSchema (its onDirtyChange row remains, its unmirrored entry emptied) = 2; union 9 + 2 = 11; shared 4 - 2 = 2. KnownDrift, WiderThanDeclared, MirroredUndeclared unchanged, correctly, since no mirrored key changed type and the instrument walks top-level members only. Every figure is pinned by the file's own instrument, not prose. RIGHT.
  8. Keys left in the ledger, each with its route on its entry: LabelSchema.content (the third spelling; label.tsx reads schema.text || schema.label || schema.content; a canonical-spelling ruling, not a mirror), ReportComponentSchema.chartConfig / reportType and DetailViewSchema.autoDiscoverRelated (zero-read narrowing candidates, reported and not narrowed, which is right: objectui#6170's route owes the runtime inertness probe first), DetailViewSchema.activities / comments / history / recordNavigation (runtime data and a required callback; a ruling owed). Not mirroring any of these is RIGHT.
  9. @object-ui/components behaviour change: the pagination renderer drops the page read, so a node carrying only page renders page 1; stated in the changeset. RIGHT.
  10. Compensating pin for the unmeasured nested shapes: the parity instrument does not see inside fieldTabs[], sectionGroups[], highlightFields[] or the formatting rules, so a nested drift would not redden it; unmirrored-declared-round3-6152.test.ts carries a valid row and a wrong-typed row per key on both faces, the report-viewer nesting, the basic-pagination.json strict parse with a non-vacuity check, and the page refusal on the mirror and both faces plus a tsc pin compiled by tsconfig.test.json. RIGHT.
  11. No describe string carries a line address (Line Citation Gate: 0 new); no model identifier in the PR body, the changeset or the commit trailers (the model-free pair only); the RuntimeOnlyNamedAllowList reasons are test-file type literals, not author-facing runtime strings. RIGHT.

② Semver level

  • Changeset .changeset/6152-unmirrored-round3.md: @object-ui/types minor, @object-ui/components minor. RIGHT. Both are released packages in the fixed group (.changeset/config.json fixed, 40 packages); objectui's version policy marks its own breaking changes minor with the break in the body, never major. The body states both breaks (the tolerant-face refusals; the page retirement with no alias window) and the one-line migration (rename page to currentPage), which is what an upgrading agent greps. The components entry is right on its own: the presence gate counts the changed renderer source and the dropped read is a behaviour change. Changeset Declaration, Changeset Bump Policy and Changeset Fixed Group Check are green on the head.
  • The three appended correction notes (6150-undeclared-but-consumed-keys, 6152-object-form-unmirrored-members, 7804-tree-view-handler-slot): each +2 / -0, frontmatter and every existing line byte-identical, dated, append-only, the maintainer's 「改成追加说明 (Recommended)」 shape (5904913872), and each names what became false and why. RIGHT. The 28 pending changesets the claim re-read named were read paragraph by paragraph against the diff: only 7804's containment paragraph went false and it is corrected; 6150's amended TreeViewSchema bullet and the round-1 changeset's "routes are open" paragraph were found by reading and corrected. None of the other quoted paragraphs is made false by this diff (7735 stays true: zero .default() added; 7344's three z.string() keys untouched; 7654's floatingConfig still in the ledger).
  • Clause-②: yes (widening) (PR body; claim 5912572626). Value yes: RIGHT, the strict face accepts sixteen keys it refused. Arm (widening): UNDER-DECLARED. The diff also narrows a published accept set (page refused at the key on both faces where the tolerant face kept it; PaginationSchema.page from number to never), and the fleet's one reader of this line (scripts/pm/clause2-line.mjs) spells a diff that widens one surface and narrows another yes (narrowing); yes (widening) is bare yes said out loud, which declares no breaking. Not blocking: the changeset already prices the break in prose and in the migration line, the bump does not move under the fixed-group policy, yes already routes to this record, and no objectui gate reads the arm. Correction owed to the dispatching seat before enqueue: rewrite the PR body's line to Clause-②: yes (narrowing) (a body edit; the head does not move) and note the amendment on the claim.

③ Boundary flags

Dev flags (report 5914454379) and the seat's answers (5914490915), each answered or escalated:

  • Q4, the three out-of-surface changeset notes. Seat: A, keep, claim widened to the three paths. Reviewer: AGREE; the shape is verified above, and the alternative (a separate docs-only PR) would let the CHANGELOG ship three sentences this release contradicts.
  • Brief conflict, @object-ui/components declared beside @object-ui/types. Seat: it stands. Reviewer: AGREE, see ②.
  • Q1, LabelSchema.content. Seat: B for round 4 (retire content now; the text / label question separate, 40 catalog documents behind it). Reviewer: inside objectui#6170's family rule and the page precedent (authored once, in a test); nothing in this diff depends on it; ACCEPTED as a round-4 order, not re-judged here.
  • Q2, activities / comments / history / recordNavigation. Seat: A, named allow-list rows in round 4. Reviewer: the list this round built is the exact carrier A needs, and recordNavigation cannot be mirrored (a required callback); ACCEPTED for round 4, with the round-1 "class (b)" question (an authored copy as a supported dialect) left open as recorded.
  • Q3, displayMode's runtime half. Dev: no recommendation, a maintainer-level call. Seat: B under objectui#7654 ruling B (stored designer documents carry displayMode: 'floating'; the missing zod refusal is deliberate and pinned by chatbot-display-mode-retired.test.ts); the ledger entries that call the mirror half "owed" are reworded in round 4. Reviewer: AGREE that it is not relitigated on this card; nothing in this diff touches the Chatbot pairs.
  • Out-of-scope, with-item-count.json authors pageSize / totalItems (neither face declares them, nothing reads them, the strict face refuses them) and the docs page's plaintext interface block lists them. Seat: carried to round 4. Reviewer: AGREE. The changeset's "the catalog's basic pagination example now parses strict" is specific to basic-pagination.json, and the new pin reads that file; accurate.
  • .changeset/6639-objectgrid-title-mirrored.md ("97 + 1 mirrored + 23 reclassified is what the seeded 121 now means"): stale before this round, not corrected here, carrier none. Reviewer: ESCALATED to the dispatching seat as a round-4 item on this card. It publishes verbatim into the same CHANGELOG as this family's notes and describes this same ledger; the append-only note shape applies. Not blocking: this diff did not make it false.
  • Noted prose: the views.zod.ts onNavigate docblock's "stays authorable where it lives" about the nested recordNavigation.onNavigate (a required function is not authorable), and the round-1 pin name "until their routes are ruled" that now reads as pending. Prose and a test name only; carriers as the dev states.
  • Dispatch correction: ObjectGridSchema carries 11 keys, not 14 (objectui#11068 closed three). Consistent with the ledger entry at the head; noted.
  • Verification: the dev's local runs (types, four consumer packages, the corpus validators, ablations A1 to A3 with proven restores) are read as reported and not re-run; the head's check-runs are the gate verdicts, and every one that ran is green.

Implemented-by: claude/issue-6152-unmirrored-round3
Reviewed-by: session_01TdiauJaVCHuj45EzZGUxHh

VERDICT: PASS

Reading time: 2026-09-30T15:54Z


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants