Skip to content

fix(types): the strict authoring face admits the widget-slot metric-card's registered inputs (objectui#11022) - #11023

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-11022-strict-slot-registry-inputs
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-11022-strict-slot-registry-inputs

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11022
Clause-②: yes — the strict face's accept set widens: a correctly authored metric-card (and any other card whose registered inputs the closed arm refused) now parses; an undeclared key is still refused.

The widening, named. StrictAnyComponentSchema now accepts a metric-card in a dashboard's widget slot carrying the inputs its registration declares (title, value, icon, trend, trendValue, description). Before this change it refused value, icon, trend and trendValue as unrecognized_keys. Nothing that parsed before is refused now. The tolerant face (AnyComponentSchema and every named mirror) does not move. The changeset is @object-ui/types minor, with no BREAKING note, because nothing narrows.

Implemented by a dispatched os-dev run under the domain:spec seat's claim 5878477986 (session https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm). Grade: triage 5878357772.

What changed

  • zod/node-derivation.ts: a side table, declareRegisteredInputs(schema, names) / registeredInputsOf(schema), keyed by node identity (a plain WeakMap, not .meta(): a registry entry would reach z.toJSONSchema output and be inherited down zod's clone chain). It only accepts a passthrough object, one with a catchall, and throws otherwise. A stripping object has no tolerant judgment for the strict face to copy.
  • strict-authoring-face.ts, the walker's object arm: for a node that carries a record, each recorded name the shape does not already declare is added as z.optional(catchall), judged by the node's own catchall exactly as the tolerant face judges it. The object is then closed with catchall: z.never() like every other object. A key no registration declares is still refused by name, and a name the shape already declares (such as description, a BaseSchema member) keeps its declared member. The module docblock gains a "Registered inputs" section, and the StrictAnyComponentSchema / deriveStrictAuthoringSchema docblocks say what counts as declared.
  • zod/complex.zod.ts: a private DASHBOARD_WIDGET_SLOT_REGISTERED_INPUTS table, one row per member of the closed DASHBOARD_COMPONENT_WIDGET_TYPES. Its satisfies clause types it as a Record from DashboardComponentWidgetType to readonly string[], so a member added without a row does not compile (ablation A5). The slot arm is wrapped in declareRegisteredInputs(...). The arm's shape, catchall, describe text and accept set are unchanged. The GlobalFilterSchema docblock that objectui#10930 edits is not touched.
  • complex.ts: docblock only. The DASHBOARD_COMPONENT_WIDGET_TYPES note now says a new member needs its row.
  • packages/types/README.md: one paragraph and a snippet in "The strict authoring face".
  • Pins: packages/types/src/__tests__/strict-widget-slot-registered-inputs-11022.test.ts (15 tests) and packages/plugin-dashboard/src/__tests__/metricCardRegisteredInputsStrictFace-11022.test.ts (9 tests). The second is the registry parity: it runs against the live ComponentRegistry and checks both directions, and it holds no copy of either list.
  • Changeset: .changeset/11022-strict-widget-slot-registered-inputs.md.

Where the grade's pin sits: metric-card is deliberately not a root arm of AnyComponentSchema (objectstack#8593). So a root { type: 'metric-card', value: 42 } is refused at type on both faces, before and after this change, and the pin puts the grade's widget in a dashboard's widgets slot. A control test records the root behaviour.

The mechanism, measured on origin/main 797a30f48 before any edit

  • Where the arm is built. const DashboardWidgetSlotComponentSchema = BaseSchema.extend({ type: z.enum(DASHBOARD_COMPONENT_WIDGET_TYPES), body: retirementTombstone(...), children: retirementTombstone(...) }) lives in packages/types/src/zod/complex.zod.ts. It is the first option of DashboardComponentSchema.widgets, which is z.array(z.union([DashboardWidgetSlotComponentSchema, DashboardWidgetSchema])). StrictAnyComponentSchema is a z.lazy over faceWalker(AnyComponentSchema) in packages/types/src/strict-authoring-face.ts.
  • How it closes. Nothing in the arm is strict-specific. Its "passthrough" is BaseSchema's loose catchall, inherited through .extend (measured: def.catchall is unknown). The walker's generic case 'object' arm (out = cloneWithDef(schema, { shape, catchall: z.never() })) replaces that catchall on every object it reaches. The derived slot arm therefore admits exactly BaseSchema's members, with type / body / children overridden.
  • Partly falsified PM assumption. The passthrough has no members, and @object-ui/types holds no registry-input source for the passthrough to "use". The package has no dependency on any registry. The inputs exist only in the registration: ComponentRegistry.register('metric-card', MetricCard, { inputs: [...] }) in @object-ui/plugin-dashboard. So the repair records the names on the arm and holds them to the live registration in a test inside that package.

Reproduction (strict face, BASE 797a30f48)

document (in a dashboard's widgets) strict face tolerant face
{ type: 'metric-card', value: 42 } one invalid_union at widgets.0; arm 1: unrecognized_keys at path [], keys ["value"]; arm 2: unrecognized_keys at [], keys ["value"] parses
{ type: 'metric-card' } parses parses
{ type: 'metric-card', bogus: 1 } one invalid_union at widgets.0; both arms unrecognized_keys at [], keys ["bogus"] parses
every registered input arm 1 refuses title, value, icon, trend, trendValue; arm 2 refuses value, icon, trend, trendValue parses

After the change: the first and fourth rows parse on the strict face. The bogus row is refused exactly as before, with arm 1 and arm 2 both unrecognized_keys ["bogus"]. children / body still get the objectui#9256 by-name refusal inside the union's errors.

Census: who else sits in the same position

Query. This was a throwaway vitest file in apps/console, deleted after the run and not committed. It eagerly imported every registering package (the console's register-plugins, components, fields, layout and 19 plugins) and read ComponentRegistry.getAllConfigs(): 508 configs, 286 unique registrations, 0 lazy stubs left. For each registration × authored type (namespaced key and, unless skipFallback, the bare fallback) × position, it parsed { type, INPUT: probe } under StrictAnyComponentSchema and recorded every input named by an unrecognized_keys issue anywhere in the error tree, union errors walked. The positions were: root, when an arm claims the type; and widget slot, when the type is in DASHBOARD_COMPONENT_WIDGET_TYPES. That came to 911 probes over 179 rows that have an arm. An arm census over AnyComponentSchema (139 object arms) found the arms built the same way, with no member beyond BaseSchema's: div, box, the seven semantic tags (aside, main, header, nav, footer, section, article) and the retired kanban. The widget-slot arm is the fifth such shape.

Controls. Positive: the widget-slot metric-card value was reported refused (["value"] from both arms) at BASE. Negative: button label was admitted and button bogus was refused, in the same run.

Population in the same position: exactly one card, metric-card. Four of its six registered inputs were refused (value, icon, trend, trendValue). title was admitted through the strict widget arm, which declares the spec's title, and description is a BaseSchema member. The bare-shaped root arms div, box and the seven semantic tags each register two inputs, and none of them is refused. No registration claims kanban. So there is no second member to pin. The registry pin walks the closed set, not a list, so a later member is covered, and tsc refuses a member without a row.

A different position, not this card, is listed under Acceptance notes: declared root arms that do not declare some registered input.

Consumers

The strict face has no non-test consumer, re-checked (matches triage). A search of every ts / tsx / mjs / js file outside node_modules and dist for StrictAnyComponentSchema, StrictSchemaNodeSchema and deriveStrictAuthoringSchema finds:

  • the defining module;
  • the barrel re-export in zod/index.zod.ts;
  • 15 test files under packages/types/src/__tests__;
  • one comment in scripts/measure-strict-authoring-face.mjs, which keeps its own walker and imports none of them.

Red on base, then green: ablation both ways

Predictions were written to a file before any mutation. Every leg went through ablation-replace.mjs in wrap mode against committed HEAD 9fe80dbbc. The anchor count and a blob change were proven on disk, and the restore was proven by blob equal to HEAD and an empty git diff HEAD. The tree was clean after each leg. The subjects are read from src: the types pin imports relatively, and vitest aliases @object-ui/types and @object-ui/core to src for the dashboard pin, so no leg needed a rebuild.

leg mutation types pin (15) dashboard pin (9)
A1 the arm's record emptied (Object.values(...).flat() → []) RED 4: value-42, every-input, loose-values, side-table non-vacuity RED 5: under-admission value / icon / trend / trendValue, over-admission non-vacuity
A2 walker ignores the record RED 4: value-42, every-input, loose-values, hand-built "WITH a record" (side-table row stays green: the record exists) RED 5: same five
A3 walker leaves a recorded object OPEN (over-admission direction) RED 2: bogus refused by name, hand-built "any other key refused" RED 1: key-no-registration-declares control
A4 record gains an unregistered name (loading) green 15/15 (holds no copy of the list) RED 1: OVER-admission, expected [ 'loading' ] to deeply equal []
A5 closed set gains a member with no row tsc --noEmit on packages/types: exactly 1 × TS1360 at the satisfies —

All five legs matched the predictions exactly.

Gates (final HEAD 9fe80dbbc; heavy runs through the shared verify lock; exit codes captured by redirecting first)

gate result
vitest run packages/types/ + the dashboard pin + examples/schema-catalog/test/plugin-dashboard-component-schema.test.ts + scripts/__tests__/check-readme-exports.test.ts exit 0, 280 files / 6519 tests (at 9fe80dbbc)
vitest run packages/plugin-dashboard/ + the files outside packages/types that read a touched file (kanban-column-cards-6939, cli check-validity-recogniser, scripts check-handler-key-read-sites, check-readme-exports, component-node-vocabulary-7434) exit 0, 157 files / 1568 passed + 6 skipped (at 067461304; the later commit touches only packages/types/README.md, and the README readers were re-run at HEAD in the row above)
@object-ui/types build (tsc + vite + dist completeness) and type-check (three programs) exit 0 (src unchanged since)
@object-ui/plugin-dashboard type-check, after building its dependency closure exit 0; --listFilesOnly shows both new test files are in their packages' test programs
check:control-bytes · check:new-line-citations (0 new) · check:test-path-roots · changeset:check · check-changeset-presence · check:changeset-claims · check:pending-changeset-literals · check:doc-types · check:doc-fences exit 0 each
governed guard --test over the 8 paths NOT GOVERNED, exit 0 (lit control AGENTS.md: exit 3)
eslint over the 6 touched TS files (eslint's own JSON: 6 entries) exit 0; 0 errors; 11 warnings, all no-explicit-any on untouched lines. The single root eslint.config.js sets no parserOptions.project / projectService, so no type-aware rule runs and no untouched file's verdict can move
README snippet, compiled --strict against the built packages/types/dist exit 0 (negative control, an unexported name: exit 2, 1 error)

NOT MEASURED locally, left to CI:

  • The pnpm test shards beyond the suites above.
  • Downstream type-check. The public type surface is unchanged: declareRegisteredInputs returns its argument's type, so DashboardComponentSchema's inferred type does not move, and the two new node-derivation exports are not reachable through the package's exports map.
  • check:doc-snippets, check:doc-examples and check:readme-exports exited with PREREQUISITE NOT MET: their 34-package build closure was not built locally. That is not a verdict about the documents.
  • pnpm check, which needs the CLI build.
  • Bundle Analysis / check:eager-closure. The main @object-ui/types entry imports these modules type-only, so the new runtime code is reachable only through @object-ui/types/zod.
  • Repo-wide pnpm lint.

Serial constraints

  • origin/main has not moved since 797a30f48. It was fetched into a private ref and read by commit.
  • git merge-tree --write-tree of this head against objectui#10930's head exits 0 (clean).
  • That PR edits the GlobalFilterSchema docblock in zod/complex.zod.ts. This PR's edits there sit above it, in the new table const and the slot arm's wrapper and docblock.
  • No rebase, no force-push.

Acceptance notes: out of scope, not changed here

  • The legacy widget envelope. At BASE and HEAD, { id, component: { type: 'metric-card', value: '1' } } in a widget slot parses on the tolerant face and is refused on the strict face: unrecognized_keys ["value"] at component. That member is plain BaseSchema by design (the objectui#8344 note), and the walker closes it the same way. The grade ruled the related observation on that member out of this card, so it is untouched. The strict face is dormant (no consumer). Noted, not filed.
  • Declared root arms that do not declare a registered input. This is a different position from the one this card repairs. The census found 45 root rows where a registered input is refused on the strict face and passes the tolerant face unjudged, 204 inputs in all. Seventeen are the ADR-0080 public blocks, whose inputs are registered flat while their arm is the spec row's properties bag (objectui#10872's flat-props batch). Fourteen name dataSource, which the registry emits through withElementDataSourceInput. The rest are hand-declared arms missing a key: for example, object-form 19, object-grid 11, calendar 9. In each case the arm declares its keys by hand or by spec row, and the tolerant face leaves those inputs undeclared too. Which side is right is objectui#4631's authority order, instrumented by check:component-surface-parity (report-only). A registry record would be the wrong repair there. Noted, not filed.
  • MetricCard also reads loading and error (its props interface), and no registration declares them. So the strict face refuses them by the grade ("It still refuses a key that no registration declares"), and the tolerant face admits them. Noted.
  • scripts/measure-strict-authoring-face.mjs keeps its own walker by design, and that walker does not read the registered-inputs record. On a corpus holding widget-slot metric-card nodes it will now name value and the other inputs as undeclared where the shipped face admits them. That is the redder direction, the opposite of objectui#10076's. It is a throwaway measurement script. Noted, not filed.
  • No content/docs page describes the strict face's per-slot behaviour. The schema-reference widgets row says a component node's other keys are that component's own props, and that is now true on both faces.

Generated by Claude Code

…t's registered inputs (objectui#11022)

`StrictAnyComponentSchema` refused every `metric-card` widget carrying
`value`, its required registration input: the slot arm admits the card's
registry `inputs` through `BaseSchema`'s passthrough by ruling
(objectstack#8593), and the strict walker closes that catchall.

The slot arm now records its registration's input names in a side table
(`declareRegisteredInputs`, `zod/node-derivation.ts`), and the walker's
`object` arm admits exactly those, each judged by the arm's own catchall,
before closing the object. A key no registration declares is still refused
by name; the content channels objectui#9256 refused stay refused; the
tolerant arm's shape, catchall and accept set do not move.

Pins: `strict-widget-slot-registered-inputs-11022.test.ts` (types) and
`metricCardRegisteredInputsStrictFace-11022.test.ts` (plugin-dashboard,
against the live ComponentRegistry, both directions).

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

Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm
Co-authored-by: Claude <noreply@anthropic.com>
…t it uses (objectui#11022)

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

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 15 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/6687-chatbot-surface-authorable.md

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

    Measured on both declaration faces before the fix, each with a control that had to hit: schema.surface appeared 0 times in renderer.tsx against schema.placeholder at 3 (one per registration) and schema.processVisibility at 1; and ChatbotSchema (packages/types/src/complex.ts) declared 34 keys, not this one. Two faces agreeing is what made the zero a reading rather than a bad query.

.changeset/6939-kanban-column-cards.md

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

  • names complex.zod.ts → packages/types/src/zod/complex.zod.ts — 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/7113-chart-data-model.md

  • names complex.zod.ts → packages/types/src/zod/complex.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/7295-chat-message-avatar-keys.md

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

    packages/plugin-chatbot/src/index.tsx:173–178 reads message.avatar || userAvatarUrl and message.avatarFallback || userAvatarFallback (and the assistant twins), the authoring-to-runtime seam spreads every unlisted key through (chatMessageAdapter.ts, ...passthrough), and the SDUI renderer feeds the authored messages[] straight in — a per-message avatar override renders, is documented, and no authoring-facing type declared it. ChatMessage in packages/types/src/complex.ts has no index signature (objectui#5155, deliberately — none is added here), so an author annotating ChatbotSchema.messages was told a value that renders is an error (TS2353); the zod mirror ChatMessageSchema is a plain strip-mode z.object, so the value parsed green and was silently DROPPED from the parsed output.

.changeset/7655-chatbot-registration-authoring-faces.md

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

    New published symbol: ChatbotSharedKey, the string-literal union of the twenty keys all three registrations read. It is exported from complex.ts because an exported interface may not extend a Pick over a private name (TS4022), so it is emitted into dist/complex.d.ts and is reachable through the published @object-ui/types/complex subpath (it is not re-exported from the package entry). It is a census, not an authoring face.

.changeset/7703-chatbot-dark-keys-retired.md

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

    Each member goes to ?: never on packages/types/src/complex.ts and to retirementTombstone(...) on packages/types/src/zod/complex.zod.ts — both halves, in lockstep, the convention MarkdownSchema.sanitize (objectui#6972), TimelineSchema.timeScale (objectui#6355) and ObjectViewSchema.viewTabBar (objectui#7779) already carry. Each refusal names the key, says why it is retired, and points at what to write instead; one string feeds both the parse-time message and the .describe() metadata, so the two cannot drift.

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

    Each member goes to ?: never on packages/types/src/complex.ts and to retirementTombstone(...) on packages/types/src/zod/complex.zod.ts — both halves, in lockstep, the convention MarkdownSchema.sanitize (objectui#6972), TimelineSchema.timeScale (objectui#6355) and ObjectViewSchema.viewTabBar (objectui#7779) already carry. Each refusal names the key, says why it is retired, and points at what to write instead; one string feeds both the parse-time message and the .describe() metadata, so the two cannot drift.

.changeset/8415-filter-builder-condition-id.md

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

    Breaking for authored metadata: a filter-builder CONDITION must now declare id (objectui#8415). It is declared on both published faces — the TypeScript interface FilterBuilderCondition in complex.ts and the Zod mirror FilterBuilderConditionSchema in zod/complex.zod.ts — so a key the renderer has always required is finally validated instead of silently discarded.

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

    Breaking for authored metadata: a filter-builder CONDITION must now declare id (objectui#8415). It is declared on both published faces — the TypeScript interface FilterBuilderCondition in complex.ts and the Zod mirror FilterBuilderConditionSchema in zod/complex.zod.ts — so a key the renderer has always required is finally validated instead of silently discarded.

.changeset/8478-describe-line-addresses.md

.changeset/8478-zod-pins-complex.md

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

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

    The remaining 6 addresses (zod/complex.zod.ts) stayed out of scope for this PR and returned to the queue rather than riding this PR's scope — the card did not close here.

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

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

.changeset/8802-8257-8008-kanban-gantt-family-retirement.md

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

    What each retirement was, measured. Three of the four were registration-only: no schema face in @object-ui/types ever declared kanban-ui, kanban-enhanced or gantt as a component node type, so unregistering is the whole retirement. The bare kanban key was the exception — it had a declared arm on both faces (KanbanSchema in complex.ts and its Zod mirror), and a plain deletion there would have been the objectui#7664 failure: BaseSchema is .passthrough(), so a document naming a dropped key validates green and renders nothing. It therefore retires as a named refusal: the Zod union keeps an arm claiming the literal and answers a { "type": "kanban" } document with a message naming object-kanban as the remedy, while the TypeScript half is the absence of the arm from ComplexSchema and of the key from SchemaRegistry, so tsc refuses it at the authoring site.

.changeset/9491-walkabledef-rest-null.md

  • names packages/types/src/zod/node-derivation.ts → packages/types/src/zod/node-derivation.ts — edited by this change

    packages/types/src/zod/node-derivation.ts declares the def member set both zod walkers in this package read. It declared rest?: z.ZodType — i.e. z.ZodType | undefined — while zod 4 spells "this tuple has no rest element" as an OWN rest key holding null, minted by const rest = hasRest ? _paramsOrRest : null in its tuple factory.

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

  • names complex.ts → packages/types/src/complex.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/calendar-readme-schema-keys-5045.md

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

    README.md's "Schema API / CalendarView" block described a CalendarViewSchema that does not exist. Measured against the interface itself (packages/types/src/complex.ts) and its zod mirror: events — the schema's only required key besides type — was published as events?, so a reader following the README omits it and TypeScript rejects the node; defaultDate was string where the schema says string | Date; and onDateClick was listed as a schema key when it is a CalendarViewProps component prop, sending readers to a different package's surface for a key calendar-view does not have (the schema's key is onDateChange). The block also listed 6 of the schema's 13 keys with nothing saying it was a summary (objectui#5045).

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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3105.0 KB 3149.4 KB
Main entry chunk (gzip) 149.3 KB 350 KB
Entry file index-C1i_xxR4.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.72KB 6.21KB
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.92KB 133.97KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.62KB 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.35KB 9.18KB
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.53KB 4.89KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
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.39KB 14.98KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.56KB 36.67KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.49KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 162.67KB 41.95KB
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.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.54KB 22.44KB
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) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.30KB 6.99KB
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

Spec Main Shape Gate is red on this head, and it is not this PR's. From the domain:spec @ objectui seat, session session_012UwY3ahMixEFkfTUxMVkYm.

  • The failure. Run 36488067207 / job 109149732486 failed in Build @objectstack/spec from objectstack main, before any objectui file was compiled: pnpm: ENOENT … open '…/objectstack/patches/tsup@8.5.1.patch', exit 254. The report step then exits 2 with no typecheck log.
  • The cause. objectstack c5ad1de0 (build: one ts.Program per DTS pass — patch tsup's bundled rollup-plugin-dts grouping (spec 4997 → 882 MB live heap) objectstack#20499, 20:19Z) added a root patchedDependencies entry. The gate's sparse objectstack checkout (packages/spec scripts) does not include patches/. Every run against objectstack main after 20:19Z fails the same way, whatever the objectui diff. The last green run compiled against 4a1df19656bf, before that commit.
  • The fix. None exists yet. objectui#11024 records the cause and the smallest fix (add patches to the sparse set). Under the maintainer's standing rule on objectui#10916, a domain:ui seat claims it as a stop-the-bleed. This PR is not widened with it. Once the fix lands, this branch takes a main merge so the gate re-runs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 9fe80dbbcf30528d19fd11f21c68c0cd982571bf
Local-runs: none

Inputs, and nothing else: card objectui#11022 (body; triage 5878357772, claim 5878477986, os-dev-report 5879350642); PR objectui#11023 (body, file list, comments 5879303736 / 5879376393 / 5879410182); the net diff origin/main 797a30f48…9fe80dbbc fetched into a private ref (8 files, +532/−6); the head's check-runs; PR objectui#11020's file list and its complex.zod.ts hunk; read-only git show of the metric-card registration, createStrictWalker, DashboardComponentSchema, the @object-ui/types exports map and the Spec Main Shape Gate workflow; objectstack c5ad1de0. No dispatch order and no dispatching-seat conclusion was an input. Nothing was built, run or re-run.

① Derived judgments

  1. The widening Clause-② declares — RIGHT, and exactly as declared. StrictAnyComponentSchema (and any deriveStrictAuthoringSchema twin of DashboardComponentSchema) now accepts, on the widget-slot component-node arm only, a metric-card carrying title, value, icon, trend, trendValue with ANY value; description was already a BaseSchema member. Verified in the diff: the walker's object arm adds z.optional(walk(def.catchall)) for each recorded name the shape does not already declare, then closes the object with catchall: z.never() exactly as before; the record on the arm is the six names of DASHBOARD_WIDGET_SLOT_REGISTERED_INPUTS; the live registration in plugin-dashboard's barrel (ComponentRegistry.register('metric-card', MetricCard, …)) declares exactly title, value (required: true), icon, trend (enum up/down/neutral), trendValue, description. The judgment copied is the arm's own unknown catchall, so the strict face judges no value the tolerant face does not (pinned: trend: 'sideways', title: 7 parse on both faces).
  2. Still refused — RIGHT, with code and path.
    • An undeclared key in the slot, { type: 'metric-card', bogus: 1 } inside widgets: one invalid_union at widgets.0; arm 1 (the slot arm) unrecognized_keys at path [] keys ['bogus']; arm 2 (the .strict() DashboardWidgetSchema) unrecognized_keys at path [] keys ['bogus']. The types pin asserts each arm's issue list with an exact-array toEqual, so the slot arm now names nothing but bogus.
    • The objectui#9256 family-D channels on metric-card, children and body: invalid_type at path children / body inside the union's errors, message carrying METRIC_CARD_NEITHER_CHANNEL (the text names metric-card reads NEITHER content channel and objectui#9256). Mechanism checked, not assumed: retirementTombstone is z.never({ error }).optional() kept as a shape MEMBER; the walker's hasOwnProperty guard keeps shape members, and the recorded names include neither channel. PR objectui#11020's hunk is where those two members entered; this diff does not touch them.
    • Root { type: 'metric-card', value: 42 }: invalid_union at path ['type'] on both faces, before and after — metric-card is not an AnyComponentSchema arm (objectstack#8593). Pinned as a control.
    • loading and error, props MetricCard reads that no registration declares: refused on the strict face, which is the grade's own rule ("still refuses a key that no registration declares"). Right, and correctly left as a note.
  3. Nothing else widened — RIGHT, with one bounded limit recorded.
    • Tolerant face: declareRegisteredInputs returns the very same node; the record is a WeakMap side table keyed by node identity, not .meta() and not a clone. Pin 4 asserts the tolerant arm's key set equals BaseSchema's and its catchall type is unknown; AnyComponentSchema and DashboardComponentSchema output and inferred types do not move.
    • The arm's own shape: unchanged (type enum over the closed set, body and children tombstones).
    • TS face: complex.ts docblock only; DashboardComponentWidgetType and the slot interface are not in the diff.
    • Legacy envelope: DashboardWidgetSchema (specFieldsExcept(stripImportedDefaults(SpecDashboardWidgetSchema).shape, …)) is not in the diff; its component stays plain BaseSchema and is refused on the strict face at component as before — the grade ruled that observation out of this card.
    • Reach of the walker change: gated on registeredInputsOf(schema); exactly one node in the tree carries a record, so no other strict twin moves.
    • The limit: the record is flattened over the whole closed set onto the ONE slot arm (Object.values(…).flat()), so were DASHBOARD_COMPONENT_WIDGET_TYPES to gain a second member, the strict face would admit member A's inputs on member B. Exact at this head because the population is one; the satisfies forces a row for a new member but not per-type judgment, and the over-admission pin compares against the union too. Not a defect on this head; written here so the next member-adder reads it.
  4. Public surface — RIGHT, nothing new published. declareRegisteredInputs and registeredInputsOf are exported from zod/node-derivation.ts, which the zod/index.zod.ts barrel does not re-export and the package exports map (., ./base, ./complex, ./data, ./data-display, ./feedback, ./form, ./internal/retired-field-keys, ./layout, ./navigation, ./overlay, ./zod) does not address — shipped bytes, not a published accept set. DASHBOARD_WIDGET_SLOT_REGISTERED_INPUTS is module-private. README.md ships (files), judged in ③(a).
  5. Consumers. The strict face's derived shape gains six optional unknown members on that arm; no non-test consumer reads it (dev's re-check: defining module, barrel, 15 test files, one comment in scripts/measure-strict-authoring-face.mjs), and a grep of the objectstack checkout finds zero references to StrictAnyComponentSchema, deriveStrictAuthoringSchema or StrictSchemaNodeSchema.

② Semver level

  • .changeset/11022-strict-widget-slot-registered-inputs.md: @object-ui/types: minor; body names the widening, what still refuses, what does not move, and "No migration". Matches the diff: a published accept set widens and nothing narrows, so no BREAKING note and no migration are owed; under the fleet rule Clause-②: yes takes at least minor; under objectui's rule no major (Changeset Bump Policy green). patch would have under-declared a widening. RIGHT.
  • Presence: guarded src/ of two fixed-group packages changed (types source and tests, plugin-dashboard a test file); one changeset is declared and the 40-package fixed group bumps as one family, so the single @object-ui/types entry covers the test-only plugin-dashboard touch. Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report green.
  • The Clause-②: line: at line start of the PR body, Clause-②: yes — reasoning, byte-copied from claim 5878477986; the fleet reader's accepted shape; no direction arm, and a bare yes reads as a widening, which is the true direction. RIGHT.
  • ADR-0087 disposition marker: owed by a BREAKING (narrowing) changeset under the objectstack rule; this is yes with no narrowing, so none is owed. ADR-0087 governs the @objectstack/spec metadata protocol; this diff touches no spec schema, and objectui carries no ADR-0087 registry or gate (its AGENTS.md, docs/adr/, scripts and workflows name none). Other-repo carrier: none owed — @objectstack/spec is untouched and objectstack has no consumer of the strict face; the widening is objectui-internal. RIGHT.
  • Pending-changeset re-read (comment 5879303736): 15 pending bodies name complex.ts, complex.zod.ts or node-derivation.ts. This diff adds a private const, a docblock, a wrapper call and a side table; none of the quoted claims (kanban cards, filter-builder condition id, chatbot keys, the tuple rest: null mint, the retired .describe() line addresses) is moved. Read; nothing went false.

③ Boundary flags

(a) File surface beyond the claim's wording (claim 5878477986: strict-authoring-face.ts, the slot arm's source under packages/types/src/zod/, pins under packages/types/src/__tests__/, one changeset; "Stop on breach and explain in the report").

  • packages/plugin-dashboard/src/__tests__/metricCardRegisteredInputsStrictFace-11022.test.ts — outside the claim's letter, inside the card's grade. Triage asked for the derivation to be fixed and censused; a transcribed name list needs its drift guard where the registry is loaded, and @object-ui/types is zero-dep by contract (AGENTS.md §3) so it cannot import ComponentRegistry. An ADDED path collides with no in-flight edit, so the serial-constraint purpose of the surface is intact. Test-only under src/, covered by the changeset. Declared as deviation (a) with the reason. Not a breach.
  • packages/types/src/complex.ts — docblock only, on the TS twin of the closed set, telling the member-adder that a row is required and that tsc refuses without it (true: the satisfies is TS1360, ablation A5). Same package the claim names, no runtime change. Not a breach.
  • packages/types/README.md — ships in the npm tarball. objectui Commandment Add automated testing infrastructure and CI/CD workflows #2 makes the README update OWED for a behaviour change on a documented face; the existing section "The strict authoring face" gains one paragraph and a snippet whose import is what it uses (README Export Check green; the dev compiled it --strict with a negative control). Prose the presence gate subtracts, and the changeset is declared anyway. Not a breach; owed.
  • Verdict on (a): three additions beyond the claim's letter, each required by the route or by a repo rule, each declared with its reason. The dev explained rather than stopped; on these three that was the right call.

(b) The route — derivation or hand list, and the drift guard.

  • Both halves, correctly divided. The DERIVATION is changed: createStrictWalker's object arm reads a generic record on any passthrough node, and block 5 of the types pin holds the rule on a hand-built z.object(…).passthrough() with and without a record, so the rule lives in the walker, not in the card. The NAMES are a transcription (DASHBOARD_WIDGET_SLOT_REGISTERED_INPUTS), unavoidably: the registration exists only in plugin-dashboard, the catchall has no members to read, and the types package has no registry. Triage's premise ("the passthrough whose members are the registry inputs") was falsified by measurement and reported under conflicts_with_brief — right.
  • The drift guard has three legs and is sound:
    1. UNDER-admission (plugin-dashboard pin): for every member of DASHBOARD_COMPONENT_WIDGET_TYPES, read from the set, every input of the LIVE ComponentRegistry.getConfig(type).inputs parses in the slot with no unrecognized_keys naming it — a registration that gains an input goes red until the row moves (ablation A1/A2 red 5/9).
    2. OVER-admission (same pin): the derived slot arm's shape keys minus the tolerant arm's must all be registered names; non-vacuity asserted (value present) — a name added to the row that no registration declares goes red (ablation A4 red 1/9), and the mirror half asserts nothing registered is left out.
    3. Compile-time: as const satisfies a Record keyed by DashboardComponentWidgetType — a closed-set member without a row is TS1360 (ablation A5).
      Controls: for every member a key no registration declares is refused by name; value is asserted required: true in the live registration.
  • Caveats checked: the record is identity-keyed, so a later .describe() or clone on the arm would drop it — leg 1 then goes red (value refused), so the drop is not silent. Leg 2 reads the arm off deriveStrictAuthoringSchema(DashboardComponentSchema) rather than StrictAnyComponentSchema; the two share the slot arm by identity (DashboardComponentSchema sits in AnyComponentSchema through the index.zod.ts barrel), so the reading is the same. The per-type precision limit in ①.3 is the one property no leg holds; it is moot at population one. The pin imports @object-ui/types/zod and @object-ui/core by specifier and runs under the root vitest config — the repo's one accepted shape — and the eight Test shards are green on this head, so it compiled and ran in CI.

(c) The census — "metric-card is the only same-position member".

  • Population: "same position" is the widget-slot component-node arm, whose members are BaseSchema's alone and whose props travel by catchall by ruling. That population is DASHBOARD_COMPONENT_WIDGET_TYPES, one member, so the claim is structurally true, and the plugin-dashboard pin re-derives it live on every run rather than quoting it (Commandment 完善设计器的每一个细节 #9).
  • The dev's wider query (a throwaway vitest file in apps/console; 286 unique registrations, each authored spelling, each position, 911 probes; an arm census of 139 object arms) has controls that hold: positive — slot metric-card value refused by both arms at BASE; negative — button label admitted and button bogus refused in the same run. It also walked the other bare-shaped arm classes (div, box, the seven semantic tags, the retired kanban) and found no refused registered input (two inputs each; kanban has no registration). The query is not committed and so not re-derivable; acceptable here because the position's population is re-derived by the pin and pinned by the satisfies.
  • The 45 root rows / 204 refused inputs are a DIFFERENT class — declared arms missing a registered key (ADR-0080 public blocks vs the spec properties bag, dataSource, hand-declared arms) — where the tolerant arm's shape IS the declaration and objectui#4631's authority order decides which side is right. A registry record would be the wrong repair there. Correctly excluded and routed as out-of-scope findings with carriers (objectui#10872, objectui#4631).

Remaining dev flags. Deviation 1 (PR opened at 067461304, final head pushed a minute later): benign — the body describes 9fe80dbbc and every check-run below is on the final head. Deviation 3 (throwaway census file): answered in (c). conflicts_with_brief 2 (the grade's pin is refused at ROOT): correct reading of objectstack#8593; pinned in the slot with a root control. conflicts_with_brief 3: answered in (c). out_of_scope_findings: the legacy envelope's strict refusal (grade ruled it out; no reach; noted, not filed — right); the 45 root rows (carriers named — right); loading / error (by the grade — right); scripts/measure-strict-authoring-face.mjs keeping its own walker and now reading redder on a corpus with slot metric-card nodes (throwaway script; noted — accepted, and a reader of its output should know). open_questions: none.

Governed surface and size. None of the eight paths is in GOVERNED_SURFACES (docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md); Governed Surface Queue Guard green; 538 changed lines, far below the 5,000-line Tier H line. The PR is a dispatched dev's draft; ready and queue are the seat's acts, not this record's. No cross-file path:line citation in the diff (Line Citation Gate green).

Check-runs on this head (read at 2026-09-28T22:00:53Z, none in_progress): 43 runs — 39 success (Lint, Type Check, Test shards 1–8 and the Test roll-up, Test (dist pins), Build & E2E, Build Docs, Bundle Analysis, README Export Check, Doc Snippet Type Check, Changeset Declaration / Bump Policy / Fixed Group Check / Overwrite Report / Claim Re-read, Governed Surface Queue Guard, Line Citation Gate, Control Byte Scan, Pre-Install Import Graph Check, Live E2E (informational), the doc and skill checks, label, Action Ref Convention); 3 skipped by event or matrix condition (Test (coverage), Test (coverage shard …/4), dependabot); 1 failure: Spec Main Shape Gate.

  • Attribution of the red, verified from the job's step list and annotations rather than from prose: steps 1–7 (Checkout objectui, Resolve objectstack main, Fetch packages/spec from that commit, pnpm and Node setup) success; step 8 Build @objectstack/spec from objectstack main failure in about one second (annotation: exit code 254); steps 9–12 (Pack the built spec, Install objectui dependencies, Inject the source-built spec into the install, Type-check objectui against it) skipped; step 13 Report which objectui file fails against which objectstack commit exit 2 — no typecheck log to attribute. No objectui file was compiled. The workflow's sparse cone is git sparse-checkout set packages/spec scripts; objectstack c5ad1de0 (objectstack#20499) added patches/tsup@8.5.1.patch and a patchedDependencies: tsup@8.5.1: patches/tsup@8.5.1.patch entry in pnpm-workspace.yaml, which the cone brings in without the patches/ directory. The compare API confirms the order: c5ad1de0 is one commit ahead of 4a1df19656bf (the objectstack sha the card names for the last green run) and 9e9bb4641704 (the sha the card names for this run) is three commits ahead of c5ad1de0. The same gate is success on base 797a30f48 (run 36481292449), and its failure signature here is not the base's, so the same-signature exception does not apply and this record does not wave the red. The job log is unreadable from this container (the log's blob host is refused by the egress proxy); the quoted ENOENT … patches/tsup@8.5.1.patch first-error line is card objectui#11024's and the seat's comment 5879410182's, and it is consistent with the step and annotation shape read here. This diff owns none of it: it touches no workflow, no lockfile and no pnpm-workspace.yaml. Landing waits for the gate to re-run green after objectui#11024's fix (a main merge on this branch), which is the seat's act.

Implemented-by: claude/issue-11022-strict-slot-registry-inputs
Reviewed-by: session_012UwY3ahMixEFkfTUxMVkYm

VERDICT: PASS


Generated by Claude Code

…trict-slot-registry-inputs

Brings in the Spec Main Shape Gate sparse-cone repair so the required gate runs against this head. No conflicts; main moved only by #11025, which touches none of this branch's files.

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

Copy link
Copy Markdown
Contributor Author

Regen-provenance: 5879599128 · 9fe80db → c357955 · git merge origin/main (12a6688) → (empty)

From the domain:spec @ objectui seat, session session_012UwY3ahMixEFkfTUxMVkYm. The contract-review record 5879599128 (PASS on 9fe80dbbc) carries over to c35795581 under the carry-over arm of the 2026-09-20 ruling. Checked on the committed trees:

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3105.0 KB 3149.4 KB
Main entry chunk (gzip) 149.3 KB 350 KB
Entry file index-C1i_xxR4.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.72KB 6.21KB
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.92KB 133.97KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.62KB 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.35KB 9.18KB
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.53KB 4.89KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
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.39KB 14.98KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.56KB 36.67KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.49KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 162.67KB 41.95KB
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.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.54KB 22.44KB
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) 3.19KB 1.62KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.30KB 6.99KB
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

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

Labels

documentation Improvements or additions to documentation package: types plugin tests

Projects

None yet

2 participants