Skip to content

feat(types)!: retire the PartialSchema export, which had no reader; a census pin keeps it gone (objectui#11608) - #11616

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-11608-partialschema-retire
Oct 4, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-11608-partialschema-retire

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11608
Clause-②: yes (narrowing)

Retires the PartialSchema utility type (generic over a node type T) from @object-ui/types. It had no reader. The census ran at work time before any write; it found no reader in any repository it could read, so the seat's premised ruling (claim 5981086062, executing triage 5979662969) applies: retire, with no replacement alias.

Census, run before the first edit

Hits are listed by file, never as a count. "Lit" means the control query returned a non-empty result in the same checkout, so a zero beside it is a reading.

Repository Read at Searched Hits Control
objectui b403bb36, the branch base (origin/main) git grep -n -w PartialSchema over every tracked file The declaration and docblock in packages/types/src/index.ts. The import, and the "PartialSchema works as written" section, of base-schema-closed-face-8347.test.ts. Listed apart: the pending .changeset/8347-baseschema-closed-face.md, where one bullet describes the type, and packages/types/CHANGELOG.md (history). A case-insensitive substring query outside CHANGELOGs found nothing more. git grep -l -w SchemaByType: lit
objectstack origin/main da40a5f6, read through the owned ref refs/os-dev/issue-11608/os-main (no file edited) git grep -n -w PartialSchema, then git grep -n -i partialschema none git grep -l '@object-ui/types': lit
hotcrm 4054ec26, an anonymous shallow clone of objectstack-ai/hotcrm git grep -n -i partialschema none git grep -l '@object-ui/': lit. There is no @object-ui/types import to use as the control: hotcrm's metadata-reference test says the repo declares no @object-ui/* dependency.
cloud n/a NOT MEASURED: not reachable from this account n/a n/a

The objectui hits are the declaration, its own docblock, the #8347 pin, and a pending note that describes the type. None of them reads it. With no reader anywhere the census reached, the ruling is retire. For any consumer the census could not see, cloud included, the changeset's migration is the answer.

What this removes

  • packages/types/src/index.ts: the PartialSchema export and its whole docblock. ⛔ No replacement alias. The sibling SchemaByType and everything else stay byte-identical.
  • base-schema-closed-face-8347.test.ts: the PartialSchema rows. That is the import, section 4 ("PartialSchema works as written now"), and the two bindings that section fed into the vitest leg, whose length assertion moves with them. The ButtonSchema import goes too, because only section 4 read it. Sections 1 to 3 and the header are byte-identical, and the numbering stays coherent because section 4 was the last.
  • ⛔ .changeset/8347-baseschema-closed-face.md is not edited (see the note under review notes).

The census pin

packages/types/src/__tests__/partial-schema-retired-11608.test.ts reads the retirement through three instruments. Each one has a lit control.

  1. tsc (tsc -p tsconfig.test.json, chained from the package's type-check) reads a @ts-expect-error row on the root barrel (import('../index').PartialSchema). Its control is the sibling SchemaByType through the same import form, with no directive.
  2. The exports map (vitest) reads every entry's types target, maps it to the src/ module that tsc emits it from, and asserts that each module exists and that none of them names PartialSchema. An entry added later is covered automatically. Control: the same read finds export type SchemaByType in the . entry's source. It reads src/, not dist/, because the per-PR test job builds nothing first.
  3. The tracked tree (vitest) runs git grep -nE for the word over every tracked file except *CHANGELOG.md, .changeset/ and the pin itself. A re-export chain is caught here: whatever module declares the alias names it, and that module is tracked. Control: the same probe finds SchemaByType.

The pin also compiles the changeset's TO spelling for ButtonSchema. It checks that the spelling keeps the same key set, that type stays required (a @ts-expect-error row), and that a misspelled key is refused (a @ts-expect-error row).

Ablation (one-off proof, nothing left in the tree)

The fix was committed first (de3dcf5b). The mutation went through objectstack's scripts/ablation-replace.mjs, which puts export type PartialSchema back into index.ts right after SchemaByType. Its own on-disk evidence showed the anchor going from one hit to none, the replacement from none to one, and the blob changing. Both readings were taken on the mutated tree:

  • vitest: the run went red, and the two absence tests failed by name. "no entry source module names PartialSchema" listed . -> packages/types/src/index.ts. "the symbol PartialSchema appears nowhere: ..." listed the restored index.ts declaration. Both lit controls and the TO-spelling leg stayed green.
  • tsc: TS2578: Unused '@ts-expect-error' directive, on the _RetiredFromTheRootBarrel row.
  • Restore: git checkout HEAD -- on the absolute path. The blob after the restore equals the HEAD blob (333196a9), and git diff HEAD is empty. After that, all of packages/types vitest and type-check went green at de3dcf5b.
  • The first attempt was a no-op. The tool refused it because the replacement contained the anchor, so the anchor count did not drop. Nothing ran, and the file was restored. It was re-anchored and run again, and only that second run's reading is reported above.

Changeset

.changeset/11608-partialschema-retire.md, @object-ui/types: minor. It carries a breaking banner, the line **Clause-②: yes (narrowing)**, and a FROM and TO pair:

  • FROM: the PartialSchema import and annotation.
  • TO: the node type's own declared members, or, for a partial value, Partial of T intersected with { type: T['type'] }, written inline.

The TO spelling was checked to compile (generic and concrete) in a scratch program, with a control that went red. It is also pinned in the census pin above.

Gates (local, at de3dcf5b)

  • packages/types: type-check (all three tsc programs) green. vitest over packages/types/ green. Both ran under os-verify-lock.
  • pnpm lint on the touched files: eslint over the three touched .ts files is clean in its JSON output. This is a declared narrowing. The population is eslint's own config, and the file set is the three paths the JSON names. The invariance: the config enables no type-aware linting (no parserOptions.project or projectService), so this diff cannot change the verdict on any untouched file. The full pnpm lint is CI's.
  • The packages built for the dist-reading gates: every package was built first (pnpm --filter … run build, no turbo cache touched). dist/index.d.ts names SchemaByType and not PartialSchema.
  • Green, with the built tree in place: pnpm check:readme-exports and pnpm check:doc-snippets. pnpm check:doc-types is green too, although no doc names the type.
  • Also green: pnpm check:new-line-citations (zero new citations) and pnpm check:control-bytes.
  • The changeset checkers: changeset:check (fixed and no-major), check-changeset-presence, check-changeset-overwrite (one added, nothing pre-existing modified), check:changeset-claims and check:pending-changeset-literals. All green, and none objects to the two pending notes describing the same type.
  • Also run because the diff implicates them: type-check:coverage, check:test-path-roots, check:spec-symbols, check:unreferenced-sources, check:component-surface-parity (report-only) and check:published-dist. All exited 0.
  • The repo-wide git grep -w PartialSchema after the change, outside CHANGELOGs, hits only the two pending changesets and the new pin. Its control, SchemaByType, is lit.

Notes for review


Generated by Claude Code

… a census pin keeps it gone (objectui#11608)

The census at work time found no reader of `PartialSchema` in objectui,
objectstack `main` or hotcrm (cloud not reachable from this account). The
export and its docblock leave `packages/types/src/index.ts` with no
replacement alias, and the #8347 pin drops its `PartialSchema` section and
the two imports only that section read.

`partial-schema-retired-11608.test.ts` pins the retirement on three
instruments: a `@ts-expect-error` row on the root barrel (with a lit
sibling control), a vitest read of the source module behind every
`exports` entry, and a tracked-tree `git grep` with a lit control. It also
compiles the changeset's TO spelling.

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

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3313.9 KB 3330.4 KB
Main entry chunk (gzip) 151.4 KB 350 KB
Entry file index-BJvlwxTW.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) 17.22KB 6.37KB
app-shell (runtime-config.js) 22.52KB 7.86KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
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.54KB 3.46KB
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.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 572.97KB 137.34KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 262.75KB 66.62KB
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.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
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.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
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.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 84.26KB 23.05KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 143.54KB 38.79KB
plugin-designer (index.js) 231.41KB 48.84KB
plugin-detail (index.js) 245.74KB 64.59KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 176.65KB 45.50KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.92KB 64.87KB
plugin-kanban (index.js) 50.06KB 15.74KB
plugin-list (index.js) 116.72KB 29.10KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
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) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
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) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 1.13KB 0.65KB
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) 5.36KB 2.51KB
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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
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

@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3313.9 KB 3330.4 KB
Main entry chunk (gzip) 151.4 KB 350 KB
Entry file index-BJvlwxTW.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) 17.22KB 6.37KB
app-shell (runtime-config.js) 22.52KB 7.86KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
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.54KB 3.46KB
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.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 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) 572.97KB 137.34KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 262.75KB 66.62KB
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.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
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.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
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.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 84.26KB 23.05KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 143.54KB 38.79KB
plugin-designer (index.js) 231.41KB 48.84KB
plugin-detail (index.js) 245.74KB 64.59KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 176.65KB 45.50KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.92KB 64.87KB
plugin-kanban (index.js) 50.06KB 15.74KB
plugin-list (index.js) 116.72KB 29.10KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
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) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
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) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
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) 1.13KB 0.65KB
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) 5.36KB 2.51KB
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) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
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

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 215ea0d32f1d0c425b47eb7bc7ad4c4266622da6
Local-runs: none

Inputs read: card #11608 (body, triage 5979662969, unlock 5981020588, claim 5981086062, dev report 5981384796); PR #11616 (body, file list, net diff against main at 2abec3a9); the check-runs on the head; read-only git grep over objectui at the head, objectstack origin/main and the anonymous hotcrm clone, for the census premise only. Nothing was checked out, built, run or re-run. The brief's premises were re-derived, not adopted.

The head. 215ea0d3 is a two-parent merge: the dev's de3dcf5b (parent 1, branched off b403bb36) and main at 2abec3a9 (parent 2, objectui#11614). Its combined diff is empty (0 bytes), so the merge brought nothing of its own. The net diff against main at 2abec3a9 has the same patch-id as the dev's commit against its own parent and as the diff the PR API serves (7e1cb8b9): four files, +215 / -46, exactly what the PR body says. The one file both sides touched, the root barrel of @object-ui/types, carries objectui#11614's GRID_FIELD_RETIRED_KEYS re-export and no PartialSchema at the head. The exports map at the head has twelve entries, including objectui#11614's new ./internal/retired-field-keys, and every entry's source module exists, so the census pin's enumeration (instrument 2 below) is satisfiable on the merged tree even though the dev's local run predates the merge.

① Derived judgments

1. The removal of the published PartialSchema export from the . entry, with no replacement alias — RIGHT. The diff deletes one export type declaration and its docblock from the root barrel and nothing else in that file; the sibling SchemaByType, the container type beside it and the rest of the barrel are untouched (the barrel's other lines at the head equal main's). The alias was type-only, so no runtime namespace changes, no zod face changes, no renderer accept set changes. Its only published entry was .; no other entry's source module names it (re-read below). This is the ruling the claim premised (retire, no alias) executed as written, and the removed docblock's own last paragraph was the pointer to this very card, so it goes with the alias.

2. The census premise — RE-DERIVED, HOLDS, with cloud NOT MEASURED. Readings taken by this review, each a count against a named tree:

  • objectui at the head 215ea0d3, git grep -n -w PartialSchema over every tracked file: hits in .changeset/11608-partialschema-retire.md (5 lines), .changeset/8347-baseschema-closed-face.md (1 line, the "works as written" bullet), packages/types/CHANGELOG.md (7 lines, released history: objectui#6397's collapse pin and its notes) and the new pin (9 lines). Nothing else. A case-insensitive substring query outside CHANGELOGs adds only the pin's own word regex and its self-exclusion row. A looser partial[-_ ]schema query outside CHANGELOGs finds one English sentence in plugin-form's master-detail derivation about "partial schemas" as a noun, not the alias, plus the pin's self-exclusion row. Control: git grep -l -w SchemaByType returns files. So no tracked file outside CHANGELOGs, the two pending changesets and the new pin names it. No README, no content/, no skills/, no apps/, no examples/ and no doc snippet names it, which is why the README-export and doc-snippet gates have nothing to object to.
  • objectstack origin/main at da40a5f6 (the same commit the dev read): git grep -n -i partialschema over every tracked file, zero hits; the loose partial[-_ ]schema query hits only prose about partial schemas in sql-driver, automation and spec comments, none naming the alias. Control: git grep -l '@object-ui/types' returns files.
  • hotcrm at 4054ec26 (the anonymous shallow clone, origin objectstack-ai/hotcrm): git grep -n -i partialschema, zero hits; the loose query, zero hits. Control: git grep -l '@object-ui/' returns files; @object-ui/types itself is imported nowhere there, as the dev reported, so the wider control is the right one.
  • cloud: NOT MEASURED, by this review as by the dev; not reachable from this account. The changeset's FROM/TO pair is the answer for any consumer the census could not see, and the pin's header says so in as many words.
    The premise the claim conditioned the ruling on ("no reader exists") stands on every tree that could be read. The ruling's branch for "a reader is found" was correctly not taken.

3. The edit to objectui#8347's pin base-schema-closed-face-8347.test.ts — RIGHT, including the ButtonSchema import. Compared byte-for-byte against main: the header (the first 41 lines) is identical; sections 1 to 3 are identical to main's lines after the two deleted imports; what is gone is the PartialSchema import, the ButtonSchema import, section 4 (the three PartialSchema rows and their comments) and, in the vitest leg, the two bindings that section fed (partialButton.type from the first array, partialMisspelled from the second, whose length assertion moves from 6 to 5). The remaining five bindings are all still declared, so the length assertion is still a live count rather than a hollow one. ButtonSchema has no other reader in the file (the button literals in the nested rows are typed through BaseSchema children), so leaving the import would have been a dead type import; the root tsconfig has noUnusedLocals off, so tsc would not have refused it, but a dead import in a pin whose header says every row is read is a wrong state, and removing it is within the surface the claim meant even though its enumeration ("an import and three rows") counted one import. The header's claims ("section 3" pins the bound; tsc -p tsconfig.test.json is the reader) remain true of the trimmed file; nothing in the header named section 4 or the alias.

4. The new census pin partial-schema-retired-11608.test.ts — RIGHT, and it would go red BY NAME if the export came back. Read instrument by instrument:

  • Instrument 1 (tsc): a @ts-expect-error row on import('../index').PartialSchema applied to ButtonSchema, with a lit control on import('../index').SchemaByType applied to 'button' through the same import form and no directive. If the export returns, the directive stops matching and TS2578 names the row _RetiredFromTheRootBarrel; if the barrel were emptied or moved, the control row fails instead. Its reader is tsc -p tsconfig.test.json, chained from the package's type-check script, which CI's Type Check job runs through pnpm type-check; tsconfig.test.json includes src/**/*.test.ts, and type-check:coverage holds that every test file is read.
  • Instrument 2 (vitest, the exports map): enumerates every entry of package.json's exports, maps each ./dist/X.d.ts types target to src/X.ts, asserts the . entry is among them (so the enumeration cannot be vacuous where it matters), that every source exists, and that none contains the word; the control asserts the . source still declares SchemaByType. All twelve entries at the head map to existing sources, the ./zod and ./internal/retired-field-keys ones included. A returning declaration in the barrel fails the test named "no entry source module names PartialSchema" with the entry and path listed. It reads src/, not dist/, because the per-PR test job builds nothing first, which is the right choice here.
  • Instrument 3 (vitest, the tracked tree): git grep -nE for the word over every tracked file with three exclusions (*CHANGELOG.md, .changeset/, the pin itself), exit 1 normalised to an empty list and any other non-zero rethrown; the control runs the same probe for SchemaByType and demands hits. This is the half that sees a re-export chain or a declaration moved into another module. A returning symbol anywhere tracked fails the test named "the symbol PartialSchema appears nowhere". Paths are rooted on import.meta.url, never process.cwd(), which is the spelling the test-path-roots gate asks for; the shape (exclusions, exit-1 normalisation, lit control) is the same one eventable-schema-retired-6497.test.ts and the other retirement pins in this package already use.
  • The absent fourth instrument (a runtime name in module leg) is correctly absent: the alias was export type and never in a runtime namespace, so that leg would pass before and after.
  • The TO-spelling rows: ButtonPatch is Partial of ButtonSchema intersected with an object type whose type member is ButtonSchema's type. The Equal row on its key set holds (the key set of that intersection is ButtonSchema's), type stays required (a @ts-expect-error row on a literal without it) and a misspelled key is refused (a @ts-expect-error row on labell), with the bindings kept alive in the vitest leg. These three rows are the pin of judgment ①.5 below.
  • The dev's ablation is a reading in the report, not re-run here: it names the two vitest failures by test name and the TS2578 row, with the restore proven by blob equality. It is consistent with the reading above of what each instrument would do, and the verdict does not rest on it.
  • One limit, stated in the pin's header and accepted: the tree scan sees this repository only; the external consumer's reader is the compile error plus the changeset. Nothing in the pin claims more.

5. The changeset's TO spelling, pinned as compiling — RIGHT. The changeset's TO is "the node type's own declared members", or, for a partial value, Partial of T intersected with an object type whose type is T's type, written inline. The pin compiles the concrete ButtonSchema form with the three rows above, so the migration a consumer is handed is a spelling the package's own type-check reads on every run. The spelling is also semantically at least as good as the retired alias: the alias was Omit over keyof T and so still collapsed on a T carrying its own index signature (the removed docblock's own caveat); Partial of T maps the declared members directly and has no such step.

No other accept-set or public-surface change is implied by the diff: nothing under src/zod, no renderer, no runtime value, no other entry.

② Semver level

The declaration matches the diff. .changeset/11608-partialschema-retire.md declares '@object-ui/types': minor, opens with a BREAKING banner, and carries **Clause-②: yes (narrowing)**; the PR body's second line is Clause-②: yes (narrowing). Both carriers read declared / yes / narrowing through the fleet's one reader (readClause2Line), and the two agree. The diff removes one published name from the . entry's type face and adds nothing: a narrowing of the public surface, a breaking change for any external importer of the name. Under this repository's version policy (one fixed group, no major in a changeset; a breaking change ships as minor with the break stated in the body, enforced by check-changeset-no-major) minor with the banner and the stated semantics is the correct level, and Changeset Bump Policy and Changeset Fixed Group Check on the head agree. The changeset carries the FROM line (the import and the annotation) and the TO line (the inline spelling), with a before/after fence that compiles under the pin. @object-ui/types is the only package whose source moves, and it is the only package the changeset names; Changeset Declaration on the head is green.

The release-text interaction — acceptable as shipped; this PR was right not to edit the other note, and nothing must change. The still-pending .changeset/8347-baseschema-closed-face.md has a bullet "PartialSchema of T works as written"; this PR's changeset has a bullet "objectui#8347's note ... This removal supersedes it". Both are minor for @object-ui/types, so if nothing is released in between they fold into the same version's "Minor Changes" list, as two entries. Judgment:

③ Boundary flags

Deviation (a): the 8347 pin also lost its ButtonSchema import, beyond "an import and three rows" — ANSWERED, correct. Judged in ①.3: section 4 was that import's only reader; a dead type import would have survived tsc (noUnusedLocals is off) and lint reports unused names as warnings, so keeping it would have left a quiet wrong state in a pin. The claim's enumeration under-counted by one import; the dev's reading of the surface's intent is right and the edit is byte-minimal otherwise.

Deviation (b): check:published-dist was run outside os-verify-lock, and it performs its own full build of all 39 packages (339 s) — ANSWERED as a process deviation, escalated as a brief defect, no effect on the diff or on this verdict. The run produced a reading (exit 0) and wrote nothing into the tree; its cost landed on the shared container as an unlocked heavy run, which the dev reported correctly and promptly. The remedy is upstream of the dev: a dispatch brief that names check:published-dist among derived gates should say that it builds every package, so the dev takes the lock before starting it. Recorded for the dispatching seat; nothing to change in this PR.

open_questions: the report lists none. The summary's "release-text note for the seat" is the one question in it, and ② answers it: acceptable as shipped; if amended at all, by the owner of #8347's note, both bullets together, in a follow-up; not a condition here.

Serial state. The dev reported it did not re-merge after #11614 landed; the head shows the seat's update-branch merge did that, and the readings above are taken on the merged head. The dev's local vitest and type-check readings are at de3dcf5b, before the merge; what the merge added that the pin reads is one exports entry whose source exists, and the check-runs on the merged head are what the verdict rests on.

Dispatch shape. The PR is a draft with the card's assignee mirrored, no labels written by the dev, the branch named by the claim; the file list touches no governed surface (Governed Surface Queue Guard green on the pull_request leg). The ready / arm steps belong to the dispatching seat, as the card's claim says.

Check-runs on the head

Every conclusion below is a gate verdict as the platform recorded it; nothing was re-run. Readings, each with its time:

  • reading 1 at 2026-10-04T15:10Z: 26 runs, 20 completed, 6 in progress, 0 not passing (open: Lint, Skill Example Check, Live E2E (informational), Doc Snippet Type Check, README Export Check, Bundle Analysis)
  • reading 2 at 2026-10-04T15:13Z: 42 runs, 28 completed, 14 in progress, 0 not passing (open: Test (shard 5/8), Test (shard 7/8), Test (shard 8/8), Test (shard 1/8), Test (shard 6/8), Test (shard 3/8), Test (shard 2/8), Test (shard 4/8), Type Check, Spec Main Shape Gate, Lint, Skill Example Check, Doc Snippet Type Check, README Export Check)
  • reading 3 at 2026-10-04T15:15Z: 42 runs, 31 completed, 11 in progress, 0 not passing (open: Test (shard 5/8), Test (shard 7/8), Test (shard 8/8), Test (shard 1/8), Test (shard 6/8), Test (shard 3/8), Test (shard 2/8), Test (shard 4/8), Type Check, Spec Main Shape Gate, Lint)
  • reading 4 at 2026-10-04T15:20Z: 42 runs, 32 completed, 10 in progress, 0 not passing (open: Test (shard 5/8), Test (shard 7/8), Test (shard 8/8), Test (shard 1/8), Test (shard 6/8), Test (shard 3/8), Test (shard 2/8), Test (shard 4/8), Type Check, Spec Main Shape Gate)
  • reading 5 at 2026-10-04T15:25Z: 42 runs, 41 completed, 1 in progress, 0 not passing (open: Test (shard 1/8))
  • reading 6 at 2026-10-04T15:28Z: 43 runs, 43 completed, 0 in progress, 0 not passing

Final reading (6, 2026-10-04T15:28Z): 43 check-runs, 0 still in progress.

Check-run Status Conclusion Completed
Action Ref Convention completed success 2026-10-04T15:10Z
Build & E2E completed success 2026-10-04T15:12Z
Build Docs completed success 2026-10-04T15:13Z
Bundle Analysis completed success 2026-10-04T15:13Z
Changeset Bump Policy completed success 2026-10-04T15:09Z
Changeset Claim Re-read completed success 2026-10-04T15:10Z
Changeset Declaration completed success 2026-10-04T15:10Z
Changeset Fixed Group Check completed success 2026-10-04T15:11Z
Changeset Overwrite Report completed success 2026-10-04T15:10Z
Control Byte Scan completed success 2026-10-04T15:10Z
dependabot completed skipped 2026-10-04T15:09Z
Doc Component Type Check completed success 2026-10-04T15:09Z
Doc Example Id Check completed success 2026-10-04T15:09Z
Doc Fence Language Check completed success 2026-10-04T15:09Z
Doc Snippet Type Check completed success 2026-10-04T15:15Z
Docs Route Eager Closure Check completed success 2026-10-04T15:10Z
Governed Surface Queue Guard completed success 2026-10-04T15:10Z
Inert vi.mock Specifier Check completed success 2026-10-04T15:10Z
Internal Docs Link Check completed success 2026-10-04T15:09Z
label completed success 2026-10-04T15:09Z
Line Citation Gate completed success 2026-10-04T15:10Z
Lint completed success 2026-10-04T15:17Z
Live E2E (informational) completed success 2026-10-04T15:11Z
Pre-Install Import Graph Check completed success 2026-10-04T15:10Z
README Export Check completed success 2026-10-04T15:14Z
Shell Escape Residue Scan completed success 2026-10-04T15:09Z
Skill Eval Token Check completed success 2026-10-04T15:09Z
Skill Example Check completed success 2026-10-04T15:14Z
Skill Guide Path Check completed success 2026-10-04T15:10Z
Spec Main Shape Gate completed success 2026-10-04T15:23Z
Test completed success 2026-10-04T15:27Z
Test (coverage shard, the unexpanded matrix placeholder row, spelled with a dollar-double-brace matrix expression in its name) completed skipped 2026-10-04T15:11Z
Test (coverage) completed skipped 2026-10-04T15:11Z
Test (dist pins) completed success 2026-10-04T15:13Z
Test (shard 1/8) completed success 2026-10-04T15:26Z
Test (shard 2/8) completed success 2026-10-04T15:24Z
Test (shard 3/8) completed success 2026-10-04T15:24Z
Test (shard 4/8) completed success 2026-10-04T15:24Z
Test (shard 5/8) completed success 2026-10-04T15:24Z
Test (shard 6/8) completed success 2026-10-04T15:24Z
Test (shard 7/8) completed success 2026-10-04T15:21Z
Test (shard 8/8) completed success 2026-10-04T15:23Z
Type Check completed success 2026-10-04T15:21Z

Tally: 40 success, 3 skipped (the matrix placeholders and the dependabot leg, which never run on a non-dependabot PR), 0 with any other conclusion, 0 in progress.

Implemented-by: claude/issue-11608-partialschema-retire
Reviewed-by: session_01CPvhwGcirXqBGEdPSb72TZ

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 4, 2026 15:30
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 4, 2026 15:30
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 4, 2026
Merged via the queue into main with commit 8b14aec Oct 4, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11608-partialschema-retire branch October 4, 2026 15:46
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Oct 7, 2026
…`InlineGridColumnSchema`, by reference (objectui#11266) (objectstack-ai#11618)

Fixes objectstack-ai#11266
Clause-②: yes (narrowing)

`@objectstack/spec` 17.6.0 judges `FormViewSchema.subforms[].columns`
with its strict `InlineGridColumnSchema`
(objectstack-ai/objectstack#20927, `bee75cebe6`). The `object-form`
mirror here still held `z.array(z.any())`, so `objectui validate`
accepted columns that `os validate` refuses. This PR makes one column
the spec's schema, by reference, on the zod face and on the TypeScript
face.

Dispatched under claim `5981407176` (`domain:spec`, size S). Session:
`https://claude.ai/code/session_01CPvhwGcirXqBGEdPSb72TZ`.

## What changes

- `packages/types/src/zod/objectql.zod.ts`, the `object-form` mirror's
`subforms[]` item: `columns` goes from `z.array(z.any())` to
`z.array(stripImportedDefaults(SpecInlineGridColumnSchema))`, imported
from `@objectstack/spec/data`. It crosses the objectui#8317 import
boundary like every other spec read. The column schema carries no
default, so the crossing is the identity and the member IS the spec's
object. No hand-copied column shape.
- `packages/types/src/objectql.ts`: `ObjectFormSchema`'s
`subforms[].columns` goes from `any[]` to the spec's `InlineGridColumn`
(its `z.input`, the authoring face), by a type-only import.
`ObjectViewSchema['form']` follows, because it picks from
`ObjectFormSchema`.
- Docs: `content/docs/plugins/plugin-view.mdx` and
`packages/plugin-view/README.md`, section "Child records
(master-detail)", wrote `columns: ['product', 'quantity', 'price']`.
Bare strings: `hydrateColumns` reads `col.name`, the spec refuses them,
and with the TypeScript face narrowed that snippet no longer compiles
under `check:doc-snippets`. Both now write `{ name: ... }` objects and
say what a column is. ⚠️ These two files are outside the claim's file
surface. They are added because this change makes them false: the
snippet gate would red on them.
- `.changeset/11266-subforms-columns-mirror.md`: `@object-ui/types:
minor`, with a BREAKING banner, FROM / TO migration lines and the
`Clause-②: yes (narrowing)` line.

## Premise readings (this worktree, base `2abec3a9`, which is
`origin/main` at PR time)

- `pnpm-lock.yaml` resolves `@objectstack/spec` at 17.6.0 only (two
entries, both 17.6.0). The package `packages/types` resolves answers
`17.6.0`.
- `@objectstack/spec/data` exports `InlineGridColumnSchema`.
- `FormViewSchema.safeParse` of a simple form whose
`subforms[0].columns` holds COL:
- COL `{ name: 'qty', bogusKey: 1 }` is refused: one `unrecognized_keys`
issue at `subforms.0.columns.0`, keys `bogusKey`.
  - COL `{ name: 'qty' }` is accepted (the control).
- Control version: 17.5.0, installed from npm into a scratch directory.
Its form view ACCEPTS the bogus key (the `z.any()` era), so the probe
can fire.

## The `currency` + `scale` finding

- A column that DECLARES `type: 'currency'` and carries `scale` (`{
name: 'amount', type: 'currency', scale: 2 }`) is refused by the spec's
`InlineGridColumnSchema` itself, at the zod level: one `custom` issue at
path `scale`. **It is pinned.** Through the mirror the same column is
refused at `form.subforms.0.columns.0.scale`, and the pin asserts that
the message equals the spec schema's own message, read in the same run.
No literal text is pinned.
- An identity-only column carrying `scale` (`{ name: 'amount', scale: 2
}`) is ACCEPTED by the spec's column schema, at 17.5.0 and at 17.6.0.
Its refusal is `defineStack`'s, which resolves `name` through
`childObject` to a `currency` field. The mirror judges a document that
does not carry the child object's fields, so it cannot reproduce that
check. The pin records the mirror ACCEPTING it, so reaching for it later
is a deliberate change. The render-time report from objectui#11238
(`reportCurrencyColumnScale` in `plugin-form`'s `hydrateColumns`) stays
the backstop. No `plugin-form` behaviour changes: patch round 1
(`b1d035bb`) corrects only its prose, see Acceptance note 2.

## The `@objectstack/spec` range

`packages/types/package.json` already reads `^17.6.0` on `origin/main`:
objectui#11438 (PR objectstack-ai#11531, the 17.6.0 lockfile move) raised it. So it
does not move here.

Would this change alone need 17.6.0? No. The mirror reads
`InlineGridColumnSchema` only, and 17.5.0 already exports it with the
same verdicts on the three probes: bogus key refused with
`unrecognized_keys`, typed currency with `scale` refused at `scale`, `{
name }` accepted. What 17.6.0 adds is the spec's own `FormViewSchema`
reading that schema. That is the other door's verdict, not this
mirror's. `check:spec-floors` is green on the built artifact.

## The pin


`packages/types/src/__tests__/object-form-subforms-columns-11266.test.ts`,
17 tests:

- **By reference.** The mirror's column schema is `toBe`
`stripImportedDefaults(SpecInlineGridColumnSchema)`, and `toBe` the spec
object itself.
- **On the tolerant face** (`safeValidateSchema`, which `objectui
validate` runs) **and on the strict authoring face**, through the
object-view `form` slot. That slot is the route an authored document
has: `subforms` is a form-VIEW member, which the `object-form` row
refuses in its `properties` bag.
  - `{ name }` is accepted: the lit control.
- A bogus key is refused: one `unrecognized_keys` issue at the column,
keys `bogusKey`.
- Typed currency with `scale` is refused: one `custom` issue at the
column's `scale`.
  - A bare string is refused: `invalid_type` at the column.
  - Identity-only with `scale` is accepted.
- **The flat mirror** gives the same codes at the same column.
- **One verdict across the two doors.** For each probe column,
`safeValidateSchema` succeeds exactly when the spec's `FormViewSchema`
does, read live.
- **Type level** (judged by `tsc -p tsconfig.test.json` inside
`type-check`): a declared column `Equal`s the spec's `InlineGridColumn`,
and a string column is a `@ts-expect-error`.

**Ablation.** The fix was committed first. The ablation used
objectstack's `scripts/ablation-replace.mjs` in wrap mode, inside
`os-verify-lock`.
- Mutation: anchor `columns:
z.array(stripImportedDefaults(SpecInlineGridColumnSchema)).optional()`
became `columns: z.array(z.any()).optional()`. Anchor count went from 1
to 0, and the blob from `27ae77d7` to `d6b4ac83`.
- Result: **11 failed, 6 passed (17)**. The six that stay green are the
two controls, the two identity-only rows, and the two cross-door rows
whose expected verdict is accept.
- Restore: the blob after restore is `27ae77d7`, the same as the blob at
HEAD, and `git diff HEAD` is empty. The pin rerun: **17 passed**.

## Tests moved

- `imported-defaults-8317.test.ts`: `IMPORTED` gains
`['InlineGridColumnSchema', SpecInlineGridColumnSchema]`. The census
"every symbol the mirrors import is covered by the differential above"
requires it. The row is measured as the identity (no default, no
`z.lazy`).
- No `zod-mirror-parity` `KnownDrift` or `SPEC_DERIVED_PAIRS` row moves:
  - `ObjectFormSchema` was already spec-derived.
- Its `KnownDrift` entry names only the five runtime-slot handler keys.
- The TypeScript and zod faces of `columns` now carry the same type.
Before, it was `any[]` against `z.any()`, so the ledger saw nothing
either way.
- `object-form-unmirrored-members-6152.test.ts`: the `subforms` row has
no columns in either value, keeps its verdicts, and is not edited.

**Repo-wide sweep.** I grepped for `subforms` and `columns` across
tests, docs, JSON and scripts. No test anywhere asserts that `objectui
validate` accepts a bogus column or a typed-currency column with
`scale`, so no pin flips. The two doc examples above were the only
documents writing a column the narrowed faces refuse.

## Gate readings, at head `ce55b465`

- `pnpm exec vitest run packages/types/` (under the lock): `Test Files
355 passed (355)`, `Tests 9492 passed (9492)`.
- `packages/types` `type-check` (the build program, the examples program
and the test program): exit 0.
- The import side of the narrowed TypeScript face: `type-check` for
`@object-ui/plugin-form`, `@object-ui/plugin-view` and
`@object-ui/app-shell` exits 0, and each echoes `type-check: Done`. They
ran against the closure that `check:doc-snippets --build-filter` names,
built with pnpm, not turbo.
- `vitest run packages/cli/` plus the `subforms`-adjacent consumer tests
(`deriveMasterDetail.currencyScale-10783`, `subformHosts`,
`MetadataProvider.merge`): 28 files, 390 tests, all passed.
- `eslint --no-inline-config --format json` over the four touched
TypeScript files: 4 files, 0 errors. The warnings are existing
`no-explicit-any` hits in `objectql.ts` and `objectql.zod.ts`; the pin
file has 0.
- This is a narrowing of `pnpm lint`, and it is declared. Population:
`eslint.config.js` lints `**/*.{ts,tsx}`, so the two edited `.md` /
`.mdx` files are outside it. File count: 4, from the JSON output.
Invariance: the config sets no `parserOptions.project` or
`projectService`, so type-aware linting is off, and this diff cannot
move the verdict on any untouched file. The full run belongs to CI.
- `check:doc-snippets`: 777 of 777 blocks judged, 0 failed.
- Exit 0 for each of: `check:doc-examples`, `check:doc-types`,
`check:doc-fences`, `check:doc-example-ids`,
`check:doc-example-readers`, `docs:check-links`.
- `check:new-line-citations`: 0 new. `check:control-bytes`: OK.
- Changeset checkers, exit 0 for each: `check:changeset-claims`,
`check:pending-changeset-literals`, `changeset:check` (fixed and
no-major), `check-changeset-presence` and `check-changeset-overwrite`.
- Exit 0 for each of: `check:spec-symbols`, `check:spec-floors`,
`check:installed-pin-claims`, `check:phantom-deps`, `check:unused-deps`,
`check:readme-exports`, `check:test-path-roots`,
`check:component-surface-parity`.
- `check:spec-floors` was first refused with `no-artifact` on the
unbuilt `@object-ui/plugin-tree`. I built that package and reran it.
- `check-governed-queue-guard --test` over the seven paths: NOT
GOVERNED.
- **NOT MEASURED locally, left to CI:** the full `pnpm test`, the
tree-wide `pnpm lint`, and `check:published-dist`.
- **Patch round 1, at head `b1d035bb`:** `@object-ui/plugin-form`
`type-check` passes, with its dependency closure built by pnpm, not
turbo. `vitest run` on `deriveMasterDetail.currencyScale-10783.test.ts`
passes, the renamed test among them. eslint over the two touched files
reports no errors; its warnings are existing `no-explicit-any` in code.
`check:new-line-citations` reports no new citation,
`check:control-bytes` passes, and the changeset checkers exit 0. The
readings above are at `ce55b465`; this round touched only the two
`plugin-form` files.

## Acceptance notes

1. **The parse output.** `InlineGridColumnSchema`'s `readonlyWhen` and
`requiredWhen` carry the spec's ExpressionInput pipe, so
`safeValidateSchema` returns a column's string predicate as `{ dialect:
'cel', source }`. Measured: `readonlyWhen: 'record.locked'` comes back
as the envelope. The input document is not changed.
   - This is the spec schema's own output, taken by reference.
- Four other imported crossings already return transformed values. I
counted them by walking `AnyComponentSchema`: a page's `slots.header`
and its `visibleWhen`, `listViews` `exportOptions`, and an action
`params` entry.
- objectui#8347's Q6 = B ("a string stays a string") was ruled for
`BaseSchema.visibleWhen`. Nothing extends it to imported subtrees.
   - The changeset states it. Flagged for the contract review.
2. **Stale prose, fixed in patch round 1 (`b1d035bb`).** Seat ruling: a
PR fixes the sentences it makes false.
- `reportCurrencyColumnScale`'s docblock in
`packages/plugin-form/src/deriveMasterDetail.ts`, the
`deriveMasterDetail.currencyScale-10783` test header and one of its test
names said a subform's `columns` is `z.array(z.any())` in the
`object-form` mirror and in the spec's `FormViewSchema`, and that the
spec does not judge that path.
- They now say both validators judge a declared column with
`InlineGridColumnSchema`. They keep the report's reason: neither zod
face sees an identity-only `{ name, scale }` column whose child field is
a currency (only `defineStack` does, at publish,
objectstack-ai/objectstack#20927), and nothing runs either validator
between a stored or code-built form view and the render.
- Comment and test-name bytes only. The TypeScript printer with
`removeComments` prints `deriveMasterDetail.ts` identically before and
after, and the test file differs only in that one test-name string. The
old test name was referenced nowhere else in the tree.
- Left as written: the released `@object-ui/plugin-form` and
`@object-ui/fields` CHANGELOG text that says the same, which is history.
3. **Looser local copies.** `DrawerForm.tsx` and `ModalForm.tsx` in
`plugin-form` declare their own `subforms` item types with `columns` as
`any[]`. They receive values from the now-typed face, so nothing breaks,
but they are looser than the spec. Carrier: none.

Serial: objectui#11608's PR objectstack-ai#11616 is still open. Its files
(`packages/types/src/index.ts` and the objectui#8347 pin) are disjoint
from these. `origin/main` has not moved since this branch was cut
(`2abec3a9`), so the merge before opening was a no-op.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01CPvhwGcirXqBGEdPSb72TZ)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Leehom <pm@objectstack.ai>
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.

types: PartialSchema<T> is a published export with zero consumers; enforce-or-remove once objectui#8347's removal lands (retire, or give it a reader)

2 participants