Skip to content

feat(types)!: the widget slot's metric-card arm declares MetricCard's registered inputs, and the widget component slot takes the arm (objectui#11467) - #11482

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-11467-metric-card-arm
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-11467-metric-card-arm

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11467

Clause-②: yes

Parent: objectui#8347, which keeps the index-signature removal. This PR is its F2 half and leaves the signature in place: every commit compiles with BaseSchema's signature present.

What this does

Seat answer A, executed. DashboardWidgetSlotComponentSchema is the widget slot's component arm. It now declares metric-card's registered inputs as members, typed as the props MetricCard reads. The zod twin declares the same members. The widget's legacy component slot takes the arm first, then any other node, on both faces.

member TypeScript face zod twin source of the type
title string | I18nLabel, optional spec I18nLabelSchema (defaults stripped), optional MetricCardProps.title
value string | number, required z.union of string and number, required MetricCardProps.value; the registration marks it required
icon string, optional z.string(), optional MetricCardProps.icon
trend 'up' | 'down' | 'neutral', optional z.enum of the three, optional the registration's enum, which equals MetricCardProps.trend
trendValue string, optional z.string(), optional MetricCardProps.trendValue
description inherited BaseSchema member, not restated inherited already string | I18nLabel on both faces

component changes too. On the TypeScript face it is DashboardWidgetSlotComponentSchema | SchemaNode. On the zod face it is z.union of the arm and BaseSchema.

"By reference" across a package boundary. @object-ui/types cannot import the registration or MetricCard, so the members are transcribed. The registering package holds them to both sources in metricCardRegisteredInputsStrictFace-11022.test.ts:

  • every registered input is a member of the arm on both faces;
  • every member the arm adds beyond BaseSchema and layout is a registered input;
  • value is required, as the registration marks it;
  • trend is exactly the registered enum;
  • a type-level equality between the arm's members and MetricCardProps for the six inputs.

No second spelling. objectui#11022's side table of input names (DASHBOARD_WIDGET_SLOT_REGISTERED_INPUTS, through declareRegisteredInputs) recorded these same names so the strict walker could admit them. Every name it held is now a member, so the table, declareRegisteredInputs / registeredInputsOf, and the walker's branch for them are removed. They were internal and not exported, and the arm was their only caller.

A guard instead of the table's satisfies. The arm's zod type is z.enum over DASHBOARD_COMPONENT_WIDGET_TYPES with a satisfies check against the one-member list metric-card. A second member of the closed set is therefore a compile error until the arm is split per component. Without the check, the new member would inherit the card's props.

Measured first

H1. Which inputs MetricCard reads. Two instruments, both at base d93e53f5.

  • Type-checker census. A TypeScript LanguageService ran over plugin-dashboard's own program. For each MetricCardProps member it looked up the references to the member symbol, then the uses of the destructured binding in the render body. title, value, icon, trend, trendValue and description are each destructured and read. A standalone tsc program over the same config: 0 errors.
  • Runtime probe. A real DashboardRenderer drew the card through the real SchemaRenderer and ComponentRegistry, one key removed at a time from a full card. The probe was a one-shot test file, deleted after the run.
    • Removing title, value or description changed the text.
    • Removing icon changed the drawn svg count.
    • Removing trend or trendValue changed the text, the svg count and the trend colour class. Each is drawn only together with the other.
    • Changing trend from up to down or neutral moved the colour class from green to red or yellow.
    • CONTROL: an unregistered key changed nothing drawn. It does land as a DOM attribute through MetricCard's rest spread (objectui#4426, pre-existing).

All five keys the card named are read. description is the sixth input and is already declared on BaseSchema.

H2. What each zod face judged at base.

  • Tolerant face: the slot arm declared only description (through BaseSchema). The other five passed through the catchall unjudged.
  • Strict face: it admitted all six names from the side table. Five were judged by the catchall (unknown), so trend: 'sideways', title: 7 and value: {} all parsed on both faces.
  • The component slot was BaseSchema alone. The strict face therefore refused a card's title, value, icon, trend and trendValue there as unrecognized_keys. That includes the plugin-dashboard README's own legacy-envelope example.

H3. PR objectui#11460 had landed (9d1c0bff) before this branch was cut from d93e53f5.

H4. zod-mirror-parity.test.ts did not move. The file is untouched, and the types suite is green with it. component stays in KnownDrift (the primitive arms of SchemaNode), and so does widgets.

Accept-set change (Clause-② yes)

document tolerant face, before → after strict face, before → after TypeScript face, before → after
card in widgets[] with all inputs well-typed accept → accept accept → accept compiles → compiles
card in widgets[], trend: 'sideways' / title: 7 / icon: 5 / trendValue: 3 / value: {} accept → refuse at that key accept → refuse compiles → error
card in widgets[] with icon, trend or trendValue and no value accept → refuse accept → refuse compiles → error
card in widgets[] with only title / description / layout and no value accept → accept (through the widget arm) accept → accept compiles → compiles (widget arm)
card in a widget's component slot with its inputs (README example) accept → accept refuse → accept compiles → compiles
card in component with trend: 'sideways' accept → accept (BaseSchema fallback) refuse → refuse (now at the arm's trend) compiles → compiles while the signature stands
any other node in component ({ type: 'chart' }) unchanged unchanged unchanged
undeclared key on a card (someProp) kept → kept refused by name → refused by name compiles → compiles while the signature stands

The TypeScript face gains no accepted key while the signature stands. The read type of title off a widgets[] entry moves from any to string | I18nLabel. Six consumer packages and two examples type-check unchanged; see Gates.

The card's own reading, two legs

The probe used the parent's mutation: only BaseSchema's documented index signature deleted, with @object-ui/types rebuilt so that dist/base.d.ts keeps 1 signature. Then tsc -p packages/types/tsconfig.test.json and pnpm check:doc-snippets ran. The mutation went through scripts/ablation-replace.mjs from the objectstack checkout: anchor 1 → 0, blob ce2bc9e31dc6 → 93c3221b8301, restored to blob == HEAD, git diff HEAD empty. Both legs ran in this container, in two worktrees.

base d93e53f5 (control) head 35a89136
types test program, errors in the arm-7952 and 9256 files value x3 (README usage copy; component slot x2), title x1 (9256 accepting literal), plus probe someProp probe someProp, probe bogus only
check:doc-snippets 70 of 776 blocks failed; 3 diagnostics in packages/plugin-dashboard/README.md 68 of 776 failed; 0 in the plugin-dashboard README
diagnostics, set difference — the 3 README diagnostics gone; nothing new

The bogus line at head is the arm-7952 file's "MEASURED LIMIT" constant. That corner shuts once the component card's value is declared, and the constant says to delete it then. This is noted for objectui#8347's last PR. The restore leg rebuilt types: dist/base.d.ts signatures 2, src 2, git diff HEAD 0.

Reverse verification of the new pins

Each leg deleted the members with ablation-replace.mjs on the committed tree (anchor 1 → 0, blob moved), ran, and restored (blob == HEAD, git diff HEAD empty). The direction was predicted before each run: red.

  • zod members deleted. vitest over the three pin files: 21 failed, 29 passed. Red: the grade pins, every value-judgment row, the value-required row, the arm-shape pin, the README-literal strict pin, plugin-dashboard UNDER-admission x6, DECLARED, typed-members, and the saved-layout strict parse.
  • TypeScript members deleted. The types test program: 10 errors. Six Equal pins went false: the five members and the zod twin. Three @ts-expect-error directives went unused (TS2578). The title read pin went false.

Corpus re-judged

scripts/measure-strict-authoring-face.mjs ran at base and at head over the schema catalog, the docs fences, the apps and the types sources: 610 documents, 2244 nodes.

  • Tolerant face: unchanged. documentsRedTodayWholeDocument is 23 → 23 and nodesRedToday 30 → 30.
  • The script's strict twin: 61 → 56 documents refused. The 5 are the three catalog dashboards and two docs dashboards with direct-slot cards. That script keeps its own walker, which never modelled the objectui#11022 side table, so at base it disagreed with the shipped strict face on exactly these 5. The shipped face accepted them at base and accepts them now. With the table gone, the script and the face agree.
  • Every metric-card in the corpus carries a string value and a trend from the three, so no document's verdict moved on the shipped faces.
  • One already-red docs dashboard (a type: 'card' widget with children, in the plugin-dashboard mdx) also lists the component arm's missing value among its union errors now. Its verdict is unchanged; only the error shape moved.

Pins moved

  • strict-widget-slot-registered-inputs-11022.test.ts. Several blocks are rewritten:
    • "admitted unjudged" becomes "refused at that key, on both faces";
    • the "side table" shape pin becomes "the arm's shape is the base plus layout and the five inputs";
    • the hand-built-node derivation block is removed with the mechanism.
    • The bogus and value-required fixtures now carry a value.
  • metricCardRegisteredInputsStrictFace-11022.test.ts. The OVER-admission test, which read "strict keys minus tolerant keys", is replaced by DECLARED-both-directions on both faces. Typed-member and MetricCardProps pins are added. The UNDER-admission and layout fixtures carry the required value.
  • dashboard-widget-slot-component-arm-7952.test.ts. value is added to two existing literals so each pin keeps refusing for its stated reason. A new objectui#11467 block holds the member types, the twin parity, the TypeScript refusals, the component slot type, the README's two literals on both faces, and the component-slot strict judgment.

Files outside the claimed surface (declared)

The claim's surface did not list five files. Each one is the same accept-set change, not a side quest:

  • packages/types/src/zod/node-derivation.ts and packages/types/src/strict-authoring-face.ts: the side table and its walker branch are removed, because every name in it is now a member.
  • strict-widget-slot-registered-inputs-11022.test.ts (types) and metricCardRegisteredInputsStrictFace-11022.test.ts (plugin-dashboard): they pinned the passthrough-plus-side-table state that the ruling replaces.
  • packages/types/README.md: its strict-face paragraph described the side table.

The renderer, the index signature and SchemaNode are untouched.

Gates (head 35a89136)

  • pnpm --filter PKG run type-check (each echoed type-check): exit 0 for types, plugin-dashboard, core, sdui-parser, plugin-list, plugin-designer, app-shell, example-schema-catalog and example-console-starter. All 40 packages were built first, with turbo Tasks: 40 successful.
  • vitest from the worktree root, each a repo-relative path:
    • packages/types/: 330 files, 8770 tests passed;
    • packages/plugin-dashboard/: 160 files, 1532 passed, 6 skipped (pre-existing);
    • examples/schema-catalog/: 39 files, 2235 passed;
    • packages/sdui-parser/: 20 files, 289 passed;
    • 8 consumer files in cli, app-shell, plugin-designer and core: 96 passed.
  • check:doc-snippets: 776 of 776 judged, 0 failed. check:doc-examples: 87 fail, all 87 declared. check:doc-types: green.
  • check:spec-symbols, check:component-surface-parity, check:new-line-citations, check:control-bytes, check:changeset-claims, check:pending-changeset-literals, check:doc-fences, check:readme-exports, check:test-path-roots and check:handler-key-reads: exit 0.
  • node scripts/check-changeset-no-major.mjs and node scripts/check-changeset-presence.mjs: exit 0.
  • check:sdui-registration-pins: NOT MEASURED. It exits 2 with "No console build to weigh", a prerequisite the run did not meet. No registration or sideEffects array moved.
  • node scripts/check-governed-queue-guard.mjs --test on the 11 paths: NOT GOVERNED.

Overlap

  • main advanced 6 commits after d93e53f5, objectui#11477 (F3) among them. git merge-tree against it is clean. The one shared file is packages/types/README.md, in a different section. Nothing on main touches complex.ts, complex.zod.ts or zod-mirror-parity.test.ts.
  • objectui#11466 (F1) is in flight on SchemaNode. component is DashboardWidgetSlotComponentSchema | SchemaNode, so it composes with whatever SchemaNode becomes, and the card stays typed by the arm.

Acceptance notes

  • value required is enforced on the arm, not on every metric-card. A { type: 'metric-card', title } entry with no card-only input still parses on both faces through the widget arm. The widget arm parses it because DashboardWidgetTypeSchema also carries the closed component types (objectui#4600). Reported to the seat as a finding.
  • Measured limit in the component slot. A card that fails the arm still parses on the tolerant face through the BaseSchema fallback, and the TypeScript union has the same corner. Only the strict face refuses it. This is recorded as a MEASURED LIMIT in the arm-7952 file, not ruled.
  • The mdx's type: 'card' widget with children is refused by the validator today. The README twin was repaired by objectui#7035; the mdx was not. Reported to the seat as a finding.

The session that made this change is https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC.


Generated by Claude Code

claude added 3 commits October 2, 2026 14:46
… registered inputs, and the widget `component` slot takes the arm (objectui#11467)

`DashboardWidgetSlotComponentSchema` declares `title`, `value`, `icon`,
`trend` and `trendValue` (the sixth input, `description`, is the base's
member) with the types of the props `MetricCard` reads, on the TypeScript
face and on the zod twin. `value` is required, as the registration marks it.
The widget's `component` slot takes the arm first, then any other node, on
both faces. The objectui#11022 side table of input names is retired with its
walker arm: every name it held is now a member.

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
…valued card; `value` is refused at its key when absent (objectui#11467)

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
…n both READMEs and the mdx twin; changeset (objectui#11467)

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

github-actions Bot commented Oct 2, 2026 •

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/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/8114-detail-tab-activity-timeline.md

  • names plugin-dashboard/README.md → packages/plugin-dashboard/README.md — edited by this change

    README.md ships in this package's files, so the example went out in every tarball. DetailTabs renders a tab's content through ANGLE-BRACKETS(SchemaRenderer schema={toRenderableSchema(tab.content)} /), which makes content.type an SDUI node position judged by the component registry — so a reader copying the tab got the registry's Unknown component type panel (OBJUI-001) where the timeline should be. Same shape as the line-chart widget in plugin-dashboard/README.md (objectui#7896's census; fixed by objectui#7951) and the fourth known instance.

.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/8760-unfulfilled-chart-stubs.md

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

    • At render. SchemaRenderer's lazy branch re-checks hasLazy(type) on every pass and returns the Loading ANGLE-BRACKETS(type)… placeholder. Registry.register() deletes a lazy entry only for keys the loaded module actually registers, so for an unfulfilled key the entry SURVIVES the load and every later pass takes the same branch. Measured on b775500af through the real chain: { "type": "line-chart" } painted role="status" / data-lazy-loading="line-chart" / Loading line-chart…, permanently. Not the OBJUI-001 panel the card expected — no alert, no error, no console warning. A skeleton that never resolves reads to a user as a slow network. - At authoring. A stub is enough to put a key into getKnownTypes(), so check:doc-types and the CLI's generated KNOWN_SCHEMA_TYPES snapshot both blessed all three. content/docs/plugins/plugin-dashboard.mdx taught "type": "line-chart" inside a card body, and every gate was green on it.

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

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

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


📦 Bundle Size Report

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

Size Limits

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 35a89136798bac58c580bf35ec5a78012f2b9d39
Local-runs: none

Inputs: card objectui#11467 (body, the claim and the os-dev report), PR objectui#11482 (body, 11-file list, net diff +572/-382), the head's check-runs, and REST reads of files at the head where a diff claim needed its source (packages/types/package.json, the two barrels, base.ts, base.zod.ts, complex.zod.ts, MetricCard.tsx, the plugin-dashboard registration, the pending .changeset/*.md entries that name this arm, and the changeset scripts' headers). Nothing built, run or re-run.

① Derived judgments

Every accept-set and public-surface change the diff implies, each named:

  1. DashboardWidgetSlotComponentSchema (TypeScript, exported) gains title?, value (required), icon?, trend?, trendValue?. RIGHT. Read at the head, MetricCardProps is exactly title?: string | I18nLabel, value: string | number, icon?: string, trend?: 'up' | 'down' | 'neutral', trendValue?: string, description?: string | I18nLabel, and the component destructures and renders all six. The registration (plugin-dashboard:metric-card) lists the same six names, marks only value required, and gives trend the three-value enum. BaseSchema (TS) declares label and description as string | I18nLabel and no title, so the new title overrides nothing. The sixth input, description, is correctly inherited and not restated.
  2. The zod twin declares the same five members, title as stripImportedDefaults(I18nLabelSchema).optional(). RIGHT. That is the spelling base.zod.ts already uses for label and description, and it is a crossing wrapped as the import-boundary rule (objectui#8317) requires. type is z.enum over the closed set with a satisfies readonly ['metric-card'] guard, so a second closed-set member fails to build until the arm is split: a sound replacement for the retired per-row satisfies.
  3. Tolerant face narrows, directly in widgets[]. RIGHT and stated in the changeset: a card with a wrong-typed title / icon / trendValue / value, a trend outside the three, or a card-only input with no value, now falls out of the component arm, and the strict widget arm refuses the card-only keys by name, so the union refuses. A valueless card carrying only widget keys (title, description, layout) still parses through the widget arm because DashboardWidgetTypeSchema carries metric-card (objectui#4600); the PR body names this limit and the changeset's "what now refuses" is scoped to match. Corpus re-judged by the dev: no document moved on the tolerant face.
  4. Strict face widens in one place: a card with its inputs in a widget's component slot. RIGHT. DashboardWidgetSchema.component (zod) is now z.union([arm, BaseSchema]); BaseSchema alone refused the five inputs as unrecognized_keys, the README's own legacy-envelope example included. Tolerant face in that slot: unchanged (the BaseSchema fallback keeps any node). Any other component node there is judged by BaseSchema as before (the arm-7952 pin's { type: 'chart' } control).
  5. TypeScript component?: DashboardWidgetSlotComponentSchema | SchemaNode. RIGHT. Assignability is unchanged (the arm extends BaseSchema, which SchemaNode already holds), and the value-through-the-signature errors the card measured in the component slot are gone because value is now a declared member of a union arm. The union, not the arm alone, is the correct reading of the ruling's "type the widget component slot with that arm": the slot also carries a custom widget's general component node, which the existing parity pins hold; the arm alone would have refused them with no ruling behind it. Declared in the PR as a route deviation.
  6. title read off a widgets[] entry moves from any to string | I18nLabel. RIGHT and stated. Nine packages type-check at the head (CI Type Check green).
  7. TypeScript face gains no accepted key while the signature stands; an annotated literal without value is now a compile error. RIGHT. The signature-removal probe (two legs, restored to blob == HEAD) shows the card's four F2 errors and the three README doc-snippet diagnostics gone with nothing new, which is this card's whole target under objectui#8347.
  8. declareRegisteredInputs / registeredInputsOf and the strict walker's branch removed. RIGHT and internal: packages/types/package.json exports only ., ./base, ./layout, ./form, ./data-display, ./feedback, ./overlay, ./navigation, ./complex, ./data, ./zod (dist/zod/index.zod) and ./internal/retired-field-keys; index.zod.ts and index.ts re-export nothing from node-derivation. At the head no file besides the rewritten pins references them (strict-authoring-face-8345.test.ts, the measure script and the audit doc are clean). The side table was the "second spelling" the card body forbids; retiring it is the card's own instruction, not a side quest.
  9. zod-mirror-parity.test.ts untouched; component and widgets stay in KnownDrift. RIGHT (the types suite is green with it).
  10. Docs. plugin-dashboard README and mdx now teach the arm's members; the types README's strict-face paragraph no longer describes the side table. Doc Snippet Type Check green. RIGHT.

② Semver level

  • .changeset/11467-metric-card-arm-inputs.md: '@object-ui/types': minor, with the narrowing stated as the breaking part and a Fix line. RIGHT for this repository: its version-alignment rule declares objectui's own breaking changes minor and refuses major mechanically (check-changeset-no-major.mjs; Changeset Bump Policy green). The feat(types)! title and the minor level are therefore consistent. @object-ui/plugin-dashboard moves only a README and a test, so no second declaration is owed (Changeset Declaration green).
  • The body states both directions (what now refuses, what now parses, what does not move, read types) and each statement matches the diff as judged in ①.
  • Clause-②: yes. RIGHT, and the claim's "declared yes until the measurement settles it" settled to yes: the tolerant face narrows directly in widgets[], the strict face widens in the component slot, and the TypeScript face refuses an annotated valueless card.

What fails ②. A .changeset/*.md body publishes verbatim into the CHANGELOG at the next release, and this repository's practice when a later change in the same release falsifies a pending entry's present-tense reading is a dated note or a superseded-readings table on that entry (the objectui#9870 pin; the "went false" class objectui#7721, objectui#8617 and objectui#9713 repaired). Both files below already carry exactly that form, added 2026-10-01 for objectui#11070 round 11, which moved far less than this head does. This PR adds none, although its own changeset names the objectui#11022 mechanism it removes:

  • .changeset/11022-strict-widget-slot-registered-inputs.md now states three readings this head falsifies: "The slot's component-node arm now records the input names ... deriveStrictAuthoringSchema admits those names on that node before closing it" (the mechanism is gone); "its component is still judged as a plain BaseSchema node on both faces" (it is the arm first, then BaseSchema); "No migration: a document that parsed on either face still parses there" (this PR's own changeset lists what now refuses and gives a Fix).
  • .changeset/11348-dashboard-widget-reads.md: its 2026-10-01 dated note reads "title and colorVariant are still declared on the widget arm only" (title is now declared on both arms, which this PR's changeset and the README say in so many words).
  • .changeset/7952-dashboard-widgets-component-arm.md ("body validated as passthrough BaseSchema"; a read through the signature "is any") is written in the frame of that change and carried no note for layout either; a reader's call, not required here.

The Changeset Claim Re-read gate is report-only by design and its went-false reading keys on a pending body naming a FILE this change touches, which neither entry does, so green there is not a verdict on this. Remedy, one commit on the branch: append a dated note to the two entries, retiring the quoted readings and naming .changeset/11467-metric-card-arm-inputs.md as what ships; then a fresh record on that head. Nothing in ① or ③ needs to move.

③ Boundary flags

  • Q1 (five files outside the claim's surface): A, as implemented. The card body says "⛔ no second spelling", and the side table plus its two helpers and the walker branch were the second spelling of five names; its only caller was this arm. The two objectui#11022 pin files asserted the passthrough-plus-side-table state and would go red under any value-checked mirror, and the card requires main to stay green when this lands alone, so they must move in the same PR. The types README paragraph described the retired mechanism. Each of the five is the same accept-set change; none goes beyond it. Option B would keep a zero-consumer mechanism and the second spelling the card forbids.
  • "Stop on breach; explain in the report." The dev did not stop; it implemented A and asked afterwards. The overrun is declared in the PR body and bounded to the retirement the card body compels, so it is answered by Q1 above and not held against the diff.
  • Route (union, not the arm alone): answered in ① item 5. Right.
  • H1 nuance (the slot was SchemaNode, whose object arm is BaseSchema): the card's reading holds; no action.
  • No merge of main: the PR is mergeable and six commits behind; complex.ts, complex.zod.ts and zod-mirror-parity.test.ts are untouched on main since the branch base. Fine; re-check at merge.
  • Report wording: "the claim's two named tests ... gained pins" — only dashboard-widget-slot-component-arm-7952.test.ts did; content-channel-public-blocks-9256 is untouched, which the file list shows. No consequence.
  • check:sdui-registration-pins NOT MEASURED: acceptable; no registration or sideEffects moves in the diff (plugin-dashboard src/ is touched only in __tests__).
  • Toolchain, vitest spelling, commit trailers: outside the contract.
  • Out-of-scope finding (b), a valueless metric-card parsing through the widget arm: real and pre-existing (objectui#4600); for the seat to file. It does not falsify this changeset, whose "value required" is scoped to a card carrying a card-only input or annotated with the arm.
  • Out-of-scope finding (c), the mdx type: 'card' widget with children: pre-existing; for the seat to file.
  • envelopeStray MEASURED LIMIT constant: correctly left for objectui#8347's carrier.
  • One further boundary, for the seat: the registration declares value as type: 'string' while MetricCardProps.value and the arm are string | number. The parity test holds the arm to MetricCardProps for types and to the registration for required and the trend enum, but not to the registration's type of value. The arm's wider spelling is governed by the renderer and the objectui#11022 grade pin (value: 42 parses), so it is the right reading; the registry/props drift predates this PR and is not its defect.
  • Check-runs on the head, read 2026-10-02T16:09Z: 43 total; 39 success, among them Type Check, Lint, Test shards 1 through 8, Test (dist pins), Spec Main Shape Gate, Build & E2E, Doc Snippet Type Check, Doc Component Type Check, README Export Check, Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Overwrite Report, Changeset Fixed Group Check, Governed Surface Queue Guard, Line Citation Gate, Control Byte Scan; 3 skipped (Test coverage, its matrix row, dependabot); 1 in_progress: the Test rollup over the eight already-green shards. No red; the pending rollup bears nothing on the contract. The derived gate families answered green; the verdict rests on ② alone.

Implemented-by: claude/issue-11467-metric-card-arm
Reviewed-by: session_01XvhGmGAP79ZB8swnkapxPC

VERDICT: FAIL

…ctui#11467 falsifies

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

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

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


📦 Bundle Size Report

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

Size Limits

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 911239e52ef05b54c9cebf01a85db2d24c0661d4
Local-runs: none

Inputs: card objectui#11467 (body, the claim 5954392494 and the os-dev report 5956160782; the card carries no ruling comment), PR objectui#11482 (body, 13-file list, net diff +576/-382 against main at 2c0ddf226, and the earlier record 5956388563 on head 35a89136), the head's check-runs, and REST reads of files at the head where a diff claim needed its source (MetricCard.tsx, the plugin-dashboard registration, base.ts, base.zod.ts, complex.ts, the two barrels, packages/types/package.json, zod-mirror-parity.test.ts, and the pending .changeset/*.md entries that name this arm or its mechanism: 11022, 11348, 11467, 7952, 6002, 9256-public-blocks, 11070-dashboard-keys-round11, 11070-strict-face-read-keys, 10859-dashboard-node-keys, 8499, dashboard-widget-type-closed-enum, 7581, both 8345, 9910, 9162, 11073, 10946, 7201, 11254). Nothing built, run or re-run.

Delta from the earlier head: compare/35a89136...911239e5 is one commit ahead, zero behind, touching exactly .changeset/11022-strict-widget-slot-registered-inputs.md (+2/-0) and .changeset/11348-dashboard-widget-reads.md (+2/-0). The net diff outside .changeset/ is therefore byte-identical to the earlier head's. ① and ③ are re-judged below in full from the net diff all the same; ② is where the head moved.

① Derived judgments

Every accept-set and public-surface change the diff implies, each named:

  1. DashboardWidgetSlotComponentSchema (TypeScript, exported from complex.ts and the package entry) gains title?: string | I18nLabel, value: string | number (required), icon?: string, trend?: 'up' | 'down' | 'neutral', trendValue?: string. RIGHT. MetricCardProps at the head is exactly those five plus description?: string | I18nLabel, and the component destructures and draws all six (trend and trendValue only together, which the mdx and README say). The registration lists the same six names, marks only value required, and gives trend the three-value enum. TypeScript BaseSchema declares label and description as string | I18nLabel and no title, so the new title overrides nothing, and description is correctly inherited, not restated.
  2. The zod twin declares the same five members; title is stripImportedDefaults(I18nLabelSchema).optional(). RIGHT: the spelling base.zod.ts uses for label and description, a wrapped crossing as the import boundary requires. BaseSchema.extend keeps the passthrough catchall (pinned). type is z.enum over the closed set with a satisfies check against the one-member list, so a second closed-set member fails the build until the arm is split: a sound replacement for the retired per-row satisfies.
  3. Tolerant face narrows, directly in widgets[]. RIGHT and stated in the changeset. A card whose title / icon / trendValue / value is outside its member, whose trend is outside the three, or that carries a card-only key with no value, fails the component arm; the strict widget arm refuses the card-only keys by name; the union refuses. A valueless card carrying only widget keys still parses through the widget arm because DashboardWidgetTypeSchema carries metric-card; the PR body names the limit and the changeset's "what now refuses" is scoped to match.
  4. TypeScript face: each of those literals is a compile error, in widgets[] and annotated as the arm. RIGHT. DashboardWidgetSchema extends Omit of Partial of the spec's DashboardWidget and carries no index signature of its own, so a card-only key is an excess property on the widget arm while the component arm judges it by its member; the arm-7952 pins hold three of the cases with @ts-expect-error, and the dev's reverse leg shows the directives go unused when the members are deleted. The TypeScript face gains no accepted key while the signature stands, which is the card's constraint.
  5. Strict face widens in one place: a card with its inputs in a widget's component slot. RIGHT. DashboardWidgetSchema.component (zod) is z.union([arm, BaseSchema]); BaseSchema alone refused the five inputs as unrecognized_keys, the README's own legacy-envelope example included. A non-card node there is judged by BaseSchema as before (the { type: 'chart' } control).
  6. TypeScript component?: DashboardWidgetSlotComponentSchema | SchemaNode. RIGHT. Assignability is unchanged (the arm extends BaseSchema, which SchemaNode holds), the value-through-the-signature errors in that slot are gone, and the union rather than the arm alone is the correct reading of "type the widget component slot with that arm": the slot also carries a custom widget's general node, which the parity pins hold and no ruling retires. Declared as a route deviation. The zod DashboardWidgetSchema's inferred component type moves with it; the changeset states the union.
  7. Measured limit in the component slot. RIGHT to record, not rule: the tolerant face's BaseSchema fallback and the TypeScript union's SchemaNode arm both keep a card that fails the arm; only the strict face refuses it. Pinned as MEASURED LIMIT in the arm-7952 file and stated in the PR body. One doc precision note for the seat: the plugin-dashboard README's new paragraph says a card's keys are "checked against that arm" in both placements and that objectui validate refuses a trend outside the three; that holds directly in widgets[] and on the strict face, not for the component slot on the tolerant face. Not held against the diff; a one-clause scoping, which the seat may fold into the remedy commit below.
  8. title read off a widgets[] entry moves from any to string | I18nLabel. RIGHT and stated; pinned.
  9. declareRegisteredInputs / registeredInputsOf, the DASHBOARD_WIDGET_SLOT_REGISTERED_INPUTS row and the strict walker's branch removed. RIGHT and internal: packages/types/package.json exports ., ./base, ./complex, ./data, ./data-display, ./feedback, ./form, ./internal/retired-field-keys, ./layout, ./navigation, ./overlay and ./zod; neither index.ts nor index.zod.ts re-exports anything from node-derivation. The side table was the second spelling the card body forbids; retiring it is the card's own instruction.
  10. zod-mirror-parity.test.ts untouched; component and widgets stay in KnownDrift. RIGHT; its component entry describes the slot's drift class, not the arm's members.
  11. Docs. The plugin-dashboard README and mdx teach the arm's members with the right types and requiredness; the types README's strict-face paragraph no longer describes the side table and adds the trend: 'sideways' refusal line. Doc Snippet Type Check and Doc Component Type Check green. RIGHT, with item 7's note.

② Semver level

  • .changeset/11467-metric-card-arm-inputs.md: '@object-ui/types': minor, the narrowing named as the breaking part, with a Fix line. RIGHT for this repository, which declares its own breaking changes minor and refuses major mechanically (Changeset Bump Policy green); the feat(types)! title is consistent. @object-ui/plugin-dashboard moves a README and a test only; Changeset Declaration green, so no second declaration is owed. The body's four directions (refuses, parses, does not move, read types) each match the diff as judged in ①. Clause-②: yes. RIGHT: the claim's provisional yes settled to yes.
  • The two entries the earlier record failed on are repaired, correctly. The new commit appends a dated note (2026-10-02, objectui#11467) to .changeset/11022-strict-widget-slot-registered-inputs.md quoting and retiring its three falsified readings, and to .changeset/11348-dashboard-widget-reads.md retiring "title and colorVariant are still declared on the widget arm only", each in the form the 2026-10-01 notes on the same entries already use and each naming .changeset/11467-metric-card-arm-inputs.md as what ships. Every statement in both notes matches the diff. (The 11022 entry's "its accept set, output and inferred types are unchanged" is the same fact its retired "No migration" sentence states, and the note's refusal list retires it in substance: a reader's call, not required.)

What fails ② on this head. The same class, on a third pending entry the earlier record did not name and this head leaves untouched: .changeset/11070-dashboard-keys-round11.md ('@object-ui/types': minor), whose "What does not move" bullet reads, verbatim: "The widget arm, the component node's other keys and the registered-input record objectui#11022 added." This head moves all three: the component node's other keys are now declared members judged on both faces (① items 1 to 4), the registered-input record is deleted (① item 9), and the widget arm's component member is now the arm-first union (① items 5 and 6). That entry publishes verbatim into the same CHANGELOG as 11467-metric-card-arm-inputs.md, which says the record is removed, and the repository's practice for a pending reading that a later change in the same release falsifies is the dated note this very branch now carries on 11022 and 11348 (the 2026-10-01 notes on those two entries were written for this same 11070 change). The Changeset Claim Re-read gate is report-only and keys on a pending body naming a FILE this change touches; the 11070 entry names symbols, which that gate says it does not cover, so its green is not a verdict on this. Remedy, one commit on the branch: append a dated note to .changeset/11070-dashboard-keys-round11.md in the form already used, retiring that bullet and naming .changeset/11467-metric-card-arm-inputs.md as what ships; then a fresh record on that head, which can be delta-only on .changeset/ once the compare endpoint shows the net diff outside .changeset/ still byte-identical. Nothing in ① or ③ needs to move. For the seat's own call, not required: .changeset/7952-dashboard-widgets-component-arm.md ("body validated as passthrough BaseSchema"; a read through the signature "is any") and .changeset/6002-dashboard-widget-strict.md ("a component node is owned by objectui's own passthrough BaseSchema") describe the arm in the frame of their own changes and carried no note for layout either.

③ Boundary flags

  • Q1 (five files outside the claim's surface): A, as implemented. The card body says "no second spelling"; the side table, its two helpers and the walker branch were the second spelling of five names, and the arm was their only caller. The two objectui#11022 pin files asserted the passthrough-plus-side-table state and go red under any value-checked mirror, and the card requires main to stay green when this lands alone, so they move in the same PR. The types README paragraph described the retired mechanism. Each of the five is the same accept-set change. Option B keeps a zero-consumer mechanism and the forbidden second spelling.
  • "Stop on breach; explain in the report." The dev implemented A and asked afterwards rather than stopping. Declared in the PR body and bounded to the retirement the card compels; answered by Q1 and not held against the diff.
  • Route (union, not the arm alone): answered in ① item 6. Right.
  • H1 nuance (the slot was SchemaNode, whose object arm is BaseSchema): the card's reading holds; no action.
  • No merge of main: the PR reads mergeable: true, state behind; complex.ts, complex.zod.ts and zod-mirror-parity.test.ts are untouched on main since the branch base per the dev's merge-tree. Fine; re-check at merge.
  • Report wording: "the claim's two named tests ... gained pins" — only dashboard-widget-slot-component-arm-7952.test.ts did; content-channel-public-blocks-9256 is untouched. No consequence.
  • check:sdui-registration-pins NOT MEASURED: acceptable; no registration or sideEffects moves (plugin-dashboard src/ is touched only under __tests__).
  • Toolchain, vitest spelling, commit trailers: outside the contract.
  • Out-of-scope finding (b), a valueless metric-card parsing through the widget arm: real, pre-existing (objectui#4600); for the seat to file. It does not falsify the changeset, whose "value required" is scoped to the arm.
  • Out-of-scope finding (c), the mdx type: 'card' widget with children: pre-existing; for the seat to file.
  • envelopeStray MEASURED LIMIT constant: correctly left for objectui#8347's carrier.
  • Registration value: type 'string' against the arm's string | number: the parity test holds the arm to MetricCardProps for types and to the registration for required and the trend enum, not to the registration's type of value; the wider spelling is the renderer's and the objectui#11022 grade pin's (value: 42 parses). Pre-existing registry/props drift, not this PR's defect; for the seat.
  • Check-runs on the head, read three times, last at 2026-10-02T16:23:40Z: 42 total; 30 success, among them Type Check, Lint, Build & E2E, Test (dist pins), Doc Snippet Type Check, Doc Component Type Check, README Export Check, Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Overwrite Report, Changeset Fixed Group Check, Governed Surface Queue Guard, Line Citation Gate, Control Byte Scan, Skill Example Check; 3 skipped (Test coverage, its matrix row, dependabot); 9 in_progress at the last read: Test shards 1 through 8 and Spec Main Shape Gate. No red. The nine pending ones were all success on the earlier head 35a89136, and the code they exercise is byte-identical on this head (only .changeset/ moved), so none bears on the contract. The derived gate families answered green; the verdict rests on ② alone.

Implemented-by: claude/issue-11467-metric-card-arm
Reviewed-by: session_01XvhGmGAP79ZB8swnkapxPC

VERDICT: FAIL

… accept-set readings objectui#11467 falsifies; scope the README's validate clause to a card in widgets[]

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

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

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


📦 Bundle Size Report

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

Size Limits

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 6919d61a3785b71a483e91ecf388e4d569edb315
Local-runs: none

Inputs: card objectui#11467 (body, the claim 5954392494 and the os-dev report 5956160782; the card carries no ruling comment and no sweep list), PR objectui#11482 (body, 14-file list, net diff +581/-382 against main, the two earlier records 5956388563 on 35a89136 and 5956625450 on 911239e5, and the two bot comments), the head's check-runs, and REST reads at the head: MetricCard.tsx, the plugin-dashboard:metric-card registration in plugin-dashboard/src/index.tsx, the four touched .changeset entries, and EVERY one of the 2,648 pending .changeset/*.md entries (the .changeset git tree at the head, not truncated, each entry fetched by its path and read as text), plus the compare endpoints 35a89136...6919d61a and 6919d61a...main, and the 16 .changeset entries main adds or changes since the merge base d93e53f5, read at main 2c0ddf22. Nothing built, run or re-run.

Delta from the earlier heads: compare/35a89136...6919d61a is two commits ahead, zero behind, and touches exactly .changeset/11022-strict-widget-slot-registered-inputs.md (+4), .changeset/11070-dashboard-keys-round11.md (+2), .changeset/11348-dashboard-widget-reads.md (+2) and packages/plugin-dashboard/README.md (+2/-1). The net diff outside those four files is therefore byte-identical to the one both earlier records judged. ① and ③ are carried from those records on that basis and re-read here against the hunks themselves (complex.ts, complex.zod.ts, strict-authoring-face.ts, node-derivation.ts, the three test files, the mdx and both READMEs). ② is re-judged in full.

① Derived judgments

Every accept-set and public-surface change the diff implies, each named:

  1. DashboardWidgetSlotComponentSchema (TypeScript, exported) gains title?: string | I18nLabel, value: string | number (required), icon?: string, trend?: 'up' | 'down' | 'neutral', trendValue?: string. RIGHT. MetricCardProps at the head is exactly those five plus description?: string | I18nLabel, and the component destructures and draws all six (trend and trendValue only together). The registration lists the same six names, marks only value required, and gives trend the three-value enum. description is inherited from BaseSchema, not restated.
  2. The zod twin declares the same five members; title is stripImportedDefaults(I18nLabelSchema).optional(), the spelling base.zod.ts uses for label and description; BaseSchema.extend keeps the passthrough catchall (pinned); type is z.enum over the closed set with a satisfies readonly ['metric-card'] guard, a sound replacement for the retired per-row satisfies. RIGHT.
  3. Tolerant face narrows, directly in widgets[]. RIGHT and stated in the changeset: a card whose title / icon / trendValue / value is outside its member, whose trend is outside the three, or that carries a card-only key with no value, fails the component arm, the strict widget arm refuses the card-only key by name, and the union refuses. A valueless card carrying only widget keys still parses through the widget arm because DashboardWidgetTypeSchema carries metric-card; the changeset's "what now refuses" is scoped to match.
  4. TypeScript face: each of those literals is a compile error, in widgets[] and annotated as the arm; the face gains no accepted key while the signature stands. RIGHT. The arm-7952 pins hold three cases with @ts-expect-error, and the dev's reverse leg shows the directives go unused when the members are deleted.
  5. Strict face widens in one place: a card with its inputs in a widget's component slot. RIGHT. DashboardWidgetSchema.component (zod) is z.union([arm, BaseSchema]); BaseSchema alone refused the five inputs as unrecognized_keys, the README's legacy-envelope example included. A non-card node there is judged by BaseSchema as before (the { type: 'chart' } control).
  6. TypeScript component?: DashboardWidgetSlotComponentSchema | SchemaNode. RIGHT. Assignability is unchanged, the value-through-the-signature errors in that slot are gone, and the union rather than the arm alone is the right reading of "type the widget component slot with that arm": the slot also carries a custom widget's general node, which the parity pins hold. Declared as a route deviation.
  7. Measured limit in the component slot. RIGHT to record, not rule: the tolerant face's BaseSchema fallback and the TypeScript union's SchemaNode arm keep a card that fails the arm; only the strict face refuses it. Pinned as MEASURED LIMIT and stated in the PR body. The head's last commit scopes the plugin-dashboard README's objectui validate sentence to "a card directly in widgets[]", which answers the earlier record's doc-precision note.
  8. title read off a widgets[] entry moves from any to string | I18nLabel. RIGHT, stated, pinned.
  9. declareRegisteredInputs / registeredInputsOf, the DASHBOARD_WIDGET_SLOT_REGISTERED_INPUTS row and the strict walker's branch removed. RIGHT and internal: the package exports and the two barrels re-export nothing from node-derivation (the earlier records' read; this head moves none of those files). The side table was the second spelling the card body forbids, so retiring it is the card's own instruction.
  10. zod-mirror-parity.test.ts untouched; component and widgets stay in KnownDrift. RIGHT.
  11. Docs. The plugin-dashboard README and mdx teach the arm's members with the right types and requiredness; the types README's strict-face paragraph no longer describes the side table and adds the trend: 'sideways' refusal line. Doc Snippet Type Check and Doc Component Type Check are green on this head. RIGHT.

② Semver level

  • .changeset/11467-metric-card-arm-inputs.md: '@object-ui/types': minor, the narrowing named as the breaking part, with a Fix line. RIGHT for this repository, which declares its own breaking changes minor and refuses major mechanically (Changeset Bump Policy green); the feat(types)! title is consistent. @object-ui/plugin-dashboard moves a README and a test only, so no second declaration is owed (Changeset Declaration green). Each of the body's five directions (what changed, what now refuses, what now parses, what does not move, read types) and its closing paragraph on the retired side table match the diff as judged in ①. Clause-②: yes. RIGHT.
  • The three entries the earlier records failed on are repaired, correctly. Each carries a dated note of 2026-10-02 naming objectui#11467, in the form the 2026-10-01 notes on the same entries already use, quoting the retired reading and naming .changeset/11467-metric-card-arm-inputs.md as what ships. 11022-strict-widget-slot-registered-inputs: two notes, the first retiring the recorded names and the walker step, "its component is still judged as a plain BaseSchema node" and the no-migration sentence (with the refusal list), the second retiring "its accept set, output and inferred types are unchanged" for the tolerant arm. 11348-dashboard-widget-reads: title is now declared on both arms and reads typed, colorVariant is still widget-only. 11070-dashboard-keys-round11: its "What does not move" bullet retired on all three counts, the widget arm's component member, the component node's other keys, and the registered-input record. Every statement in the four notes matches the diff.
  • The full sweep, read, not recalled. All 2,648 pending entries at the head were read as text in two passes: first the names of the arm, the component slot, metric-card / MetricCard, the retired mechanism and its three symbols, and widgets[] (34 entries); then the wider spellings (component node, DASHBOARD_COMPONENT_WIDGET_TYPES, widget slot, the strict face, DashboardWidgetSchema, index signature, passthrough; 154 entries), each hit judged by its line in context. The three retired symbols and "side table" are named by 11467 alone. DashboardWidgetSlotComponentSchema is named by 11070-round11, 11348, 11467, 7952 and 9256. Beyond the four touched entries, every entry that names one of the four things, and the verdict on each:
    • 7952-dashboard-widgets-component-arm (types minor): describes the arm as it was at its own change ("body validated as passthrough BaseSchema"; "BaseSchema with type narrowed to the closed set"; a read through the signature "is any"). Its one standing claim, "What still refuses", holds at this head: the spec-family excess key, a type outside both vocabularies, and the no-type envelope corner, which the MEASURED LIMIT pin still holds while the signature stands. Its consumer advice holds for every widget key but title and layout, and layout moved in round 11 with no note here either. Not a non-movement claim; a reader's call, not required.
    • 6002-dashboard-widget-strict (types minor): "Not affected: a metric-card COMPONENT node in a dashboard's widget slot. Its props stay legal ... The legacy envelope also still parses." Holds: the component arm still routes before the strict widget schema, a well-typed card parses on both faces, the envelope parses. The mechanism description ("owned by objectui's own passthrough BaseSchema") is superseded by 11467's own "used to" sentence. Not required.
    • 8344-node-recursion-point-redirect (types minor), fact 1 on DashboardWidgetSchema.component: "names BaseSchema explicitly instead of following the redirect, so the widget slot keeps admitting metric-card ... a PRIMITIVE in it is refused." Holds: the slot still names BaseSchema (its second arm), follows no redirect, admits a card, refuses a primitive. Not required.
    • 9256-public-blocks-content-channels (types minor): children?: never / body?: never on the TypeScript arm and the by-name refusals on the zod twin, first arm of the slot's union. Unchanged at this head. Holds.
    • dashboard-widget-type-closed-enum, 8499-node-slot-registered-arms, 9659-node-recursion-point-inert-clause, 6442-catalog-blocks-claimant-list: metric-card as the closed component type the slot holds directly, refused at the root and at node slots, a catalog claimant. All hold.
    • 10859-dashboard-node-keys, 10859-console-phase-2b-stubs, 10859-known-types-phase-2b: node keys and stubs; "the dashboard WIDGET vocabulary is unchanged: the metric-card slot entry authors exactly as before" is that change's own scope statement, and a well-formed card authors the same. Hold.
    • 11070-strict-face-read-keys (types minor): "the dashboard widget keys stay refused until objectui#11070 settles them" on the strict face. This head declares no widget key the strict face refused: title has been admitted on the component node since 11022, and the keys declared here are registry inputs. Holds (round 11's layout already moved that sentence with no note; not this PR's).
    • 9165-metric-percent-converged, widget-config-panel-locale-map-5301, 11388-widget-layout-complete, 8871-page-node-refuses-breadcrumbs (the "component envelope still passes unknown renderer props through" pin, still green on the tolerant face), 10392-registration-record-source-inputs, 8068-mandatory-per-block-member-pin: name MetricCard, widgets[] or a registration's inputs for other reasons; none describes the arm's shape or judgment. Hold.
    • The 16 entries main adds or changes since the merge base (11468-bag-carrier-node-types among them): none names the arm, the slot, metric-card or the mechanism.
  • The Changeset Claim Re-read gate (green) is report-only and keys on a pending body naming a FILE this change touches; the sweep above is what answers the went-false class for symbols.

So ② holds on this head: the changeset says what the diff publishes, and every pending reading this head falsifies now carries its dated note.

③ Boundary flags

  • Q1 (five files outside the claim's surface): A, as implemented. The card body says "no second spelling"; the side table, its two helpers and the walker branch were the second spelling of five names, and the arm was their only caller. The two objectui#11022 pin files asserted the passthrough-plus-side-table state and go red under any value-checked mirror, and the card requires main to stay green when this lands alone, so they move in the same PR. The types README paragraph described the retired mechanism. Each of the five is the same accept-set change. Option B keeps a zero-consumer mechanism and the forbidden second spelling.
  • "Stop on breach; explain in the report." The dev implemented A and asked afterwards rather than stopping. Declared in the PR body and bounded to the retirement the card compels; answered by Q1 and not held against the diff.
  • Route (union, not the arm alone): answered in ① item 6. Right.
  • H1 nuance (the slot was SchemaNode, whose object arm is BaseSchema): the card's reading holds; no action.
  • No merge of main: mergeable: true, state behind; main is six commits past the merge base d93e53f5 (compare read). The one file both sides touch is packages/types/README.md; nothing on main touches complex.ts, complex.zod.ts, strict-authoring-face.ts, node-derivation.ts or the three test files. Fine; re-check at merge.
  • Report wording: "the claim's two named tests ... gained pins", only dashboard-widget-slot-component-arm-7952.test.ts did; content-channel-public-blocks-9256 is untouched. No consequence.
  • check:sdui-registration-pins NOT MEASURED: acceptable; no registration or sideEffects moves (plugin-dashboard src/ is touched only under __tests__).
  • Toolchain, vitest spelling, commit trailers: outside the contract.
  • Out-of-scope finding (b), a valueless metric-card parsing through the widget arm: real, pre-existing (objectui#4600); for the seat to file. It does not falsify the changeset, whose "value required" is scoped to the arm.
  • Out-of-scope finding (c), the mdx type: 'card' widget with children: pre-existing; for the seat to file.
  • envelopeStray MEASURED LIMIT constant: correctly left for objectui#8347's carrier.
  • Registration value: type 'string' against the arm's string | number: the parity test holds the arm to MetricCardProps for types and to the registration for required and the trend enum, not to the registration's type of value; the wider spelling is the renderer's and the objectui#11022 grade pin's (value: 42 parses). Pre-existing registry/props drift, not this PR's defect; for the seat.
  • README precision, residual: "Either way its keys are checked against that arm" is exact on the strict face and on the TypeScript face; the tolerant component slot keeps the BaseSchema fallback (① item 7). The objectui validate sentence beneath it is now scoped to widgets[]. A nit for the seat, not held.
  • Check-runs on the head, read three times, last at 2026-10-02T16:44:56Z: 42 total; 36 success, among them Type Check, Lint, Build & E2E, Build Docs, Bundle Analysis, Spec Main Shape Gate, Test (dist pins), Test shards 1, 3, 5, 6 and 8, Doc Snippet Type Check, Doc Component Type Check, README Export Check, Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Overwrite Report, Changeset Fixed Group Check, Governed Surface Queue Guard, Line Citation Gate, Control Byte Scan and Skill Example Check; 3 skipped (Test coverage, its matrix row, dependabot); 3 in_progress: Test shards 2, 4 and 7. No red at any of the three reads. The three pending shards exercise code byte-identical to 35a89136, where all eight shards were success (record 5956388563), and this head's two commits touch three changesets and README prose only, so none bears on the contract. The derived gate families answered green.

Implemented-by: claude/issue-11467-metric-card-arm
Reviewed-by: session_01XvhGmGAP79ZB8swnkapxPC

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 2, 2026 16:48
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 2, 2026 16:48
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 401611b Oct 2, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11467-metric-card-arm branch October 2, 2026 17:04
os-tesla pushed a commit that referenced this pull request Oct 2, 2026
Brings in objectui#11485, #11486 and #11482; the parity ledger merged
without a conflict (#11485 moved rows in other blocks).

Claude-Session: https://claude.ai/code/session_01XvhGmGAP79ZB8swnkapxPC
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation package: types plugin tests

Projects

None yet

2 participants