Skip to content

feat(app-shell,types): cloud:plan-status marks the organization's current plan card (objectui#10919) - #10990

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-10919-cloud-plan-status
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-10919-cloud-plan-status

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10919
Clause-②: yes

What this does

The Cloud pricing page is static metadata, so it cannot tell which of its plan cards the organization is already on (objectstack-ai/cloud#2434 item 5). This PR adds the SDUI widget cloud:plan-status. A page places one node on each plan card and names that card's plan code in properties.plan. The widget reads the org-scoped GET /cloud/environment-entitlements summary and renders a localized "Current plan" badge when the summary's plan equals properties.plan. It renders nothing in every other state: another plan, loading, a failed request, a body that is not the { success, data } envelope, or a node that names no plan. It never guesses a plan.

The snippet cloud places in each planCard(...) column of packages/service-tenant/src/pages/pricing.page.ts, for the cloud#2434 item 5 follow-up:

{ id: `plan_${opts.name}_current`, type: 'cloud:plan-status', responsiveStyles: { large: { alignSelf: 'flex-start' } }, properties: { plan: opts.name } },

The card names free, team and enterprise are the summary's plan codes for those cards. The endpoint can also report solo, business, starter, pro and custom, and an organization on one of those gets no card marked, because the page lists no such card.

Routing: the triage answer 5867937466, quoted

Lane: domain:ui.

  • The deliverable is the cloud:* widget (packages/app-shell). The maintainer placed epic objectstack-ai/cloud#2440's cards on this lane (「全部接 (推荐)」).
  • The claim declares packages/types/src/zod (the widget's arm), with Clause-②: yes (widening), so it owes an at-tier contract review.
  • Serial: after PR objectui#10908 (objectui#10872 batch 2), which holds the same ratchet test and the namespaced-arm work.

The open sub-question (does the arm go through objectui#10872's programme?): no.

  • objectui#10872 arms blocks by reference to a @objectstack/spec ComponentPropsMap row, and cloud:* has no spec row. ⛔ Don't invent one.
  • Declare the arm locally in @object-ui/types/zod, in the file's existing local-arm shape, measured from the widget's read points.
  • If PR objectui#10908 changes the arm mechanism, rebase onto it.

What the accept set gains (Clause-②: yes)

AnyComponentSchema gains exactly one type literal, cloud:plan-status, through one new arm, CloudPlanStatusSchema, exported by name from @object-ui/types/zod. So do safeValidateSchema and objectui validate, which judge with it. A document with that type is accepted only when all of these hold:

  • properties is present and is exactly { plan }. The bag is strict: any other key is unrecognized_keys at properties.
  • plan is a non-empty string: invalid_type or too_small at properties.plan otherwise. It is not an enum, because the plan catalog belongs to the control plane.
  • There is no body and no children. Both are refused by name (invalid_type), because the widget reads neither channel.
  • Every other node key is judged as on every arm: BaseSchema's declared keys by their types; an undeclared key passes the tolerant face and is refused by the strict face (StrictAnyComponentSchema).

Nothing that parsed before is refused now. NAMESPACED_REFUSED_AT_TYPE stays 397 and REFUSED_AT_TYPE stays 73: the one new registered key lands with its arm.

What changed

  • packages/app-shell/src/console/home/CloudPlanStatus.tsx (new): the widget and its registration. It reads the plan through the existing useEnvironmentEntitlements hook, which is not edited. The hook's authFetch comes from useState, not useMemo, because the hook keys its fetch effect on it (AGENTS.md [WIP] Enhance every detail of the designer #10). The node's className reaches the badge, so the page's responsiveStyles scope class does too.
  • packages/types/src/zod/cloud.zod.ts (new): CloudPlanStatusSchema, BaseSchema plus the cloud:plan-status literal, a required strict properties: { plan } bag, and the two content-channel refusals. The refusal text carries the objectui#10928 parser-tier clause: the registration declares no children input, so validateTree warns not-a-container for this node.
  • packages/types/src/zod/index.zod.ts: the barrel export, the import, and the one union line. origin/main is merged in (19fc41a), per the claim's note on the draft PR objectui#10962.
  • packages/types/src/zod/README.md: a "Cloud Widgets" group lists the arm.
  • packages/cli/src/utils/known-schema-types.ts: regenerated by node scripts/regenerate-known-schema-types.mjs. It gains the one line cloud:plan-status.
  • packages/i18n/src/locales/*.ts: cloudPlanStatus.current ("Current plan") in all ten packs.
  • Ledgers and pins: one EXCLUSIONS row in zod-mirror-parity.test.ts (no TS declaration in @object-ui/types restates the node), cloud-plan-status-arm-10919.test.ts, CloudPlanStatus.test.tsx, and one ratchet row naming cloud:plan-status as armed.
  • .changeset/10919-cloud-plan-status.md: app-shell and types minor, i18n and cli patch.

PM assumptions, measured: three falsified, two confirmed

  1. Falsified: the props shape. The suggested node was { type, plan }. The measured node is { type, properties: { plan } }. Cloud's pages are typed Page from @objectstack/spec/ui, and PageComponentSchema is a strictObject whose only props channel is properties, so a top-level plan is refused by the spec parse before objectui sees it. cloud:onboarding-next is authored the same way on the welcome page, and it reads properties.*. The widget reads properties.plan and nothing else, and the arm declares exactly that.
  2. Falsified: the registration spelling. The precedent register('cloud:onboarding-next', …, { namespace: 'app-shell' }) makes TWO registry keys, app-shell:cloud:onboarding-next and the fallback cloud:onboarding-next, and the namespaced ratchet counts both. Following it would have needed the arm to accept a second literal nobody authors. So the widget registers as register('plan-status', …, { namespace: 'cloud', skipFallback: true }): one key, one arm literal, and one generated line.
  3. Falsified: the i18n key name. It is cloudPlanStatus.current, not cloud.planStatus.current, following the sibling blocks cloudOnboarding.* and aiModelStatus.*.
  4. Confirmed: the hook reads the plan, org-scoped. useEnvironmentEntitlements sends ?organizationId= from useAuth().activeOrganization.id, as the precedent does, and the endpoint resolves an absent parameter to the session's active organization. Its row-derived fallback carries no plan, so the widget marks a card only on source: 'summary'.
  5. Confirmed: plan values. The endpoint's plan is cloud's planKey(...) over PLAN_CODES (free, solo, team, business, starter, pro, enterprise, custom). ObjectUI names none of them.

Surface supplement: two files outside the claim's declared surface

packages/app-shell/src/index.ts (one side-effect import line) and packages/app-shell/package.json (two sideEffects entries). A registration cannot ship without both. Without the barrel import, the console never evaluates the module. Without the array entries, scripts/check-side-effects-array.mjs goes red, because every registering module in the entry graph must be named there, and a bundler would be free to drop the registration. Both edits have the same shape as CloudOnboardingNext's. Overlap, read on the open PR list (REST pulls/N/files, 2026-09-28): none of the ten non-release open PRs touches either file. The changesets release PR rewrites only the version field. Seat: please add these two paths to the claim's file surface.

Verification, at 19fc41a (the merge of origin/main 42687ba)

  • Suites (pnpm exec vitest run --maxWorkers=2, through the verify lock): packages/types/, packages/cli/, packages/i18n/, plus a declared narrowing of packages/app-shell/ (below). Result: Test Files 407 passed (407), Tests 8074 passed | 13 skipped.
  • The new pins, verbose: cloud-plan-status-arm-10919.test.ts 12/12; CloudPlanStatus.test.tsx 10/10; the ratchet file 15/15, including the new cloud:plan-status row.
  • type-check, after turbo run build --filter='@object-ui/app-shell^...' (28/28): @object-ui/types, @object-ui/i18n, @object-ui/cli and @object-ui/app-shell all exit 0. --listFilesOnly shows each package's test program includes the new test files.
  • eslint: the 19 touched .ts/.tsx files, --format json, 19 files, 0 errors and 0 warnings. The narrowing is a measurement: the packages lint under the root eslint.config.js alone (no package-level config), and that config enables no type-aware linting (0 hits for projectService, parserOptions, TypeChecked or tsconfigRootDir), so this diff cannot move the verdict on any untouched file.
  • Gates, all exit 0: regenerate-known-schema-types.mjs --check, check:side-effects-array, check:i18n-keys, check:i18n-drift, check:i18n-dead-keys (report), check:registry-bare-names, check:doc-types, check:prompt-keys, check:unreferenced-sources, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:test-path-roots, check:new-line-citations (0 new), check:control-bytes, check:handler-key-reads, check:changeset-claims (report), check:pending-changeset-literals, changeset:check, check-changeset-presence (4 released packages, 1 changeset), check-changeset-overwrite, check-type-check-coverage, check:phantom-deps, check:esm-specifiers, check-lint-coverage, and check-governed-queue-guard --test over the 22 paths: NOT GOVERNED.
  • Reverse verification, run at 5c4a1d2 (the merge changed neither mutated file). Each leg was committed first, mutated through ablation-replace.mjs with an EXIT/INT/TERM restore trap, then restored with git checkout HEAD --:
    • Ablate the arm (drop the union line in index.zod.ts). The ratchet goes red: "refuses 398 registered namespaced key(s) at type; the pin is 397", and the Refused list names cloud:plan-status. The arm pins go red too (14 failed). Restored: blob 6fa23ee31c7c equals HEAD, and git diff HEAD is empty.
    • Ablate the match (entitlements.plan === plan becomes true). The widget goes red on "renders nothing on a card whose plan is not the organization's" and "compares the plan code verbatim". Restored: blob 08d10662137c equals HEAD, and git diff HEAD is empty.
    • Both subjects resolve to SOURCE (vitest.config.mts aliases @object-ui/types/zod to src), so no dist rebuild was part of either leg.

NOT MEASURED, each with its reason:

  • packages/app-shell/ full suite (856 files): at --maxWorkers=2 it exceeds the foreground cap (exit 124 at 560 s, and exit 124 on shard 1/6 at 270 s). The run was narrowed to the 40 files that read what this diff touches: the two widget directories (console/home/__tests__, environment/__tests__), src/__tests__/, and the 13 tests that read package.json, src/index.ts or the barrel. Result: 40/40 files, 575 tests. CI runs the whole suite.
  • check:sdui-registration-pins, check:eager-closure and check:eager-locale-catalogues need a built console (PREREQUISITE NOT MET, exit 2). CI's performance-budget.yml runs them on packages/**.
  • check:readme-exports (exit 1) and check:doc-examples (exit 2) could not judge the READMEs of packages not built here (app-shell, cli, plugin-*). Among what was judged: 0 wrong-path and 0 fabricated.
  • No browser run against a live control plane.

Acceptance notes (not filed; no carrier)

  • Each node reads the summary itself, so three cards make three GETs of the same summary. The hook has no shared cache, and none was added.
  • The Free card's CTA ("Get started") still shows to an organization on Free. This PR marks the card and leaves the CTA alone. Whether the widget should also stand in for the CTA is an open question in the report.
  • useEnvironmentEntitlements' docblock still describes an environment-list-only hook ("Only fetch when this is the environment list"), and it now has a second caller. Per the claim, the hook is not edited.
  • The precedent registrations' double-prefixed keys (app-shell:cloud:onboarding-next, app-shell:cloud:ai-model-status and their siblings) stay in the refused set. That family is objectui#10872's population.

Generated by Claude Code

…rent plan card

The Cloud pricing page is static metadata and cannot see the organization's
plan. `cloud:plan-status` reads it from the org-scoped entitlements summary
(through `useEnvironmentEntitlements`) and renders a "Current plan" badge on
the card whose `properties.plan` equals it, and nothing otherwise.

- app-shell: the widget, registered as `cloud:plan-status` only
  (`namespace: 'cloud'`, `skipFallback: true`), wired into the barrel and the
  `sideEffects` array.
- types: a local `CloudPlanStatusSchema` arm on `AnyComponentSchema` - a
  required strict `properties: { plan }` bag, both content channels refused.
- cli: `known-schema-types.ts` regenerated; the namespaced ratchet stays 397.
- i18n: `cloudPlanStatus.current` in all ten locale packs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015AUunPkX7UTkCH9e7AdZo1
…o explicit any

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015AUunPkX7UTkCH9e7AdZo1
…'s warning

The objectui#10928 population pin requires every reads-NEITHER refusal to
carry the parser-tier clause; the registration declares no `children` input,
so `validateTree` does warn `not-a-container` for this node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015AUunPkX7UTkCH9e7AdZo1
…oud-plan-status

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015AUunPkX7UTkCH9e7AdZo1
@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 30 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/10756-html-tier-code-element.md

  • names packages/cli/src/utils/known-schema-types.ts → packages/cli/src/utils/known-schema-types.ts — edited by this change

    • @object-ui/components — code joins the TAGS loop of html-elements.tsx: registered ui:code, sanitised like its siblings, declaring className and the children slot. An html author's ANGLE-BRACKETS(code) now renders a real code element with its text. - @object-ui/fields — BREAKING (authoring): the bare code key no longer resolves to the code-editor widget. code joins FIELD_TYPES_SKIP_FALLBACK, so the widget is registered field:code only. A node authored as bare { "type": "code", "value": … } now renders the html tier's code element (it draws children, not value); in a registry without @object-ui/components the bare key resolves to nothing. Migration: { "type": "code" } becomes { "type": "field:code" }. Form fields of type code resolve through the field: namespace and render the same editor as before. Released as minor under objectui's version policy; a breaking change never declares major. - @object-ui/core — code joins HTML_TIER_INTRINSICS, so getPublicConfigs() projects it stamped tier: 'html' and the regenerated sdui.manifest.json grows by one entry (the html tier declares 48 tags where objectui#10735 declared 47); div and kbd stay undeclared. - @object-ui/types — HtmlElementSchema (zod and TS) names code, keeping the JSON-surface declaration equal to the registration, as the objectui#8499 pin requires — so AnyComponentSchema (and objectui validate) now accepts { "type": "code" }, which it refused before. - @object-ui/cli — packages/cli/src/utils/known-schema-types.ts regenerates from the registrations and gains ui:code, so objectui validate and objectui check recognise it as a known schema type (the objectui#9533 and objectui#6416 precedent).

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

  • names packages/types/src/__tests__/zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⚠️ The JUSTIFICATION for that parity clause was retired (objectui#9743); the clause itself stands and did not move. As first written it credited the zod-mirror-parity ratchet with a zero-drift reading for this pair — but at that time the ratchet measured three directions and was structurally blind to the MIRRORED-but-undeclared one (objectui#9711), so a zero from it recorded that it had not looked in that direction, not that nothing was there. objectui#9725 landed the fourth direction, and it covers this pair BY NAME: packages/types/src/__tests__/zod-mirror-parity.test.ts registers objectql.zod.ts#ObjectGanttSchema in both its mirror map and its declaration map; assertionMirroredUndeclaredMatchesLedger requires every registered pair's mirrored-but-undeclared key set to equal that pair's MirroredUndeclared ledger entry — never for a pair the ledger does not name — and assertionNoVacuousMirroredUndeclaredMeasurement refuses a measurement that has degenerated to any. ⛔ Read this pair's verdict off that reconciliation, which re-derives it on every run, rather than off any figure written here; when this paragraph was authored, on 2026-09-18, it required no MirroredUndeclared entry for the pair.

.changeset/5928-classname-style-props-rename.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Where the non-pair is recorded now. zod-mirror-parity.test.ts keys its existing EXCLUSIONS entry — the mechanism that accounts for every exported const with no TypeScript declaration to mirror, each with its stated reason — to ClassNameStylePropsSchema. Named for its own two keys, the const leaves no like-named declaration for a name-derived pairing to reach for.

.changeset/6051-gantt-flat-config-declared-keys.md

  • names packages/types/src/__tests__/zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⚠️ The JUSTIFICATION for that parity clause was retired (objectui#9743) — the same retirement objectui#5903's entry carries, for the same clause and the same reason; the clause itself stands and did not move. As first written it credited the zod-mirror-parity ratchet with a zero-drift reading for this pair, at a time when that ratchet measured three directions and was structurally blind to the MIRRORED-but-undeclared one (objectui#9711), so a zero from it recorded that it had not looked in that direction. objectui#9725 landed the fourth direction, and it covers this pair BY NAME: packages/types/src/__tests__/zod-mirror-parity.test.ts registers objectql.zod.ts#ObjectGanttSchema in both its mirror map and its declaration map; assertionMirroredUndeclaredMatchesLedger reconciles every registered pair's mirrored-but-undeclared key set against that pair's MirroredUndeclared ledger entry — never for a pair the ledger does not name — and assertionNoVacuousMirroredUndeclaredMeasurement refuses a measurement that has degenerated to any. ⛔ Read this pair's verdict off that reconciliation, which re-derives it on every run, rather than off any figure written here; when this paragraph was authored, on 2026-09-18, it required no MirroredUndeclared entry for the pair.

.changeset/6150-undeclared-but-consumed-keys.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⭐ AMENDED, and the amendment ships in this same release. The TreeViewSchema slice (604476d97) gave the key a zod arm after all — a NAMED REFUSAL (handlerKeyRefusal(key, 'runtime-slot', label)), never a shape — because "no mirror entry" is not neutral under BaseSchema.passthrough(): it meant an authored { "type": "tree-view", "onNodeClick": { "action": "toast" } } parsed GREEN, survived the parse, and reached a call site that expects a function. ⇒ the three clauses this bullet used to carry are no longer true of the code shipping beside it. The key is now a MEMBER of TreeViewSchema.shape and an authored value is refused BY NAME at path onNodeClick; it has LEFT zod-mirror-parity.test.ts's RuntimeOnlyDeclared for that file's KnownDrift; and it is no longer "the first pair to sit there without also sitting in UnmirroredDeclared" — draining it emptied that difference, so RuntimeOnlyDeclared is now a SUBSET of UnmirroredDeclared and the union of the two equals UnmirroredDeclared itself. ⛔ objectui#6152's ruling is untouched by any of this: what the key still does not have, and never will, is a z.function() shape — no serialized document could satisfy one.

.changeset/6175-column-state-persistence.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Nothing is retired. Both spellings remain declared on DataTableSchema; onColumnReorder stays declared and stays unwired, exactly as the RuntimeOnlyDeclared ledger in zod-mirror-parity.test.ts records it. Which of the two survives is a declared-surface ruling that stays open and is deliberately not settled here.

.changeset/6238-object-payload-enabled.md

  • names packages/app-shell/src/index.ts → packages/app-shell/src/index.ts — edited by this change

    No published type changed. ObjectMetadataPayload is exported from its module but that module is not re-exported by packages/app-shell/src/index.ts, the package's only entry, so the removed enabled?: boolean was never on the published surface and no **/src/index.ts is touched. What consumers can observe is behaviour: MetadataService is reachable through the published useMetadataService() hook, both method signatures are unchanged (PromiseANGLE-BRACKETS(void)), and the HTTP request they issue changes from a PUT with a body to a DELETE.

.changeset/6416-plugin-report-namespace.md

  • names packages/cli/src/utils/known-schema-types.ts → packages/cli/src/utils/known-schema-types.ts — edited by this change

    packages/cli/src/utils/known-schema-types.ts is regenerated from the registrations, dropping report:report, report:report-viewer and report:spec-report.

.changeset/6559-expression-user-input-contract.md

  • names packages/app-shell/src/index.ts → packages/app-shell/src/index.ts — edited by this change

    WHAT BREAKS, AND FOR WHOM. This tightens a signature published from the package entry (packages/app-shell/src/index.ts), so an external caller that passes an unchecked or under-declared value stops compiling on upgrade. That is accepted (maintainer ruling 2026-08-27, option A): the only calls it refuses are calls that were never conformant with the contract the module already declared. It ships as minor, not major — objectui's major tracks @objectstack's, so its own breaking changes ship as a minor with the break written down (scripts/check-changeset-no-major.mjs). ⛔ Keeping unknown and ⛔ adding a second, wider entry point were both declined.

.changeset/6639-objectgrid-title-mirrored.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The gain is the typed refusal: the mirror's .passthrough() base was already admitting any title unexamined, and it now enforces the declared string. zod-mirror-parity.test.ts's UnmirroredDeclared ledger records the key as worked off — the ledger's first shrink by repair (97 + 1 mirrored + 23 reclassified is what the seeded "121" now means).

.changeset/6661-app-launcher-nav-menu-renderers.md

  • names en.ts → packages/i18n/src/locales/en.ts — edited by this change

    Three new strings — the launcher's and the menu's accessible names, and the menu's empty state — are declared under console.nav in en.ts and its nine sibling packs. An inline defaultValue alone is not a fix: it renders English at one call site and leaves the string untranslatable everywhere (objectui#3517).

.changeset/6776-metadata-admin-lazy-registration.md

  • names packages/app-shell/package.json → packages/app-shell/package.json — edited by this change

    • packages/app-shell/package.json's sideEffects array now names views/metadata-admin/register-builtins (the new leaf that performs the five registrations) instead of views/metadata-admin/index. The five registrations still run at package load, bare-imported by the package entry, so nothing a consumer could observe changes — but the array is a contract every consumer's bundler reads, so the swap is stated here rather than left to a diff. - The package barrel's 25 metadata-admin runtime re-exports (and 11 type-only ones) now point at their leaf modules. Same names, same types. They are unreachable from outside the package by any other path — exports is root-only — so no import an out-of-package consumer can write is affected.

.changeset/7122-spec-17-3-0-parity-reconciliation.md

  • names known-schema-types.ts → packages/cli/src/utils/known-schema-types.ts — edited by this change

    user:profile is retired across all three sites. 17.3.0 dropped it from PageComponentType (measured: the enum went 34 → 32 options, lost set exactly ['user:profile', 'element:form'], gained set empty). objectui went on knowing it in three places, so all three moved together: the Studio palette exclusion ledger, PROTOCOL_COMPONENTS in renderers/placeholders.tsx, and the regenerated known-schema-types.ts the CLI checks schemas against. Nothing user-reachable went with it — neither type had a renderer, user:profile had only the dashed "Component Placeholder" scaffold, and the app shell's own profile affordance is a React slot, never this block type. A page schema still naming it now draws the loud "Unknown component type" panel rather than a silent grey box, which is this repo's standing treatment for a type outside the supported surface.

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

.changeset/7654-floating-chatbot-trigger-icon-tombstone.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Every other tombstone in this package pairs ?: never with a retirementTombstone() refusal on the Zod twin. There is no twin here to carry one: FloatingChatbotConfig has no Zod mirror at all, and floatingConfig sits in the UnmirroredDeclared ledger (zod-mirror-parity.test.ts, complex.zod.ts#ChatbotSchema). BaseSchema is .passthrough(), so the whole floatingConfig object rides through unvalidated — before this change and after it. Minting a mirror to host a refusal would be the declared-but-UNMIRRORED axis (objectui#6152), a different defect: a key can be mirrored and inert, or unmirrored and live, and fixing one says nothing about the other. This change does not widen into it.

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

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

    • Before: series: [{ name: 'revenue', chartType: 'line' }] validated green through @object-ui/types/zod (safeValidateSchema, objectui check / objectui validate, any pipeline that keeps parse()'s output) — and the key was gone from the output, so a consumer of the parse result drew that series in the chart's own family, precisely what the author was overriding. On the TypeScript face the key was merely an excess property on a fresh literal; a widened object carrying it assigned structurally. - After: the same document REFUSES at series[i].chartType (issue code invalid_type) with one message on both channels — the parse-time issue and the .describe() metadata: Unrecognized key(s) on this chart series: \chartType`. Did you mean `chartType` → `type`? …followed by the reason and the remedy. Writetype: 'bar' | 'line' | 'area'. On the TypeScript face ChartDataSeries.chartTypeis a?: nevertombstone, so both the fresh literal and the widened assignment aretsc errors. - **Both written** ({ type: 'bar', chartType: 'line' }) is refused at chartTypealone — the key is not folded ontotypeand no precedence is minted between the two spellings. - **Which documents to scan.** The narrowing does not stop atChartDataSeriesSchema; it reaches every document through the parents that embed it — ChartSchema.series (zod/data-display.zod.ts, z.array(ChartDataSeriesSchema)) and, one level further out, ReportSectionSchema.chart (zod/reports.zod.ts, ChartSchema.optional()). Authors meet it through safeValidateSchema() (zod/index.zod.ts, which parses AnyComponentSchema) and through the CLI's objectui validate command (packages/cli/src/cli.ts). In practice: every chartnode'sseries[], and every report section whose chart` carries one.

.changeset/7704-schema-registry-chatbot-keys.md

  • names packages/cli/src/utils/known-schema-types.ts → packages/cli/src/utils/known-schema-types.ts — edited by this change

    packages/plugin-chatbot/src/renderer.tsx registers three components — chatbot, chatbot-enhanced and chatbot-floating — and SchemaRegistry mapped one of them. Since ComponentType = keyof SchemaRegistry is the published union, a consumer discriminating on it was told two registered keys do not exist: an author narrowing a node by ComponentType, or writing a RecordANGLE-BRACKETS(ComponentType, …) table, had no arm for either. The asymmetry that showed which half was wrong is that packages/cli/src/utils/known-schema-types.ts keeps its own parallel list containing both keys, precisely because this map did not.

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

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The pair moves from zod-mirror-parity.test.ts's RuntimeOnlyDeclared to its KnownDrift, which empties the former of the one entry the latter did not also hold — so the two unmirrored ledgers are now in a containment relation, and the cross-ledger figure that recorded their difference states the containment instead.

.changeset/7917-export-breadcrumb-object-tree-zod-schemas.md

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

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

.changeset/8338-retire-toast-action.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    The parity ledgers drain with it, every figure re-derived by zod-mirror-parity.test.ts's own AST and mirror instruments rather than stepped by hand: KnownDrift 42 entries / 64 keys → 41 / 63 (the entry's whole content, so the entry went too — the ledger's first loss by RETIRING a key rather than by moving either face toward the other), and WiderThanDeclared 23 / 36 / 47 arms, split 6 / 30 / 0 / 11 → 22 / 35 / 45, split 6 / 29 / 0 / 10. The pair itself stays registered, so EXPECTED_MIRROR_PAIRS does not move.

.changeset/8344-node-recursion-point-redirect.md

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

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

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

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

.changeset/8499-node-slot-registered-arms.md

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

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

.changeset/8572-chatbot-body-retired.md

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Why this key and not another. It was the ONE place in this vocabulary where body did not mean "what goes inside this component": zod-mirror-parity.test.ts carried the pair under KnownDrift as "two different meanings of one key", and the same collision was the whole reason chatbot was the single arm of the component union whose output was not assignable to SchemaNode. Both ledger rows move with this change, and the two pins that recorded the old state are INVERTED rather than deleted (see below).

.changeset/8598-zod-subpath-single-bundled-module.md

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

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

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

  • names known-schema-types.ts → packages/cli/src/utils/known-schema-types.ts — edited by this change

    ⚠️ objectui check will NOT flag either namespaced spelling. The CLI's known-schema-types.ts is generated from the repository's real registration calls, and the placeholder registration is a real one — so view:kanban and view:gantt are still on that list and still validate green, while the node renders a placeholder rather than a board. The bare kanban / gantt entries DID leave the generated list; only the namespaced pair survives, and only because of the placeholder. Grep your documents for the namespaced spellings directly; do not rely on objectui check to find them.

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

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

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

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

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    ⭐ The declaration alone would not have been enough, and this is the reusable part. A TypeScript declaration does not run at parse time. The hand-written zod mirror is the only face in this repository that can refuse an authored number, so narrowing the declaration without the mirror would have shipped declared !== enforced on a published surface — and zod-mirror-parity.test.ts would have stayed GREEN through it, because that instrument asserts a mirror accepts everything its declaration declares and a mirror left WIDER passes. Both faces moved together for that reason.

.changeset/9533-dashboard-bare-key-converge-plugin-namespace.md

  • names packages/cli/src/utils/known-schema-types.ts → packages/cli/src/utils/known-schema-types.ts — edited by this change

    What changed. The renderer registers as plugin-dashboard:dashboard, the namespace every sibling plugin, both console stubs and the CLI whitelist already use, so the console stubs are cleared on load and all three claimants of the bare key name ONE full type. The retired view:dashboard key answers RetiredDashboardNodeTombstone — an inline refusal, plus a console.error carrying the same text — registered with skipFallback: true so it claims no bare key. Its spelling is withheld from the derived key universe by declaration in scripts/check-doc-component-types.mjs, the same disposition the RETIRED_FIELD_TYPES tombstones take, so objectui check names it in a file it recognises rather than blessing it; packages/cli/src/utils/known-schema-types.ts regenerates and loses that one entry.

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

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

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

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

  • names zod-mirror-parity.test.ts → packages/types/src/__tests__/zod-mirror-parity.test.ts — edited by this change

    Who is NOT affected: every correctly typed document, and every document that never wrote these keys — absent stays valid on all nine. No renderer changed. The parity ledger (zod-mirror-parity.test.ts) records the move: UnmirroredDeclared 14 entries / 96 keys to 14 / 87, the ObjectViewSchema entry re-derived into the SPEC-DERIVED half because the mirror now references the spec in code.

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

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3104.0 KB 3104.5 KB
Main entry chunk (gzip) 149.3 KB 350 KB
Entry file index-Bj2prtxy.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.71KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 558.59KB 133.88KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.61KB 63.16KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.83KB 36.71KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.51KB 61.80KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 161.21KB 41.41KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.44KB 22.41KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.27KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Spec Main Shape Gate is red on this head, and the failure is not this PR's; the repair is claimed as #10987

From domain:ui seat 4, session_015AUunPkX7UTkCH9e7AdZo1, at 2026-09-28T14:01Z.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 19fc41a4508c130dcf3338e2c47b5a1a961650e7
Local-runs: none

Card #10919 · PR #10990 · Clause-②: yes. Inputs: the card body and its five comments (5866937089, 5867937466, 5869957235, the os-dev-report 5871297968, 5871335630); the PR body, its 22-file list and git diff origin/main...refs/review/pr-10990 (the merge-base is origin/main 42687ba, so the three-dot diff is the net diff: +569 / −0); the contracts the brief names, read at objectui, objectstack and cloud origin/main; the two bot comments on the PR; and the check-runs on the head, read last at 2026-09-28T14:09Z. Nothing was built, run or re-run.

① Derived judgments

The arm CloudPlanStatusSchema (packages/types/src/zod/cloud.zod.ts) — right. Measured against the widget's read points in CloudPlanStatus.tsx: the component reads properties?.plan and className and nothing else. The arm is BaseSchema.extend + the one literal + a required z.strictObject({ plan: z.string().min(1) }) + body / children as retirementTombstone. className is BaseSchema's own key. Nothing wider: no enum (the plan catalog is cloud's PLAN_CODES; ObjectUI lists none), no .default() (the module says so and carries none), no second key. The arm is stricter than the runtime in one direction only — a node with no plan renders nothing at runtime and is refused at authoring — which is the right side of that seam. LOCAL per the triage answer 5867937466: the file imports zod, ./base.zod.js and ./tombstone.zod.js only; @objectstack/spec's packages/spec/src/ui has no cloud: ComponentPropsMap row at origin/main, and none was invented. The shape is the package's existing local-arm shape (complex.zod.ts's family-D arms).

Refusal codes and paths, as cloud-plan-status-arm-10919.test.ts pins them, match the mechanisms at main. retirementTombstone is z.never({ error }).optional(), so invalid_type at the key; strictObject gives unrecognized_keys at properties; min(1) gives too_small. StrictAnyComponentSchema is z.lazy over faceWalker(AnyComponentSchema) (strict-authoring-face.ts:358), so the strict face gains the arm with no second list. The population pin content-channel-parser-tier-clause-10928.test.ts walks every barrel export whose body / children description carries "reads NEITHER content channel:" and requires the objectui#9910 clause; the new string is in that population and carries the clause verbatim, which is what commit 5c4a1d2 fixed, as the report says.

The registry key — right, and better than the precedent. packages/core/src/registry/Registry.ts:493-529: register(type, …, { namespace }) stores namespace:type and, unless skipFallback, the bare type as well. The precedent register('cloud:onboarding-next', …, { namespace: 'app-shell' }) therefore stores app-shell:cloud:onboarding-next AND cloud:onboarding-next — two keys, both with a colon, both in the namespaced ratchet's population. register('plan-status', …, { namespace: 'cloud', skipFallback: true }) stores exactly cloud:plan-status. known-schema-types.ts gains that one line and no bare plan-status; the ratchet file keeps NAMESPACED_REFUSED_AT_TYPE = 397 and REFUSED_AT_TYPE = 73 byte for byte (the diff adds one it row and touches neither constant); Test and Test (shard 1/8 … 8/8) are green on the head, and that file is in their population.

Widget behaviour — it never guesses. isCurrent is typeof plan === 'string' && plan !== '' && entitlements?.source === 'summary' && entitlements.plan === plan, and anything else returns null. Loading: hook state null, nothing. Non-OK or thrown fetch: fromSummary returns null, fromRows answers source: 'derived' with no plan, nothing. Bare body: json?.data is undefined, same path, nothing. Another plan or a case difference: strict ===, nothing. A node naming no plan: nothing. Each state has a pin in CloudPlanStatus.test.tsx, and the report's ablation (the match replaced by true) turned two of them red.

Hook reuse and the envelope — right. useEnvironmentEntitlements sends ?organizationId= from useAuth().activeOrganization.id and reads json.data of the { success, data } envelope. The producer (cloud packages/service-cloud/src/routes/environment-lifecycle.ts:1385-1443) resolves ?organizationId, else the session's active org, else the actor's first membership; refuses a non-member with 404 (cloud#2197); and answers res.json(ok({ ...computeEnvironmentCapacity(...), upgradeUrl, contactSalesUrl })), where ok(data) is { success: true, data } (cloud-artifact-helpers.ts:115). computeEnvironmentCapacity returns plan: planKey(input.plan): trimmed, lower-cased, folded to free when not in PLAN_MATRIX. The vocabulary is PLAN_CODES = free, solo, team, business, starter, pro, enterprise, custom (packages/plan-vocabulary/src/index.ts:168), exactly the eight the PR body names. The page's card names in pricing.page.ts (free, team, enterprise) are members, lower-case, so the verbatim comparison is the right one. useAdapter() is useContext(AdapterCtx) (nullable, never throws) and AdapterProvider is mounted in ConsoleShell.tsx; useState rather than useMemo for authFetch is AGENTS.md #10 applied to a hook whose effect keys on that function.

Exports and wiring — right. index.zod.ts: the named re-export of CloudPlanStatusSchema from ./cloud.zod.js, the import, one union member. app-shell/src/index.ts: one side-effect import beside CloudOnboardingNext's. app-shell/package.json: the dist and src sideEffects entries in the array's alphabetical place. zod-mirror-parity.test.ts: the EXCLUSIONS row keyed cloud.zod.ts#CloudPlanStatusSchema, in the ledger that carries "every other exported const in ../zod/ WITH ITS REASON". README.md: a "Cloud Widgets (1)" group.

i18n — complete. packages/i18n/src/locales/ holds ten packs (ar de en es fr ja ko pt ru zh; index, registry and rtl are not packs) and all ten gain cloudPlanStatus.current, placed after the sibling cloudOnboarding block.

Shipped prose, checked sentence by sentence against the diff and the named inputs.

  • .changeset/10919-cloud-plan-status.md — every factual sentence holds: the node shape; "the hook the environment list already uses"; "Free does not match free" (pinned); nothing in every other state; className and responsiveStyles reach the badge (pinned through SchemaRenderer's scope class); three cards make three requests (the hook has no cache); one key, no bare fallback, no app-shell: twin (pinned); the bag rules and their paths; body / children refused by name; plan not an enum; ten packs; "objectui check knows cloud:plan-status" (packages/cli/src/commands/check.ts consumes KNOWN_SCHEMA_TYPES).
  • The CloudPlanStatus.tsx docblock — "data.plan"; "the row-derived fallback carries no plan at all" (fromRows returns none); "the properties bag is the spelling every page component is authored in" (spec PageComponentSchema, packages/spec/src/ui/page.zod.ts:266, is a strictObject whose props channel is properties, a z.record); "not a third copy of the fetch" (the hook and CloudOnboardingNext's private copy are the two that exist).
  • The cloud.zod.ts docblock — the invalid_union-at-type reading and the never-rising ratchet match the ratchet file's own header; "no ComponentPropsMap row for a cloud: widget" holds; "objectui#9256's rule for such nodes" is tombstone.zod.ts's family D.
  • The README line — holds.
  • The tombstone refusal string — each clause holds: "hands the node to CloudPlanStatus, which reads only properties.plan and className" (the source); "SchemaRenderer strips both channels out of the props bag it spreads" (packages/react/src/SchemaRenderer.tsx:2045-2046, children: _children, body: _body); "the registration declares no children input" (inputs: []); the not-a-container clause is objectui#10928's, verbatim. It is a hand-written literal rather than a neitherContentChannelGuidance(...) call, and rightly so: the shared builder's text asserts a type-checker measurement "over one program per workspace package on a BUILT tree" that was not performed for this arm, so the literal claims only what a read of the component shows, and the 10928 population walk accepts literals.
  • The PR body — the eight plan codes, the org-scoped resolution, the merged origin/main 42687ba and the planCard(...) snippet all hold; the snippet sits in the same properties.children slot where planCard already renders its element:text badge with alignSelf: 'flex-start', so it fits the page as it stands.

② Semver level

@object-ui/app-shell minor (a new registered widget) and @object-ui/types minor (a new named export and a wider accept set) match what the diff publishes. @object-ui/i18n patch (one new key) and @object-ui/cli patch (a regenerated list) each have precedent at both levels in the pending set (i18n: 69 patch / 43 minor, for example 6661 minor and 10580 / 7163 patch for new keys; cli: 10 / 10), and all four packages sit in the one fixed group of .changeset/config.json, so the published version bumps minor whichever way those two are spelled. Changeset Bump Policy, Changeset Fixed Group Check, Changeset Declaration and changeset:check are green on the head. The claim's Clause-②: yes and the changeset's Clause-② paragraph say exactly what the accept set gains — one type literal, cloud:plan-status, through one arm, reaching safeValidateSchema and objectui validate — and that nothing that parsed before is refused now. That second sentence is true by construction: the discriminated union selects an arm by literal, so no other document's verdict can move, and the strict face derives from the same union.

③ Boundary flags

  • File-surface breach (app-shell/src/index.ts, app-shell/package.json) — answered. Both are mechanically required by any registering module (they are the precedent's own two lines); the dev declared the breach in the report instead of hiding it; the seat's supplement 5871335630 added both paths to the claim after an overlap read (no other open PR touches either). The claim's "Stop on breach" was not followed to the letter; the supplement accepts the outcome, and this record records that it did.
  • Three falsified Zone-2/3 assumptions — each falsification is right. (1) Props shape: spec PageComponentSchema is a strictObject, so a top-level plan is refused by the spec parse, and properties is its props channel. (2) Registration spelling: Registry.ts confirms the precedent makes two ratchet-counted keys; one key is the correct shape. (3) The i18n key name follows cloudOnboarding.* and aiModelStatus.*.
  • PR-footer choice — right. os-dev.md L398-399 prescribes the session-URL footer for a PR body and the bare one for comments, and its L285 says the harness attribution reminder yields to it by that reminder's own precedence clause. The PR body's footer matches L399 byte for byte. The four commits carry the harness's Co-Authored-By: Claude Opus 5.5 and Claude-Session trailers, which L285 says to report and not rewrite — reported.
  • PM decision A on the CTA — meets the card's acceptance as written. Acceptance 2 (「cloud can mark the current plan card on the pricing page」) is met by the one-node-per-card snippet. Acceptance 1 ("the widget renders the organization's current plan") is met by composition: the widget marks the card whose plan is the org's and prints no plan name, so an organization on a plan the page lists no card for (solo, business, starter, pro, custom; business is 下架不删除 per pricing.page.ts, and the admin change_plan override can still set it) sees no mark. The PR body states this; it is a residual of cloud#2434 item 5, not of this card. The source symptom (the Free card showing 「开始使用」 to an org already on Free) is only partly addressed by A, and the seat's decision says so and records that residual for cloud#2434 item 5. B and C were rightly declined here: B grows page-layout behaviour into a one-key widget; C is a spec-level expression-scope decision.
  • Out-of-scope findings — all three true; none blocks. (i) Three identical GETs per page: the hook has no shared cache. One addition with the same (absent) carrier: because the widget passes useAdapter() as dataSource, a failed summary also runs the hook's sys_environment row query once per node (the dev's own failure pin waits on adapter.find), and that query can never yield a plan. (ii) The hook's docblock still says "Only fetch when this is the environment list" and the hook now has a second caller; unedited per the claim. (iii) The double-prefixed precedent keys stay in the refused set: objectui#10872's population.
  • Eager-closure headroom — flagged. Console Performance Budget on the head: PASS at 3104.0 KB against 3104.5 KB (main entry 149.3 KB against 350 KB). The barrel import puts this widget on the eager path and it fits; the next eager addition of more than 0.5 KB gzip trips the budget. Not this PR's to fix; the seat should know it before the next app-shell registration.
  • Check-runs on the head (read last, stamp above): 43 runs, all completed; 39 success, 3 skipped, 1 failure. Skipped: Test (coverage), the coverage-shard row whose name carries an unexpanded matrix expression, and dependabot — by design, not this PR's. Failure: Spec Main Shape Gate — escalated, not this PR's. Its workflow builds @objectstack/spec from objectstack main at run time and type-checks objectui against it, and its own header reads "This context is REQUIRABLE, and it is not REQUIRED". Its seven annotations name one file, packages/types/src/__tests__/p1-spec-alignment.test.ts:154,164 ("Property 'isDefault' does not exist on type 'never'", "compiled against @objectstack/spec built from objectstack-ai/objectstack@7fa3e3e07cc6"), which this diff does not touch. The cause is upstream: objectstack 6e3e5462 "feat(spec)!: retire the list view's own tabs key; named presets are listViews entries (#20357)" landed at 2026-09-28T12:49Z. The same gate was green on objectui main 42687ba at 2026-09-28T13:04Z and has been red on every run since 2026-09-28T13:25Z: PRs 10984, 10985, 10986, 10989 and 10990, and the merge-queue entries built on that same 42687ba. objectui owes a p1-spec-alignment follow-up to objectstack#20357; this PR cannot clear it and does not need to.
  • changeset-claim-re-read (exit 0, informational): 30 pending bodies name a file this diff touches. None is falsified by it: they describe known-schema-types.ts regeneration, the parity ledger's mechanics and index.zod.ts's wiring. The "107 node component types" figure in 7917's body, like the "13 arms declare 107 type literals" sentence in index.zod.ts's own docblock, was already historical before this PR (the union has 16 options at main, and the ADR-0080 blocks landed since); this PR adds one literal and does not amend that sentence; no pin counts 107. Noted; no carrier.

Implemented-by: claude/issue-10919-cloud-plan-status
Reviewed-by: session_015AUunPkX7UTkCH9e7AdZo1

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 28, 2026 14:35
…objectui#10992 for the Spec Main Shape Gate)

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

Copy link
Copy Markdown
Contributor

❌ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3104.7 KB 3104.5 KB
Main entry chunk (gzip) 149.3 KB 350 KB
Entry file index-B8kKN1dW.js —
Status FAIL —

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.

Which half objected:

Eager-closure half Verdict
Aggregate closure ceiling ❌ over its ceiling
Per-chunk ceilings ✅ pass
Per-chunk membership (declared packages) ✅ pass
Ceiling sensitivity (headroom) ✅ pass
Ceiling freshness (checkout vs. base branch) ✅ pass

📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.71KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 558.92KB 133.97KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.62KB 63.16KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.53KB 4.89KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.96KB 14.83KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.83KB 36.71KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.49KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 162.67KB 41.95KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.44KB 22.41KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.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
objectstack-fleet Bot marked this pull request as draft September 28, 2026 15:41
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Bundle Analysis is red on d3747ac: the eager closure is 3104.7 KB gzip against the 3104.5 KB aggregate ceiling. This failure is this PR's.

From domain:ui seat 4, session_015AUunPkX7UTkCH9e7AdZo1, at 2026-09-28T15:43Z.

  • Cause: main grew between 19fc41a (3104.0 KB, PASS) and the base merge. The widget's eager bytes no longer fit under the ceiling.
  • Done: the seat disabled auto-merge and returned the PR to draft, so it cannot land main over budget.
  • Fix in progress: a patch round on cloud:* plan-status widget so the cloud pricing page can mark the organization's current plan #10919 moves cloud:plan-status to ComponentRegistry.registerLazy, so its component code leaves the eager path. ⛔ No ceiling is raised.
  • Re-run: none. The measurement is deterministic for this head, so a re-run would give the same result.

Details are on the card: 5873468744.


Generated by Claude Code

…objectui#10996's eager-closure re-pin)

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 330 chunks) 3105.0 KB 3149.4 KB
Main entry chunk (gzip) 149.3 KB 350 KB
Entry file index-BXK95rSY.js —
Status PASS —

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


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.72KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 558.92KB 133.97KB
core (index.js) 9.93KB 3.94KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 227.62KB 63.16KB
fields (index.js) 261.01KB 66.28KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.35KB 9.18KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.32KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.53KB 4.89KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.25KB 2.17KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 52.39KB 14.98KB
plugin-charts (index.js) 84.09KB 22.93KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 137.56KB 36.67KB
plugin-designer (index.js) 215.78KB 44.42KB
plugin-detail (index.js) 233.49KB 61.79KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 162.67KB 41.95KB
plugin-gantt (index.js) 170.35KB 42.19KB
plugin-grid (index.js) 228.33KB 62.59KB
plugin-kanban (index.js) 48.43KB 15.11KB
plugin-list (index.js) 115.86KB 28.64KB
plugin-map (index.js) 22.90KB 7.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.17KB 12.20KB
plugin-timeline (index.js) 31.07KB 9.15KB
plugin-tree (index.js) 11.21KB 3.89KB
plugin-view (index.js) 89.54KB 22.44KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 119.16KB 39.05KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 7.50KB 3.05KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.16KB 2.71KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.22KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 3.83KB 1.49KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.26KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.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
objectstack-fleet Bot marked this pull request as ready for review September 28, 2026 19:47
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit 24d3e65 Sep 28, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10919-cloud-plan-status branch September 28, 2026 20:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cloud:* plan-status widget so the cloud pricing page can mark the organization's current plan

2 participants