Skip to content

docs(types): SchemaRegistry's docblock owns its key set, not its values - #9677

Merged
os-justin merged 1 commit into
mainfrom
claude/issue-7665-schema-registry-docblock-scope
Sep 17, 2026
Merged

os-justin merged 1 commit into
mainfrom
claude/issue-7665-schema-registry-docblock-scope

Conversation

@os-justin

@os-justin os-justin commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Part of #7665

The split, not a weakening

SchemaRegistry documented itself as the Single Source of Truth for component type
lookups, unqualified. The key-set half of that is true and load-bearing; the value half
is structurally unachievable for any component whose renderer lives in a plugin package.
This branch changes one docblock — the comment immediately above
export interface SchemaRegistry — so the sentence claims only the half it can keep.

The key set KEEPS the Single-Source-of-Truth wording; only the value side is qualified.

⚠️ Citations below are by CONTENT, per AGENTS.md #11. The card located the sentence at
line 105 on 15b33aeb4; this branch is cut from 72f55c9ec, where the same sentence is
one line further down — the rule demonstrating itself inside its own card.

Before

/**
 * Registry mapping component types to their schema definitions.
 * This interface is the Single Source of Truth for component type lookups.
 */

After

/**
 * Registry mapping component types to their schema definitions.
 *
 * Two halves with different standing, split deliberately (objectui#7665),
 * because one of them is reachable from this package and the other is not:
 *
 * **The KEY SET is the Single Source of Truth for component type lookups.**
 * `keyof SchemaRegistry` IS the published `ComponentType` union (declared at the
 * end of this file), so whether a key is declared here is the whole answer to
 * whether a component type is registered — and a consumer discriminating on
 * `ComponentType` is entitled to that answer.
 *
 * **A VALUE is the strongest type THIS LAYER can reach for that key**, which for
 * a component whose renderer lives in a plugin package may be NARROWER than the
 * type that renderer honours. Structural, not an oversight: such a component's
 * authoring face is declared in the plugin, and this package cannot name it —
 *
 *   - `pnpm check:phantom-deps` (`scripts/check-phantom-dependencies.mjs`)
 *     judges `import type` exactly as it judges a value import, so importing a
 *     specifier `@object-ui/types` does not declare is refused; and
 *   - declaring the dependency instead closes a cycle — a plugin package depends
 *     on this one, directly or through what it depends on — and the build graph
 *     is then rejected as cyclic.
 *
 *   Both are re-derived by those two tools; this paragraph is not the evidence.
 *
 * ⇒ The limit is this layer's reach, ⛔ not a licence for an entry to describe a
 * type it cannot name: an entry states what it can prove from here, and closing
 * the gap is a move in the plugin, not a wider claim here (objectui#7664 closed
 * the kanban one by moving the dialect down into this package).
 *
 * Worked example: objectui#7645, where the type the registered kanban renderer
 * honoured lived in `@object-ui/plugin-kanban` — unreachable from here for both
 * reasons above, while the entry described it anyway.
 */

The premise, measured on this branch rather than taken on trust

The card rests on one structural claim: @object-ui/types cannot name a type that lives
in a plugin package. Both legs were measured here with a temporary mutation that was then
restored (each probe carried a trap restore and finished with git diff HEAD empty for
the mutated path — checked, not assumed):

  1. The gate refuses the import, type-only included. With
    import type { KanbanColumnConfig } from '@object-ui/plugin-kanban'; injected into
    registry.ts (injection confirmed on disk by a marker grep, count 0 to 1),
    node scripts/check-phantom-dependencies.mjs exited 1 and printed, with the
    address trimmed because it described the mutated tree and not this one:
    [undeclared-runtime] @object-ui/types imports '@object-ui/plugin-kanban' (type-only).
    The gate's own header states the grading it applies: "Type-only imports are STRICT.
    import type … is judged exactly like a value import."
  2. Declaring the dependency closes a cycle. With @object-ui/plugin-kanban added to
    packages/types/package.json dependencies as workspace:*,
    turbo run build --dry --filter=@object-ui/types exited 1 with
    x Cyclic dependency detected: over a task list containing both
    @object-ui/types#build and @object-ui/plugin-kanban#build. Restored.

⚠️ One precision the docblock is written around: it is zero workspace runtime
dependencies, not zero dependencies — this package does declare external runtime
dependencies, and its only workspace: entry is a devDependency. So the docblock rests on
the two instruments above, which re-derive the consequence on every run, rather than on a
dependency count that would rot (AGENTS.md #9).

Clause-② stays no — no member moved

Measured, not asserted: the SchemaRegistry members were read off the TypeScript AST at
the branch point and at HEAD (key + value type text + optionality, in source order). Both
readings give 70 members and the lists are identical, so keyof SchemaRegistry — and
therefore the published ComponentType union — is unchanged. Two non-vacuity controls ran
with it: the reader detects a dropped member, and it really read this map (a live key is
present in the HEAD reading). The diff is prose inside one comment plus a changeset.

Verification

check reading
node scripts/check-changeset-presence.mjs exit 0 — "1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s) … Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption and a complete answer to this gate."
node scripts/check-phantom-dependencies.mjs (unmutated) exit 0
node scripts/check-new-cross-file-line-citations.mjs exit 0 — 0 new citations added by this branch
node scripts/check-control-bytes.mjs exit 0
pnpm --filter @object-ui/types test exit 0 — 198 files, 4656 tests passed
pnpm --filter @object-ui/types type-check exit 0
pnpm --filter @object-ui/types lint exit 0 — 0 errors (288 pre-existing warnings, none in the edited file: a targeted eslint --no-inline-config --format json on it reports 0 errors, 0 warnings)

Both heavy runs went through the shared verify lock. The repo-wide farm is CI's; nothing
here was narrowed silently.

Why a changeset at all, and why an empty one

The gate decides, not taste: packages/types/src/ is published source of a released
package, so a declaration is owed even for a comment; and nothing user-visible ships, so
the empty frontmatter is the honest form. The same shape has precedent in this tree —
see the comment-only changeset for the retired element:filter header note. Doc comments
DO reach the published .d.ts, which is why this is declared rather than skipped.

Acceptance notes — found, deliberately not fixed here

  • Two comments in this same file describe objectui#7665 as the sweep card. They say
    the map's missing kanban-family coverage is "the objectui#7665 sweep's territory" and
    that objectui#7665 "holds" the map's other entries. This card is option C and its
    triage forbids the sweep; option A has no card yet. Once this lands, those two
    comments point a reader at a closed card for work it explicitly excluded. Reported for
    the PM to route, ⛔ not re-pointed here: the successor card does not exist to cite.
  • The kanban-family key-set gap is already recorded in this file and is not a value
    divergence.
    The bare kanban key was retired (objectui#8802) and this map has never
    carried an object-kanban entry, so the family has no entry at all. That is a key-set
    question, and adding one would widen ComponentType — a ruling, not an implementation
    choice. Named here, not touched.
  • Pre-existing cross-file line addresses in this file were left alone. The chatbot
    comment carries several. AGENTS.md [WIP] Update documentation for project #11 invites opportunistic repair when you are in a
    file anyway, while this card's constraint is one docblock and prose only. The conflict
    is surfaced rather than resolved silently; the line-citation gate is differential and
    this branch adds none, so it reads 0.
  • ⛔ No sweep of the other keys against their registered renderers was performed, and no
    second diverging key is claimed — finding one would require exactly the sweep this card
    excludes.

⚠️ This body was edited once after creation, to repair the After block: the first
publish quoted the new docblock two lines short, so the quotation ended mid-sentence —
precisely the defect class this card is about. Editing a PR body downgrades or duplicates
the attribution footer (AGENTS.md, the body-bytes section), so the session reference is
stated here in prose as well: session_012EpHzwH4wTy5sd7ibkD2yq.


Generated by Claude Code


Generated by Claude Code

The interface documented itself as "the Single Source of Truth for component
type lookups" without qualification. The key-set half of that is true and
load-bearing — `keyof SchemaRegistry` IS the published `ComponentType` union.
The value half is structurally unachievable for a component whose renderer
lives in a plugin package: `check:phantom-deps` judges `import type` exactly as
it judges a value import and refuses the undeclared specifier, and declaring
the dependency closes a cycle through the plugin, which already depends on this
package.

The claim is split rather than weakened: the key set keeps the wording, a value
is documented as the strongest type this layer can reach for that key, and
objectui#7645 is named as the worked example in the citation style this file
already uses.

Prose inside one comment. No member of `SchemaRegistry` is added, removed,
renamed or retyped, so `ComponentType` is byte-identical.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3048.9 KB 3104.5 KB
Main entry chunk (gzip) 145.7 KB 350 KB
Entry file index-Ckww2Jo5.js
Status PASS

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


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 16.69KB 6.21KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 545.84KB 130.66KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 215.98KB 59.97KB
fields (index.js) 249.27KB 62.92KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.83KB 10.95KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 14.81KB 3.63KB
plugin-calendar (index.js) 49.92KB 14.22KB
plugin-charts (index.js) 71.33KB 19.90KB
plugin-chatbot (index.js) 195.34KB 46.51KB
plugin-dashboard (index.js) 131.44KB 34.65KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 253.29KB 65.88KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.71KB 34.16KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 212.64KB 57.91KB
plugin-kanban (index.js) 46.41KB 14.49KB
plugin-list (index.js) 112.73KB 27.69KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 85.04KB 21.01KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 104.82KB 34.67KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.04KB 5.36KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@os-justin
os-justin marked this pull request as ready for review September 17, 2026 11:24
@os-justin
os-justin added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit 4633703 Sep 17, 2026
38 checks passed
@os-justin
os-justin deleted the claude/issue-7665-schema-registry-docblock-scope branch September 17, 2026 11:44
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.

2 participants