Skip to content

fix(components): spell the shipped tabs default items content, the key TabItemSchema declares (objectui#9941) - #9988

Merged
os-tesla merged 2 commits into
mainfrom
claude/issue-9941-tabs-defaultprops-content
Sep 19, 2026
Merged

os-tesla merged 2 commits into
mainfrom
claude/issue-9941-tabs-defaultprops-content

Conversation

@os-tesla

@os-tesla os-tesla commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator

Fixes #9941

Clause-②: yes — packages/components publishes src/, and this moves the ui:tabs
registration's defaultProps, which is the metadata a designer palette writes into a new
node. Declared as .changeset/9941-tabs-default-items-content.md
(@object-ui/components: patch); node scripts/check-changeset-presence.mjs exits 0 and
names it.

What was wrong, re-taken at source on this branch's base (1db82373f)

The contract, packages/types/src/zod/layout.zod.ts:427-:433 on this base, not the
:420-:426 the triage audit cites:

:427  export const TabItemSchema = z.object({
:428    value: z.string().describe('Unique tab identifier'),
:429    label: z.string().describe('Tab label'),
:430    icon: z.string().optional().describe('Tab icon'),
:431    disabled: z.boolean().optional().describe('Whether tab is disabled'),
:432    content: z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]).describe('Tab content'),
:433  });

:432 carries no .optional(), so content is REQUIRED, and the object declares no body
member at all. The seed, packages/components/src/renderers/layout/tabs.tsx :88-:90, spelled
all three items the other way — and the defaults were unmoved at the numbers the card gives.

The parse, before and after

Measured against the BUILT schema (packages/types/dist/zod/index.zod.js, from
pnpm --filter @object-ui/types build), not the source module:

item TabItemSchema.safeParse reading
shipped BEFORE — { label, value, body: [...] } success: false invalid_type at path ["content"], expected: "nonoptional"
lit control / shipped AFTER — { label, value, content: [...] } success: true parsed keys value,label,content

One step up, the whole registration seed as a node — TabsSchema.safeParse({ type: 'tabs', ...defaultProps })
— went from three invalid_type issues at ["items",0,"content"], ["items",1,"content"],
["items",2,"content"] to success: true.

Control A quoted — the refusal, verbatim from the run

--- SHIPPED-TODAY (body) => success: false
    issues: [ { "code": "invalid_type", "path": ["content"],
                "expected": "nonoptional",
                "message": "Invalid input: expected nonoptional, received undefined" } ]
--- LIT CONTROL (content) => success: true
    parsed keys: value,label,content

⚠️ The refusal is about the ABSENT key and never about the present one. TabItemSchema is a
stripping z.object, so the undeclared body the seed carried was silently DROPPED by the
parse rather than rejected. The pin records that as its own row rather than leaving a reader to
assume a strict face.

Control B — the UI did not move

tabs.tsx :67 reads

{renderChildren(item.content || (item as any).body)}

content first, body as a fallback through an any cast — so the seeded nodes were already
drawing, from the fallback arm. Respelling moves the same nodes onto the primary arm. Asserted,
not assumed: the pin renders the seed both ways through the real registry renderer and compares
document.body.textContent and innerHTML (with id / aria-controls / aria-labelledby
blanked, the only attributes Radix mints per mount). Both are equal, and a lit control requires
Content for Tab 1 to be in the text so that two EMPTY renders cannot pass as equal.

The relation pin was reachable, and it parses rather than spell-checks

packages/components/src/renderers/layout/__tests__/tabs-default-items-parse-9941.test.tsx.

A test that checks the three items spell content is satisfied forever by a copy-paste. This one
reads the seed off ComponentRegistry.getMeta('tabs', 'ui') — the object a designer palette
consumes — and the contract off @object-ui/types/zod — the object a validator consumes — and
feeds one through the other. Nothing in it is a copy of either face. It fails the day anyone
re-spells a member, adds a fourth item that omits content, or moves the seed.

Its rows, and which of them discriminate:

row kind ablated fixed
the registration exists and carries a defaultProps seed anti-vacuity green green
the seed carries a non-empty items array anti-vacuity green green
controlA — the pre-fix item is refused, naming content control green green
controlA — undeclaredBodyIsDropped control green green
every seeded item parses green against TabItemSchema SUBJECT red green
the whole registration seed parses as a tabs node SUBJECT red green
each seeded item keeps a child list after the parse SUBJECT red green
controlB — stillRenders control green green
controlB — equality of the two spellings' render control green green

The controls being green in BOTH worlds is the claim, not a gap: the repair is at the parse, so a
control row that moved with it would be evidence the repair had been visual after all.

Reverse verification — on-disk proof, then restore proved by hash

Run from the COMMITTED state, mutation written by an anchored replace whose anchor had to match
exactly once per item, with a trap ... EXIT INT TERM restore:

HEAD blob = cfa19819de28366b6997cf5ccba0936470f08b24
mutation written
ON-DISK PROOF: injected `body:` seed items = 3 (want 3) · remaining `content:` seed items = 0 (want 0)
mutated blob = 502350f2bc346ec1cefc3312a4d1465199b1710e (differs from HEAD: yes)
fallback arm still present: 1
ABLATED VITEST EXIT=1   ->   Tests  3 failed | 6 passed (9)
RESTORED: blob cfa19819de28366b6997cf5ccba0936470f08b24 == HEAD, `git diff HEAD` empty

The three reds are exactly the three subject rows; the failure text is the one an author would
want: items[0]: invalid_type [content], and the same for 1 and 2. Restore is proved by the blob
hash matching HEAD and by git diff HEAD being empty — not by the exit code of the checkout.

What is fenced out of this change

  • packages/types/src/zod/layout.zod.ts is untouched. Widening TabItemSchema to admit body
    would turn a misspelled default into an already-published accept set growing, and it would
    answer the dialect question ahead of the maintainer.
  • ⛔ The || (item as any).body fallback at :67 is untouched. Retiring that tolerance belongs to
    the dialect family — objectui#9871 and objectui#9910, both open and both awaiting a user
    decision. ⚠️ The controlB equality row is the one row in the pin that reads that arm; when the
    fallback is retired, that row retires WITH it, and its own docblock says so. The subject rows and
    the durable stillRenders row do not depend on the arm.
  • ⛔ The pin is not widened past ui:tabs. Whether a registration's defaultProps may diverge from
    its published face repo-wide is the open question on objectui#4631, and the sibling pin in the
    same directory (registration-defaults-match-renderer-8229.test.ts) documents at length why
    deciding that by gate rather than by ruling is the trap.
  • ⛔ Not the vscode extension's hover text (objectui#9942), ⛔ not objectui#6771 — TabItem's
    canonical child key is content, not children, so that remedy names a key this face does not
    have.

Verification

what command reading
dependency closure pnpm --workspace-concurrency=2 --filter '@object-ui/components^...' build exit 0, 7 packages built
the new pin pnpm exec vitest run packages/components/src/renderers/layout/__tests__/tabs-default-items-parse-9941.test.tsx exit 0 — Test Files 1 passed, Tests 9 passed
affected package, whole suite pnpm exec vitest run packages/components/ (under the shared verify lock) exit 0 — Test Files 283 passed (283), Tests 2752 passed (2752)
types pnpm --filter @object-ui/components type-check exit 0 (both tsc --noEmit and tsconfig.test.json)
lint pnpm --filter @object-ui/components lint exit 0 — 0 errors. The three warnings on tabs.tsx (:21 twice, :67) are pre-existing and untouched; the new test file produces none
changeset presence node scripts/check-changeset-presence.mjs exit 0, names the changeset
changeset shape node scripts/check-changeset-no-major.mjs, pnpm check:changeset-claims, pnpm check:pending-changeset-literals exit 0
gates the diff touches pnpm check:control-bytes, check:new-line-citations, check:component-surface-parity, check:test-path-roots, check:unreferenced-sources exit 0 each
governed surface node scripts/check-governed-queue-guard.mjs --test over the three paths NOT GOVERNED — ordinary route

⚠️ NOT MEASURED locally, declared to CI: pnpm check:sdui-registration-pins exits 2
(PREREQUISITE NOT MET, ⛔ not a failure) — it weighs apps/console/dist/assets, and building the
console needs the whole workspace build closure. That is a repo-level run CI owns. This diff
changes a literal inside an existing registration's defaultProps and adds no registration, so it
cannot move that gate's verdict; CI is the place that reads it.

The cross-file line addresses above are deliberately in this body and ⛔ not in the source: the
pin cites TabItemSchema by SYMBOL, per objectui#8875, and
node scripts/check-new-cross-file-line-citations.mjs exits 0 on this branch.

⭐ Seat addendum — the second commit, and why it is in this PR at all

Written by the domain:ui#2 seat at 2026-09-19T04:20Z under the standing clause that a dev writes a PR body once and the seat writes later corrections. Ruling and four-axis reasoning: objectui#9941 comment 5739237546. auto-adjudicated, 代裁 channel, with the 否决窗口 open.

Respelling the three seed items to content emptied the body-dialect producer table, and two scan test files assert that table is non-empty. The implementer stopped at the face boundary and measured both sides before anything was edited — that measurement is what decided the route.

BASE 1db82373f HEAD 4e2b7a615
producers 3 0
by carrier {item: 3} (empty)
by channel {default-props: 3} (empty)
filesScanned 5152 5153
hits 1069 1068
failedC2 313 313
unclassified 5 5
readers 4 4

The three BASE rows in full: tabs.tsx:88, :89, :90, each carrier item, channel default-props, disposition unruled:item-carrier. Zero node-carried and zero string-carried producers on either side. The deltas reconcile exactly — filesScanned +1 is the new pin file, hits −3 from tabs.tsx and +2 from body mentions inside that pin which bucket as test corpus and fail C2 — so nothing but this change moved.

⇒ ⭐ those three lines were the tree's last producers of any carrier, not merely its last item-carried ones. That is why re-pointing only the carrier === 'item' assertions was not enough: producers.length > 0 would have stayed red in both files.

The affected assertions, including the ones that never reported

file assertion before after
producer-scan.test.ts :556 carrier === 'item' REPORTED red re-pointed to toBe(0)
producer-scan.test.ts :557 files.size latent red re-pointed with it
producer-scan.test.ts :608 producers.length latent red re-pointed
producer-scan.test.ts string-carried toBe(0) green now vacuous — marked in place
census.test.ts :393 producers.length REPORTED red re-pointed
census.test.ts :405 carrier === 'item' latent red re-pointed
census.test.ts :487 disposition loop green now vacuous — marked in place

⚠️ Four of the seven never reported: an earlier assertion in the same block fails first. A count of "two failing tests" would have understated the blast radius by five.

⛔ What the re-pointed claims deliberately do NOT say

Each block now states that the table is empty under objectui#9871's emission criterion, that its last subject was a conformance fix against TabItemSchema rather than a dialect migration, and — explicitly — that ⛔ this is not objectui#9590's finish line and this PR does not claim it is. The evidence they carry for that refusal: failedC2 313 and unclassified 5 identical on both sides (318 rows outside what the criterion scores as emission, none of them moved), and objectui#9989 — a published doc still teaching the spelling. ⭐ Emitted and taught are different verbs; this scan reads only the first.

Each block also names the DIFFERENTIAL fixture at the top of producer-scan.test.ts, which plants ITEM_PRODUCER in a synthetic root and still asserts carrier ['item'], line 6, channel 'default-props' — it passed in the run that reported these reds. ⭐ That is what makes standing down a live-tree control safe rather than blind: the SHAPE stays reachable, only its live occupancy is zero.

⚠️ A real change in what these blocks guard, and not a cosmetic re-word: they flip from > 0 to toBe(0), so they are now ratchets in the opposite direction — a returning producer goes red, with a message naming what a non-zero means. That follows the file's own idiom rather than introducing one. It is also why objectui#9590's record is where these blocks now point.

Nothing was skipped to reach green

Test Files 2 failed (2) / Tests 2 failed | 44 passed (46)Test Files 2 passed (2) / Tests 46 passed (46). ⭐ 2 + 44 = 46 = 46, so the denominator is identical. Confirmed structurally as well as by the totals: expect( 94 → 94 and 92 → 92, it( 24 → 24 and 22 → 22, and it.skip / describe.skip / .todo / .only return nothing across both files. packages/components/ stayed at 283 files / 2752 tests across all three runs.

scripts/body-dialect-producer-scan.mjs is untouched — the scan's behaviour is right, its subject moved. ⛔ TabItemSchema is untouched. ⛔ The first commit's work is byte-identical: git diff HEAD -- packages/ .changeset/ against 4e2b7a615 is empty.

⚠️ Declared, not hidden: scripts/** has no type-check in this repo — no scripts/tsconfig*.json, and root type-check is turbo run type-check, per package. These two files are TypeScript executed by vitest without type-checking, so eslint plus execution is the whole local type surface for them.

Acceptance notes

Out of scope for this pull request, ⛔ not ridden along, each measured:

  • To filecontent/docs/guide/schema-rendering.md, the "Lazy Loading" JSON fence, teaches a
    tabs node as { "type": "tabs", "lazyLoad": true, "tabs": [ { "title": ..., "body": ... } ] }.
    Copied verbatim it is refused by the published TabsSchema: invalid_type at path ["items"],
    expected array, received undefined — measured against the built schema. tabs, title, body
    and lazyLoad are all undeclared on that face and are stripped, so the node renders an empty tab
    bar. Same class as this card (a shipped sample its own schema refuses), different carrier.
    Dedupe words: schema-rendering lazy loading tabs example · tabs key not items · doc example fails TabsSchema · tab title body undeclared · lazyLoad undeclared on tabs.
  • Noted, not filedexamples/schema-catalog/src/schemas/components-layout-tabs/basic-tabs.json
    already spelled items[].content correctly, so the catalog and the registration disagreed with
    each other and the catalog was the one that was right. Nothing to repair; recorded because it is
    the reason nothing downstream had to change with this seed. Handler: none — no pull request or
    person is heading for that file.

Generated by Claude Code


Generated by Claude Code

…e key `TabItemSchema` declares

All three items in the `ui:tabs` registration's `defaultProps` spelled `body`
and omitted `content`, so the default an author drops on a canvas was refused
by the validator this repository publishes for it:

    invalid_type ["content"] expected nonoptional, received undefined

`TabItemSchema` declares `content` with no `.optional()` and declares no `body`
member at all, so the key the seed carried was stripped by the parse and the
key the parse required was absent.

No rendered output moves. `tabs.tsx` reads `item.content` first and falls back
to `body` through an `any` cast, so the same nodes move from the fallback arm
onto the primary one; the repair is at the parse. Both halves are asserted, not
assumed, by the new pin.

The pin parses whatever the registry actually carries through the published
schema rather than checking the spelling, so a re-spelling, a fourth item that
omits `content`, or a moved seed all fail it. Its control A feeds the pre-fix
item to the same call and requires a refusal naming `content`, so a parse that
cannot refuse anything cannot pass as green.

The `|| (item as any).body` fallback and `TabItemSchema` itself are untouched:
widening the published accept set to admit `body` would grow an accept set that
is already shipped and pre-empt the dialect question that is still open.

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

github-actions Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 2 address(es) in this pull request's own prose name a tree it replaced

Each was read from a tree this change itself moves, so a reader who follows it lands somewhere else. ⛔ Nothing here blocks and nothing here says the sentence is false — the question asked is arithmetic: does this diff move the line that number points at?

  • in this body, :88 — this change rewrites packages/components/src/renderers/layout/tabs.tsx:88

    The seed, packages/components/src/renderers/layout/tabs.tsx :88-:90, spelled

  • in this body, :90 — this change rewrites packages/components/src/renderers/layout/tabs.tsx:90

    The seed, packages/components/src/renderers/layout/tabs.tsx :88-:90, spelled

The repair is not to correct the number. Changing :246 to :274 is true today and born false again on the next insertion — objectui#9509 states that before anything else. Bind the number to the tree it was read from (`:246` at `b8a006883d`, `:274` at this head), which cannot re-stale because each number names its own tree; or state a rule instead of a coordinate, the way objectui#9495 replaced a file count with "every file in git diff --name-only against the merge base".

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

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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3053.8 KB 3104.5 KB
Main entry chunk (gzip) 145.7 KB 350 KB
Entry file index-DeXZrNJg.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.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 545.92KB 130.72KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 216.90KB 60.15KB
fields (index.js) 249.62KB 63.02KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.83KB 10.95KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.92KB 14.22KB
plugin-charts (index.js) 71.73KB 20.08KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 132.96KB 35.17KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 254.30KB 66.21KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 137.49KB 34.64KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 213.44KB 58.21KB
plugin-kanban (index.js) 48.10KB 14.94KB
plugin-list (index.js) 113.42KB 27.95KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.05KB 21.01KB
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.66KB 3.50KB
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) 108.85KB 36.01KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.38KB 1.98KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.74KB 2.54KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 15.71KB 5.30KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.04KB 5.36KB
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

…ped to the emission criterion

The three `tabs.tsx` `defaultProps` items respelled in the previous commit were
the whole producer table under objectui#9871's criterion, so it is now empty and
four live-tree claims across the two scan test files went red. Each of them
carried an instruction from its own author for exactly this event — re-point the
block and say so in those terms, do not delete the claim — and that is what this
does.

Measured both sides with the unchanged scan against the same uninstalled tree:

  base  producers=3  filesScanned=5152  readers=4  hits=1069  failedC2=313  unclassified=5
        all three carrier 'item', channel 'default-props',
        disposition 'unruled:item-carrier', at tabs.tsx :88 :89 :90
  head  producers=0  filesScanned=5153  readers=4  hits=1068  failedC2=313  unclassified=5

Each re-pointed claim says, and does not exceed:

  1. the table is empty UNDER objectui#9871's EMISSION criterion;
  2. its last subject was the three tabs.tsx items, respelled to `content`
     because `TabItemSchema` declares `content` required and declares no
     `body` — a conformance fix, not a dialect migration;
  3. this is NOT the repo-wide finish line and no block here claims it is.
     `failedC2` held at 313 and `unclassified` at 5 across the same change, so
     318 rows outside what the criterion scores as emission did not move, and a
     published doc is still teaching the spelling. Emitted and taught are
     different verbs; this scan reads only the first;
  4. the instrument is not blind. The DIFFERENTIAL test plants `ITEM_PRODUCER`
     in a synthetic root and still asserts carrier ['item'], line 6, channel
     'default-props', and it passed in the same run that reported the reds.
     Every re-pointed block names that fixture, because it is what makes
     standing down a live-tree control safe rather than blind.

Two assertions that now pass over an empty set are marked vacuous in place
rather than left silent: the string-carried `toBe(0)` and the disposition loop
over item-carried producers. Both are kept, because each is the claim that fires
the moment a producer returns.

Nothing was deleted, no assertion was skipped, and the scan itself is untouched:
its behaviour is right, its subject moved. Totals are unchanged at 46 tests
across the two files (was 2 failed | 44 passed, now 46 passed), and
packages/components/ holds at 283 files / 2752 tests.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3054.3 KB 3104.5 KB
Main entry chunk (gzip) 145.7 KB 350 KB
Entry file index-BrupwDjD.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.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 545.92KB 130.72KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 216.90KB 60.15KB
fields (index.js) 249.62KB 63.02KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.83KB 10.95KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.92KB 14.22KB
plugin-charts (index.js) 71.73KB 20.08KB
plugin-chatbot (index.js) 195.35KB 46.52KB
plugin-dashboard (index.js) 132.96KB 35.17KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 254.30KB 66.21KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 137.49KB 34.64KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 213.44KB 58.21KB
plugin-kanban (index.js) 48.10KB 14.94KB
plugin-list (index.js) 113.42KB 27.95KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.05KB 21.01KB
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.66KB 3.50KB
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) 109.04KB 36.08KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.38KB 1.98KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.74KB 2.54KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 15.71KB 5.30KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.04KB 5.36KB
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

Copy link
Copy Markdown
Collaborator Author

Test (shard 1/4) is CANCELLED, ⛔ not failed — and the test body passed. Standing down with the one permitted re-run.

domain:ui#2 execution seat, session_018HrVaotisyhgmot9o2MLRq, 2026-09-19T04:38Z. Head 2b807400ca37889358340d2bf6297c837d05e4a3.

What the job actually says, step by step

Job 105838196947, run 35420871004 attempt 1. ⭐ Its conclusion is cancelled, ⛔ not failure — and the steps say where it stopped:

step
7 Install dependencies ✅ success
8 Run tests (shard 1/4) success
9 Run built-artifact pins (dist project) cancelled

⇒ ⭐ the test body ran and passed. What was cut off is a later step. ⛔ No assertion in this PR failed, and ⛔ nothing in the shard reported a red test.

Why it is ⛔ not this PR's

shard conclusion started → completed duration
4/4 success 04:17:10 → 04:34:18 17m08s
2/4 success 04:17:11 → 04:36:05 18m54s
3/4 success 04:17:10 → 04:34:53 17m43s
1/4 cancelled 04:17:10 → 04:37:24 20m14s

The cancelled shard is the longest, by more than a minute over its nearest sibling — and Test (shard 1/4)'s duration is already a carded problem in another lane: objectui#9499 (domain:devx, pm:awaiting-maintainer) measures it at 18.0–19.6 min against its own timing budget. This run put it at 20m14s.

⚠️ Stated as what it is: this seat measured the step table, the four durations and objectui#9499's recorded range. It has ⛔ not proved the mechanism — that a timeout-minutes cap cancelled it is an inference from 「longest shard, cut off mid-step, run conclusion cancelled」, ⛔ not a reading of a cap. The inference is not load-bearing: 「the test body passed and a later step was cut off」 is enough on its own to say this is not an assertion failing in this PR.

The one re-run, spent here

POST …/actions/runs/35420871004/rerun-failed-jobs — accepted. ⭐ That is the one re-run this PR gets. ⛔ 「Flake」 is not a root cause and a second cancellation would be treated as real, not retried.

⛔ Nothing was skipped, disabled or quarantined to get here, and ⛔ no empty commit was pushed to kick CI.

For the record, since this PR's own history invites the question

This PR's earlier red at head 4e2b7a615 was genuine and this PR's own: body-dialect-producer-scan.test.ts and body-dialect-census.test.ts asserted a producer table this change empties. That was root-caused, ruled (objectui#9941 comment 5739237546) and fixed in commit 2b807400c, with Tests 2 failed | 44 passed (46)Tests 46 passed (46) — identical denominator, so nothing was removed to reach green. ⛔ This cancellation is a different thing entirely and ⛔ does not re-open that.

domain:ui#2 execution seat · session_018HrVaotisyhgmot9o2MLRq · the job's step list, the four shard durations, the run's conclusion and objectui#9499's recorded range were all read from the API in this act.


Generated by Claude Code

@os-tesla
os-tesla marked this pull request as ready for review September 19, 2026 04:58
@os-tesla
os-tesla added this pull request to the merge queue Sep 19, 2026
Merged via the queue into main with commit ff0c384 Sep 19, 2026
47 of 48 checks passed
@os-tesla
os-tesla deleted the claude/issue-9941-tabs-defaultprops-content branch September 19, 2026 05:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants