Skip to content

fix(types): SchemaRegistry's kanban entry stops describing a component it cannot name - #7662

Draft
os-sam wants to merge 1 commit into
mainfrom
claude/issue-7645-schema-registry-kanban-honesty
Draft

fix(types): SchemaRegistry's kanban entry stops describing a component it cannot name#7662
os-sam wants to merge 1 commit into
mainfrom
claude/issue-7645-schema-registry-kanban-honesty

Conversation

@os-sam

@os-sam os-sam commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Fixes #7645

SchemaRegistry documents itself as "the Single Source of Truth for component
type lookups"
. Its 'kanban' entry named DeclarativeKanbanSchema, while the
renderer registered for that key is ObjectKanbanRenderer in
@object-ui/plugin-kanban, which consumes that package's own KanbanSchema.
For this one key the map's value did not describe the component the key names.

Option taken: B — the value stops asserting; the key is untouched. The
entry is now BaseSchema & { type: 'kanban' }: the strongest claim this layer
can prove, and one that is true of both dialects.

Why A was unreachable — three routes, all measured

A1 — import the honest type. check:phantom-deps, exit 1:

❌  1 import(s) are not declared where a consumer would find them (1 distinct package/dependency pair(s)):
      packages/types/src/registry.ts:9:1  [undeclared-runtime]  @object-ui/types imports '@object-ui/plugin-kanban' -> @object-ui/plugin-kanban (type-only)

Baseline for that reading, same gate, unmutated tree: ✅ Every in-scope import is declared by the package that publishes it. (exit 0). Type-only
imports are not exempt — the gate names the pair explicitly.

A2 — declare the dependency instead. Read off the manifests: @object-ui/types
has 0 workspace runtime deps, and @object-ui/plugin-kanban declares
@object-ui/types. Adding the edge closes the cycle
@object-ui/types -> @object-ui/plugin-kanban -> @object-ui/types. (Reader
proven live by the same pass returning 7 workspace deps for plugin-kanban.)

A3 — name a proxy this layer can already reach. @objectstack/spec/ui is a
declared dependency of @object-ui/types and does export ObjectKanbanProps,
mirroring the same renderer. It is not the same type: of the plugin
KanbanSchema's 19 members it shares 11, cannot express 8 (allowCollapse,
cardTemplates, className, columnWidths, limit, onCardMove,
onQuickAdd, and the type discriminant every other registry value carries),
and adds 2 the plugin does not have (filter, titleField). Pointing there
swaps one false claim for a differently false one, so it was rejected.

Relocating the plugin dialect into this layer is the remaining route, and it is
the one objectui#6172's ruling declined; it would also widen the published
surface, which the dispatch ruled out for a latent defect.

Latency re-verified, with a lit control

The card's claim that nothing indexes the value side was re-measured, not
inherited. Repo-wide, excluding node_modules and dist, SchemaRegistry[
returns exactly 1 hit — prose in .changeset/3965-mint-box-neutral-container.md,
not a TypeScript indexed access. Control for the query shape, same command,
same file set: ComponentPropsMap[ returns 39. An earlier control for this
measurement read zero; it was discarded as a dark instrument and rebuilt rather
than published. Also measured: 0 gate scripts mention SchemaRegistry
(control: 36 hits for packages/types/src in the same corpus), and the
@object-ui/types ComponentType has no in-repo consumer but its own
declaration.

The pins, and proof they are lit

Type-level assertions are erased at runtime, so vitest says nothing about them.
The instruments are tsc -p packages/types/tsconfig.test.json and
tsc -p packages/plugin-kanban/tsconfig.test.json, both chained off their
packages' type-check scripts. Program membership proved with --listFiles:
each pin file appears once; the plugin project resolves @object-ui/types to
packages/types/dist/index.d.ts (0 hits for the source path), so
@object-ui/types was rebuilt before every reading on that side.

Three ablation legs, prediction written before each run:

Leg Mutation Predicted Observed
2 restore the defect ('kanban': DeclarativeKanbanSchema) non-zero, exactly 1 error, at _ValueIsNotTheDeclarativeFace exit 2, 1 error, …test.ts(77,7): error TS2344 — that pin, only that pin
3 delete the 'kanban' key non-zero, several errors incl. _KeyKept exit 2, 5 errors: line 72 (_KeyKept) plus TS2339 at 77/83/89
4 add a member the plugin face lacks, rebuild types non-zero at _PluginFaceSatisfiesTheEntry exit 2, 1 error, …test.ts(69,7): error TS2344

No prediction missed. Every mutation was proved on disk by anchored
before/after counts and git hash-object movement off the HEAD blob; every
restore by blob equality and an empty git diff HEAD. Leg 4's restore leg
also rebuilt and re-checked, confirming the marker left dist (1 -> 0) and the
plugin project returned to 0 errors — an unrebuilt restore would have left
every later reading measuring the wrong tree. One earlier attempt at leg 1
mutated only half of its target; the count-based guard caught it, the trap
restored, and it was re-run rather than quietly repeated.

Leg 3 is the one that matters most for the published surface: it is the proof
that keyof SchemaRegistry still yields 'kanban', and that deleting the key
— which also removes the false claim and leaves every runtime suite green — is
now a loud compile error.

Verification, at commit 41ff58ff

Union re-run after the final commit, on a clean tree. Each gate's own verdict
line:

  • check:phantom-deps✅ Every in-scope import is declared by the package that publishes it.
  • check:control-bytes✅ check-control-bytes: OK (scanned 6255 tracked text file(s); skipped 85 binary).
  • check:changeset-presence✅ 3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)
  • check:changeset-no-major — verdict: no changeset declares a major bump
  • check:doc-types✅ Every documented component type is registered.
  • check:readme-exports✅ check-readme-exports: OK (… 0 unbuilt …)
  • check:dist-completeness✓ dist completeness: 12 package(s) complete (1609 emitted files verified)
  • check:spec-symbols✅ spec symbol derivation: 1344 files scanned against 4959 spec export names
  • check:node-esm-load, check:published-dist, check:entry-guard — exit 0
  • pnpm --filter @object-ui/types run type-check and the same for
    @object-ui/plugin-kanban — exit 0
  • pnpm exec vitest run packages/types/ packages/plugin-kanban/ — 126 files,
    1844 tests passed
  • eslint on the three touched files — exit 0

check:readme-exports first reported the population COLLAPSED -- this run proves nothing (24 of 40 packages unbuilt locally). That is a precondition
failure, not a red: all packages were built and it was re-run, which is the
green quoted above (0 unbuilt, 421 self-imports judged, up from 312
unjudgeable).

Declared narrowing. check:sdui-registration-pins is not measured here
— it reads the built console bundle and refuses to judge without one
(a run with nothing to read has measured nothing). It is not implicated: the
diff changes no registration. The full repo-wide lint and the remaining gate
farm are left to CI, which runs them unfiltered. Exit codes throughout were
captured by redirect before any pipe.

Contract review

Clause-②: yes. SchemaRegistry is published, and this changes the type of
one of its members — a falsifiable contract-semantics claim, not a spelling. It
adds no exported symbol and no key (the diff contains 0 changed export
lines), so keyof SchemaRegistry and the published ComponentType union are
byte-identical; what moves is the value side of one key.
needs:contract-review is on both carriers and this stays a draft — not
ready, not enqueued.

Semver: minor, per this repo's version-alignment rule that objectui never
declares major, with the consumer-visible effect spelled out in the
changeset.

Scope

Untouched, as fenced: packages/plugin-kanban's own KanbanSchema (the face
objectui#6172 kept — the new file there only reads it), KanbanSchema.data
(#7651, open maintainer decision), scripts/check-doc-links.mjs and
scripts/github-slug.mjs (#7644), and the getDataConfig producers (#7632).

One question is left for the PM rather than answered here: the card also asks
whether the one-authority family rule should be extended so that every
SchemaRegistry value must be the type its registered renderer honours. This
PR settles the 'kanban' key; the generalization is a contract decision, and
whether other keys diverge the same way is an unmeasured sweep.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3


Generated by Claude Code

…t it cannot name

`SchemaRegistry` advertises itself as the Single Source of Truth for component
type lookups, but its `'kanban'` entry named `DeclarativeKanbanSchema` — this
package's authoring/validation face — while the renderer registered for that
key is `ObjectKanbanRenderer` in `@object-ui/plugin-kanban`, which consumes
that package's own `KanbanSchema`. Unrelated dialects, so for this one key the
map's value did not describe the component the key names (objectui#7645).

Correcting the value is not reachable from this layer, measured rather than
assumed: importing the plugin is a phantom dependency (`check:phantom-deps`
rejects the pair, type-only included), and declaring the dependency closes the
cycle `@object-ui/types` -> `@object-ui/plugin-kanban` -> `@object-ui/types`.
objectui#6172's ruling kept the plugin's bare names rather than relocating the
dialect into this zero-workspace-dependency layer, so the gap is by decision.

The entry therefore asserts only what this layer can prove and what both
dialects satisfy: `BaseSchema & { type: 'kanban' }`. `keyof SchemaRegistry` is
unchanged — removing the key would silently narrow the published
`ComponentType` union — and two compile-time pins hold that shape: one here
(the key survives; the value no longer claims the declarative face) and one in
`@object-ui/plugin-kanban`, the only package that can name both sides, proving
the renderer's own `KanbanSchema` satisfies what the entry now asserts.

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

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3180.1 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-CI1E9WNz.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) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 510.70KB 116.21KB
core (index.js) 6.70KB 2.68KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 180.00KB 50.20KB
fields (index.js) 242.27KB 61.22KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
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) 5.12KB 1.74KB
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) 15.75KB 3.80KB
plugin-calendar (index.js) 48.05KB 13.34KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.43KB
plugin-dashboard (index.js) 132.86KB 34.68KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.41KB 41.04KB
plugin-grid (index.js) 209.40KB 56.80KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.57KB 6.82KB
plugin-markdown (index.js) 13.70KB 4.68KB
plugin-report (index.js) 43.57KB 11.96KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.35KB 3.23KB
plugin-view (index.js) 85.24KB 20.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
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 (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.58KB 2.23KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

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

Projects

None yet

2 participants