Skip to content

feat(types)!: an authored object-form takes its props in the spec's properties bag; the flat spelling is refused by name (objectui#10859, batch 4) - #11291

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-10859-object-form-properties-bag
Oct 1, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-10859-object-form-properties-bag

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #10859
Clause-②: yes

Clause-②: yes, copied from the PM claim 5920453930: @object-ui/types' authoring faces narrow (a flat object-form node that parsed is refused) and widen (a spec-shaped bag node that was refused parses). This is objectui's narrowing-as-minor rule with a BREAKING (authoring) banner in the changeset, as batches 1 to 3 and the answer's step 2 state.

Batch 4 of objectui#10859. It executes the seat's flat-arm answer at PR objectui#11248's ACCEPT (5917261346) for ONE block, object-form: "A governs, by inheritance" from objectui#10872's triage answer A (5906817482), "The properties bag is the contract". The card stays open for the rest of the flat-arm fork (object-map, object-gantt), the designer six and the (ii) / (iii) keys.

What changed

  • packages/types/src/zod/objectql.zod.ts: new ObjectFormBlockSchema, the batch-2/3 construct. It is BaseSchema, plus the object-form literal, plus NODE_ENVELOPE, plus properties = propsBag('object-form', stripImportedDefaults(SpecObjectFormPropsSchema)): the shared helper and the installed 17.5.0 row by reference, with no copy. Beside it:
    • dataSource, the spec's ElementDataSourceSchema by reference (the registration is elementDataSourceBlock-wrapped; the flat mirror has declared it since objectui#11070);
    • one aliasKeyRefusal per member of the row, keyed by the row's own key set read off the crossed row, so each prop written flat is refused on both faces with Did you mean KEY → properties.KEY? and one prescription string that shows the whole bag document;
    • the five runtime slots ObjectForm reads off the node (onSuccess, onCancel, onError, onOpenChange, onStepChange), declared exactly as on the flat mirror;
    • body / children refused with the flat mirror's objectui#9256 string.
  • The arm joins ObjectQLPublicBlockComponentSchema. ObjectFormSchema (the flat mirror) leaves ObjectQLComponentSchema, stays exported, and stays paired with its TypeScript twin in the parity census.
  • packages/types/src/objectql.ts: the TypeScript ObjectFormSchema is re-declared, not retired. Its members are unchanged; its docblock now says what it is: the node as ObjectForm reads it after SchemaRenderer hoists properties, and as code composes it. The TypeScript union's docblock names the zod union's new count.
  • packages/types/src/zod/form.zod.ts: the form node's mode refusal taught a flat object-form document. Its prescription now writes the bag.
  • packages/types/src/zod/index.zod.ts (one barrel export) and packages/types/src/zod/README.md (the ObjectQL public-block group lists the arm).
  • objectui's authored flat producers, rewritten to the bag (census below): the root README.md, content/docs/api/schema-reference.md, content/docs/guide/building-crud-app.md, content/docs/guide/objectos-integration.mdx, content/docs/plugins/plugin-form.mdx, both schema-catalog object-form fixtures, and packages/plugin-form/README.md.
  • New pins: packages/types/src/__tests__/object-form-properties-bag-10859-b4.test.ts and packages/plugin-form/src/__tests__/objectFormPropertiesBag-10859.test.tsx.
  • Two changesets: .changeset/10859-object-form-properties-bag.md (@object-ui/types minor, BREAKING (authoring) banner) and .changeset/10859-plugin-form-readme-object-form-bag.md (@object-ui/plugin-form patch, README only).

Files beyond the claimed surface, each forced and named:

  • zod-mirror-parity.test.ts: the one EXCLUSIONS row the new export forces; no pair moved (EXPECTED_MIRROR_PAIRS untouched).
  • imported-defaults-8317.test.ts: one IMPORTED row for the new spec import.
  • registered-type-arms-10859-b2.test.ts: its exact ObjectQLPublicBlockComponentSchema literal list gains object-form.
  • objectql-union-arms-7363.test.ts: ObjectQLComponentSchema holds 11 arms, not 12, and a row that object-form is not among them.
  • public-block-responsive-styles-10872.test.ts: object-form joins the named DECLARES_DATA_SOURCE set (it moved into the public-block population carrying the binding).
  • Fixture triage (respelled, the flat node is now refused): strict-face-read-keys-11070.test.ts (one BOUND_NODES row), content-channel-e3-residual-9256.test.ts (the authoring-face reach row reads the bag; the mirror rows keep the post-hoist spelling), object-form-unmirrored-members-6152.test.ts (details under Acceptance notes), guideCrudAppRenders.test.tsx (reads the guide snippet's bag), catalog-gallery-render.test.tsx (its control uses the entries' spelling).

Zone 2: the PM's mechanism assumptions, measured before building

  1. Held (the gate for the batch). Installed @objectstack/spec 17.5.0, and objectstack origin/main 75519e1c0a (spec source run with tsx):
    • ComponentPropsMap carries an object-form row, and it is the exported ObjectFormPropsSchema (same key set at both refs).
    • PageComponentSchema.safeParse({ type: 'object-form', objectName: 'x' }) refuses: unrecognized_keys ['objectName'] ("Unrecognized key(s) on this page component … ADR-0089 D3a"). With mode beside it: ['objectName', 'mode'].
    • The same props in properties are accepted, and the row accepts them.
    • Control: flat action:button actionType is refused the same way.
  2. Held, red first. On base f16c01e90e, the showcase wizard node (objectstack NewProjectWizardPage) was refused by safeValidateSchema (objectName invalid_type, mode invalid_value) and by the strict face (the same two, plus unrecognized_keys ['properties']). The flat control was accepted on both faces. The new pin on base, before the arm: Tests 58 failed | 7 passed (65). The 7 green rows are the lit controls, the row size check, the content-channel rows and the flat-mirror row, which all hold on base too.
  3. Settled: re-declared, not retired. Readers of the TypeScript twin, measured:
    • ObjectForm and its five variant forms (the schema prop);
    • the code composers (ObjectView, RecordFormPage, ScreenView, AppContent, useActionModal, EmbeddableForm, MasterDetailForm, ViewPreview, StudioDesignSurface, ObjectManager, FieldDesigner);
    • the object-view form slot (ObjectFormSlotKey; the zod slot is ObjectFormSchema.omit(…).partial());
    • the TypeScript unions;
    • the mirror-parity census pair.
      Every one reads the post-hoist or code-composed flat node. None needs the authored shape, so the twin keeps its members and its publication. The authored node is typed by BaseSchema (index signature) or the spec's ObjectFormProps. The bag arm is unpaired (one EXCLUSIONS row), as batch 2's MasterDetailFormSchema split is. Type-level pins: the bag input equals ObjectFormProps | undefined, every row key is a refused flat member, and the twin stays type: 'object-form' with objectName: string and mode required, still the object-form member of the TypeScript ObjectQL union.
  4. Census (classifier: a document or doc example an author writes, versus code that builds the node or mounts a component with its post-hoist schema prop):
class files action
authored, rewritten to the bag root README.md, schema-reference.md, building-crud-app.md, objectos-integration.mdx, plugin-form.mdx, object-form-record.json, object-form-tabbed-sections.json, packages/plugin-form/README.md (2 of its 3 nodes), form.zod.ts (a refusal's prescription) rewritten (9 files)
authored, already bag-compatible content/docs/utilities/data-objectstack.mdx ({ type, dataSource } only) none
authored, governed skills/objectui/guides/page-builder.md ("Form plugin example") listed, not edited (ruling step 4)
authored, held by another PR packages/cli/src/commands/validate.ts (a hint naming type: 'object-form' with objectName + sections) not edited: the file is PR objectui#11069's
component mount (post-hoist schema prop, no hoist) examples/byo-backend-console/src/App.tsx (2), packages/plugin-form/README.md (the WizardForm mount), the @example blocks of ObjectForm / TabbedForm / WizardForm kept flat: rewriting them would break them
runtime composers the eleven listed under item 3 kept flat (item 5)
test fixtures the triage above; the plugin-form render tests build flat nodes as code does respelled only where a face judges them

This differs from batch 3's count ("13 authored") in two classes: the two byo-backend-console nodes and the two designer nodes are component mounts and code composers, not authored documents.
5. Held. No composed node passes through safeValidateSchema. SchemaRenderer runs @object-ui/core's structural validateSchema, and the only non-test callers of the zod faces are objectui validate / objectui check. The render pin draws the flat spelling beside the bag.
6. Not forced. REFUSED_AT_TYPE does not move: object-form was never refused at type. PR objectui#11069's files are untouched, and git merge-tree of this head with origin/main is clean.

Tests (the union at head a32a4470cd unless marked; heavy runs through os-verify-lock.sh, slot issue-10859)

  • Red first (base f16c01e90e, pin present, arm absent): Tests 58 failed | 7 passed (65).
  • Green at a32a4470cd:
    • pnpm exec vitest run packages/types/ packages/cli/: Test Files 323 passed (323), Tests 7875 passed (7875), VERDICT command-exit 0.
    • The render pin, the guide test, the b4 pin and examples/schema-catalog/test/: Test Files 39 passed (39), Tests 2290 passed (2290).
  • Arm-list consumer sweep at 8e741e3fe4:
    • The files are every tracked test file outside packages/types and packages/cli that names AnyComponentSchema, StrictAnyComponentSchema, safeValidateSchema, ObjectQLPublicBlockComponentSchema, ObjectQLComponentSchema, ObjectFormBlockSchema, nodeOracles, KNOWN_SCHEMA_TYPES, propsLevelOf, PUBLIC_BLOCKS, unmirroredAlias, collectArms, getPublicConfigs, ComponentPropsMap, validateSchema or StrictAuthoringFace; or references check-handler-key-read-sites, block-config-schema-parity, registry-inputs-spec-parity, strict-authoring-face, zod-mirror-parity, objectql.zod, index.zod, ObjectFormSchema or object-form.
    • That is 312 files, run in two chunks: Test Files 180 passed (180) / Tests 4016 passed (4016) and Test Files 132 passed (132) / Tests 1626 passed (1626).
    • The only changes since are the changeset text and the render pin's casts; that pin re-ran green at a32a4470cd.
  • Build / type-check:
    • pnpm --filter @object-ui/types build && pnpm --filter @object-ui/types type-check: VERDICT 0, dist completeness: 1 package(s) complete (138 emitted files verified). The pin is in tsconfig.test.json's program (--listFilesOnly).
    • pnpm --filter @object-ui/plugin-form type-check: exit 0, after building its dependency closure. Both plugin-form test files are in its test program.
  • Runtime ablation (objectstack scripts/ablation-replace.mjs WRAP, plus the script's own EXIT/INT/TERM trap on absolute paths):
    • The mutation removed ObjectFormBlockSchema from the public-block union: anchor x1 to x0, blob 03e9d3554ceb to 5981e32df7dd.
    • Pin + ratchet: Tests 60 failed | 25 passed (85), with "objectui validate refuses 73 registered bare key(s) at type; the pin is 72". Direction: red, as expected.
    • Restored: blob == HEAD 03e9d3554ceb, git diff HEAD empty.
  • Type-level reverse validation (same tool):
    • Bag narrowed to { objectName? }: tsc -p tsconfig.test.json exit 2, TS2344: Type 'false' does not satisfy the constraint 'true' on the bag row.
    • Flat refusals removed: exit 2 (TS2339 on the three flat-member rows, TS2344 on the every-row-key row).
    • Restored as above.
  • Render (committed pin, real registry): the bag node fetches getObjectSchema('showcase_project') and draws the three step labels and the first step's field. The flat spelling draws the same. Control: a bag naming another object fetches that object and never showcase_project.

Gates (exit codes read before any pipe)

  • exit 0, each with its own verdict line:
    • check:handler-key-reads: "OK 140 arm(s), 212 registration(s) (138 keyed onto an arm), 56 reachable handler read(s), 56 judged";
    • check:spec-symbols;
    • check:new-line-citations: "VERDICT new-cross-file-line-citations: 0 new citation(s)";
    • check:control-bytes: "OK (scanned 9718 tracked text file(s); skipped 85 binary)"; a self-scan over the 27 changed files gave 0 hits;
    • node scripts/check-changeset-presence.mjs: "16 source file(s) of 2 released package(s) changed, and this change declares 2 changeset(s)";
    • changeset:check: "No changeset declares a major bump.";
    • check:doc-fences, docs:check-links, markdown-test-inputs.mjs --audit ("107 candidate test files, all adjudicated");
    • check:doc-snippets: "697 of 697 block(s) judged, 0 failed", after its scoped build;
    • check:doc-examples, check:doc-types, check:readme-exports;
    • check:element-data-source-declaration, check:installed-pin-claims, check:test-path-roots, check:pending-changeset-literals, check:unreferenced-sources, check:esm-specifiers, type-check:coverage.
  • check:changeset-claims (report-only): the pending .changeset/6152-object-form-unmirrored-members.md describes the flat members this change moves into the bag. Its body is not edited. This PR's changeset names how those members are authored now, so the release notes read together.
  • check-governed-queue-guard.mjs --test over the 27 paths: NOT GOVERNED.
  • eslint narrowing: eslint --no-inline-config --format json over the 16 changed .ts / .tsx files gives 0 errors. Warnings equal the base blobs file for file (43; the two new files carry 0). eslint.config.js sets no projectService / parserOptions.project, so no untouched file's verdict can move. The full pnpm lint is CI's.

Acceptance notes

  • The bag follows the spec row, which is not the flat mirror. Measured in the pin. The row keeps objectName / mode optional; layout is vertical / horizontal only (17.5.0 retired inline / grid); recordId takes a number (objectui#9511 refuses one on the flat mirror, and batch 2's object-master-detail-form bag already takes one). The row types sections entries as unknown, so a section's keys are unjudged inside the bag (the flat mirror closed them).
  • buttons / defaults / subforms / groups are the spec's form-VIEW members, not row members, so the bag refuses them. object-form-unmirrored-members-6152.test.ts now says so for the three objectui#6152 declared: refused in the bag on both faces, still taken by the object-view form slot. The "a section is a closed shape on the strict face" row reads that slot too.
  • The TypeScript twin does not name contentLayout / confirmOnDiscard, two row members ModalForm / DrawerForm read; BaseSchema's index signature types them any. This is an observation, not filed.
  • Governed producer left flat: skills/objectui/guides/page-builder.md ("Form plugin example", os:check-marked). It still parses as JSON, which is all check:skill-examples asks of a JSON fence, but objectui validate now refuses it. It goes through the governed path.
  • Hint held by PR objectui#11069: packages/cli/src/commands/validate.ts still suggests type: 'object-form' with objectName + sections flat.
  • The objectos-integration.mdx fragment also dropped fieldNames (no such key; the row's is fields), a node-level adapter and an onSubmit function, none of which the node ever took.

Generated by Claude Code

…the flat spelling is refused by name (objectui#10859, batch 4)

New `ObjectFormBlockSchema`: BaseSchema + the `object-form` literal +
`properties` = propsBag('object-form', stripImportedDefaults(SpecObjectFormPropsSchema)),
the node's `dataSource` binding by reference, one aliasKeyRefusal per member
of the row (read off the row) pointing at `properties.KEY`, the five runtime
slots and the objectui#9256 content-channel refusals. It joins
ObjectQLPublicBlockComponentSchema; the flat ObjectFormSchema mirror leaves
ObjectQLComponentSchema and stays published as the post-hoist reading paired
with its TypeScript twin.

Census rows and fixture respellings the move forces, and the two
schema-catalog object-form fixtures rewritten to the bag.

Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
Co-authored-by: Claude <noreply@anthropic.com>
…oist reading; point the form `mode` refusal at the bag (objectui#10859, batch 4)

The TypeScript twin stays published with its members unchanged: it is the
node as ObjectForm reads it after SchemaRenderer hoists `properties`, and as
code composes it. The zod README lists ObjectFormBlockSchema, and the `form`
node's `mode` refusal teaches the bag spelling of an object-form node.

Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
Co-authored-by: Claude <noreply@anthropic.com>
…ties bag; render pin and changesets (objectui#10859, batch 4)

The root README, the schema reference, the CRUD guide, the ObjectOS
migration fragment, the plugin-form page and the plugin-form README author
object-form in the bag. The guide's render test reads the snippet's bag. A
plugin-form render pin draws the showcase wizard node through the real
registry, beside the flat spelling code composers still build.

Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
Co-authored-by: Claude <noreply@anthropic.com>
…e authored after the bag move (objectui#10859, batch 4)

Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
Co-authored-by: Claude <noreply@anthropic.com>
…ctui#10859, batch 4)

Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation package: types plugin examples tests labels Sep 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 69 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/10872-container-children-channel.md

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

    Also in this change, with no behaviour change: objectql.zod.ts's two public-block arms (object-metric, object-master-detail-form) build their properties member with the same propsBag helper as the other public-block arms, instead of a byte copy of it. The member's description text is unchanged.

.changeset/11068-grid-declared-keys.md

  • names schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    What was wrong. ObjectGridSchema declared eight keys the grid never read. An author who wrote description, emptyState, name, placeholder, rowSpecActions or bulkSpecActions on an object-grid got no type error, no validator refusal and no effect. The reference example in schema-reference.md taught description and showFilters as if they worked. It now authors only keys the grid reads.

.changeset/3917-retire-action-condition-branch.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    • ActionCondition is removed from @object-ui/types (and from the barrel export). - ActionSchema.condition is retyped to the predicate the runtime actually honours: boolean | string | { dialect?: string; source: string } — the same three arms ActionRunner's own ActionDef.condition carries, and the same vocabulary visible and disabled use. - ActionConditionSchema is removed from @object-ui/types/zod (and from the zod barrel); the condition key now validates against that predicate union. - The two teaching sites (content/docs/core/enhanced-actions.mdx Conditional Execution, content/docs/api/schema-reference.md ActionSchema table) are rewritten to the live vocabulary: condition is a gate; a branch is expressed as separate actions with mutually exclusive conditions.

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

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

    Both halves move together. The TS declaration (packages/types/src/objectql.ts) and its zod mirror (src/zod/objectql.zod.ts) gain the same ten keys at the same requiredness — all optional — and no KnownDrift entry is added. navigation is taken from @objectstack/spec's NavigationConfigSchema by reference rather than restated, matching ObjectGridSchema.navigation.

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

    Both halves move together. The TS declaration (packages/types/src/objectql.ts) and its zod mirror (src/zod/objectql.zod.ts) gain the same ten keys at the same requiredness — all optional — and no KnownDrift entry is added. navigation is taken from @objectstack/spec's NavigationConfigSchema by reference rather than restated, matching ObjectGridSchema.navigation.

  • 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/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/6152-object-form-unmirrored-members.md

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

    ObjectFormSchema in @object-ui/types (objectql.ts) declared a set of members that its zod mirror in @object-ui/types/zod had never heard of. Every one of them is read by the object-form renderer (ObjectForm in @object-ui/plugin-form). The two published faces answered differently:

.changeset/6170-retire-timeline-dead-keys.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Also in this change: the in-repo example packages/types/examples/data-display-examples.json (its timeline node authored all three) is migrated to items / variant; the two content/docs/api/schema-reference.md snippets that authored events: [] now author items: []; and the plugin-timeline docs callout says retired rather than deprecated.

.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/6237-wizard-step-config-split.md

  • names content/docs/plugins/plugin-form.mdx → content/docs/plugins/plugin-form.mdx — edited by this change

    Documentation repair in the same stroke: the support table in the README and in content/docs/plugins/plugin-form.mdx still said formType: 'tabbed' sections drop the predicate. That stopped being true when the tabbed arm landed — the row now reads Yes, the surrounding prose no longer claims two inert arms or a diagnostic that fires for tabbed, and the wizard row stays No, which is still exactly true.

.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/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/6896-retire-chart-inline-data.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    ⚠️ One correction to the record the ruling rests on. The ruling states zero authorship of a populated series[].data outside tests across packages/ / apps/ / examples/. The re-measurement finds one such site inside those roots — packages/types/examples/data-display-examples.json (2 series) — plus four outside them, in documentation: content/docs/api/schema-reference.md (3) and content/docs/core/report-schema.mdx (1).

.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/6939-kanban-column-cards.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Breaking, deliberately. KanbanColumn declared its card list as items in complex.ts and in the zod mirror complex.zod.ts. Every board reads cards. Measured on origin/main 78a3cc238: KanbanImpl.tsx reads .cards on 12 lines, KanbanEnhanced.tsx on 8, and bucketCardsIntoColumns twice more as col.cards || []; .items had zero read sites in either board (a same-shaped .title control on the same two files returns 8 and 3, so those zeros are readings and not a mis-shaped probe). Both catalog entries, the plugin docs and content/docs/api/schema-reference.md all author cards.

.changeset/6940-rowactions-boolean-mirror.md

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

    The list view's same-named rowActions in zod/objectql.zod.ts — z.array(z.string()), the legacy bare-name action list on ObjectGridSchema — is a different key that is correct as it stands, is in parity with its own TS twin (rowActions?: string[]), and is not touched.

.changeset/6951-tree-view-data-retired.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    What was measured, on this branch's base. TreeViewSchema declared two spellings for its one inline-nodes slot — nodes (read second) and data (read third: boundData || schema.nodes || schema.data || [] at renderers/data-display/tree-view.tsx:105), both declared by objectui#6150. data had been REQUIRED until 777e5c6f4 (PR fix(types): tree-view mirror stops requiring the limb it reads third (group 2 of objectui#6939) #7533) made it optional, so this retirement starts from a declared-and-optional member on both faces. The in-repo corpus at the retirement: seven tree-view nodes under examples/schema-catalog and packages/types/examples plus one content/docs fence — six on nodes, two on data (packages/types/examples/data-display-examples.json and content/docs/api/schema-reference.md), both rewritten; no package source authored either spelling.

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

  • names objectql.zod.ts → packages/types/src/zod/objectql.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.

  • 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/7200-object-form-section-style-keys-undeclared.md

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

    The authored-metadata type now agrees with @objectstack/spec, whose FormSectionSchema is a strict object declaring neither key, and with the ruling's rationale (maintainer 2026-09-01, verbatim): "retire the reads … Declaring the keys was weighed and not adopted: it would formally invite free Tailwind strings into authored metadata, the exact class the boundary exists to keep out." A ?: never tombstone was not used: ObjectFormSection has no zod mirror (ObjectFormSchema in zod/objectql.zod.ts does not declare sections), so there is no parse door to refuse at, and a tombstone is still a declaration in completion and in the published .d.ts.

.changeset/7265-types-user-filter-field-derives.md

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

    zod/objectql.zod.ts declared two schemas under names @objectstack/spec/ui already exports. They were triaged separately, by reading their sites, and went different ways.

.changeset/7322-object-kanban-group-by-limit.md

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

    Breaking for authored metadata: ObjectKanbanSchema.groupField is RETIRED (objectui#7322, ADR-0049 enforce-or-remove), and the two keys the object-kanban renderer actually reads — groupBy and limit — are now DECLARED and validated on both published faces: the TypeScript interface in objectql.ts and the Zod mirror in zod/objectql.zod.ts.

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

    Breaking for authored metadata: ObjectKanbanSchema.groupField is RETIRED (objectui#7322, ADR-0049 enforce-or-remove), and the two keys the object-kanban renderer actually reads — groupBy and limit — are now DECLARED and validated on both published faces: the TypeScript interface in objectql.ts and the Zod mirror in zod/objectql.zod.ts.

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

.changeset/7352-drill-down-config-mirror.md

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

    DrillDownConfigSchema is the zod mirror of DrillDownConfig, and both declarations that carry drillDown reference it — ChartSchema (zod/data-display.zod.ts) and ObjectDataTableSchema (zod/objectql.zod.ts) — so the published validator under @object-ui/types/zod reads the key for the first time (objectui#7352).

.changeset/7363-objectql-union-arms.md

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

    ObjectGallerySchema and ObjectDataTableSchema are members of ObjectQLComponentSchema on both faces — the TS union in objectql.ts and the zod union in zod/objectql.zod.ts — so AnyComponentSchema, and with it validateSchema / safeValidateSchema / objectui validate, has an arm for object-gallery and object-data-table nodes (objectui#7363).

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

    ObjectGallerySchema and ObjectDataTableSchema are members of ObjectQLComponentSchema on both faces — the TS union in objectql.ts and the zod union in zod/objectql.zod.ts — so AnyComponentSchema, and with it validateSchema / safeValidateSchema / objectui validate, has an arm for object-gallery and object-data-table nodes (objectui#7363).

.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/7632-shared-record-source-config.md

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

    That ladder is published contract on both faces — packages/types/src/objectql.ts and its zod mirror both ship .describe() strings naming getDataConfig's order (77cb489b4, maintainer ruling 2026-09-02), pinned by objectql-record-source-refinement-6939.test.ts — and it was hand-copied into five plugin components with no gate holding them together. A change to the ruled order had five edit sites and nothing that noticed a missed one; that is the AGENTS.md #0.1 drift class.

.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/7664-kanban-arm-plugin-dialect.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Migration. Author boards in the plugin dialect — objectName + groupBy for an object-bound board, or columns[].cards[] with badges for a static one. Replace DeclarativeKanbanSchema imports with KanbanSchema (from @object-ui/types, or the Zod KanbanSchema from @object-ui/types/zod; @object-ui/plugin-kanban re-exports the same KanbanSchema type). Delete draggable (drag-and-drop is always on) and column color (style a lane through className). content/docs/api/schema-reference.md's kanban section now documents this dialect.

.changeset/7694-chart-series-chart-type-alias-refusal.md

  • names zod/index.zod.ts → packages/types/src/zod/index.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'.

.changeset/7804-objectql-handler-key-arms.md

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

    The four plain objectql.ts node faces declare the nine handler keys their registered renderers read (the objectql.ts slice): ObjectFormSchema.onCancel / .onError / .onOpenChange / .onStepChange / .onSuccess, ObjectGallerySchema.onCardClick / .onRowClick, ObjectGridSchema.onNavigate and ObjectViewSchema.onNavigate.

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

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

    'runtime-slot' and not 'retired', measured at this key's own channel. 'retired' publishes "no renderer reads this key, so nothing could ever run it" — true of the two siblings already tombstoned on this arm (onSelectChange, onExpandChange) and flatly false here, since the read is live and INVOKED. ⚠️ No in-repo host builds a tree-view node carrying the key: the channel is wired end to end and only the supplier is absent, which is the same shape as ObjectFormSchema.onStepChange in this card's objectql.ts slice and is not evidence of a dead read. The TypeScript declaration is unchanged and still callable, so a programmatic host supplies it exactly as before.

  • 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 objectql.zod.ts → packages/types/src/zod/objectql.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.

  • names index.zod.ts → packages/types/src/zod/index.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/7963-alert-dialog-footer-keys-retired.md

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

    Nothing else moves. These spellings are overloaded across the tree and every other owner is a live key on a different declaration — FormSchema.cancelLabel, objectql.ts's confirmLabel, plugin-designer's ConfirmDialog React props, plugin-grid's def.confirmLabel, and plugin-form's ModalForm / DrawerForm, which build a local cancelLabel from schema.cancelText. None is an AlertDialogSchema; none is touched, and a pin asserts it. No fixture, catalog schema, example app or doc fence authored any of the three on an alert-dialog node, so no shipped document is stranded.

.changeset/7997-detail-view-related-retired.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Documentation. packages/plugin-detail/README.md and content/docs/api/schema-reference.md stop teaching the retired array and gain a migration block each.

.changeset/8268-testid-emitted-as-data-testid.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    The other direction — retiring the promise — was considered and declined. It is what c1fe272ad did for BaseSchema.hidden, but that key had a working behaviour to describe and zero named consumers, and the ruling's decline turned on exactly that. This promise already has carriers outside the type declaration: content/docs/api/schema-reference.md states it as a table row and authors testId in that page's own base-schema example, @object-ui/cli's OBJECTUI_STRUCTURAL_KEYS identifies a file as an ObjectUI schema node by this key, at this change ObjectGridSlotKey / ObjectFormSlotKey pin it, SchemaBuilder.testId() writes it, and ADR-0054 C4 — shipped — reads "the renderer emits data-testid … derived from metadata".

.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/8344-node-recursion-point-redirect.md

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

    Two mechanical notes for anyone editing the wiring. AnyComponentSchema is built in zod/index.zod.ts from all 13 category modules and 14 modules import zod/base.zod.ts, so the arm cannot be an import — z.lazy defers evaluation, not the module graph. It is a written option slot that index.zod.ts fills inside AnyComponentSchema's own initializer, and it is a z.union option rather than a z.lazy holder because z.lazy memoises its getter: a holder would let whichever module graph parsed first decide the accept set for the whole process. Both constraints are measured, and the reasoning lives on defineNodeComponentUnion in zod/base.zod.ts.

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

    Two mechanical notes for anyone editing the wiring. AnyComponentSchema is built in zod/index.zod.ts from all 13 category modules and 14 modules import zod/base.zod.ts, so the arm cannot be an import — z.lazy defers evaluation, not the module graph. It is a written option slot that index.zod.ts fills inside AnyComponentSchema's own initializer, and it is a z.union option rather than a z.lazy holder because z.lazy memoises its getter: a holder would let whichever module graph parsed first decide the accept set for the whole process. Both constraints are measured, and the reasoning lives on defineNodeComponentUnion in zod/base.zod.ts.

.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).
  • names zod/index.zod.ts → packages/types/src/zod/index.zod.ts — edited by this change

    Why 49 is acceptable here where 2 was a defect: the 2 were data blocks a consumer had a standing reason to validate on their own, and objectui#7917 (PR feat(types): re-export BreadcrumbSchema and ObjectTreeSchema from the zod barrel (objectui#7917) #8777, open at the time of writing, and the holder of zod/index.zod.ts) exists to export exactly those. The 47 added here are HTML primitives and two input aliases — they have no per-tag consumer to serve, and exporting a SemanticElementSchema / HtmlElementSchema pair would publish a NAMED authoring surface (z.enum families, not per-tag schemas) that this card's ruling does not cover: the ruling is "arm the registered renderers", not "add public exports to @object-ui/types". So the metric is left to move and said out loud instead. ⇒ Whoever next runs that measurement should expect 49, and whoever wants the number back down should treat naming these families as its own decision. If PR feat(types): re-export BreadcrumbSchema and ObjectTreeSchema from the zod barrel (objectui#7917) #8777 lands first, the same movement reads 0 to 47.

.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/8598-zod-subpath-single-bundled-module.md

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

    The defect. This package declares "sideEffects": false, and src/zod/index.zod.ts fills the node recursion point as the initializer of its AnyComponentSchema const. tsc emitted that barrel as a module whose only other content is re-exports — so a bundler resolving import { CardSchema } from '@object-ui/types/zod' followed the re-export to dist/zod/layout.zod.js, needed nothing from the barrel's own body, and the flag let it drop that body whole. The fill went with it, and every child slot then validated against the pre-spec(types): redirect the node recursion point from BaseSchemaCore to AnyComponentSchema — measured at 9 newly-refused documents, and it drops 118 phantom strict refusals #8344 BaseSchemaCore arm — the ~21 base keys and nothing type-specific — with no error, no warning and no way for the guard inside the dropped code to notice. objectui#8344 shipped that window DECLARED and pointed here to close it.

.changeset/8653-listview-title-retired-rowactiondefs-pinned.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    title — retired. ListView resolved its export filename through schema.label || (schema as any).title. @objectstack/spec/ui's ListViewSchema refuses title by name (unrecognized_keys: ['title']) while ObjectGridPropsSchema accepts it; packages/types mirrors the platform contract rather than ruling over it, so declaring title on ListViewSchema would have made this repo accept what the platform save gate rejects. That asymmetry is also why objectui#6639 could take the declare branch for ObjectGridSchema.title one package over and this site could not. A parse-based census of apps/ examples/ content/ and packages/ found zero list-view nodes authoring title, so the retirement costs no author a filename. Over that same corpus the instrument reports three object-grid nodes carrying the key: two authored ones, both in content/docs/api/schema-reference.md, plus one that is not authored at all — packages/plugin-view/src/ObjectView.tsx composes title: schema.table?.title onto a grid node it builds, so it is a producer writing the key rather than an author declaring it. ObjectGrid's own title reads are untouched — they remain declared, ruled and read.

.changeset/8735-objectql-mirror-docblocks-not-defaulted.md

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

    Correct four zod/objectql.zod.ts docblocks that described the behaviour objectui#8317 removed. Since that change the zod mirrors strip imported @objectstack/spec defaults at this package's import boundary, but the docblocks on HttpRequestSchema, ListColumnSchema, SelectionConfigSchema and PaginationConfigSchema still said, in the present tense, that method, prefix.type, type and pageSize are defaulted on parse — the opposite of what each export does. Each now says the key is declared and accepted but NOT defaulted on parse.

.changeset/8767-object-grid-refuses-string-sort.md

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

    Migration. Write the array: sort: [{ field: 'name', order: 'desc' }]. Both keys are required. SortConfig.order carries no ? in @object-ui/types (packages/types/src/objectql.ts) and no .optional() in its zod mirror, and the protocol's own reusable SortItemSchema requires order as well — measured: that schema refuses [{ field: 'name' }] with invalid_value at 0.order. Do not omit it: this block's array arm interpolates whatever is present, so an omitted order lowers to $orderby: 'name undefined' today. That is pre-existing behaviour on the arm this change does not touch, and it is filed as a successor card rather than widened into here.

.changeset/8801-object-kanban-allow-collapse-retired.md

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

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.
  • names packages/types/src/zod/objectql.zod.ts → packages/types/src/zod/objectql.zod.ts — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.
  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    • the declarations retired here — packages/types/src/objectql.ts and its mirror packages/types/src/zod/objectql.zod.ts; - the pins that assert the retirement — object-kanban-allow-collapse-retired-8801.test.ts and bare-kanban-node-key-retired-8802.test.ts; - a comment in packages/types/src/zod/complex.zod.ts, recording that the deleted retiredZeroReadKanbanKey helper once carried this spelling on the SIBLING arm; - one row of content/docs/api/schema-reference.md; - the .changeset/ release notes that discuss it — this one, the two historical entries covering the sibling arm's own spelling, and objectui#9629's note recording the correction to this paragraph.

.changeset/8885-object-chart-drilldown-title-compareto.md

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

    ObjectChart.tsx reads all three off schema, and until now neither published copy declared any of them: not the TS interface (packages/types/src/objectql.ts) and not the zod mirror (packages/types/src/zod/objectql.zod.ts). They rode BaseSchema's index signature / .passthrough() and arrived unvalidated. drillDown was the sharpest case — this component's registry inputs advertise it to the designer palette, and @objectstack/spec publishes ChartDrillDownSchema for exactly this carrier, so an author was offered a key that neither published shape mentioned.

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

    ObjectChart.tsx reads all three off schema, and until now neither published copy declared any of them: not the TS interface (packages/types/src/objectql.ts) and not the zod mirror (packages/types/src/zod/objectql.zod.ts). They rode BaseSchema's index signature / .passthrough() and arrived unvalidated. drillDown was the sharpest case — this component's registry inputs advertise it to the designer palette, and @objectstack/spec publishes ChartDrillDownSchema for exactly this carrier, so an author was offered a key that neither published shape mentioned.

.changeset/8913-object-kanban-columns-declared.md

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

    What moved. ObjectKanbanSchema gains columns on both halves that move together — the TypeScript interface (objectql.ts) and its Zod mirror (zod/objectql.zod.ts). Retiring the bare kanban node type key (objectui#8802) removed the only face that judged a lane, and object-kanban had never declared the key, so it rode BaseSchema's [key: string]: any / .passthrough(): read by the renderer at three sites, named by no published face.

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

    What moved. ObjectKanbanSchema gains columns on both halves that move together — the TypeScript interface (objectql.ts) and its Zod mirror (zod/objectql.zod.ts). Retiring the bare kanban node type key (objectui#8802) removed the only face that judged a lane, and object-kanban had never declared the key, so it rode BaseSchema's [key: string]: any / .passthrough(): read by the renderer at three sites, named by no published face.

.changeset/8990-object-kanban-groupby-optional.md

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

    @objectstack/spec declares the key optional — groupBy: z.string().optional() on ObjectKanbanPropsSchema — while this package required it on the TypeScript declaration (packages/types/src/objectql.ts) and on the Zod mirror (packages/types/src/zod/objectql.zod.ts). objectui was therefore narrower than the protocol on a published key: ObjectKanbanSchema.safeParse and safeValidateSchema refused an object-kanban node the protocol accepts, and such a node could not be annotated with its own type.

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

    @objectstack/spec declares the key optional — groupBy: z.string().optional() on ObjectKanbanPropsSchema — while this package required it on the TypeScript declaration (packages/types/src/objectql.ts) and on the Zod mirror (packages/types/src/zod/objectql.zod.ts). objectui was therefore narrower than the protocol on a published key: ObjectKanbanSchema.safeParse and safeValidateSchema refused an object-kanban node the protocol accepts, and such a node could not be annotated with its own type.

.changeset/8992-user-actions-collapse-and-docblock.md

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

    objectql.zod.ts's UserActionsSchema read stripImportedDefaults(Spec).extend({ group, hideFields, rowColor }), an extension that existed only because @objectstack/spec did not declare those three keys while normalizeListViewSchema folded objectui's legacy showGroup / showHideFields / showColor onto them. The protocol adopted all three in 17.3.0 (objectui#5435's ruling), so the extension is now a second local copy of a protocol declaration — the shape two faces start drifting from — and it collapses into the plain by-reference re-export its own note always said it would become.

.changeset/9067-zod-barrel-named-arms.md

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

    Two new exported symbols, both on packages/types/src/zod/index.zod.ts:

  • 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/9092-inline-locale-declared-face.md

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

    • AppComponentSchema.label (app.ts) - ObjectGridSchema.label and .description (objectql.ts) - PageNodeSchema.aria.ariaLabel (layout.ts)

.changeset/9187-record-highlights-layout-two-values.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    ⚠️ The census behind this narrowing covers this repository only, and it found no in-repo authoring to migrate: every in-tree layout: 'grid' belongs to a different component (detail-view in content/docs/api/schema-reference.md and phase2-schemas.test.ts, ai-recommendations in packages/plugin-ai/README.md), and the one in-repo consumer of this interface that writes a layout (p1-spec-alignment.test.ts) writes 'horizontal'. So no document in this repository stops type-checking. A TypeScript consumer outside this repo that wrote grid is not observable from here and gets a compile error (TS2322) naming the key — which is why the FROM/TO is spelled out above.

.changeset/9309-object-gallery-filter-destination-typed.md

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

    ObjectGallerySchema.filter is typed as the destination its own docblock names — QueryParams['$filter'] — on both faces, the TS interface in objectql.ts and the zod mirror in zod/objectql.zod.ts (objectui#9309).

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

    ObjectGallerySchema.filter is typed as the destination its own docblock names — QueryParams['$filter'] — on both faces, the TS interface in objectql.ts and the zod mirror in zod/objectql.zod.ts (objectui#9309).

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

  • names objectql.ts → packages/types/src/objectql.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/objectql.zod.ts → packages/types/src/zod/objectql.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/9549-tree-filter-declared.md

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

    ObjectTreeSchema.filter is declared on both faces, in the shape objectui#9309 settled for ObjectGallerySchema.filter: QueryParams['$filter'] by indexed access on the TS interface in objectql.ts, and the same two-arm union (array first) on the zod mirror in zod/objectql.zod.ts (objectui#9549).

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

    ObjectTreeSchema.filter is declared on both faces, in the shape objectui#9309 settled for ObjectGallerySchema.filter: QueryParams['$filter'] by indexed access on the TS interface in objectql.ts, and the same two-arm union (array first) on the zod mirror in zod/objectql.zod.ts (objectui#9549).

.changeset/9550-object-tree-root-barrel.md

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

    ObjectQLComponentSchema declares the node types an ObjectQL block may be. Every one of its arms was a named export of this package's root barrel except ObjectTreeSchema, which was declared in objectql.ts, applied by the union, and re-exported by the ./zod barrel (objectui#7917) — while no TypeScript consumer could name it. There is no ./objectql subpath to reach around the barrel: the package's exports map is pinned by packages/types/src/__tests__/package-exports-manifest.test.ts, and the root barrel was the only route to this type.

.changeset/9606-object-kanban-card-title.md

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

    Both published faces of the object-kanban arm now name the key: the zod mirror ObjectKanbanSchema in zod/objectql.zod.ts and its TypeScript twin, the ObjectKanbanSchema interface in objectql.ts. Both declare it OPTIONAL, at the same requiredness the other face uses, so the two faces accept and refuse the same documents. (Located and cited by SYMBOL: line addresses in zod/objectql.zod.ts have drifted before, and this change is itself about a drifted mirror.)

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

    Both published faces of the object-kanban arm now name the key: the zod mirror ObjectKanbanSchema in zod/objectql.zod.ts and its TypeScript twin, the ObjectKanbanSchema interface in objectql.ts. Both declare it OPTIONAL, at the same requiredness the other face uses, so the two faces accept and refuse the same documents. (Located and cited by SYMBOL: line addresses in zod/objectql.zod.ts have drifted before, and this change is itself about a drifted mirror.)

.changeset/9628-kanban-column-collapsed-honoured.md

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

    The key was declared on both published faces of the object-kanban arm — the lane element of ObjectKanbanSchema (objectql.ts and its Zod mirror) and the runtime lane KanbanColumn (complex.ts and its mirror) — and read by KanbanEnhanced alone, a module no production source imports. An authored { "id": "todo", "title": "To Do", "collapsed": true } therefore parsed green on both faces and reached a board that did nothing with it: KanbanImpl's only collapse is the SWIMLANE row's, held in viewer state under objectui:kanban-collapsed:ANGLE-BRACKETS(swimlaneField) and never keyed to a lane's declared value. That is the ADR-0049 declared-but-unhonoured shape.

.changeset/9659-node-recursion-point-inert-clause.md

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

    Prose amended where this change falsified it, ⛔ not rewritten. zod/index.zod.ts said defineNodeComponentUnion "wraps it rather than replacing it" — true only while a wrapper existed. zod/base.zod.ts's note on the loose parameter bound still names ChatbotSchema's record body in the present tense; the paragraph is kept for the reason it records and carries an AMENDED note saying the exclusion set that pin reads is now empty.

.changeset/calendar-view-schema-converge.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Runtime renderer behaviour is unchanged. @object-ui/plugin-calendar's README and content/docs/api/schema-reference.md are repaired to the converged surface in the same change, so no copy of the old contradiction survives.

.changeset/issue-5373-retire-crud-schema.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    crud had four declaration faces and no registered renderer, for the whole life of the key: the TS interface (packages/types/src/crud.ts), the zod mirror (packages/types/src/zod/crud.zod.ts), a dedicated branch in validateSchema that affirmatively PASSED it, and CRUDBuilder in @object-ui/core. A node spelling it painted the OBJUI-001 "Unknown component type" panel, and content/docs/api/schema-reference.md published it as reference material — so a reader (or an AI author) who copied the page got a red panel.

  • names api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    Authoring crud is now REFUSED BY NAME rather than passed or silently ignored. validateSchema returns an error with code: 'RETIRED_TYPE' on schema.type — at any depth, since it is what validateChildren recurses with — so assertValidSchema throws and isValidSchema answers false. The message names the migration: object-grid for the record table with its toolbar, filters, pagination and row/batch actions, object-form for the create/edit form, and detail for the record view. api/schema-reference.md is rewritten around those shapes.

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

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    listViews stays unmirrored, on the ruling's own fallback clause. The declaration's value is the local NamedListView — 64 declared top-level members (⚠️ re-taken at objectui#8980, which added the seventeen the protocol declares on this surface to the 47 this entry first measured), of which the renderer reads 21 off a named view. data is one of the 21 now: it used to reach the renderer through an as any cast on the named-view config in packages/plugin-view/src/ObjectView.tsx and be declared nowhere, and the objectui#8980 ruling declared it by name — objectui#7928's open half, answered. The spec's ViewSchema.listViews is a record of the STRICT ObjectListViewSchema, which requires columns and refuses options, ObjectQL tuple filters and default — that is, it refused the named views this package's own README and content/docs/api/schema-reference.md taught when this entry was written ({ label: 'All Users' } fails at columns; filter: [["owner", "=", "..."]] fails at filter.0), and objectui#8255 has since rewritten them in the spec shape. Mirroring the spec value would have lost documented behaviour; mirroring the local value would enforce 43 unread members (64 declared, minus the 21 that are both declared and read) into the contract — the very thing ruling B refused for the six local keys. The key therefore stays in the parity ledger with that measurement, pinned, until the maintainer decides its value type. It is not papered over with z.any().

  • 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.

.changeset/table-renderer-declared-column-contract-5350.md

  • names content/docs/api/schema-reference.md → content/docs/api/schema-reference.md — edited by this change

    What makes this site different from its three siblings is that the alias was not merely tolerated, it was published. content/docs/api/schema-reference.md §TableSchema shipped a copyable { "name": "id", "label": "#" } example and a property row reading "Column definitions with name, label, …", while packages/types declared the opposite pair. Docs and type disagreed about one type they both call TableColumn, each internally consistent. Retiring the alias without correcting the page would have turned a documented, working example into a silently broken one, so both halves land together: the page now authors accessorKey/header. The same row also advertised a render property that TableColumn has never declared — the renderer's hook is cell — and that claim is dropped rather than re-spelled.

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.

Angle-bracketed names in the quoted prose above are rewritten as ANGLE-BRACKETS(name): GitHub deletes tag-shaped fragments from a stored body, and a quote that silently loses the identifier it is about is worse than a visible repair.

Compared the checked-out tree with 1b30c0fe0 (merge-base with origin/main): 25 file(s) changed outside .changeset/, read against 1855 pending declaration(s) that publish a body (2467 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3584.5 KB 3607.4 KB
Main entry chunk (gzip) 150.0 KB 350 KB
Entry file index-dVraMO0X.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) 570.18KB 136.44KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 228.86KB 63.53KB
fields (index.js) 261.11KB 66.26KB
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) 41.23KB 11.53KB
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) 139.27KB 37.26KB
plugin-designer (index.js) 216.32KB 44.56KB
plugin-detail (index.js) 244.72KB 64.45KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 173.61KB 44.59KB
plugin-gantt (index.js) 173.03KB 43.07KB
plugin-grid (index.js) 231.61KB 63.61KB
plugin-kanban (index.js) 48.62KB 15.21KB
plugin-list (index.js) 116.63KB 28.97KB
plugin-map (index.js) 23.50KB 7.82KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.04KB 12.21KB
plugin-timeline (index.js) 33.05KB 9.67KB
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.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 (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) 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: a32a4470cd9f702b91413a79b9c231fb096ac20a
Local-runs: none

PR objectui#11291 (draft, 27 files, +874 / −140 against the merge base f16c01e90e), batch 4 of card objectui#10859, claim 5920453930, dev report 5921715583. Read: the card body and all 24 comments, the PR body and file list, the net diff at the head, the head's check-runs (two snapshots), the spec source at objectstack origin/main (page.zod.ts, component.zod.ts, shared/strict-object.ts), and the head's tree where the diff's forced edits point. Nothing built, run or re-run. The governing text is the seat's flat-arm answer at the batch-3 ACCEPT 5917261346 (A by inheritance from objectui#10872's triage answer A 5906817482, five execution steps), read against the claim's scope and refusals.

① Derived judgments

  1. The batch's gate (meta-rule ①'s limit) holds at source — right. At objectstack origin/main 5f6b63a6fd (later than the dev's 75519e1c0a), PageComponentSchema is a strictObject (shared/strict-object.ts: z.object(shape, { error }).strict()), declares properties: z.record(z.string(), z.unknown()) and declares no objectName / mode, so a flat object-form node is refused unrecognized_keys exactly as the parent's reason states (ADR-0089 D3a). ComponentPropsMap['object-form'] is ObjectFormPropsSchema, itself a strictObject row. The installed 17.5.0 copy is not in this checkout (no node_modules), so its half of the dev's measurement is read off the pin's lit-control rows (SpecPageComponentSchema.safeParse(FLAT) → unrecognized_keys ['objectName', 'mode']; the same props in properties accepted), which run against the installed spec in CI. The parent's reason holds for this arm; the claim's stop condition ("the spec does not refuse the flat spelling") did not fire.

  2. The route: a NEW export ObjectFormBlockSchema — right, and inside the claim's intent. Verified in the diff: BaseSchema.extend with the object-form literal, ...NODE_ENVELOPE, properties = propsBag('object-form', stripImportedDefaults(SpecObjectFormPropsSchema)) (the row by reference; the row carries no default, so the boundary hands back the export itself — the pin asserts toBe), dataSource as the spec's ElementDataSourceSchema by reference, one aliasKeyRefusal(key, 'properties.KEY', …) per member of the row keyed off Object.keys(stripImportedDefaults(SpecObjectFormPropsSchema).shape) (so the refusal set follows the spec, not a transcribed list), the five handlerKeyRefusal runtime slots as on the flat mirror, and body / children as retirementTombstone(OBJECT_FORM_NEITHER_CHANNEL). It joins ObjectQLPublicBlockComponentSchema (4 arms), and the flat ObjectFormSchema leaves ObjectQLComponentSchema (11 arms). The claim's file surface named ObjectFormSchema as "the arm takes its props in the properties bag … the flat props retire from both authoring faces"; what it rules is the arm and the faces, and both are delivered. Converting the zod mirror in place would have changed the published shape the object-view form slot is built from (ObjectFormSchema.omit({ type, objectName, mode }).partial(), at head) and broken the census pair with the TypeScript twin that step 3 keeps, so the new export is the route that honours step 3; the name follows batches 2–3 (*BlockSchema).

  3. The flat mirror staying exported is a post-hoist type, not a second dialect — right. Triage's refusal of B was "a flat channel in objectui's ARMS". At head AnyComponentSchema is z.discriminatedUnion('type', [… ObjectQLComponentSchema, ObjectQLPublicBlockComponentSchema …]) (index.zod.ts), safeValidateSchema / validateSchema parse that union, and the strict face is derived from the same arms, so the object-form literal resolves to the bag arm only. No file under packages/cli/src, packages/core/src or packages/react/src reads ObjectFormSchema; the only zod readers at head are the object-view form slot and direct-mirror tests. The pin's rows hold it both ways: the flat node is refused at objectName and mode by name on both faces with the properties.KEY remedy, every row key written flat is refused, and ObjectFormSchema.safeParse(FLAT) still succeeds while ObjectFormSchema is absent from ObjectQLComponentSchema's literals. The 12 → 11 move is forced: a discriminated union cannot carry the literal twice, and leaving the flat arm in would keep the flat spelling accepted.

  4. The TypeScript twin — step 3 executed: re-declared, not retired — right. The objectql.ts diff is docblock only (+21 / −2, all comment lines); the interface body is unchanged, and the type-level pin holds type: 'object-form', objectName: string, mode required, and membership in the TypeScript ObjectQL union. No decision-box condition arose.

  5. The producers — nine rewritten, right; the four exclusions are right. Rewritten (each diff read): root README.md, schema-reference.md (its table now says the bag refuses groups and the retired layout values), building-crud-app.md, objectos-integration.mdx, plugin-form.mdx, both schema-catalog fixtures, packages/plugin-form/README.md (two of three nodes), and the form node's mode prescription in form.zod.ts. Excluded on measured grounds, confirmed at head: the two examples/byo-backend-console/src/App.tsx nodes are ObjectForm component mounts with a schema prop and no SchemaRenderer hoist (ObjectForm reads schema.objectName; a bag would break them); packages/plugin-designer/src/ObjectManager.tsx (useMemo of a ModalFormSchema with open, onOpenChange, i18n customFields, mounted as a ModalForm component) and FieldDesigner.tsx (a DrawerFormSchema composed the same way) are code composers no authoring face judges. content/docs/utilities/data-objectstack.mdx ({ type, dataSource: { object } }) is bag-compatible — the pin's BOUND fixture. The governed skills/objectui/guides/page-builder.md "Form plugin example" (os:check-marked, flat) is listed, not edited, per step 4, and packages/cli/src/commands/validate.ts is PR objectui#11069's file, untouched per the claim.

  6. The Appsmith fragment's dropped keys are inside "rewrite the producer" — right. fieldNames is declared by neither the flat mirror nor the row (the row's member is fields, which the rewrite uses); a node-level adapter value at dataSource is refused now that the key is the spec binding (the pin's adapterShaped row); onSubmit is declared by neither face. The bag is strict and the strict face refuses undeclared flat keys, so each of the three had to go for the producer to parse; the fragment's intent (a create form on contact over four fields) is kept.

  7. The narrowings — the Acceptance note is right; none is a regression this PR must carry. (a) The flat node refused: the batch's deliverable, under the BREAKING (authoring) banner. (b) sections entries typed unknown by the row: a section's keys (the objectui#7200-retired className among them) go unjudged inside the bag where the flat mirror's section entry judged them. Route A judges properties by reference (batch-2 review 5866078131), so the accept set inside the bag is the spec row's; the flat mirror still judges sections member by member through the object-view form slot, where the objectui#6152 closed-shape row now reads. Fileable, on the spec side (see ③.9). (c) A numeric recordId in the bag: the row is string | number; objectui#9511's refusal stays on the flat mirror (authorable-record-id-string-9511.test.ts reads ObjectFormSchema directly, so it stays green non-vacuously), and batch 2's object-master-detail-form bag already takes one. (d) buttons / defaults / subforms / groups refused in the bag: form-VIEW members, not row members; the three objectui#6152 declared stay authorable in the object-view form slot, and the pending .changeset/6152-object-form-unmirrored-members.md (unedited, still at head) and this PR's changeset read together as the changeset says. (e) layout: inline | grid refused with the spec's own 17.5.0 prescription, by reference. (f) objectName / mode optional in the bag: a widening the spec row declares.

  8. Forced edits and fixtures — each forced, none a pin of behaviour that should have stayed. zod-mirror-parity.test.ts: one EXCLUSIONS row for the new export, EXPECTED_MIRROR_PAIRS untouched. imported-defaults-8317.test.ts: one IMPORTED row for the new spec crossing. registered-type-arms-10859-b2.test.ts: the exact public-block literal list. objectql-union-arms-7363.test.ts: toHaveLength(11) plus not.toContain('object-form'); its remaining rows concern object-gallery / object-data-table only. public-block-responsive-styles-10872.test.ts: the test enumerates ObjectQLPublicBlockComponentSchema.options and asserts the declared dataSource set equals DECLARES_DATA_SOURCE, so the arm's binding forces the row. Fixtures: strict-face-read-keys-11070 (one BOUND_NODES row), content-channel-e3-residual-9256 (AUTHORED_REQUIRED for the AnyComponentSchema reach row; the mirror rows keep the flat spelling), object-form-unmirrored-members-6152 (face rows in the bag; the three form-view members inverted to refused-in-bag and accepted-in-slot; the closed-shape row moved to the slot; the catalog row reads doc.properties), guideCrudAppRenders (reads the rewritten guide snippet's bag), catalog-gallery-render (a provenance control respelled to the entries' spelling — it renders through SchemaRenderer, so either spelling draws; consistent, not load-bearing).

  9. The ratchet — untouched, right. packages/cli/src/__tests__/registered-types-validate-ratchet-10859.test.ts is not among the 27 files (empty diff stat) and holds REFUSED_AT_TYPE = 72 at head. object-form was accepted at type before (through the flat arm) and after (through the bag arm), so the count does not move; the dev's ablation (arm removed → 73 against the pin of 72) is the direction control. PR objectui#11069's 15 files and this PR's 27 are disjoint.

  10. Scope guards — right. The objectql.zod.ts diff touches only the object-form hunks and the two union docblocks: no flex / object-grid / object-chart. skills/** untouched (Governed Surface Queue Guard success). Part of #10859. Since the merge base main moved six commits; the one overlapping file is form.zod.ts (objectui#11262's hunk at the top of the file; this PR's hunk is the mode refusal string), and git merge-tree --write-tree of the head with origin/main is clean.

  11. New pins — right instruments. object-form-properties-bag-10859-b4.test.ts: type-level equality of the bag's input to ObjectFormProps | undefined, every row key refused flat (the Exclude of the spec row's keys by the arm's keys is never), the twin's identity keys, a non-vacuity row; runtime rows for the showcase wizard, the bare node, the bound node, the child slot, per-key flat refusals read off the installed row, bag strictness, the spec's own prescriptions by reference, dataSource, the runtime slots, the content channels, and the arm/mirror split. objectFormPropertiesBag-10859.test.tsx: the bag node draws the wizard through the real registry, the flat spelling draws the same, and a different-object control shows the rows measure the bag.

  12. Check-runs on the head, second snapshot: 42 runs, 32 success, 3 skipped (the coverage matrix and dependabot), 0 failure, 7 not concluded. Concluded green include Type Check, Lint, Test (shard 6/8), Test (shard 7/8), Test (dist pins), Build & E2E, Doc Snippet Type Check, Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Governed Surface Queue Guard, README Export Check, Skill Example Check. Still in_progress, not concluded, neither pass nor fail: Test (shard 1/8), Test (shard 2/8), Test (shard 3/8), Test (shard 4/8), Test (shard 5/8), Test (shard 8/8), Spec Main Shape Gate. The ACCEPT waits on those seven; this record does not.

② Semver level

  • @object-ui/types: minor with a BREAKING (authoring) banner — right. objectui's rule (AGENTS.md §版本号策略: no major in the fixed group; a breaking change ships as minor with the break spelled out in the body; enforced by check-changeset-no-major.mjs, Changeset Bump Policy success) and the answer's step 2. The body carries the FROM → TO mapping (a flat prop → properties.KEY), the one-line fix as a JSON example, the render-time invariance, and the row's divergences from the flat mirror. The same text batches 1–3 used.
  • @object-ui/plugin-form: patch, README only — right. The presence gate is forced by the new test file under packages/plugin-form/src/__tests__/ (files under src/ count; a root *.md alone would not). An empty frontmatter was also legal for a test-only change; patch is the honest level given the README ships in the tarball, and the level is moot for the fixed group's version (the types minor carries all 39).
  • Clause-②: yes — right. The PR body's second line, matching the claim. The closed pair allows at most one arm; this change both narrows (a flat node that parsed is refused) and widens (a spec-shaped bag node that was refused parses), so the bare yes, which takes at least minor, is the well-formed spelling, as batches 1–3 wrote it.

③ Boundary flags

  1. Route deviation (new export, not in-place conversion) — answered: inside the claim's intent (①.2–①.3). The arm list's object-form entry moved to the bag; the flat mirror is the twin's paired post-hoist reading, which step 3 keeps.
  2. Union move 12 → 11 — answered: forced (①.3, ①.8).
  3. Census differs from batch 3's "13 authored" — answered: right. The four are component mounts and code composers, measured at head (①.5).
  4. Appsmith fragment's dropped keys — answered: inside the rewrite (①.6).
  5. Second changeset — answered: forced by the test file under src/; patch is right (②).
  6. Render pin's replaced control — answered: the different-object control is the right instrument. The abandoned control's observation (a bag without objectName still called getObjectSchema once, argument uninspected) is unmeasured and is not a finding of this PR. Beside it: the bare node { type: 'object-form' } is now accepted by both faces, which is the spec row's contract (objectName optional because dataSource can name the object); what the elementDataSourceBlock gate does at render with neither key is a runtime question outside this batch. Not filed here; the seat may commission the measurement.
  7. Container restart, resumed from disk — answered: process only; the five commits are contiguous on the branch and the diff is whole.
  8. Attribution — answered: right. All five commits end with Claude-Session: … and the model-free Co-authored-by: Claude trailer; the PR body's footer is the session URL; no model identifier in the PR title, body, changesets or comments.
  9. out_of_scope_findings, each answered or escalated:
    • Governed skills/objectui/guides/page-builder.md "Form plugin example" now authors a node objectui validate refuses (check:skill-examples only parses the fence) — escalated to the governed path per step 4: the seat carries it to the maintainer's approval; not this PR's.
    • packages/cli/src/commands/validate.ts spec-vocabulary hint teaches the flat spelling — carrier PR objectui#11069, right; the seat should note it on that PR.
    • The spec row types sections entries as unknown, so a section's keys (including objectui#7200's retired className) are unjudged in the bag by both os validate and objectui validate — fileable, on objectstack (packages/spec, ObjectFormPropsSchema.sections), not objectui: the dev's "no carrier" is right for this repo, and the seat should route it to triage rather than leave it noted only. Not blocking: route A takes the row by reference.
    • A numeric recordId passes the bag while objectui#9511 refuses it on the flat mirror — observation, spec's contract; fileable only if objectui#9511's reason is meant platform-wide, which is a triage question, not this batch's.
    • The TypeScript twin does not name contentLayout / confirmOnDiscard, two row members ModalForm / DrawerForm read — fileable, objectui, small (the objectui#6152 family: a TS-face member gap); not blocking.
  10. open_questions: none.

Implemented-by: claude/issue-10859-object-form-properties-bag
Reviewed-by: session_0122Knsowci76D2rBWReCzzZ

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 23:57
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit e3782d2 Oct 1, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10859-object-form-properties-bag branch October 1, 2026 00:12
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 examples package: types plugin tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants