Skip to content

fix(types): header-bar refusals and nine body docblocks name the parser tier's unknown-prop warning (objectui#10959) - #10980

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-10959-unknown-prop-clause
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-10959-unknown-prop-clause

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10959
Clause-②: no — message, .describe() and docblock text only; every accept or refuse verdict stays byte-identical.

Implemented by the os-dev agent dispatched from the domain:spec @ objectui seat, session https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm (mode:subagent). Base 1345e182d, head 7089f691c.

What changed

Only the false clause changes. It changes on 18 sites of two kinds. The objectui#10928 wording names not-a-container, which would be false here, so it is not reused.

  • Refusal strings (zod face). These are the 9 HeaderBarSchema refusals: title, logo, nav, left, center, right, sticky, height (objectui#10387) and variant (objectui#10286). Each message is also its .describe() text, through retirementTombstone.
    • Before: "no error, no warning, no element" (for variant, "no class").
    • After: "no render-time error or warning and no element; only the parser tier's unknown-prop warning noticed it". variant keeps "no class" in place of "no element".
  • body?: never docblocks (TS face, emitted .d.ts). There are 9: box, span, container, flex, stack, grid, scroll-area in layout.ts, and toggle, form in form.ts (objectui#8284).
    • Before: "rendered an EMPTY element with no error and no warning".
    • After: "rendered an EMPTY element with no render-time error or warning; only the parser tier's unknown-prop warning noticed it".
  • New pin: packages/types/src/__tests__/unknown-prop-parser-tier-clause-10959.test.ts.
  • Changeset: .changeset/10959-unknown-prop-parser-tier-clause.md, @object-ui/types: patch.

The parser tier, measured before the clause was written

Instrument. A one-shot probe, deleted and not committed. It sat under packages/components/src/renderers/__tests__/ and built the manifest from the live registry the way container-declaration-ratchet.test.tsx does: every KNOWN key, through manifestFromConfigs. It imported components, fields and 18 of the 19 plugin-* packages (all but plugin-designer). Then it ran validateTree once per leg. It also rendered each leg through the real SchemaRenderer, spying on console.warn and console.error, and compared the HTML with the leg's control.

Population and result.

population legs what each leg adds
header-bar and ui:header-bar (inputs crumbs, search, actions, rightContent) × the 9 keys × every value the objectui#10387 / objectui#10286 pins use (strings, a node, a node list, booleans, numbers, the 4 variant spellings) 44 exactly one unknown-prop warning; HTML byte-identical to the control; 0 console warnings, 0 console errors
the bare and ui: key of the 9 body nodes (18 keys, each declares the children input and no body input) × body as a list, a single node and a scalar 54 exactly one unknown-prop warning (the checkRetiredBodyDialect sentence that names children); HTML byte-identical to the control; 0 console warnings, 0 console errors

Answer to the mechanism assumption about value shape. Scalar and list give the same code on these nine. checkRetiredBodyDialect answers not-a-container only where the registration declares no children input, and all 18 keys declare it. So the list, the single node and the scalar all draw unknown-prop.

Controls.

  • accordion and input declare no children input. There a body list draws not-a-container, a scalar body draws unknown-prop, and an invented key draws unknown-prop. So the probe can tell the two codes apart.
  • The header-bar control renders a header element with 0 console warnings and 0 console errors.
    • A first run rendered header-bar bare. It logged the useSidebar provider error on the control and on every leg alike, so its render comparison proved nothing.
    • That run was discarded. The render leg was re-run inside sidebar-provider, the context the containment ratchet uses for the sidebar parts.

The same answer before each retirement (source reading, not executed). The strings and docblocks are in the past tense, so the pre-retirement tree was read too.

  • At 236a71fe3, the parent of the objectui#8284 tombstone commit b1777aef3:
    • validate.ts had no body in its base-prop set and no checkRetiredBodyDialect, so an undeclared key drew unknown-prop;
    • none of the nine registrations declared a body input (zero quoted 'body' / "body" in the nine files).
  • header-bar.tsx did not change between 6214db63f (2026-09-11) and the two retirements (objectui#10286 a05c35063, objectui#10387 9b281519f). Read at a05c35063, its registration declared the same four inputs it declares today.

Census, base 1345e182d vs head 7089f691c

These counts are over joined text: JSDoc continuations and '…' + '…' joins are removed, and an escaped quote is unescaped.

file old string clause new string clause old docblock clause new docblock clause control: header-bar refusal head control: objectui#8284 docblock sentence control: objectui#10928 clause absent token
zod/navigation.zod.ts 9 → 0 0 → 9 0 → 0 0 → 0 9 / 9 0 / 0 10 / 10 0 / 0
layout.ts 0 → 0 0 → 0 7 → 0 0 → 7 0 / 0 7 / 7 12 / 12 0 / 0
form.ts 0 → 0 0 → 0 2 → 0 0 → 2 0 / 0 2 / 2 28 / 28 0 / 0
  • Over all 67 non-test .ts files of packages/types/src at base (enumerated from the ref with git ls-tree, not from the working tree), the old string clause and the old docblock clause occur only in these three files.
  • Emitted dist, after pnpm --filter @object-ui/types build at head:
    • old string clause 0, new 18 (zod/navigation.zod.js and zod/index.zod.js, 9 each);
    • old docblock clause 0, new 9 (layout.d.ts 7, form.d.ts 2).
  • On the built dist, safeValidateSchema on header-bar answers as follows:
    • with title: invalid_type at path title, and the message carries the new clause and not the old one;
    • with variant: the same;
    • control (only crumbs): success.

Every other byte, and only string and comment lines

  • Take each head file and substitute the old clause back for the new one: 9, 7 and 2 substitutions. Each result is byte-identical to the base file.
  • git diff -U0 over the three files: every changed line is a + '…' string-literal line (zod) or a * JSDoc continuation line. No other line changed.
  • On the docblock face, the replaced text wraps onto three lines where the old text used two. The joined text differs only in the clause.
  • The objectui#10387 and objectui#10286 pins (codes, paths, message heads) pass unchanged.

Reverse verification (ablation), after the fix was committed

Instrument. objectstack's scripts/ablation-replace.mjs, in WRAP mode, run from this worktree. The anchor must hit exactly once, and the on-disk blob must move. The tool proves each restore (blob == HEAD, git diff HEAD empty), and the driver re-proves it independently. The types tests import ../zod/* source and read layout.ts / form.ts from disk, so no build sits between a mutation and the run.

leg: the old clause put back into blob mutated to red / 7 red tests
variant's string 59f587b96 2 the variant arm, the header-bar population
title's string c80665c61 2 the title arm, the header-bar population
BoxSchema.body docblock 2383fde4f 2 the BoxSchema docblock, the docblock population
GridSchema.body docblock (not named by the pin) a3ac19fa8 1 the docblock population
ToggleSchema.body docblock in form.ts (not named by the pin) 5e4c2ac67 1 the docblock population

Each leg restored to its HEAD blob (ad46ba82c, a90b246c7, daad6d395) with an empty git diff HEAD. After the run, git status --porcelain was empty.

The pin

It follows the house style of PR objectui#10956's pin. @object-ui/types declares no workspace dependency, so the pin holds the prose, not the parser.

  • Zod face.
    • For title and variant, one per clause form, it asserts code invalid_type and path [key] first, then the clause substring. It also asserts that neither the old clause nor not-a-container appears.
    • Then the population: every HeaderBarSchema member whose refusal is an objectui#10387 / objectui#10286 retirement carries the clause. Non-vacuity is by name (title, variant).
  • TS face. Precedent for pinning docblock text: layout-default-jsdoc-7361.test.ts.
    • The body docblocks of BoxSchema and FormSchema carry the clause.
    • Then the population: every docblock in layout.ts and form.ts that carries objectui#8284's sentence carries it too. Non-vacuity: both files are reached.
    • The repo root comes from the pin's own import.meta.url, never the cwd (check:test-path-roots passes).
  • Where the parser half lives. The body half is pinned in packages/sdui-parser, by "the retired body spelling follows the SAME predicate" (containment-declared-slot-9910.test.ts) and "a body child list under a CONTAINER draws unknown-prop naming children" (body-dialect-6771.test.ts). The header-bar half is validateTree's generic undeclared-key branch, and no pin there names header-bar. See Open question.

Dispatch assumptions, checked

  1. Holds. Counts are 9 / 7 / 2 on base 1345e182d, a descendant of 328abeb55 (merge-base --is-ancestor exit 0), with controls. See Census.
  2. Holds, and made more exact. Every key and every value shape draws unknown-prop. None draws not-a-container, because all 18 body keys declare children. See the table.
  3. Does not hold. The objectui#10928 pin (content-channel-parser-tier-clause-10928.test.ts) does not name the 8 HeaderBarSchema sites. Its population walk reads only the family-D body / children members (reads NEITHER content channel:). It passes unchanged at head, so it is not edited. The 8 sites appear only in PR objectui#10956's description.
  4. Not re-scanned by this dev. The PM files-API scan is taken as given. This diff touches none of PR objectui#10972's or PR objectui#10977's files.

Gates, head 7089f691c

All ran after the last commit. Exit codes were captured before any pipe.

  • pnpm --filter @object-ui/types build → 0. It includes check-dist-completeness: 134 emitted files verified.
  • pnpm --filter @object-ui/types type-check → 0. tsc -p tsconfig.test.json --listFiles includes the new pin.
  • pnpm --filter @object-ui/types lint → 0, with 0 errors. The new file has no warnings; the package has 295 existing no-explicit-any warnings.
  • pnpm exec vitest run packages/types/ → 0: 269 files and 5962 tests passed.
  • The tests outside types that read the edited files, plus the parser pins named above → 0: 9 files, 276 tests.
    • scripts/__tests__/check-doc-component-types.test.ts, check-spec-symbol-derivation.test.ts, one-authority-per-exported-name-6273.test.ts and check-pending-changeset-literals.test.ts;
    • the three header-bar component tests;
    • body-dialect-6771 and containment-declared-slot-9910.
  • check-changeset-presence, check-changeset-no-major, check-changeset-fixed and check-changeset-overwrite → 0.
  • check-changeset-claims → 0. It is report-only. It lists 17 pending changesets that name an edited file. None of them quotes either clause this PR changes. The only pending changeset carrying the old string clause is objectui#10928's, about the family-D refusals, and that is still true.
  • pnpm check:control-bytes, check:new-line-citations (0 new), check:pending-changeset-literals, check:test-path-roots and check:phantom-deps → 0.
  • check-governed-queue-guard --test over the 5 paths → NOT GOVERNED.
  • Declared to CI: repo-wide pnpm lint, full pnpm test and the other workflow gates.

Acceptance notes

Same family, not changed here, and left for the seat to route. Each carries the old silence claim, and the parser tier answers it (live registry, same probe):

  • zod/form.zod.ts, the FormSchema.mode refusal (objectui#10286): "every spelling rendered the same form — no error, no warning". form with mode draws unknown-prop.
  • data-display.ts, the TimelineSchema.events docblock (objectui#6170): "drew an EMPTY rail, with no error and no warning". A bare timeline with events draws unknown-prop. The live registry resolves that key to plugin-timeline's view registration, so this reading is about that registration. This file is PR objectui#10972's surface.
  • base.ts, the bind docblock: "data-table … renders its header over an empty body, with no error and no warning". data-table with bind draws unknown-prop.
  • zod/objectql.zod.ts, the exportOptions describe text (objectui#7762): "no error, no warning, no console line". object-grid with an exportOptions array draws type-mismatch.

Boundary: the names grid and form also exist as view:grid, view:form and field:grid. Those are different registrations and node types, which the GridSchema / FormSchema docblocks do not describe. None of them declares children, so a body list there draws not-a-container.

Open question

Should a live-registry pin in packages/components also hold the parser behaviour the header-bar clause describes? No such pin exists today. This PR does not add one: it is outside the claimed file surface, and the same question is open from PR objectui#10956.


Generated by Claude Code

…er tier's unknown-prop warning

The nine HeaderBarSchema refusal strings (title, logo, nav, left, center,
right, sticky, height, variant) and the nine `body?: never` docblocks in
layout.ts (box, span, container, flex, stack, grid, scroll-area) and
form.ts (toggle, form) said an authored value drew "no error, no warning".
The render path is as described, but the parser tier is not: validateTree
answers each of those keys, on the bare and the ui: registration, with an
`unknown-prop` warning. Only that clause changes; every other byte of each
string and docblock is kept.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
…ody docblocks; changeset

The pin reads one header-bar key of each clause form through its refusal
(invalid_type at the key's own path, then the clause substring), every
header-bar member of the objectui#10387 / objectui#10286 retirements, the
body docblock of BoxSchema and FormSchema, and every objectui#8284 body
docblock in layout.ts and form.ts. Changeset: @object-ui/types patch.

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

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 17 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/4895-retire-block-schema-family.md

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

    ⚠️ Not this family, and not touched. The live slotted record-page vocabulary — PageNodeSchema.kind === 'slotted' with slots?: PageSlotMap (packages/types/src/layout.ts), rendered by usePageAssignment / PageBlockCanvas / PageBlockInspector in @object-ui/app-shell — shares the words "block" and "slot" with the retired family and shares no declaration, type or file with it. Neither is the type: 'component' NAVIGATION item kind ({ type: 'component', componentRef }, NavigationItemSchema in zod/app.zod.ts), a different declaration in a different module.

.changeset/5632-svg-host-dom-passthrough.md

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

    Both renderers forwarded their whole prop bag to the SVG they render, so every authored SDUI key on the node became an attribute — 14 per target, and icon="check" on all 71 icon nodes in the schema catalog. The two nodes declare different keys, and each renderer consumes its own node's keys by name: IconSchema (packages/types/src/layout.ts) against the pass-through docblock in renderers/basic/icon.tsx, and SpinnerSchema (packages/types/src/feedback.ts) against the one in renderers/feedback/spinner.tsx. icon and color are IconSchema's alone — SpinnerSchema declares neither. Read those two declarations for what they carry; this paragraph deliberately copies no member list. So the SDUI pass-through list withholds nothing they need.

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

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

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

.changeset/6396-previous-values-dom-leak.md

  • names packages/types/src/form.ts → packages/types/src/form.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/6397-partial-schema-collapse-pin.md

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

    ⚠️ Dated note, 2026-09-28 — a generic re-spelling of this Omit does not collapse — objectui#9256. Later in this same release objectui#9256 declared a key-remapping OmitDeclared ({ [P in keyof T as P extends K ? never : P]: T[P] }, in packages/types/src/form.ts) and re-spelled the heritage of InputShorthandSchema and UiCalendarSchema with it. Such a mapped type iterates the named members and the index signature separately, so it drops exactly K, keeps every other named member with its modifiers, and keeps the index signature. The same spelling of this alias declares 28 properties at ButtonSchema and 62 at ObjectGridSchema in this release, where the shipped alias declares 1 at each; at this change's own commit, under the same TypeScript, it declares 27 and 61, the source counts in the table above. So "every generic re-spelling collapses" was not true when it was written. The alias is still left as written: re-spelling it would narrow what every instantiation accepts, a contract change and not a pin's business. The PartialSchema docblock and partial-schema-collapse-pin.test.ts carry the correction. The rest of this entry is kept as the reading of this change.

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

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

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

.changeset/6938-checkbox-wrapper-class.md

  • names packages/types/src/form.ts → packages/types/src/form.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/6951-text-value-retired.md

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

    Two published faces, one retirement. The TypeScript interface TextSchema (@object-ui/types, layout.ts) declares value?: never; the Zod mirror TextSchema (@object-ui/types/zod, layout.zod.ts) declares value as a retirementTombstone(), so the key stays DECLARED and is refused BY NAME — a plain deletion would have let an authored value ride BaseSchema's .passthrough() into a silent blank, which is worse than the tolerated fallback it replaces. The value?: string members of TextSpanSchema and TabsSchema in the same file are other schemas' contracts and are unchanged.

.changeset/7697-combobox-option-root-barrel.md

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

    Additive only. ComboboxOption is added to the root barrel's existing named re-export list from ./form.js, next to the sibling option types that were already there (SelectOption, RadioOption). Nothing is removed, retyped or narrowed: the declaration stays in src/form.ts, its three members (value, label, disabled?) are unchanged, and the @object-ui/types/form subpath spelling keeps working exactly as before. Both spellings now resolve to the same declaration.

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

  • names form.ts → packages/types/src/form.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/7917-export-breadcrumb-object-tree-zod-schemas.md

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

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

.changeset/8072-input-wrapper-class-mirrored.md

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

    packages/components/src/renderers/form/input.tsx reads cn("grid w-full items-center gap-1.5", schema.wrapperClass) onto the wrapper div around the input and its label. The TypeScript face has declared the key all along (form.ts, docblock "Input wrapper CSS class"); the zod mirror never did, so the value rode through .passthrough() and { type: 'input', wrapperClass: 42 } validated GREEN — while the identical document on any of the other eight schema.wrapperClass readers (checkbox, file-upload, filter-builder — objectui#6150 / b74a8598d; switch, textarea, date-picker, select, list — objectui#7722) was refused at the key.

.changeset/8871-page-node-refuses-breadcrumbs.md

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

    What was measured, on this branch's base 93127bd6f. Zero readers, with a point-access probe rather than a bare word: on that base \.breadcrumbs scores 0 tree-wide (exit 1) against \.breadcrumb\b's 12 files tree-wide (10 under packages/) as the lit control. At head the same two probes read 16 and 13 and \.breadcrumbs is exit 0 over 4 files — every hit one of this branch's own four files (this changeset, the refusal pin, layout.ts, zod/layout.zod.ts) quoting the probe string, and the pin's own exclusions put head back at exit 1. The base reading is the measurement; the head reading is this branch's echo of it. The bare word would have lied — it also names Sentry's own unrelated concept (app-shell/src/observability/sentry.ts) and appears in two comments listing UI surfaces (core/src/utils/record-title.ts, layout/src/NavigationRenderer.tsx), so a bare probe reports five readers that do not exist.

.changeset/9092-inline-locale-declared-face.md

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

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

.changeset/9406-types-root-barrel-two-names.md

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

    form-barrel-mirror-9406.test.ts keeps it closed, and it is DERIVED rather than a pair of presence assertions: it reads form.ts's export list and the root barrel's ./form.js re-export list on every run and names whatever is in the first and not the second. A pin asserting "these two names are present" would pass on the day the next declaration lands in form.ts and is forgotten, which is this class reopening yet again. Names deliberately left off the list get a ledger row carrying the reason instead, and a row goes red once its name reaches the barrel or stops being declared.

.changeset/9526-types-root-barrel-command-element-types.md

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

    The change is purely additive: the two names join the root barrel's existing named re-export list from ./form.js, beside CommandSchema. Nothing is removed, renamed or narrowed, the declarations stay in form.ts, and the /form subpath keeps working. It is the third instance of one class, repaired by the same route each time: objectui#7697 for ComboboxOption, and objectui#9406 (director decision batch 133, item 2, letter (a)) for InputShorthandSchema and UiCalendarSchema.

.changeset/layout-default-jsdoc-7361.md

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

    The published @default documentation on two layout.ts members now matches the value the renderer actually applies. ContainerSchema.maxWidth documented 'lg' while container.tsx applies schema.maxWidth ?? 'xl', and the shared FlexLayoutProps.align documented 'center' while flex.tsx applies schema.align || 'start' and stack.tsx applies schema.align || 'stretch'. The renderers are unchanged — they are the authority for what runs — so only the docblocks moved; align now states both consumers in prose instead of carrying a single @default, because one member shared by two deliberately divergent component types cannot have one correct default.

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

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

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

Compared the checked-out tree with cc4e47638 (merge-base with origin/main): 4 file(s) changed outside .changeset/, read against 1693 pending declaration(s) that publish a body (2293 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3103.5 KB 3104.5 KB
Main entry chunk (gzip) 149.8 KB 350 KB
Entry file index-WedL3Gze.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.58KB 6.17KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
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.52KB 3.45KB
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) 558.56KB 133.87KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.61KB 63.16KB
fields (index.js) 261.01KB 66.28KB
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.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
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.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
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) 51.96KB 14.83KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.83KB 36.71KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.51KB 61.80KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.15KB 9.14KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.15KB 22.35KB
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.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
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) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 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: 7089f691c36e8160c79347588c489313b89cd78c
Local-runs: none

PR #10980 for card objectui#10959. Read-only inputs: the card body and its three comments (triage 5869393211, claim 5869714607, os-dev-report 5870138391), the PR body and file list, the check-runs on the head, and the net diff origin/main...refs/review/pr-10980 (the head fetched into a ref of this review's own; merge-base 1345e182d). origin/main had moved to a2de9e943 by reading time; the three edited files are byte-identical between the merge-base and that main, so the base the dev measured is the base this review judged. Parser-tier and registration claims were checked by reading the head's tree (git show / git grep on the fetched ref), nothing built or run.

Check-runs on 7089f691c, re-read at 2026-09-28T13:01Z after polling until none was in progress (43 of 43 listed; total_count equals the array length): 40 success, 3 skipped (the coverage matrix placeholder, Test (coverage), dependabot), 0 in_progress, 0 failure. Their conclusions are the gate verdicts: Lint, Type Check, all eight Test shards and the Test aggregate, Test (dist pins), Build & E2E, Spec Main Shape Gate, Line Citation Gate, Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Claim Re-read, Doc Component Type Check, Governed Surface Queue Guard and the rest all success. An earlier reading in this round saw 42 listed with 7 in_progress; the Test aggregate registered after it.

① Derived judgments

Five files: packages/types/src/zod/navigation.zod.ts (+9/−9), packages/types/src/layout.ts (+21/−14), packages/types/src/form.ts (+6/−4), one new pin packages/types/src/__tests__/unknown-prop-parser-tier-clause-10959.test.ts, one changeset — exactly the claimed file surface, no breach.

  1. Accept-set of HeaderBarSchema: unchanged — right. All nine members (title, logo, nav, left, center, right, sticky, height, variant) stay retirementTombstone(guidance), which is z.never({ error: guidance }).optional().describe(guidance); only the guidance literal moved. git diff -U0 over navigation.zod.ts changes string-continuation lines only, and substituting the old clause back into the head file reproduces the base file byte-for-byte (9 substitutions). The same substitution reproduces base layout.ts (7) and form.ts (2). So every accept or refuse verdict, issue code and path is byte-identical, and Clause-②: no is the right declaration.
  2. Message and .describe() move together — right. retirementTombstone feeds one string to both, so the zod error message and the description the pin's population walk reads are the same text; the pin reads .description for the population and issue.message for the arms, and both carry the clause.
  3. TS face — right. Nine body?: never docblocks (BoxSchema, TextSpanSchema, ContainerSchema, FlexSchema, StackSchema, GridSchema, ScrollAreaSchema; ToggleSchema, FormSchema); only JSDoc continuation lines changed, the never type is untouched. The emitted .d.ts carries the text: the dev's dist reading (old 0, new 9) is a record of one run and is not re-derived here; Build & E2E and Test (dist pins) are green on the head.
  4. The new clause is true at head for the header-bar keys — right. In validateTree (packages/sdui-parser/src/validate.ts) a key with no declared input enters the !input branch; checkRetiredBodyDialect returns null for any key other than body, and the generic unknown-prop warning (has no prop) is pushed. The header-bar registration (packages/components/src/renderers/navigation/header-bar.tsx, namespace: 'ui') declares exactly crumbs, search, actions, rightContent, so all nine retired keys draw unknown-prop on header-bar and ui:header-bar.
  5. The new clause is true at head for body on the nine nodes, and unknown-prop rather than objectui#10928's not-a-container is the right code — right. checkRetiredBodyDialect (packages/sdui-parser/src/body-dialect.ts) answers not-a-container only when the value is a child list AND the registration declares no children input (acceptsChildren is "some input named children"); every other body draws unknown-prop. Each of the nine registrations (box, span, container, flex, stack, grid, scroll-area, form, toggle, all namespace: 'ui') declares { name: 'children', type: 'slot' }, so a list, a single node and a scalar all draw unknown-prop. Sharpening dispatch assumption 2 this way is correct.
  6. The past tense holds — right. At 236a71fe3 (the parent of the objectui#8284 tombstone commit b1777aef3, confirmed with rev-parse) validate.ts already had BASE_PROPS without body and the generic unknown-prop branch, and body-dialect.ts's own header records that the tier never knew body; so before each retirement the parser tier drew unknown-prop too. Read, not executed.
  7. Census, over string-joined and comment-joined text — right. Base → head: old string clause 9 → 0, new 0 → 9 (navigation.zod.ts); old docblock clause 7 → 0 and 2 → 0, new 0 → 7 and 0 → 2 (layout.ts, form.ts). Controls stable: the REFUSED (objectui#10387 or #10286, ADR-0049) — header-bar reads no head 9/9; the objectui#8284 mark 7/7 and 2/2; not-a-container 10/10, 12/12, 28/28. Over all 67 non-test .ts files under packages/types/src at head (enumerated from the ref): zero residue of either old clause. Outside packages/types/src, the retired clause appears only in .changeset/10928-content-channel-parser-tier-clause.md, describing the text it retired — correct to leave. No README, doc or skill quotes it, so no docs surface was missed.
  8. Sibling pins untouched and unbroken — right. header-bar-unread-keys-10387 and mirror-groups-cd-10286 assert the REFUSED (objectui#NNNNN, ADR-0049) head and the codes, both preserved. content-channel-parser-tier-clause-10928's population is filtered on reads NEITHER content channel:; the nine header-bar retirements do not carry that sentence (only HeaderBarSchema.body and .children, objectui#9256, do), so the dev's finding that dispatch assumption 3 does not hold — and the choice to leave that pin unedited — is right.
  9. The new pin — right. Zod arms title (element form) and variant (class form) assert invalid_type at [key], the clause present, the retired clause absent and not-a-container absent; the population walk over HeaderBarSchema.shape is filtered on the REFUSED (objectui#(10387|10286), ADR-0049) head — which excludes the objectui#9256 body/children members — with by-name non-vacuity (title, variant). The TS face reads layout.ts and form.ts from a root derived from bare import.meta.url (five segments up, the objectui#7791 spelling), filters docblocks on the objectui#8284 mark, by-file non-vacuity. The dev's five ablation legs each redden 1 or 2 of 7 tests — a record of one run, consistent with the pin's shape.
  10. Governance, size and PR hygiene — right. No governed path (.changeset/ and packages/types/src/ only; Governed Surface Queue Guard green); 263 changed lines; draft with no auto-merge, as a dispatched dev's PR must be. PR body: one close keyword (the Fixes line naming the card, intended), zero angle brackets, no model identifier, citations by content. Dispatch assumption 4 is verified here rather than taken as given: PR fix(types,plugin-dashboard): retire drillDown on the bare pivot node; object-pivot is where a pivot drills (objectui#10932) #10972 (10 files, merged) and PR fix(types): the spec-shape rule condition and BulkActionDef.visible read the named view expression slots by reference (objectui#10946) #10977 (6 files) share no file with this PR.

Nothing judged wrong.

② Semver level

.changeset/10959-unknown-prop-parser-tier-clause.md: '@object-ui/types': patch — right. @object-ui/types is a released package in the fixed group (17.6.0 at head), and the diff changes text that ships in it: zod messages and .describe() strings in dist/zod/*.js, and docblocks in dist/*.d.ts. A bug fix in a released package takes patch, never skip-changeset; no major (Changeset Bump Policy, Changeset Fixed Group Check, Changeset Declaration green). The body names the FROM → TO wording for both site kinds and states in words that every document is accepted or refused exactly as before.

Clause-②: line — no, on the PR body's second line, with its reason (message, .describe() and docblock text only). It matches the diff: judgment 1 shows the accept-set is byte-identical, so neither (widening) nor (narrowing) applies and no ADR-0087 disposition marker is owed.

③ Boundary flags

  • Deviation (report summary): one sub-second single-file vitest run of the new pin ran outside os-verify-lock. Answered — accepted, not a breach. The lock serialises heavy verification (a full vitest run, a timed measurement, a whole-repo build or suite); a single-file, sub-second run of one new pin is none of those, it was declared, and its result is not load-bearing here — the head's Test shards re-derive it.
  • open_questions[0]: should a live-registry behavioural pin in packages/components hold the header-bar half (undeclared key → unknown-prop on header-bar / ui:header-bar)? Answered — routed, not owed by this PR. The seat folded it into objectui#10981 as an optional item, and that is the right weight: the clause rests on mechanisms already pinned in packages/sdui-parser (body-dialect-6771, containment-declared-slot-9910, and validateTree's generic unknown-prop branch, named in seven of that package's test files) plus the registration's four-input list read in judgment 4; a header-bar-named row would add early warning should a title-like input ever be declared. It is outside the claimed file surface, exactly as the dev said; option A, folded, is correct.
  • out_of_scope_findings: four measured sites and one boundary note. Answered — routed correctly. objectui#10981 exists (open, finding, filed by the seat from this report): it names the four sites by content — FormSchema.mode (zod/form.zod.ts), TimelineSchema.events (data-display.ts), BaseSchema.bind (base.ts), exportOptions (zod/objectql.zod.ts) — with the parser code each draws (unknown-prop three times; type-mismatch for exportOptions, correctly distinguished), adds an enumeration nail (a census of every "no error … no warning" phrasing under packages/types/src, each hit corrected or recorded as measured-silent) as the closure criterion, sets the serial after this PR, and records that data-display.ts's prior PR fix(types,plugin-dashboard): retire drillDown on the bare pivot node; object-pivot is where a pivot drills (objectui#10932) #10972 has merged (verified: merged). One family-closure card in place of four single-point cards is the right routing for a third wave of one class-(b) family, and each site carries its live-registry measurement as Prime Directive [WIP] Enhance every detail of the designer #10 asks. The fifth entry — view:grid, view:form, field:grid are different registrations the GridSchema / FormSchema docblocks do not describe — is a scoping note with no false published text behind it; "noted, not filed" is right.
  • Dispatch assumptions 1–4 — 1 holds (judgment 7), 2 holds sharpened (judgment 5), 3 does not hold and was correctly acted on (judgment 8), 4 verified here (judgment 10). premise_still_valid: true is consistent with origin/main today, which still carries the 9/7/2 old clauses.
  • Landing note for the seat (no flag against the dev): mergeable_state was behind at reading time; the queue rebuilds on current main. The PR is a dispatched dev's draft — ready, arm and any post-landing re-arm are the seat's acts, once every check on the head is green.

Implemented-by: claude/issue-10959-unknown-prop-clause
Reviewed-by: session_012UwY3ahMixEFkfTUxMVkYm

VERDICT: PASS

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(types): header-bar refusals and nine body?: never docblocks say a value drew "no error, no warning", but the parser tier answers unknown-prop

2 participants