Skip to content

refactor(types)!: retire ThemeComponentSchema — the type: 'theme' component kind no renderer implements - #5650

Merged
os-sales merged 1 commit into
mainfrom
claude/issue-5489-retire-themecomponentschema
Aug 22, 2026
Merged

refactor(types)!: retire ThemeComponentSchema — the type: 'theme' component kind no renderer implements#5650
os-sales merged 1 commit into
mainfrom
claude/issue-5489-retire-themecomponentschema

Conversation

@os-sales

@os-salesos-sales commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Fixes#5489

Retires ThemeComponentSchema (type: 'theme') from @object-ui/types — a component kind no renderer ever implemented. Under the maintainer ruling of 2026-08-21 on objectstack#10485, option B (verbatim, untranslated):

「B:退役授权面 —— 收掉 themes 载体键与 schema,app.branding 留作唯一颜色面;objectui 引擎代码与单测保留」

The theme system is untouched.ThemeEngine (packages/core/src/theme/ThemeEngine.ts), ThemeContext / ThemeProvider (packages/react/src/context/ThemeContext.tsx) and their unit tests are explicitly retained by that ruling and are not in this diff. Theme, ThemeDefinitionSchema and ThemeModeSchema also stand. Retiring the dead component kind is not retiring theming.

Consumer sweep — the search terms, and the positive control for each

The card records an explicit confidence gap: no in-repo consumer was found, but out-of-repo consumers of the published types package are not measurable from here. A zero-hit sweep is only evidence if the command is proven able to hit, so every term below is paired with a control run through the identical pipeline.

#Search (--include='*.ts' --include='*.tsx' plus .js/.mjs/.json/.md/.mdx where noted; node_modules and dist/ excluded)ResultPositive control, same command
1grep -rn "ThemeComponentSchema" .15 hits, all in packages/types (2 declaration sites, 2 export barrels, its own unit test, prose in 1 doc page) + 2 gate-script exemption strings. No consumer.n/a — non-zero result
2grep -rn "ThemeComponentSchemaType" .1 hit: its own declaration. Not re-exported from zod/index.zod.ts; zero consumers.n/a — non-zero result
3grep -rn "ThemeUnionSchema"3 hits, all packages/typesn/a — non-zero result
4grep -rn "AnyComponentSchema" packages/ apps/ examples/ e2e/ scripts/ | grep -v '^packages/types/'0same command for BaseSchema131
5grep -rhoE "ComponentRegistry\.(register|registerLazy)\(\s*'[a-z0-9-]+'" packages/*/src | sed -E "s/.*'([a-z0-9-]+)'/\1/" | sort -u | grep -cx 'theme'0 (659 registered keys enumerated)same pipeline, grep -cx 'tooltip'1
6awk '/PROTOCOL_COMPONENTS = \[/,/^\];/' packages/components/src/renderers/placeholders.tsx | grep -c "'theme'"0same slice, grep -c "'ai:input'"1
7grep -rn '"type": *"theme"' . (JSON/TS/MD fixtures repo-wide)0same command for "type": "form"98
8grep -rn "export \* from '@object-ui/types" packages/ apps/0 — no package re-exports the barrel wholesale, so the removal cannot leak through a star re-exportn/a
9grep -rn "ThemeComponentSchema" over the sibling objectstack checkout0n/a

Conclusion:'theme' is registered by nothing — not by a renderer, and not even by the placeholder registry, so it did not resolve to a placeholder either. A page declaring one got the registry's "Unknown component type" panel (OBJUI-001), never a theme manager. Declared-but-unenforced, the ADR-0078 class. No in-repo consumer exists, so stop-and-report condition 1 did not trigger. Out-of-repo consumers of the published @object-ui/types remain unmeasurable from this repository — that gap is unchanged by this PR, and is the reason the ruling, not this PR, is the authority for the removal.

ADR-0087 registry entry: NO, none is owed

Checked rather than assumed, in both directions:

  • objectui has no ADR-0087 registry and no ADR-0087 at all — docs/adr/ holds the 0001…0059-era ADRs, and find . -name '*0087*' outside .git returns nothing. The only two mentions of the number in the whole repo are a code comment in packages/types/src/ui-action.ts:444 naming a spec-side conversion, and one packages/types/CHANGELOG.md line.
  • The ADR lives in objectstack (docs/adr/0087-metadata-protocol-upgrade-contract.md) and its registries are @objectstack/spec artefacts: packages/spec/spec-changes.json, packages/spec/src/migrations/. Its stated Consumers are @objectstack/spec, @objectstack/cli, the runtime metadata loader, @objectstack/mcp, @objectstack/create-objectstack and the Release workflow. objectui is not among them, and @object-ui/types is not the metadata protocol — it is objectui's TypeScript/Zod surface for UI components.
  • Cross-check that the two surfaces really are disjoint: the spec's own UI vocabulary declares no theme node — grep -rn "'theme'" packages/spec/src/ui/ in objectstack returns 1 hit, a field namedtheme in a view test, while the control 'form' returns 57. Nothing on the protocol side references the kind being removed.

The changeset is this removal's release-notes input, per the repo's own contract.

Sequencing / shared artefacts

Independent of the objectstack-side retirement, as the card predicted: different repo, different package, no shared gate and no shared generated artefact. Measured, not assumed — see the spec-vocabulary cross-check above, and check:spec-symbols below, which passes with the removal in place.

What changed

  • packages/types/src/theme.ts — interface removed, replaced by a tombstone carrying the ruling verbatim and the unregistered-kind measurement.
  • packages/types/src/zod/theme.zod.ts — the Zod object, its 'theme' membership in ThemeUnionSchema, and ThemeComponentSchemaType.
  • packages/types/src/index.ts, packages/types/src/zod/index.zod.ts — both export barrels.
  • packages/types/src/__tests__/phase2-schemas.test.ts — the acceptance test that pinned the dead shape as valid becomes a retirement pin proving it refused by both ThemeUnionSchema and AnyComponentSchema, with the theme document it carried (ThemeDefinitionSchema.safeParse) as the control leg that makes the refusal attributable to the retired wrapper rather than to a broken fixture.
  • .changeset/retire-theme-component-schema-5489.md@object-ui/types: minor (never major; fixed group).

Bounded in-place fixes, named here with their evidence

Two follow-on edits are the same defect class as the removal (references to the retired kind), mechanically determined by it, and in the same gate family. Naming them is the condition for making them here rather than filing them:

  1. content/docs/guide/schema-overview.md imported and annotated ThemeComponentSchema in typescript-fenced blocks (an import type naming it from @object-ui/types, and two const theme: ThemeComponentSchema = { … } declarations), plus a comparison-table row and two prose bullets. Left alone it would teach a symbol this PR deletes. The theming section now describes the retained surface — a theme document handed to ThemeProvider — with no type: literal in any fence. The page is in UNGATED_DOCS, so it is not compiled; the ledger conditions are re-derived per run and all still hold (document exists; typescript fences 8 → 7, still ≥ 1, and typescript is in TS_FENCE_LANGUAGES; reason string still over the minimum).
  2. scripts/check-doc-component-types.mjs — that page's theme exemption would have failed as stale-exemption the moment the literal left its code blocks (the gate re-derives every entry and only checks that the file still spells the value, confirmed by reading analyze()), so it is deleted. The content/docs/core/theme-schema.mdx entry stays live — that page still spells the literal — but its reason named packages/types/src/theme.ts as the declaration site, which is now false; it is re-pointed at the retirement and at the rewrite card. scripts/check-doc-snippet-types.mjs's ledger prose for schema-overview.md gets one appended clause for the same reason. Gate verdict after both edits: ✅ Every documented component type is registered.

Verification — all of it on the final commit b06fdf397

pnpm --filter '@object-ui/types^...' build → "No projects matched the filters"

Genuine, not the zero-match trap: packages/types/package.json declares @objectstack/spec and zod only — no workspace dependency exists to build.

pnpm --filter '@object-ui/types' build EXIT=0 (tsc)
pnpm --filter '@object-ui/types' type-check EXIT=0 (tsc --noEmit && tsconfig.examples.json && tsconfig.test.json — script name echoed, so not a zero match)
npx vitest run packages/types --maxWorkers=2 EXIT=0 Test Files 40 passed (40) · Tests 460 passed (460)

Gates, each verdict quoted from the gate's own output (exit codes captured before any pipe):

GateVerdict line
check-doc-component-types✅ Every documented component type is registered. (183 docs, 1053 code blocks, 886 type literals, 743 registered / 143 exempted)
check-changeset-presence✅ 5 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)
check-changeset-no-major✅ No changeset declares a 'major' bump.
check-changeset-fixed✅ All workspace packages are in the changeset fixed group.
check-control-bytes✅ check-control-bytes: OK (scanned 4696 tracked text file(s); skipped 85 binary).
check-spec-symbol-derivation✅ spec symbol derivation: 1290 files scanned against 4912 spec export names
check-package-self-import✅ No package names itself inside its own src/.
check-lint-coverage✅ lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).
check-type-check-coverage✅ type-check coverage: 45/46 via 'type-check', 0 known-broken
check-doc-linksLinks are valid across 13 scan roots.

Ablation — the retirement pin proven able to fail, both legs

The test resolves '../zod/index.zod' as a relative source import, and the root vitest config additionally aliases @object-ui/typespackages/types/src; nothing in this path reads dist/, so this ablation is source-level and the rebuild-then-re-hash step does not apply here. The script carried a trap on EXIT INT TERM that ran git checkout on both mutated files, so a cap-kill mid-mutation could cost at most one reading and never leave a mutated tree behind. Each leg proved its mutation on disk with anchored counts before the run was believed — an editor's exit code is not evidence.

  • Leg A — re-added a type: z.literal('theme') member to ThemeUnionSchema. On-disk proof: injected marker → 2, grep -cxF " ABLATION_ThemeComponentSchema," → 1. Result: Tests 1 failed | 30 passed, AssertionError: expected true to be false on the refusal assertion. Predicted red, observed red.
  • Leg B — the control leg given its own mutation: put the spec-retired typography.fontSize back into the fixture, leaving the union alone. On-disk proof: grep -cxF " fontSize: 16," → 1. Result: Tests 1 failed | 30 passed, AssertionError: expected false to be true — the opposite direction and the other assertion, which is what shows the control is scoped to the same subject and can fail independently.
  • Restore leg — both files git checkout-ed; markers re-measured at 0 and 0, git status --porcelain empty for both, re-run Tests 31 passed (31).

Declared narrowings — measurement, not omission

Each is a repo-scale run CI performs in full regardless; each is declared with its own evidence rather than silently skipped.

  1. pnpm lint (turbo run lint, 46 packages). Narrowed to the 7 changed .ts/.mjs files. (a) The population comes from eslint's own flat config, not a guess: eslint.config.js matches **/*.{ts,tsx} with js.configs.recommended + tseslint.configs.recommended. (b) File count read from --format json: 7 files linted, 0 errors, 10 warnings. (c) Invariance for untouched files: the config declares noparserOptions.project and no projectService, so linting is not type-aware and no edit here can move any untouched file's verdict. The 10 warnings are pre-existing and unchanged — the base revision of that same file, linted through the identical command, also reports 10 warnings, 0 errors. CI sets no --max-warnings.
  2. check-published-dist-tooling was killed by the container's 10-minute foreground cap (exit 143) at 9m42s with 15 of 26 turbo builds done and no finding emitted — it builds the entire workspace by design (its own header cites the 2026-08-16 ruling on objectui#4846 that assigns exactly this cost to CI). It inspects every package's dist/ for tooling material and does not read this diff, which adds no test or bench file to any dist/. CI owns it.
  3. check-doc-snippet-types is a known-broken gauge (exits 1 on main) and compiles a scoped package set. Not run; its ledger conditions were instead re-derived by hand from analyze() and all hold — see the bounded-fix note above. CI owns it, as it already does.

Out-of-scope findings, filed unassigned, not touched here


Generated by Claude Code

…mponent kind no renderer implements (#5489)
`packages/types/src/theme.ts` declared a theme-manager COMPONENT — `type: 'theme'`
carrying `themes[]`, `activeTheme`, `allowSwitching`, `persistPreference`,
`storageKey` — and `zod/theme.zod.ts` published the matching Zod object as a member
of `ThemeUnionSchema`, hence of `AnyComponentSchema`. Nothing rendered it: the
literal is absent from every `ComponentRegistry.register(...)` / `registerLazy(...)`
site in `packages/*/src` and from both `PROTOCOL_COMPONENTS` and
`PALETTE_PLACEHOLDER_BLOCKS`, so it did not even resolve to a placeholder. A page
declaring one got the "Unknown component type" panel, never a theme manager.
Removed under the maintainer ruling of 2026-08-21 on objectstack#10485 (option B).
The theme SYSTEM is explicitly retained by that same ruling and is untouched here:
`Theme`, `ThemeDefinitionSchema`, `ThemeModeSchema`, `ThemeEngine` and
`ThemeProvider` all stand.
- types + zod declaration, `ThemeComponentSchemaType`, both export barrels
- `ThemeUnionSchema` loses its `'theme'` member
- the acceptance test that pinned the dead shape as VALID becomes a retirement pin
proving it REFUSED, with the theme document it carried as the control leg
- `content/docs/guide/schema-overview.md` stops teaching the removed type; the
`check-doc-component-types` exemption it needed goes with it, and the
theme-schema.mdx exemption is re-pointed at the retirement and at #5648
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3785.8 KB3867.2 KB
Main entry chunk (gzip)151.6 KB350 KB
Entry fileindex-CftgFQXz.js
StatusPASS

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

PackageSizeGzipped
app-shell (index.js)10.04KB3.72KB
app-shell (runtime-config.js)11.22KB3.78KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)29.34KB7.05KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)6.35KB2.43KB
auth (index.js)2.77KB1.22KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.02KB0.89KB
auth (useIsWorkspaceAdmin.js)3.04KB1.45KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)507.00KB113.72KB
core (index.js)4.51KB1.80KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)159.80KB44.33KB
fields (index.js)238.85KB60.13KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.35KB3.31KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.42KB1.42KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.81KB0.83KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.65KB18.32KB
plugin-chatbot (index.js)181.41KB43.22KB
plugin-dashboard (index.js)128.33KB32.93KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)242.16KB60.90KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.07KB30.43KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.70KB27.17KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.50KB20.68KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)43.66KB14.77KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.33KB0.69KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (index.js)4.77KB2.16KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)10.76KB3.17KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)6.92KB2.40KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.08KB1.53KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@os-sales
os-sales marked this pull request as ready for review August 22, 2026 01:08
@os-sales
os-sales added this pull request to the merge queueAug 22, 2026
Merged via the queue into main with commit 78cbdb5Aug 22, 2026
23 checks passed
@os-sales
os-sales deleted the claude/issue-5489-retire-themecomponentschema branch August 22, 2026 01:09
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-sales@claude