fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914) - #7355

Merged
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors
Sep 2, 2026
Merged

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914)#7355
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#6576
Fixes#6914
Clause-②: yes — contract review required before release

Maintainer ruling 2026-08-31 (director seat, batch #10, option A, verbatim 「同意」), executed as written: two new exported schema types in @object-ui/types, each extends BaseSchema, and the two widget prop types that declared their schema as a hand-rolled inline literal are anchored to them. #6914 (the two keys the data-table reads and declared nowhere) is closed in the same stroke, per the erratum: drillDown and onRowClick.

Session for this implementation: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho (kept in prose because a PR body edit strips the footer form).

What changed

No runtime behaviour changes; both widgets render exactly as before (792 plugin-list and 805 plugin-dashboard tests unchanged and green).

Contract-review pack — accept-set before / after

Real tsc --noEmit against the real prop types, one labelled statement per line, diagnostics mapped by line number (origin/main at 2956d7af8, then this head). Each row is the exact literal assigned to the prop's schema type. The origin/main column repeats the prior report's probe and re-measures it on the same instrument.

ObjectGalleryProps.schema — PUBLISHED (plugin-list/src/index.tsx exports the type)

caseliteralorigin/mainthis headmovement
G1{ objectName, visibleWhen } (no type)REJECTED — TS2353 visibleWhen does not existREJECTED — TS2741 type is missingreason changed, see G6
G6{ type: 'object-gallery', objectName, visibleWhen }REJECTED — TS2353 type does not existACCEPTEDWIDENS — a real base member is writable
G2{ type: 'object-gallery', objectName }REJECTED — TS2353 type does not existACCEPTEDWIDENS — the node's own type key can be spelled
G4{ objectName } (minimal)ACCEPTEDREJECTED — TS2741 type is missingNARROWS — type is required (the prior report's G6)
G12{ type: 'gallery', objectName }REJECTED — TS2353 type does not existREJECTED — TS2322 "gallery" not assignable to "object-gallery"NARROWS for the right reason — the literal is the registry key
G3 / G7{ [type,] objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to array of recordsREJECTED — same TS2322UNCHANGED — the prior report predicted data would collapse to any under a BaseSchema INTERSECTION; extends overrides the member instead, so the widening does not happen
G11{ type, objectName, data: [{ id: 1 }] }REJECTED — TS2353 typeACCEPTEDWIDENS (via type)
G9{ objectName, visible: 42 } (no type)REJECTED — TS2353 visible does not existREJECTED — TS2322 number not assignable to string or booleanreason changed: unknown key → wrong-typed declared member
G8{ type, objectName, visible: 42 }REJECTED — TS2353 typeREJECTED — TS2322same as G9 with type present
G10{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited bind?: string)UNCHANGED — inherited now
G5{ type, objectName, visibleWhn: 'typo' }REJECTED — TS2353 type (the unknown key would also have been refused)ACCEPTEDWIDENS — the ceiling below

Net for the published type: WIDENS to every BaseSchema member (21 members gained; visibleWhen, visible, id, hidden, disabled, testId, ariaLabel, label, style, … are writable and type-checked), NARROWS in one place (type required and pinned to 'object-gallery'). data, bind, filter, gallery, navigation, grouping, imageField, titleField keep their exact types.

ObjectDataTableProps.schema — NOT exported from plugin-dashboard/src/index.tsx (reaches consumers structurally as the exported component's prop)

caseliteralorigin/mainthis headmovement
D1{ type: 'object-data-table', objectName, bogusKey: 1 }ACCEPTEDACCEPTEDUNCHANGED — the ceiling below
D2{ type, objectName, visible: 42 }ACCEPTED (absorbed by the literal's index signature)REJECTED — TS2322 number not assignable to string or booleanNARROWS — a wrong-typed base member is refused
D3{ type, objectName, drillDown: { enabled: true, mode: 'record' } }ACCEPTED (as any)ACCEPTED (as DrillDownConfig)UNCHANGED in verdict; DECLARED now (#6914)
D4{ type, objectName, drillDown: { enabled: 'yes' } }ACCEPTED (absorbed)REJECTED — TS2322 string not assignable to booleanNARROWS — a wrong-shaped drillDown is refused
D5{ type, objectName, onRowClick: an arrow function of one untyped parameter }REJECTED — TS7006 row implicitly any (the key resolved to any, so nothing typed the parameter)ACCEPTED — row is contextually typedWIDENS in declaration — the handler can be written without an annotation (#6914)
D10{ type, objectName, onRowClick: 'not-a-function' }ACCEPTED (absorbed)REJECTED — TS2322NARROWS
D6{ type, objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to any[]REJECTED — sameUNCHANGED
D7{ type: 'data-table', objectName }ACCEPTED (type: string)REJECTED — TS2322 "data-table" not assignable to "object-data-table"NARROWS — the literal is the registry key
D8{ type: 'object-data-table' }ACCEPTEDACCEPTEDUNCHANGED
D9{ objectName } (no type)REJECTED — TS2741 type missingREJECTED — TS2741UNCHANGED (type was already required)
D11{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited)UNCHANGED
D12{ type, objectName, visibleWhen: 'ready' }ACCEPTED (absorbed as any)ACCEPTED (declared string)UNCHANGED in verdict; typed now

Net for the unpublished type: NARROWS (the literal's own index signature is gone, so declared base members and the two #6914 keys are type-checked; type is the registry key). In-repo call sites re-checked: every plugin-dashboard test and DrillDownDrawer.tsx compile unchanged — the tests that write other spellings already carry as any.

The ceiling, stated plainly

BaseSchema still carries [key: string]: any (#5155 remains open). Anchoring inherits it, so an UNKNOWN key (visibleWhn, bogusKey) remains ACCEPTED on BOTH prop types after this PR — on the data-table it always was; on the gallery it is new (G5). The ruling accepted that cost; the pins record it as a counter-probe rather than hiding it.

Verification (on the committed heads f84c14c17 — the implementation — and the final head named below, tree clean)

Every heavy command ran under scripts/pm/os-verify-lock.sh (slot dev-6576); verdict lines quoted from the tools, not from a bare exit status.

  • Dependency closure built first (turbo run build for the three plugins' dependency closures, then every packages/*: Tasks: 39 successful, 39 total).
  • pnpm --filter @object-ui/types run buildtsc && node ../../scripts/check-dist-completeness.mjs, exit 0; dist/index.d.ts carries both new names.
  • pnpm --filter … run type-check — script echoed for each: @object-ui/types@17.6.0 type-check, @object-ui/plugin-list@17.6.0 type-check, @object-ui/plugin-dashboard@17.6.0 type-check, @object-ui/plugin-view@17.6.0 type-check — 0 errors each (each script is tsc --noEmit plus the package's test project, so every pin and both ledgers are compiled). plugin-view is the other producer of object-gallery nodes; it builds a plain node and never passes through ObjectGalleryProps, so nothing there moved.
  • pnpm --filter … run lint (types / plugin-list / plugin-dashboard): 0 errors each (warnings only, pre-existing no-explicit-any in files that already carried it; the new ObjectDataTableSchema transcribes the literal's any spellings unchanged).
  • Vitest (root-relative paths, no --), on the final head e5b24a3bb: packages/types/Test Files 88 passed (88), Tests 1434 passed (1434); packages/plugin-list/Test Files 63 passed (63), Tests 792 passed (792); the six ledger and pin files together (zod-mirror-parity, handler-keys-json-refusal-6124, base-bind-declared, widget-schema-anchors-6576, both schemaAnchor-6576 pins) → Test Files 6 passed (6), Tests 286 passed (286). packages/plugin-dashboard/ on f84c14c17 (the second commit touches only a plugin-list test file) → Test Files 86 passed (86), Tests 805 passed (805). The second commit exists because the first post-commit run of base-bind-declared went RED: its git grep scan covers tracked files, and the plugin-list pin's synthetic control spelled the pre-finding(types): two widget prop types declare their schema as a hand-rolled inline object with no BaseSchema in its ancestry #6576 literal with a bind member — the ratchet did exactly its job; the control now uses className.
  • Red-first, on origin/main with the pins added and nothing else: tsc -p tsconfig.test.json — types 17 errors (all in the new pin: TS2724 the two names do not exist, TS2344 on every type assertion), plugin-list 9 errors, plugin-dashboard 10 errors (TS2724, TS2344, four TS2578 unused directives where the literal absorbed the wrong value); vitest on the types pin: 6 failed. GREEN here on all three.
  • Ablation (each leg on the committed tree; mutation proven by blob hash equal to the origin/main blob, restore proven by blob hash equal to HEAD and git diff HEAD empty; a trap restored both files on every exit path): reverting only ObjectGallery.tsx → the types pin fails exactly one test naming packages/plugin-list/src/ObjectGallery.tsx, and plugin-list's test project reports 8 errors in ObjectGallery.schemaAnchor-6576.test.ts (plus the two gallery test literals); reverting only ObjectDataTable.tsx → the types pin fails exactly two tests naming packages/plugin-dashboard/src/ObjectDataTable.tsx (anchor shape; the finding(plugin-dashboard): ObjectDataTableProps.schema reads drillDown and onRowClick and declares neither — both absorbed by its [key: string]: any #6914 casts are back), and plugin-dashboard's test project reports 9 errors, all in ObjectDataTable.schemaAnchor-6576.test.ts.
  • Drift census on this head (read keys off schema, cast-aware, minus the members the checker resolves on the built dist declaration; both widgets in one run): ObjectGallery read-not-declared [] (10 reads, 28 declared), ObjectDataTable read-not-declared [] (7 reads, 29 declared); synthetic control with drillDown removed from the declared set reports ["drillDown"], so the instrument sees a missing key. On origin/main the same census read ['drillDown','onRowClick'] for the data-table (prior report).
  • Gates: check-changeset-presence (11 published source files changed, changeset present), check-changeset-no-major (✅ No changeset declares a major bump), check-changeset-fixed (✅ All workspace packages are in the changeset fixed group), check:readme-exports (✅ OK … 406 self-imports judged (406 real, 0 wrong-path, 0 fabricated) — the @object-ui/types README lists no object-* schema types, so no README edit is required by the gate), check:spec-symbols (exit 0 — neither new name is a spec export; control GalleryConfigSchema found in the spec api-surface), check:control-bytes OK, check:phantom-deps OK, check:self-import OK, check:esm-specifiers OK, check:published-dist OK, check:side-effects-array OK, check:element-data-source-declaration OK, check:spec-floors OK, check:doc-types OK. NOT MEASURED locally: check:sdui-registration-pins (❌ No console build to weigh at apps/console/dist/assets — a prerequisite, not a verdict; registrations are untouched) and check:eager-closure (needs the console bundle). CI owns the repo-wide farm.
  • Governed-surface check (from ../objectstack, final file list): governed-surface predicate: 0 of 15 path(s) hit the register✅ NOT governed.

Deviations from the dispatch, declared

  1. drillDown on the zod mirror: ledgered instead of mirrored. The ruling admits either route (「新类型入镜或按台账既有词表申报」); mirroring it needs a paired DrillDownConfigSchema — a third new export the ruling did not name, whose wiring into ChartSchema would move that published validator — and an inline unpaired restatement would strip nested keys silently (z.object drops unknown keys), the very drift class the ledger exists for. So the new pair is SEEDED in UnmirroredDeclared with the ChartSchema.drillDown vocabulary (LOCAL), its doc states why this is a seed and not growth on an existing pair, and the repair is filed as finding(types): DrillDownConfig has no zod mirror — drillDown is declared on ChartSchema and ObjectDataTableSchema and validated on neither #7352. The reviewer should weigh this: the ledger's shrink-only rule is written for keys that appear on registered pairs.
  2. onRowClick on the mirror is a handlerKeyRefusal arm (as the dispatch said), which also means the 6124 census and its 36 / 58 pins moved to 37 / 59 and its file list to nine. The alternative — no arm, RuntimeOnlyDeclared (the pre-finding(types): 28 zod-mirror keys are declared z.function(), which NO JSON document can satisfy — 14 in-repo corpus files author them and are silently invalid #6124 objectql policy group) — was not taken because the 2026-08-30 ruling's rationale (an undeclared key is a silent accept that forwards the value) applies to a new mirror exactly as to an old one.
  3. Fresh gallery literals: TWO test files needed the type key (cells and coverValue), not one — the dispatch assumed only cells. Both are tests; no producer changed.
  4. ObjectDataTableProps.schema is the bare named type, not NamedSchema & {…}: with a dedicated schema type there are no widget-only extras to intersect, which is the ObjectMapProps shape; PivotTable / Timeline intersect because their schemas are generic nodes.
  5. The prior report's G7 prediction (gallery data collapses to any) did not hold: it was measured on a BaseSchema & {…} intersection; an extends member overrides. Reported as UNCHANGED above and pinned.
  6. Not extended: the ObjectQLComponentSchema unions (TS and zod) and AnyComponentSchema do not include the two new types. Adding them changes what validateSchema accepts for object-gallery / object-data-table nodes — a third published surface the ruling did not name. Flagged for the reviewer rather than decided.

Not in this PR (for the PM)

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

… to exported schema types that extend BaseSchema (objectui#6576, #6914)
Two widget prop types declared their `schema` as a hand-rolled inline object
literal with no `BaseSchema` in its ancestry — the only two `Object*Props` in
the repo whose `schema` anchored to no named type. Maintainer ruling
2026-08-31 (option A): mint `ObjectGallerySchema` and `ObjectDataTableSchema`
in `@object-ui/types`, each `extends BaseSchema`, and anchor both.
- `ObjectGalleryProps.schema` (published) WIDENS to every `BaseSchema` member
(`visibleWhen` was a compile error on the literal) and NARROWS in one place:
`type` is required and pinned to `'object-gallery'`. `data` stays typed —
an interface member overrides, it does not intersect.
- `ObjectDataTableProps.schema` (not exported) loses the literal's own
`[key: string]: any` and declares the two keys the widget read behind casts,
`drillDown: DrillDownConfig` and `onRowClick` (objectui#6914); the casts go.
A wrong-typed base member and a wrong-shaped `drillDown` are refused now.
- Zod mirrors of both under `@object-ui/types/zod`, registered in the parity
ledger: `onRowClick` is a `handlerKeyRefusal` runtime-slot arm (the first on
an objectql mirror; `KnownDrift` + the 6124 census updated), `drillDown` is
seeded in `UnmirroredDeclared` — `DrillDownConfig` has no zod mirror and
minting one is outside this ruling.
- The objectui#6574 `bind` ratchet entries for both files are removed and the
ratchet's prose rewritten: the defect was #6576's, not #5155 / #6269's.
- Red-first pins: `widget-schema-anchors-6576.test.ts` (types), and one
compile-time pin beside each widget. Two gallery test literals gain the now
required `type` key.
Unchanged and stated: an unknown key still compiles on both, because
`BaseSchema`'s index signature is inherited (objectui#5155, open).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
… `bind` member (objectui#6576)
`base-bind-declared.test.ts` scans every TRACKED file for a schema-side
`bind?:` re-declaration. The new pin's "this assertion can fail" control was
written as the pre-#6576 literal shape — `{ objectName?: string; bind?: string }`
— which read as exactly such a re-declaration the moment the file was
committed (green while untracked, red on the first post-commit run). The
control keeps its job with `className` in that slot; nothing else moves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3166.7 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-CbDKJ2sq.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 (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
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)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
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.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
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)514.59KB117.40KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)194.82KB46.02KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.63KB63.90KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.15KB8.40KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
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-litant
os-litant marked this pull request as ready for review September 2, 2026 13:22
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 4ca30d0Sep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-6576-widget-schema-anchors branch September 2, 2026 13:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914) - #7355

Merged
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors
Sep 2, 2026
Merged

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914)#7355
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#6576
Fixes#6914
Clause-②: yes — contract review required before release

Maintainer ruling 2026-08-31 (director seat, batch #10, option A, verbatim 「同意」), executed as written: two new exported schema types in @object-ui/types, each extends BaseSchema, and the two widget prop types that declared their schema as a hand-rolled inline literal are anchored to them. #6914 (the two keys the data-table reads and declared nowhere) is closed in the same stroke, per the erratum: drillDown and onRowClick.

Session for this implementation: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho (kept in prose because a PR body edit strips the footer form).

What changed

No runtime behaviour changes; both widgets render exactly as before (792 plugin-list and 805 plugin-dashboard tests unchanged and green).

Contract-review pack — accept-set before / after

Real tsc --noEmit against the real prop types, one labelled statement per line, diagnostics mapped by line number (origin/main at 2956d7af8, then this head). Each row is the exact literal assigned to the prop's schema type. The origin/main column repeats the prior report's probe and re-measures it on the same instrument.

ObjectGalleryProps.schema — PUBLISHED (plugin-list/src/index.tsx exports the type)

caseliteralorigin/mainthis headmovement
G1{ objectName, visibleWhen } (no type)REJECTED — TS2353 visibleWhen does not existREJECTED — TS2741 type is missingreason changed, see G6
G6{ type: 'object-gallery', objectName, visibleWhen }REJECTED — TS2353 type does not existACCEPTEDWIDENS — a real base member is writable
G2{ type: 'object-gallery', objectName }REJECTED — TS2353 type does not existACCEPTEDWIDENS — the node's own type key can be spelled
G4{ objectName } (minimal)ACCEPTEDREJECTED — TS2741 type is missingNARROWS — type is required (the prior report's G6)
G12{ type: 'gallery', objectName }REJECTED — TS2353 type does not existREJECTED — TS2322 "gallery" not assignable to "object-gallery"NARROWS for the right reason — the literal is the registry key
G3 / G7{ [type,] objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to array of recordsREJECTED — same TS2322UNCHANGED — the prior report predicted data would collapse to any under a BaseSchema INTERSECTION; extends overrides the member instead, so the widening does not happen
G11{ type, objectName, data: [{ id: 1 }] }REJECTED — TS2353 typeACCEPTEDWIDENS (via type)
G9{ objectName, visible: 42 } (no type)REJECTED — TS2353 visible does not existREJECTED — TS2322 number not assignable to string or booleanreason changed: unknown key → wrong-typed declared member
G8{ type, objectName, visible: 42 }REJECTED — TS2353 typeREJECTED — TS2322same as G9 with type present
G10{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited bind?: string)UNCHANGED — inherited now
G5{ type, objectName, visibleWhn: 'typo' }REJECTED — TS2353 type (the unknown key would also have been refused)ACCEPTEDWIDENS — the ceiling below

Net for the published type: WIDENS to every BaseSchema member (21 members gained; visibleWhen, visible, id, hidden, disabled, testId, ariaLabel, label, style, … are writable and type-checked), NARROWS in one place (type required and pinned to 'object-gallery'). data, bind, filter, gallery, navigation, grouping, imageField, titleField keep their exact types.

ObjectDataTableProps.schema — NOT exported from plugin-dashboard/src/index.tsx (reaches consumers structurally as the exported component's prop)

caseliteralorigin/mainthis headmovement
D1{ type: 'object-data-table', objectName, bogusKey: 1 }ACCEPTEDACCEPTEDUNCHANGED — the ceiling below
D2{ type, objectName, visible: 42 }ACCEPTED (absorbed by the literal's index signature)REJECTED — TS2322 number not assignable to string or booleanNARROWS — a wrong-typed base member is refused
D3{ type, objectName, drillDown: { enabled: true, mode: 'record' } }ACCEPTED (as any)ACCEPTED (as DrillDownConfig)UNCHANGED in verdict; DECLARED now (#6914)
D4{ type, objectName, drillDown: { enabled: 'yes' } }ACCEPTED (absorbed)REJECTED — TS2322 string not assignable to booleanNARROWS — a wrong-shaped drillDown is refused
D5{ type, objectName, onRowClick: an arrow function of one untyped parameter }REJECTED — TS7006 row implicitly any (the key resolved to any, so nothing typed the parameter)ACCEPTED — row is contextually typedWIDENS in declaration — the handler can be written without an annotation (#6914)
D10{ type, objectName, onRowClick: 'not-a-function' }ACCEPTED (absorbed)REJECTED — TS2322NARROWS
D6{ type, objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to any[]REJECTED — sameUNCHANGED
D7{ type: 'data-table', objectName }ACCEPTED (type: string)REJECTED — TS2322 "data-table" not assignable to "object-data-table"NARROWS — the literal is the registry key
D8{ type: 'object-data-table' }ACCEPTEDACCEPTEDUNCHANGED
D9{ objectName } (no type)REJECTED — TS2741 type missingREJECTED — TS2741UNCHANGED (type was already required)
D11{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited)UNCHANGED
D12{ type, objectName, visibleWhen: 'ready' }ACCEPTED (absorbed as any)ACCEPTED (declared string)UNCHANGED in verdict; typed now

Net for the unpublished type: NARROWS (the literal's own index signature is gone, so declared base members and the two #6914 keys are type-checked; type is the registry key). In-repo call sites re-checked: every plugin-dashboard test and DrillDownDrawer.tsx compile unchanged — the tests that write other spellings already carry as any.

The ceiling, stated plainly

BaseSchema still carries [key: string]: any (#5155 remains open). Anchoring inherits it, so an UNKNOWN key (visibleWhn, bogusKey) remains ACCEPTED on BOTH prop types after this PR — on the data-table it always was; on the gallery it is new (G5). The ruling accepted that cost; the pins record it as a counter-probe rather than hiding it.

Verification (on the committed heads f84c14c17 — the implementation — and the final head named below, tree clean)

Every heavy command ran under scripts/pm/os-verify-lock.sh (slot dev-6576); verdict lines quoted from the tools, not from a bare exit status.

  • Dependency closure built first (turbo run build for the three plugins' dependency closures, then every packages/*: Tasks: 39 successful, 39 total).
  • pnpm --filter @object-ui/types run buildtsc && node ../../scripts/check-dist-completeness.mjs, exit 0; dist/index.d.ts carries both new names.
  • pnpm --filter … run type-check — script echoed for each: @object-ui/types@17.6.0 type-check, @object-ui/plugin-list@17.6.0 type-check, @object-ui/plugin-dashboard@17.6.0 type-check, @object-ui/plugin-view@17.6.0 type-check — 0 errors each (each script is tsc --noEmit plus the package's test project, so every pin and both ledgers are compiled). plugin-view is the other producer of object-gallery nodes; it builds a plain node and never passes through ObjectGalleryProps, so nothing there moved.
  • pnpm --filter … run lint (types / plugin-list / plugin-dashboard): 0 errors each (warnings only, pre-existing no-explicit-any in files that already carried it; the new ObjectDataTableSchema transcribes the literal's any spellings unchanged).
  • Vitest (root-relative paths, no --), on the final head e5b24a3bb: packages/types/Test Files 88 passed (88), Tests 1434 passed (1434); packages/plugin-list/Test Files 63 passed (63), Tests 792 passed (792); the six ledger and pin files together (zod-mirror-parity, handler-keys-json-refusal-6124, base-bind-declared, widget-schema-anchors-6576, both schemaAnchor-6576 pins) → Test Files 6 passed (6), Tests 286 passed (286). packages/plugin-dashboard/ on f84c14c17 (the second commit touches only a plugin-list test file) → Test Files 86 passed (86), Tests 805 passed (805). The second commit exists because the first post-commit run of base-bind-declared went RED: its git grep scan covers tracked files, and the plugin-list pin's synthetic control spelled the pre-finding(types): two widget prop types declare their schema as a hand-rolled inline object with no BaseSchema in its ancestry #6576 literal with a bind member — the ratchet did exactly its job; the control now uses className.
  • Red-first, on origin/main with the pins added and nothing else: tsc -p tsconfig.test.json — types 17 errors (all in the new pin: TS2724 the two names do not exist, TS2344 on every type assertion), plugin-list 9 errors, plugin-dashboard 10 errors (TS2724, TS2344, four TS2578 unused directives where the literal absorbed the wrong value); vitest on the types pin: 6 failed. GREEN here on all three.
  • Ablation (each leg on the committed tree; mutation proven by blob hash equal to the origin/main blob, restore proven by blob hash equal to HEAD and git diff HEAD empty; a trap restored both files on every exit path): reverting only ObjectGallery.tsx → the types pin fails exactly one test naming packages/plugin-list/src/ObjectGallery.tsx, and plugin-list's test project reports 8 errors in ObjectGallery.schemaAnchor-6576.test.ts (plus the two gallery test literals); reverting only ObjectDataTable.tsx → the types pin fails exactly two tests naming packages/plugin-dashboard/src/ObjectDataTable.tsx (anchor shape; the finding(plugin-dashboard): ObjectDataTableProps.schema reads drillDown and onRowClick and declares neither — both absorbed by its [key: string]: any #6914 casts are back), and plugin-dashboard's test project reports 9 errors, all in ObjectDataTable.schemaAnchor-6576.test.ts.
  • Drift census on this head (read keys off schema, cast-aware, minus the members the checker resolves on the built dist declaration; both widgets in one run): ObjectGallery read-not-declared [] (10 reads, 28 declared), ObjectDataTable read-not-declared [] (7 reads, 29 declared); synthetic control with drillDown removed from the declared set reports ["drillDown"], so the instrument sees a missing key. On origin/main the same census read ['drillDown','onRowClick'] for the data-table (prior report).
  • Gates: check-changeset-presence (11 published source files changed, changeset present), check-changeset-no-major (✅ No changeset declares a major bump), check-changeset-fixed (✅ All workspace packages are in the changeset fixed group), check:readme-exports (✅ OK … 406 self-imports judged (406 real, 0 wrong-path, 0 fabricated) — the @object-ui/types README lists no object-* schema types, so no README edit is required by the gate), check:spec-symbols (exit 0 — neither new name is a spec export; control GalleryConfigSchema found in the spec api-surface), check:control-bytes OK, check:phantom-deps OK, check:self-import OK, check:esm-specifiers OK, check:published-dist OK, check:side-effects-array OK, check:element-data-source-declaration OK, check:spec-floors OK, check:doc-types OK. NOT MEASURED locally: check:sdui-registration-pins (❌ No console build to weigh at apps/console/dist/assets — a prerequisite, not a verdict; registrations are untouched) and check:eager-closure (needs the console bundle). CI owns the repo-wide farm.
  • Governed-surface check (from ../objectstack, final file list): governed-surface predicate: 0 of 15 path(s) hit the register✅ NOT governed.

Deviations from the dispatch, declared

  1. drillDown on the zod mirror: ledgered instead of mirrored. The ruling admits either route (「新类型入镜或按台账既有词表申报」); mirroring it needs a paired DrillDownConfigSchema — a third new export the ruling did not name, whose wiring into ChartSchema would move that published validator — and an inline unpaired restatement would strip nested keys silently (z.object drops unknown keys), the very drift class the ledger exists for. So the new pair is SEEDED in UnmirroredDeclared with the ChartSchema.drillDown vocabulary (LOCAL), its doc states why this is a seed and not growth on an existing pair, and the repair is filed as finding(types): DrillDownConfig has no zod mirror — drillDown is declared on ChartSchema and ObjectDataTableSchema and validated on neither #7352. The reviewer should weigh this: the ledger's shrink-only rule is written for keys that appear on registered pairs.
  2. onRowClick on the mirror is a handlerKeyRefusal arm (as the dispatch said), which also means the 6124 census and its 36 / 58 pins moved to 37 / 59 and its file list to nine. The alternative — no arm, RuntimeOnlyDeclared (the pre-finding(types): 28 zod-mirror keys are declared z.function(), which NO JSON document can satisfy — 14 in-repo corpus files author them and are silently invalid #6124 objectql policy group) — was not taken because the 2026-08-30 ruling's rationale (an undeclared key is a silent accept that forwards the value) applies to a new mirror exactly as to an old one.
  3. Fresh gallery literals: TWO test files needed the type key (cells and coverValue), not one — the dispatch assumed only cells. Both are tests; no producer changed.
  4. ObjectDataTableProps.schema is the bare named type, not NamedSchema & {…}: with a dedicated schema type there are no widget-only extras to intersect, which is the ObjectMapProps shape; PivotTable / Timeline intersect because their schemas are generic nodes.
  5. The prior report's G7 prediction (gallery data collapses to any) did not hold: it was measured on a BaseSchema & {…} intersection; an extends member overrides. Reported as UNCHANGED above and pinned.
  6. Not extended: the ObjectQLComponentSchema unions (TS and zod) and AnyComponentSchema do not include the two new types. Adding them changes what validateSchema accepts for object-gallery / object-data-table nodes — a third published surface the ruling did not name. Flagged for the reviewer rather than decided.

Not in this PR (for the PM)

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

… to exported schema types that extend BaseSchema (objectui#6576, #6914)
Two widget prop types declared their `schema` as a hand-rolled inline object
literal with no `BaseSchema` in its ancestry — the only two `Object*Props` in
the repo whose `schema` anchored to no named type. Maintainer ruling
2026-08-31 (option A): mint `ObjectGallerySchema` and `ObjectDataTableSchema`
in `@object-ui/types`, each `extends BaseSchema`, and anchor both.
- `ObjectGalleryProps.schema` (published) WIDENS to every `BaseSchema` member
(`visibleWhen` was a compile error on the literal) and NARROWS in one place:
`type` is required and pinned to `'object-gallery'`. `data` stays typed —
an interface member overrides, it does not intersect.
- `ObjectDataTableProps.schema` (not exported) loses the literal's own
`[key: string]: any` and declares the two keys the widget read behind casts,
`drillDown: DrillDownConfig` and `onRowClick` (objectui#6914); the casts go.
A wrong-typed base member and a wrong-shaped `drillDown` are refused now.
- Zod mirrors of both under `@object-ui/types/zod`, registered in the parity
ledger: `onRowClick` is a `handlerKeyRefusal` runtime-slot arm (the first on
an objectql mirror; `KnownDrift` + the 6124 census updated), `drillDown` is
seeded in `UnmirroredDeclared` — `DrillDownConfig` has no zod mirror and
minting one is outside this ruling.
- The objectui#6574 `bind` ratchet entries for both files are removed and the
ratchet's prose rewritten: the defect was #6576's, not #5155 / #6269's.
- Red-first pins: `widget-schema-anchors-6576.test.ts` (types), and one
compile-time pin beside each widget. Two gallery test literals gain the now
required `type` key.
Unchanged and stated: an unknown key still compiles on both, because
`BaseSchema`'s index signature is inherited (objectui#5155, open).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
… `bind` member (objectui#6576)
`base-bind-declared.test.ts` scans every TRACKED file for a schema-side
`bind?:` re-declaration. The new pin's "this assertion can fail" control was
written as the pre-#6576 literal shape — `{ objectName?: string; bind?: string }`
— which read as exactly such a re-declaration the moment the file was
committed (green while untracked, red on the first post-commit run). The
control keeps its job with `className` in that slot; nothing else moves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3166.7 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-CbDKJ2sq.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 (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
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)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
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.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
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)514.59KB117.40KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)194.82KB46.02KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.63KB63.90KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.15KB8.40KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
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-litant
os-litant marked this pull request as ready for review September 2, 2026 13:22
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 4ca30d0Sep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-6576-widget-schema-anchors branch September 2, 2026 13:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914) - #7355

Merged
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors
Sep 2, 2026
Merged

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914)#7355
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#6576
Fixes#6914
Clause-②: yes — contract review required before release

Maintainer ruling 2026-08-31 (director seat, batch #10, option A, verbatim 「同意」), executed as written: two new exported schema types in @object-ui/types, each extends BaseSchema, and the two widget prop types that declared their schema as a hand-rolled inline literal are anchored to them. #6914 (the two keys the data-table reads and declared nowhere) is closed in the same stroke, per the erratum: drillDown and onRowClick.

Session for this implementation: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho (kept in prose because a PR body edit strips the footer form).

What changed

No runtime behaviour changes; both widgets render exactly as before (792 plugin-list and 805 plugin-dashboard tests unchanged and green).

Contract-review pack — accept-set before / after

Real tsc --noEmit against the real prop types, one labelled statement per line, diagnostics mapped by line number (origin/main at 2956d7af8, then this head). Each row is the exact literal assigned to the prop's schema type. The origin/main column repeats the prior report's probe and re-measures it on the same instrument.

ObjectGalleryProps.schema — PUBLISHED (plugin-list/src/index.tsx exports the type)

caseliteralorigin/mainthis headmovement
G1{ objectName, visibleWhen } (no type)REJECTED — TS2353 visibleWhen does not existREJECTED — TS2741 type is missingreason changed, see G6
G6{ type: 'object-gallery', objectName, visibleWhen }REJECTED — TS2353 type does not existACCEPTEDWIDENS — a real base member is writable
G2{ type: 'object-gallery', objectName }REJECTED — TS2353 type does not existACCEPTEDWIDENS — the node's own type key can be spelled
G4{ objectName } (minimal)ACCEPTEDREJECTED — TS2741 type is missingNARROWS — type is required (the prior report's G6)
G12{ type: 'gallery', objectName }REJECTED — TS2353 type does not existREJECTED — TS2322 "gallery" not assignable to "object-gallery"NARROWS for the right reason — the literal is the registry key
G3 / G7{ [type,] objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to array of recordsREJECTED — same TS2322UNCHANGED — the prior report predicted data would collapse to any under a BaseSchema INTERSECTION; extends overrides the member instead, so the widening does not happen
G11{ type, objectName, data: [{ id: 1 }] }REJECTED — TS2353 typeACCEPTEDWIDENS (via type)
G9{ objectName, visible: 42 } (no type)REJECTED — TS2353 visible does not existREJECTED — TS2322 number not assignable to string or booleanreason changed: unknown key → wrong-typed declared member
G8{ type, objectName, visible: 42 }REJECTED — TS2353 typeREJECTED — TS2322same as G9 with type present
G10{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited bind?: string)UNCHANGED — inherited now
G5{ type, objectName, visibleWhn: 'typo' }REJECTED — TS2353 type (the unknown key would also have been refused)ACCEPTEDWIDENS — the ceiling below

Net for the published type: WIDENS to every BaseSchema member (21 members gained; visibleWhen, visible, id, hidden, disabled, testId, ariaLabel, label, style, … are writable and type-checked), NARROWS in one place (type required and pinned to 'object-gallery'). data, bind, filter, gallery, navigation, grouping, imageField, titleField keep their exact types.

ObjectDataTableProps.schema — NOT exported from plugin-dashboard/src/index.tsx (reaches consumers structurally as the exported component's prop)

caseliteralorigin/mainthis headmovement
D1{ type: 'object-data-table', objectName, bogusKey: 1 }ACCEPTEDACCEPTEDUNCHANGED — the ceiling below
D2{ type, objectName, visible: 42 }ACCEPTED (absorbed by the literal's index signature)REJECTED — TS2322 number not assignable to string or booleanNARROWS — a wrong-typed base member is refused
D3{ type, objectName, drillDown: { enabled: true, mode: 'record' } }ACCEPTED (as any)ACCEPTED (as DrillDownConfig)UNCHANGED in verdict; DECLARED now (#6914)
D4{ type, objectName, drillDown: { enabled: 'yes' } }ACCEPTED (absorbed)REJECTED — TS2322 string not assignable to booleanNARROWS — a wrong-shaped drillDown is refused
D5{ type, objectName, onRowClick: an arrow function of one untyped parameter }REJECTED — TS7006 row implicitly any (the key resolved to any, so nothing typed the parameter)ACCEPTED — row is contextually typedWIDENS in declaration — the handler can be written without an annotation (#6914)
D10{ type, objectName, onRowClick: 'not-a-function' }ACCEPTED (absorbed)REJECTED — TS2322NARROWS
D6{ type, objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to any[]REJECTED — sameUNCHANGED
D7{ type: 'data-table', objectName }ACCEPTED (type: string)REJECTED — TS2322 "data-table" not assignable to "object-data-table"NARROWS — the literal is the registry key
D8{ type: 'object-data-table' }ACCEPTEDACCEPTEDUNCHANGED
D9{ objectName } (no type)REJECTED — TS2741 type missingREJECTED — TS2741UNCHANGED (type was already required)
D11{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited)UNCHANGED
D12{ type, objectName, visibleWhen: 'ready' }ACCEPTED (absorbed as any)ACCEPTED (declared string)UNCHANGED in verdict; typed now

Net for the unpublished type: NARROWS (the literal's own index signature is gone, so declared base members and the two #6914 keys are type-checked; type is the registry key). In-repo call sites re-checked: every plugin-dashboard test and DrillDownDrawer.tsx compile unchanged — the tests that write other spellings already carry as any.

The ceiling, stated plainly

BaseSchema still carries [key: string]: any (#5155 remains open). Anchoring inherits it, so an UNKNOWN key (visibleWhn, bogusKey) remains ACCEPTED on BOTH prop types after this PR — on the data-table it always was; on the gallery it is new (G5). The ruling accepted that cost; the pins record it as a counter-probe rather than hiding it.

Verification (on the committed heads f84c14c17 — the implementation — and the final head named below, tree clean)

Every heavy command ran under scripts/pm/os-verify-lock.sh (slot dev-6576); verdict lines quoted from the tools, not from a bare exit status.

  • Dependency closure built first (turbo run build for the three plugins' dependency closures, then every packages/*: Tasks: 39 successful, 39 total).
  • pnpm --filter @object-ui/types run buildtsc && node ../../scripts/check-dist-completeness.mjs, exit 0; dist/index.d.ts carries both new names.
  • pnpm --filter … run type-check — script echoed for each: @object-ui/types@17.6.0 type-check, @object-ui/plugin-list@17.6.0 type-check, @object-ui/plugin-dashboard@17.6.0 type-check, @object-ui/plugin-view@17.6.0 type-check — 0 errors each (each script is tsc --noEmit plus the package's test project, so every pin and both ledgers are compiled). plugin-view is the other producer of object-gallery nodes; it builds a plain node and never passes through ObjectGalleryProps, so nothing there moved.
  • pnpm --filter … run lint (types / plugin-list / plugin-dashboard): 0 errors each (warnings only, pre-existing no-explicit-any in files that already carried it; the new ObjectDataTableSchema transcribes the literal's any spellings unchanged).
  • Vitest (root-relative paths, no --), on the final head e5b24a3bb: packages/types/Test Files 88 passed (88), Tests 1434 passed (1434); packages/plugin-list/Test Files 63 passed (63), Tests 792 passed (792); the six ledger and pin files together (zod-mirror-parity, handler-keys-json-refusal-6124, base-bind-declared, widget-schema-anchors-6576, both schemaAnchor-6576 pins) → Test Files 6 passed (6), Tests 286 passed (286). packages/plugin-dashboard/ on f84c14c17 (the second commit touches only a plugin-list test file) → Test Files 86 passed (86), Tests 805 passed (805). The second commit exists because the first post-commit run of base-bind-declared went RED: its git grep scan covers tracked files, and the plugin-list pin's synthetic control spelled the pre-finding(types): two widget prop types declare their schema as a hand-rolled inline object with no BaseSchema in its ancestry #6576 literal with a bind member — the ratchet did exactly its job; the control now uses className.
  • Red-first, on origin/main with the pins added and nothing else: tsc -p tsconfig.test.json — types 17 errors (all in the new pin: TS2724 the two names do not exist, TS2344 on every type assertion), plugin-list 9 errors, plugin-dashboard 10 errors (TS2724, TS2344, four TS2578 unused directives where the literal absorbed the wrong value); vitest on the types pin: 6 failed. GREEN here on all three.
  • Ablation (each leg on the committed tree; mutation proven by blob hash equal to the origin/main blob, restore proven by blob hash equal to HEAD and git diff HEAD empty; a trap restored both files on every exit path): reverting only ObjectGallery.tsx → the types pin fails exactly one test naming packages/plugin-list/src/ObjectGallery.tsx, and plugin-list's test project reports 8 errors in ObjectGallery.schemaAnchor-6576.test.ts (plus the two gallery test literals); reverting only ObjectDataTable.tsx → the types pin fails exactly two tests naming packages/plugin-dashboard/src/ObjectDataTable.tsx (anchor shape; the finding(plugin-dashboard): ObjectDataTableProps.schema reads drillDown and onRowClick and declares neither — both absorbed by its [key: string]: any #6914 casts are back), and plugin-dashboard's test project reports 9 errors, all in ObjectDataTable.schemaAnchor-6576.test.ts.
  • Drift census on this head (read keys off schema, cast-aware, minus the members the checker resolves on the built dist declaration; both widgets in one run): ObjectGallery read-not-declared [] (10 reads, 28 declared), ObjectDataTable read-not-declared [] (7 reads, 29 declared); synthetic control with drillDown removed from the declared set reports ["drillDown"], so the instrument sees a missing key. On origin/main the same census read ['drillDown','onRowClick'] for the data-table (prior report).
  • Gates: check-changeset-presence (11 published source files changed, changeset present), check-changeset-no-major (✅ No changeset declares a major bump), check-changeset-fixed (✅ All workspace packages are in the changeset fixed group), check:readme-exports (✅ OK … 406 self-imports judged (406 real, 0 wrong-path, 0 fabricated) — the @object-ui/types README lists no object-* schema types, so no README edit is required by the gate), check:spec-symbols (exit 0 — neither new name is a spec export; control GalleryConfigSchema found in the spec api-surface), check:control-bytes OK, check:phantom-deps OK, check:self-import OK, check:esm-specifiers OK, check:published-dist OK, check:side-effects-array OK, check:element-data-source-declaration OK, check:spec-floors OK, check:doc-types OK. NOT MEASURED locally: check:sdui-registration-pins (❌ No console build to weigh at apps/console/dist/assets — a prerequisite, not a verdict; registrations are untouched) and check:eager-closure (needs the console bundle). CI owns the repo-wide farm.
  • Governed-surface check (from ../objectstack, final file list): governed-surface predicate: 0 of 15 path(s) hit the register✅ NOT governed.

Deviations from the dispatch, declared

  1. drillDown on the zod mirror: ledgered instead of mirrored. The ruling admits either route (「新类型入镜或按台账既有词表申报」); mirroring it needs a paired DrillDownConfigSchema — a third new export the ruling did not name, whose wiring into ChartSchema would move that published validator — and an inline unpaired restatement would strip nested keys silently (z.object drops unknown keys), the very drift class the ledger exists for. So the new pair is SEEDED in UnmirroredDeclared with the ChartSchema.drillDown vocabulary (LOCAL), its doc states why this is a seed and not growth on an existing pair, and the repair is filed as finding(types): DrillDownConfig has no zod mirror — drillDown is declared on ChartSchema and ObjectDataTableSchema and validated on neither #7352. The reviewer should weigh this: the ledger's shrink-only rule is written for keys that appear on registered pairs.
  2. onRowClick on the mirror is a handlerKeyRefusal arm (as the dispatch said), which also means the 6124 census and its 36 / 58 pins moved to 37 / 59 and its file list to nine. The alternative — no arm, RuntimeOnlyDeclared (the pre-finding(types): 28 zod-mirror keys are declared z.function(), which NO JSON document can satisfy — 14 in-repo corpus files author them and are silently invalid #6124 objectql policy group) — was not taken because the 2026-08-30 ruling's rationale (an undeclared key is a silent accept that forwards the value) applies to a new mirror exactly as to an old one.
  3. Fresh gallery literals: TWO test files needed the type key (cells and coverValue), not one — the dispatch assumed only cells. Both are tests; no producer changed.
  4. ObjectDataTableProps.schema is the bare named type, not NamedSchema & {…}: with a dedicated schema type there are no widget-only extras to intersect, which is the ObjectMapProps shape; PivotTable / Timeline intersect because their schemas are generic nodes.
  5. The prior report's G7 prediction (gallery data collapses to any) did not hold: it was measured on a BaseSchema & {…} intersection; an extends member overrides. Reported as UNCHANGED above and pinned.
  6. Not extended: the ObjectQLComponentSchema unions (TS and zod) and AnyComponentSchema do not include the two new types. Adding them changes what validateSchema accepts for object-gallery / object-data-table nodes — a third published surface the ruling did not name. Flagged for the reviewer rather than decided.

Not in this PR (for the PM)

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

… to exported schema types that extend BaseSchema (objectui#6576, #6914)
Two widget prop types declared their `schema` as a hand-rolled inline object
literal with no `BaseSchema` in its ancestry — the only two `Object*Props` in
the repo whose `schema` anchored to no named type. Maintainer ruling
2026-08-31 (option A): mint `ObjectGallerySchema` and `ObjectDataTableSchema`
in `@object-ui/types`, each `extends BaseSchema`, and anchor both.
- `ObjectGalleryProps.schema` (published) WIDENS to every `BaseSchema` member
(`visibleWhen` was a compile error on the literal) and NARROWS in one place:
`type` is required and pinned to `'object-gallery'`. `data` stays typed —
an interface member overrides, it does not intersect.
- `ObjectDataTableProps.schema` (not exported) loses the literal's own
`[key: string]: any` and declares the two keys the widget read behind casts,
`drillDown: DrillDownConfig` and `onRowClick` (objectui#6914); the casts go.
A wrong-typed base member and a wrong-shaped `drillDown` are refused now.
- Zod mirrors of both under `@object-ui/types/zod`, registered in the parity
ledger: `onRowClick` is a `handlerKeyRefusal` runtime-slot arm (the first on
an objectql mirror; `KnownDrift` + the 6124 census updated), `drillDown` is
seeded in `UnmirroredDeclared` — `DrillDownConfig` has no zod mirror and
minting one is outside this ruling.
- The objectui#6574 `bind` ratchet entries for both files are removed and the
ratchet's prose rewritten: the defect was #6576's, not #5155 / #6269's.
- Red-first pins: `widget-schema-anchors-6576.test.ts` (types), and one
compile-time pin beside each widget. Two gallery test literals gain the now
required `type` key.
Unchanged and stated: an unknown key still compiles on both, because
`BaseSchema`'s index signature is inherited (objectui#5155, open).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
… `bind` member (objectui#6576)
`base-bind-declared.test.ts` scans every TRACKED file for a schema-side
`bind?:` re-declaration. The new pin's "this assertion can fail" control was
written as the pre-#6576 literal shape — `{ objectName?: string; bind?: string }`
— which read as exactly such a re-declaration the moment the file was
committed (green while untracked, red on the first post-commit run). The
control keeps its job with `className` in that slot; nothing else moves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3166.7 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-CbDKJ2sq.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 (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
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)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
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.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
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)514.59KB117.40KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)194.82KB46.02KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.63KB63.90KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.15KB8.40KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
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-litant
os-litant marked this pull request as ready for review September 2, 2026 13:22
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 4ca30d0Sep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-6576-widget-schema-anchors branch September 2, 2026 13:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914) - #7355

Merged
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors
Sep 2, 2026
Merged

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914)#7355
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#6576
Fixes#6914
Clause-②: yes — contract review required before release

Maintainer ruling 2026-08-31 (director seat, batch #10, option A, verbatim 「同意」), executed as written: two new exported schema types in @object-ui/types, each extends BaseSchema, and the two widget prop types that declared their schema as a hand-rolled inline literal are anchored to them. #6914 (the two keys the data-table reads and declared nowhere) is closed in the same stroke, per the erratum: drillDown and onRowClick.

Session for this implementation: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho (kept in prose because a PR body edit strips the footer form).

What changed

No runtime behaviour changes; both widgets render exactly as before (792 plugin-list and 805 plugin-dashboard tests unchanged and green).

Contract-review pack — accept-set before / after

Real tsc --noEmit against the real prop types, one labelled statement per line, diagnostics mapped by line number (origin/main at 2956d7af8, then this head). Each row is the exact literal assigned to the prop's schema type. The origin/main column repeats the prior report's probe and re-measures it on the same instrument.

ObjectGalleryProps.schema — PUBLISHED (plugin-list/src/index.tsx exports the type)

caseliteralorigin/mainthis headmovement
G1{ objectName, visibleWhen } (no type)REJECTED — TS2353 visibleWhen does not existREJECTED — TS2741 type is missingreason changed, see G6
G6{ type: 'object-gallery', objectName, visibleWhen }REJECTED — TS2353 type does not existACCEPTEDWIDENS — a real base member is writable
G2{ type: 'object-gallery', objectName }REJECTED — TS2353 type does not existACCEPTEDWIDENS — the node's own type key can be spelled
G4{ objectName } (minimal)ACCEPTEDREJECTED — TS2741 type is missingNARROWS — type is required (the prior report's G6)
G12{ type: 'gallery', objectName }REJECTED — TS2353 type does not existREJECTED — TS2322 "gallery" not assignable to "object-gallery"NARROWS for the right reason — the literal is the registry key
G3 / G7{ [type,] objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to array of recordsREJECTED — same TS2322UNCHANGED — the prior report predicted data would collapse to any under a BaseSchema INTERSECTION; extends overrides the member instead, so the widening does not happen
G11{ type, objectName, data: [{ id: 1 }] }REJECTED — TS2353 typeACCEPTEDWIDENS (via type)
G9{ objectName, visible: 42 } (no type)REJECTED — TS2353 visible does not existREJECTED — TS2322 number not assignable to string or booleanreason changed: unknown key → wrong-typed declared member
G8{ type, objectName, visible: 42 }REJECTED — TS2353 typeREJECTED — TS2322same as G9 with type present
G10{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited bind?: string)UNCHANGED — inherited now
G5{ type, objectName, visibleWhn: 'typo' }REJECTED — TS2353 type (the unknown key would also have been refused)ACCEPTEDWIDENS — the ceiling below

Net for the published type: WIDENS to every BaseSchema member (21 members gained; visibleWhen, visible, id, hidden, disabled, testId, ariaLabel, label, style, … are writable and type-checked), NARROWS in one place (type required and pinned to 'object-gallery'). data, bind, filter, gallery, navigation, grouping, imageField, titleField keep their exact types.

ObjectDataTableProps.schema — NOT exported from plugin-dashboard/src/index.tsx (reaches consumers structurally as the exported component's prop)

caseliteralorigin/mainthis headmovement
D1{ type: 'object-data-table', objectName, bogusKey: 1 }ACCEPTEDACCEPTEDUNCHANGED — the ceiling below
D2{ type, objectName, visible: 42 }ACCEPTED (absorbed by the literal's index signature)REJECTED — TS2322 number not assignable to string or booleanNARROWS — a wrong-typed base member is refused
D3{ type, objectName, drillDown: { enabled: true, mode: 'record' } }ACCEPTED (as any)ACCEPTED (as DrillDownConfig)UNCHANGED in verdict; DECLARED now (#6914)
D4{ type, objectName, drillDown: { enabled: 'yes' } }ACCEPTED (absorbed)REJECTED — TS2322 string not assignable to booleanNARROWS — a wrong-shaped drillDown is refused
D5{ type, objectName, onRowClick: an arrow function of one untyped parameter }REJECTED — TS7006 row implicitly any (the key resolved to any, so nothing typed the parameter)ACCEPTED — row is contextually typedWIDENS in declaration — the handler can be written without an annotation (#6914)
D10{ type, objectName, onRowClick: 'not-a-function' }ACCEPTED (absorbed)REJECTED — TS2322NARROWS
D6{ type, objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to any[]REJECTED — sameUNCHANGED
D7{ type: 'data-table', objectName }ACCEPTED (type: string)REJECTED — TS2322 "data-table" not assignable to "object-data-table"NARROWS — the literal is the registry key
D8{ type: 'object-data-table' }ACCEPTEDACCEPTEDUNCHANGED
D9{ objectName } (no type)REJECTED — TS2741 type missingREJECTED — TS2741UNCHANGED (type was already required)
D11{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited)UNCHANGED
D12{ type, objectName, visibleWhen: 'ready' }ACCEPTED (absorbed as any)ACCEPTED (declared string)UNCHANGED in verdict; typed now

Net for the unpublished type: NARROWS (the literal's own index signature is gone, so declared base members and the two #6914 keys are type-checked; type is the registry key). In-repo call sites re-checked: every plugin-dashboard test and DrillDownDrawer.tsx compile unchanged — the tests that write other spellings already carry as any.

The ceiling, stated plainly

BaseSchema still carries [key: string]: any (#5155 remains open). Anchoring inherits it, so an UNKNOWN key (visibleWhn, bogusKey) remains ACCEPTED on BOTH prop types after this PR — on the data-table it always was; on the gallery it is new (G5). The ruling accepted that cost; the pins record it as a counter-probe rather than hiding it.

Verification (on the committed heads f84c14c17 — the implementation — and the final head named below, tree clean)

Every heavy command ran under scripts/pm/os-verify-lock.sh (slot dev-6576); verdict lines quoted from the tools, not from a bare exit status.

  • Dependency closure built first (turbo run build for the three plugins' dependency closures, then every packages/*: Tasks: 39 successful, 39 total).
  • pnpm --filter @object-ui/types run buildtsc && node ../../scripts/check-dist-completeness.mjs, exit 0; dist/index.d.ts carries both new names.
  • pnpm --filter … run type-check — script echoed for each: @object-ui/types@17.6.0 type-check, @object-ui/plugin-list@17.6.0 type-check, @object-ui/plugin-dashboard@17.6.0 type-check, @object-ui/plugin-view@17.6.0 type-check — 0 errors each (each script is tsc --noEmit plus the package's test project, so every pin and both ledgers are compiled). plugin-view is the other producer of object-gallery nodes; it builds a plain node and never passes through ObjectGalleryProps, so nothing there moved.
  • pnpm --filter … run lint (types / plugin-list / plugin-dashboard): 0 errors each (warnings only, pre-existing no-explicit-any in files that already carried it; the new ObjectDataTableSchema transcribes the literal's any spellings unchanged).
  • Vitest (root-relative paths, no --), on the final head e5b24a3bb: packages/types/Test Files 88 passed (88), Tests 1434 passed (1434); packages/plugin-list/Test Files 63 passed (63), Tests 792 passed (792); the six ledger and pin files together (zod-mirror-parity, handler-keys-json-refusal-6124, base-bind-declared, widget-schema-anchors-6576, both schemaAnchor-6576 pins) → Test Files 6 passed (6), Tests 286 passed (286). packages/plugin-dashboard/ on f84c14c17 (the second commit touches only a plugin-list test file) → Test Files 86 passed (86), Tests 805 passed (805). The second commit exists because the first post-commit run of base-bind-declared went RED: its git grep scan covers tracked files, and the plugin-list pin's synthetic control spelled the pre-finding(types): two widget prop types declare their schema as a hand-rolled inline object with no BaseSchema in its ancestry #6576 literal with a bind member — the ratchet did exactly its job; the control now uses className.
  • Red-first, on origin/main with the pins added and nothing else: tsc -p tsconfig.test.json — types 17 errors (all in the new pin: TS2724 the two names do not exist, TS2344 on every type assertion), plugin-list 9 errors, plugin-dashboard 10 errors (TS2724, TS2344, four TS2578 unused directives where the literal absorbed the wrong value); vitest on the types pin: 6 failed. GREEN here on all three.
  • Ablation (each leg on the committed tree; mutation proven by blob hash equal to the origin/main blob, restore proven by blob hash equal to HEAD and git diff HEAD empty; a trap restored both files on every exit path): reverting only ObjectGallery.tsx → the types pin fails exactly one test naming packages/plugin-list/src/ObjectGallery.tsx, and plugin-list's test project reports 8 errors in ObjectGallery.schemaAnchor-6576.test.ts (plus the two gallery test literals); reverting only ObjectDataTable.tsx → the types pin fails exactly two tests naming packages/plugin-dashboard/src/ObjectDataTable.tsx (anchor shape; the finding(plugin-dashboard): ObjectDataTableProps.schema reads drillDown and onRowClick and declares neither — both absorbed by its [key: string]: any #6914 casts are back), and plugin-dashboard's test project reports 9 errors, all in ObjectDataTable.schemaAnchor-6576.test.ts.
  • Drift census on this head (read keys off schema, cast-aware, minus the members the checker resolves on the built dist declaration; both widgets in one run): ObjectGallery read-not-declared [] (10 reads, 28 declared), ObjectDataTable read-not-declared [] (7 reads, 29 declared); synthetic control with drillDown removed from the declared set reports ["drillDown"], so the instrument sees a missing key. On origin/main the same census read ['drillDown','onRowClick'] for the data-table (prior report).
  • Gates: check-changeset-presence (11 published source files changed, changeset present), check-changeset-no-major (✅ No changeset declares a major bump), check-changeset-fixed (✅ All workspace packages are in the changeset fixed group), check:readme-exports (✅ OK … 406 self-imports judged (406 real, 0 wrong-path, 0 fabricated) — the @object-ui/types README lists no object-* schema types, so no README edit is required by the gate), check:spec-symbols (exit 0 — neither new name is a spec export; control GalleryConfigSchema found in the spec api-surface), check:control-bytes OK, check:phantom-deps OK, check:self-import OK, check:esm-specifiers OK, check:published-dist OK, check:side-effects-array OK, check:element-data-source-declaration OK, check:spec-floors OK, check:doc-types OK. NOT MEASURED locally: check:sdui-registration-pins (❌ No console build to weigh at apps/console/dist/assets — a prerequisite, not a verdict; registrations are untouched) and check:eager-closure (needs the console bundle). CI owns the repo-wide farm.
  • Governed-surface check (from ../objectstack, final file list): governed-surface predicate: 0 of 15 path(s) hit the register✅ NOT governed.

Deviations from the dispatch, declared

  1. drillDown on the zod mirror: ledgered instead of mirrored. The ruling admits either route (「新类型入镜或按台账既有词表申报」); mirroring it needs a paired DrillDownConfigSchema — a third new export the ruling did not name, whose wiring into ChartSchema would move that published validator — and an inline unpaired restatement would strip nested keys silently (z.object drops unknown keys), the very drift class the ledger exists for. So the new pair is SEEDED in UnmirroredDeclared with the ChartSchema.drillDown vocabulary (LOCAL), its doc states why this is a seed and not growth on an existing pair, and the repair is filed as finding(types): DrillDownConfig has no zod mirror — drillDown is declared on ChartSchema and ObjectDataTableSchema and validated on neither #7352. The reviewer should weigh this: the ledger's shrink-only rule is written for keys that appear on registered pairs.
  2. onRowClick on the mirror is a handlerKeyRefusal arm (as the dispatch said), which also means the 6124 census and its 36 / 58 pins moved to 37 / 59 and its file list to nine. The alternative — no arm, RuntimeOnlyDeclared (the pre-finding(types): 28 zod-mirror keys are declared z.function(), which NO JSON document can satisfy — 14 in-repo corpus files author them and are silently invalid #6124 objectql policy group) — was not taken because the 2026-08-30 ruling's rationale (an undeclared key is a silent accept that forwards the value) applies to a new mirror exactly as to an old one.
  3. Fresh gallery literals: TWO test files needed the type key (cells and coverValue), not one — the dispatch assumed only cells. Both are tests; no producer changed.
  4. ObjectDataTableProps.schema is the bare named type, not NamedSchema & {…}: with a dedicated schema type there are no widget-only extras to intersect, which is the ObjectMapProps shape; PivotTable / Timeline intersect because their schemas are generic nodes.
  5. The prior report's G7 prediction (gallery data collapses to any) did not hold: it was measured on a BaseSchema & {…} intersection; an extends member overrides. Reported as UNCHANGED above and pinned.
  6. Not extended: the ObjectQLComponentSchema unions (TS and zod) and AnyComponentSchema do not include the two new types. Adding them changes what validateSchema accepts for object-gallery / object-data-table nodes — a third published surface the ruling did not name. Flagged for the reviewer rather than decided.

Not in this PR (for the PM)

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

… to exported schema types that extend BaseSchema (objectui#6576, #6914)
Two widget prop types declared their `schema` as a hand-rolled inline object
literal with no `BaseSchema` in its ancestry — the only two `Object*Props` in
the repo whose `schema` anchored to no named type. Maintainer ruling
2026-08-31 (option A): mint `ObjectGallerySchema` and `ObjectDataTableSchema`
in `@object-ui/types`, each `extends BaseSchema`, and anchor both.
- `ObjectGalleryProps.schema` (published) WIDENS to every `BaseSchema` member
(`visibleWhen` was a compile error on the literal) and NARROWS in one place:
`type` is required and pinned to `'object-gallery'`. `data` stays typed —
an interface member overrides, it does not intersect.
- `ObjectDataTableProps.schema` (not exported) loses the literal's own
`[key: string]: any` and declares the two keys the widget read behind casts,
`drillDown: DrillDownConfig` and `onRowClick` (objectui#6914); the casts go.
A wrong-typed base member and a wrong-shaped `drillDown` are refused now.
- Zod mirrors of both under `@object-ui/types/zod`, registered in the parity
ledger: `onRowClick` is a `handlerKeyRefusal` runtime-slot arm (the first on
an objectql mirror; `KnownDrift` + the 6124 census updated), `drillDown` is
seeded in `UnmirroredDeclared` — `DrillDownConfig` has no zod mirror and
minting one is outside this ruling.
- The objectui#6574 `bind` ratchet entries for both files are removed and the
ratchet's prose rewritten: the defect was #6576's, not #5155 / #6269's.
- Red-first pins: `widget-schema-anchors-6576.test.ts` (types), and one
compile-time pin beside each widget. Two gallery test literals gain the now
required `type` key.
Unchanged and stated: an unknown key still compiles on both, because
`BaseSchema`'s index signature is inherited (objectui#5155, open).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
… `bind` member (objectui#6576)
`base-bind-declared.test.ts` scans every TRACKED file for a schema-side
`bind?:` re-declaration. The new pin's "this assertion can fail" control was
written as the pre-#6576 literal shape — `{ objectName?: string; bind?: string }`
— which read as exactly such a re-declaration the moment the file was
committed (green while untracked, red on the first post-commit run). The
control keeps its job with `className` in that slot; nothing else moves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3166.7 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-CbDKJ2sq.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 (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
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)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
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.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
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)514.59KB117.40KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)194.82KB46.02KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.63KB63.90KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.15KB8.40KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
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-litant
os-litant marked this pull request as ready for review September 2, 2026 13:22
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 4ca30d0Sep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-6576-widget-schema-anchors branch September 2, 2026 13:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914) - #7355

Merged
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors
Sep 2, 2026
Merged

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914)#7355
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#6576
Fixes#6914
Clause-②: yes — contract review required before release

Maintainer ruling 2026-08-31 (director seat, batch #10, option A, verbatim 「同意」), executed as written: two new exported schema types in @object-ui/types, each extends BaseSchema, and the two widget prop types that declared their schema as a hand-rolled inline literal are anchored to them. #6914 (the two keys the data-table reads and declared nowhere) is closed in the same stroke, per the erratum: drillDown and onRowClick.

Session for this implementation: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho (kept in prose because a PR body edit strips the footer form).

What changed

No runtime behaviour changes; both widgets render exactly as before (792 plugin-list and 805 plugin-dashboard tests unchanged and green).

Contract-review pack — accept-set before / after

Real tsc --noEmit against the real prop types, one labelled statement per line, diagnostics mapped by line number (origin/main at 2956d7af8, then this head). Each row is the exact literal assigned to the prop's schema type. The origin/main column repeats the prior report's probe and re-measures it on the same instrument.

ObjectGalleryProps.schema — PUBLISHED (plugin-list/src/index.tsx exports the type)

caseliteralorigin/mainthis headmovement
G1{ objectName, visibleWhen } (no type)REJECTED — TS2353 visibleWhen does not existREJECTED — TS2741 type is missingreason changed, see G6
G6{ type: 'object-gallery', objectName, visibleWhen }REJECTED — TS2353 type does not existACCEPTEDWIDENS — a real base member is writable
G2{ type: 'object-gallery', objectName }REJECTED — TS2353 type does not existACCEPTEDWIDENS — the node's own type key can be spelled
G4{ objectName } (minimal)ACCEPTEDREJECTED — TS2741 type is missingNARROWS — type is required (the prior report's G6)
G12{ type: 'gallery', objectName }REJECTED — TS2353 type does not existREJECTED — TS2322 "gallery" not assignable to "object-gallery"NARROWS for the right reason — the literal is the registry key
G3 / G7{ [type,] objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to array of recordsREJECTED — same TS2322UNCHANGED — the prior report predicted data would collapse to any under a BaseSchema INTERSECTION; extends overrides the member instead, so the widening does not happen
G11{ type, objectName, data: [{ id: 1 }] }REJECTED — TS2353 typeACCEPTEDWIDENS (via type)
G9{ objectName, visible: 42 } (no type)REJECTED — TS2353 visible does not existREJECTED — TS2322 number not assignable to string or booleanreason changed: unknown key → wrong-typed declared member
G8{ type, objectName, visible: 42 }REJECTED — TS2353 typeREJECTED — TS2322same as G9 with type present
G10{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited bind?: string)UNCHANGED — inherited now
G5{ type, objectName, visibleWhn: 'typo' }REJECTED — TS2353 type (the unknown key would also have been refused)ACCEPTEDWIDENS — the ceiling below

Net for the published type: WIDENS to every BaseSchema member (21 members gained; visibleWhen, visible, id, hidden, disabled, testId, ariaLabel, label, style, … are writable and type-checked), NARROWS in one place (type required and pinned to 'object-gallery'). data, bind, filter, gallery, navigation, grouping, imageField, titleField keep their exact types.

ObjectDataTableProps.schema — NOT exported from plugin-dashboard/src/index.tsx (reaches consumers structurally as the exported component's prop)

caseliteralorigin/mainthis headmovement
D1{ type: 'object-data-table', objectName, bogusKey: 1 }ACCEPTEDACCEPTEDUNCHANGED — the ceiling below
D2{ type, objectName, visible: 42 }ACCEPTED (absorbed by the literal's index signature)REJECTED — TS2322 number not assignable to string or booleanNARROWS — a wrong-typed base member is refused
D3{ type, objectName, drillDown: { enabled: true, mode: 'record' } }ACCEPTED (as any)ACCEPTED (as DrillDownConfig)UNCHANGED in verdict; DECLARED now (#6914)
D4{ type, objectName, drillDown: { enabled: 'yes' } }ACCEPTED (absorbed)REJECTED — TS2322 string not assignable to booleanNARROWS — a wrong-shaped drillDown is refused
D5{ type, objectName, onRowClick: an arrow function of one untyped parameter }REJECTED — TS7006 row implicitly any (the key resolved to any, so nothing typed the parameter)ACCEPTED — row is contextually typedWIDENS in declaration — the handler can be written without an annotation (#6914)
D10{ type, objectName, onRowClick: 'not-a-function' }ACCEPTED (absorbed)REJECTED — TS2322NARROWS
D6{ type, objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to any[]REJECTED — sameUNCHANGED
D7{ type: 'data-table', objectName }ACCEPTED (type: string)REJECTED — TS2322 "data-table" not assignable to "object-data-table"NARROWS — the literal is the registry key
D8{ type: 'object-data-table' }ACCEPTEDACCEPTEDUNCHANGED
D9{ objectName } (no type)REJECTED — TS2741 type missingREJECTED — TS2741UNCHANGED (type was already required)
D11{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited)UNCHANGED
D12{ type, objectName, visibleWhen: 'ready' }ACCEPTED (absorbed as any)ACCEPTED (declared string)UNCHANGED in verdict; typed now

Net for the unpublished type: NARROWS (the literal's own index signature is gone, so declared base members and the two #6914 keys are type-checked; type is the registry key). In-repo call sites re-checked: every plugin-dashboard test and DrillDownDrawer.tsx compile unchanged — the tests that write other spellings already carry as any.

The ceiling, stated plainly

BaseSchema still carries [key: string]: any (#5155 remains open). Anchoring inherits it, so an UNKNOWN key (visibleWhn, bogusKey) remains ACCEPTED on BOTH prop types after this PR — on the data-table it always was; on the gallery it is new (G5). The ruling accepted that cost; the pins record it as a counter-probe rather than hiding it.

Verification (on the committed heads f84c14c17 — the implementation — and the final head named below, tree clean)

Every heavy command ran under scripts/pm/os-verify-lock.sh (slot dev-6576); verdict lines quoted from the tools, not from a bare exit status.

  • Dependency closure built first (turbo run build for the three plugins' dependency closures, then every packages/*: Tasks: 39 successful, 39 total).
  • pnpm --filter @object-ui/types run buildtsc && node ../../scripts/check-dist-completeness.mjs, exit 0; dist/index.d.ts carries both new names.
  • pnpm --filter … run type-check — script echoed for each: @object-ui/types@17.6.0 type-check, @object-ui/plugin-list@17.6.0 type-check, @object-ui/plugin-dashboard@17.6.0 type-check, @object-ui/plugin-view@17.6.0 type-check — 0 errors each (each script is tsc --noEmit plus the package's test project, so every pin and both ledgers are compiled). plugin-view is the other producer of object-gallery nodes; it builds a plain node and never passes through ObjectGalleryProps, so nothing there moved.
  • pnpm --filter … run lint (types / plugin-list / plugin-dashboard): 0 errors each (warnings only, pre-existing no-explicit-any in files that already carried it; the new ObjectDataTableSchema transcribes the literal's any spellings unchanged).
  • Vitest (root-relative paths, no --), on the final head e5b24a3bb: packages/types/Test Files 88 passed (88), Tests 1434 passed (1434); packages/plugin-list/Test Files 63 passed (63), Tests 792 passed (792); the six ledger and pin files together (zod-mirror-parity, handler-keys-json-refusal-6124, base-bind-declared, widget-schema-anchors-6576, both schemaAnchor-6576 pins) → Test Files 6 passed (6), Tests 286 passed (286). packages/plugin-dashboard/ on f84c14c17 (the second commit touches only a plugin-list test file) → Test Files 86 passed (86), Tests 805 passed (805). The second commit exists because the first post-commit run of base-bind-declared went RED: its git grep scan covers tracked files, and the plugin-list pin's synthetic control spelled the pre-finding(types): two widget prop types declare their schema as a hand-rolled inline object with no BaseSchema in its ancestry #6576 literal with a bind member — the ratchet did exactly its job; the control now uses className.
  • Red-first, on origin/main with the pins added and nothing else: tsc -p tsconfig.test.json — types 17 errors (all in the new pin: TS2724 the two names do not exist, TS2344 on every type assertion), plugin-list 9 errors, plugin-dashboard 10 errors (TS2724, TS2344, four TS2578 unused directives where the literal absorbed the wrong value); vitest on the types pin: 6 failed. GREEN here on all three.
  • Ablation (each leg on the committed tree; mutation proven by blob hash equal to the origin/main blob, restore proven by blob hash equal to HEAD and git diff HEAD empty; a trap restored both files on every exit path): reverting only ObjectGallery.tsx → the types pin fails exactly one test naming packages/plugin-list/src/ObjectGallery.tsx, and plugin-list's test project reports 8 errors in ObjectGallery.schemaAnchor-6576.test.ts (plus the two gallery test literals); reverting only ObjectDataTable.tsx → the types pin fails exactly two tests naming packages/plugin-dashboard/src/ObjectDataTable.tsx (anchor shape; the finding(plugin-dashboard): ObjectDataTableProps.schema reads drillDown and onRowClick and declares neither — both absorbed by its [key: string]: any #6914 casts are back), and plugin-dashboard's test project reports 9 errors, all in ObjectDataTable.schemaAnchor-6576.test.ts.
  • Drift census on this head (read keys off schema, cast-aware, minus the members the checker resolves on the built dist declaration; both widgets in one run): ObjectGallery read-not-declared [] (10 reads, 28 declared), ObjectDataTable read-not-declared [] (7 reads, 29 declared); synthetic control with drillDown removed from the declared set reports ["drillDown"], so the instrument sees a missing key. On origin/main the same census read ['drillDown','onRowClick'] for the data-table (prior report).
  • Gates: check-changeset-presence (11 published source files changed, changeset present), check-changeset-no-major (✅ No changeset declares a major bump), check-changeset-fixed (✅ All workspace packages are in the changeset fixed group), check:readme-exports (✅ OK … 406 self-imports judged (406 real, 0 wrong-path, 0 fabricated) — the @object-ui/types README lists no object-* schema types, so no README edit is required by the gate), check:spec-symbols (exit 0 — neither new name is a spec export; control GalleryConfigSchema found in the spec api-surface), check:control-bytes OK, check:phantom-deps OK, check:self-import OK, check:esm-specifiers OK, check:published-dist OK, check:side-effects-array OK, check:element-data-source-declaration OK, check:spec-floors OK, check:doc-types OK. NOT MEASURED locally: check:sdui-registration-pins (❌ No console build to weigh at apps/console/dist/assets — a prerequisite, not a verdict; registrations are untouched) and check:eager-closure (needs the console bundle). CI owns the repo-wide farm.
  • Governed-surface check (from ../objectstack, final file list): governed-surface predicate: 0 of 15 path(s) hit the register✅ NOT governed.

Deviations from the dispatch, declared

  1. drillDown on the zod mirror: ledgered instead of mirrored. The ruling admits either route (「新类型入镜或按台账既有词表申报」); mirroring it needs a paired DrillDownConfigSchema — a third new export the ruling did not name, whose wiring into ChartSchema would move that published validator — and an inline unpaired restatement would strip nested keys silently (z.object drops unknown keys), the very drift class the ledger exists for. So the new pair is SEEDED in UnmirroredDeclared with the ChartSchema.drillDown vocabulary (LOCAL), its doc states why this is a seed and not growth on an existing pair, and the repair is filed as finding(types): DrillDownConfig has no zod mirror — drillDown is declared on ChartSchema and ObjectDataTableSchema and validated on neither #7352. The reviewer should weigh this: the ledger's shrink-only rule is written for keys that appear on registered pairs.
  2. onRowClick on the mirror is a handlerKeyRefusal arm (as the dispatch said), which also means the 6124 census and its 36 / 58 pins moved to 37 / 59 and its file list to nine. The alternative — no arm, RuntimeOnlyDeclared (the pre-finding(types): 28 zod-mirror keys are declared z.function(), which NO JSON document can satisfy — 14 in-repo corpus files author them and are silently invalid #6124 objectql policy group) — was not taken because the 2026-08-30 ruling's rationale (an undeclared key is a silent accept that forwards the value) applies to a new mirror exactly as to an old one.
  3. Fresh gallery literals: TWO test files needed the type key (cells and coverValue), not one — the dispatch assumed only cells. Both are tests; no producer changed.
  4. ObjectDataTableProps.schema is the bare named type, not NamedSchema & {…}: with a dedicated schema type there are no widget-only extras to intersect, which is the ObjectMapProps shape; PivotTable / Timeline intersect because their schemas are generic nodes.
  5. The prior report's G7 prediction (gallery data collapses to any) did not hold: it was measured on a BaseSchema & {…} intersection; an extends member overrides. Reported as UNCHANGED above and pinned.
  6. Not extended: the ObjectQLComponentSchema unions (TS and zod) and AnyComponentSchema do not include the two new types. Adding them changes what validateSchema accepts for object-gallery / object-data-table nodes — a third published surface the ruling did not name. Flagged for the reviewer rather than decided.

Not in this PR (for the PM)

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

… to exported schema types that extend BaseSchema (objectui#6576, #6914)
Two widget prop types declared their `schema` as a hand-rolled inline object
literal with no `BaseSchema` in its ancestry — the only two `Object*Props` in
the repo whose `schema` anchored to no named type. Maintainer ruling
2026-08-31 (option A): mint `ObjectGallerySchema` and `ObjectDataTableSchema`
in `@object-ui/types`, each `extends BaseSchema`, and anchor both.
- `ObjectGalleryProps.schema` (published) WIDENS to every `BaseSchema` member
(`visibleWhen` was a compile error on the literal) and NARROWS in one place:
`type` is required and pinned to `'object-gallery'`. `data` stays typed —
an interface member overrides, it does not intersect.
- `ObjectDataTableProps.schema` (not exported) loses the literal's own
`[key: string]: any` and declares the two keys the widget read behind casts,
`drillDown: DrillDownConfig` and `onRowClick` (objectui#6914); the casts go.
A wrong-typed base member and a wrong-shaped `drillDown` are refused now.
- Zod mirrors of both under `@object-ui/types/zod`, registered in the parity
ledger: `onRowClick` is a `handlerKeyRefusal` runtime-slot arm (the first on
an objectql mirror; `KnownDrift` + the 6124 census updated), `drillDown` is
seeded in `UnmirroredDeclared` — `DrillDownConfig` has no zod mirror and
minting one is outside this ruling.
- The objectui#6574 `bind` ratchet entries for both files are removed and the
ratchet's prose rewritten: the defect was #6576's, not #5155 / #6269's.
- Red-first pins: `widget-schema-anchors-6576.test.ts` (types), and one
compile-time pin beside each widget. Two gallery test literals gain the now
required `type` key.
Unchanged and stated: an unknown key still compiles on both, because
`BaseSchema`'s index signature is inherited (objectui#5155, open).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
… `bind` member (objectui#6576)
`base-bind-declared.test.ts` scans every TRACKED file for a schema-side
`bind?:` re-declaration. The new pin's "this assertion can fail" control was
written as the pre-#6576 literal shape — `{ objectName?: string; bind?: string }`
— which read as exactly such a re-declaration the moment the file was
committed (green while untracked, red on the first post-commit run). The
control keeps its job with `className` in that slot; nothing else moves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3166.7 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-CbDKJ2sq.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 (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
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)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
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.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
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)514.59KB117.40KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)194.82KB46.02KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.63KB63.90KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.15KB8.40KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
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-litant
os-litant marked this pull request as ready for review September 2, 2026 13:22
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 4ca30d0Sep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-6576-widget-schema-anchors branch September 2, 2026 13:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914) - #7355

Merged
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors
Sep 2, 2026
Merged

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914)#7355
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#6576
Fixes#6914
Clause-②: yes — contract review required before release

Maintainer ruling 2026-08-31 (director seat, batch #10, option A, verbatim 「同意」), executed as written: two new exported schema types in @object-ui/types, each extends BaseSchema, and the two widget prop types that declared their schema as a hand-rolled inline literal are anchored to them. #6914 (the two keys the data-table reads and declared nowhere) is closed in the same stroke, per the erratum: drillDown and onRowClick.

Session for this implementation: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho (kept in prose because a PR body edit strips the footer form).

What changed

No runtime behaviour changes; both widgets render exactly as before (792 plugin-list and 805 plugin-dashboard tests unchanged and green).

Contract-review pack — accept-set before / after

Real tsc --noEmit against the real prop types, one labelled statement per line, diagnostics mapped by line number (origin/main at 2956d7af8, then this head). Each row is the exact literal assigned to the prop's schema type. The origin/main column repeats the prior report's probe and re-measures it on the same instrument.

ObjectGalleryProps.schema — PUBLISHED (plugin-list/src/index.tsx exports the type)

caseliteralorigin/mainthis headmovement
G1{ objectName, visibleWhen } (no type)REJECTED — TS2353 visibleWhen does not existREJECTED — TS2741 type is missingreason changed, see G6
G6{ type: 'object-gallery', objectName, visibleWhen }REJECTED — TS2353 type does not existACCEPTEDWIDENS — a real base member is writable
G2{ type: 'object-gallery', objectName }REJECTED — TS2353 type does not existACCEPTEDWIDENS — the node's own type key can be spelled
G4{ objectName } (minimal)ACCEPTEDREJECTED — TS2741 type is missingNARROWS — type is required (the prior report's G6)
G12{ type: 'gallery', objectName }REJECTED — TS2353 type does not existREJECTED — TS2322 "gallery" not assignable to "object-gallery"NARROWS for the right reason — the literal is the registry key
G3 / G7{ [type,] objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to array of recordsREJECTED — same TS2322UNCHANGED — the prior report predicted data would collapse to any under a BaseSchema INTERSECTION; extends overrides the member instead, so the widening does not happen
G11{ type, objectName, data: [{ id: 1 }] }REJECTED — TS2353 typeACCEPTEDWIDENS (via type)
G9{ objectName, visible: 42 } (no type)REJECTED — TS2353 visible does not existREJECTED — TS2322 number not assignable to string or booleanreason changed: unknown key → wrong-typed declared member
G8{ type, objectName, visible: 42 }REJECTED — TS2353 typeREJECTED — TS2322same as G9 with type present
G10{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited bind?: string)UNCHANGED — inherited now
G5{ type, objectName, visibleWhn: 'typo' }REJECTED — TS2353 type (the unknown key would also have been refused)ACCEPTEDWIDENS — the ceiling below

Net for the published type: WIDENS to every BaseSchema member (21 members gained; visibleWhen, visible, id, hidden, disabled, testId, ariaLabel, label, style, … are writable and type-checked), NARROWS in one place (type required and pinned to 'object-gallery'). data, bind, filter, gallery, navigation, grouping, imageField, titleField keep their exact types.

ObjectDataTableProps.schema — NOT exported from plugin-dashboard/src/index.tsx (reaches consumers structurally as the exported component's prop)

caseliteralorigin/mainthis headmovement
D1{ type: 'object-data-table', objectName, bogusKey: 1 }ACCEPTEDACCEPTEDUNCHANGED — the ceiling below
D2{ type, objectName, visible: 42 }ACCEPTED (absorbed by the literal's index signature)REJECTED — TS2322 number not assignable to string or booleanNARROWS — a wrong-typed base member is refused
D3{ type, objectName, drillDown: { enabled: true, mode: 'record' } }ACCEPTED (as any)ACCEPTED (as DrillDownConfig)UNCHANGED in verdict; DECLARED now (#6914)
D4{ type, objectName, drillDown: { enabled: 'yes' } }ACCEPTED (absorbed)REJECTED — TS2322 string not assignable to booleanNARROWS — a wrong-shaped drillDown is refused
D5{ type, objectName, onRowClick: an arrow function of one untyped parameter }REJECTED — TS7006 row implicitly any (the key resolved to any, so nothing typed the parameter)ACCEPTED — row is contextually typedWIDENS in declaration — the handler can be written without an annotation (#6914)
D10{ type, objectName, onRowClick: 'not-a-function' }ACCEPTED (absorbed)REJECTED — TS2322NARROWS
D6{ type, objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to any[]REJECTED — sameUNCHANGED
D7{ type: 'data-table', objectName }ACCEPTED (type: string)REJECTED — TS2322 "data-table" not assignable to "object-data-table"NARROWS — the literal is the registry key
D8{ type: 'object-data-table' }ACCEPTEDACCEPTEDUNCHANGED
D9{ objectName } (no type)REJECTED — TS2741 type missingREJECTED — TS2741UNCHANGED (type was already required)
D11{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited)UNCHANGED
D12{ type, objectName, visibleWhen: 'ready' }ACCEPTED (absorbed as any)ACCEPTED (declared string)UNCHANGED in verdict; typed now

Net for the unpublished type: NARROWS (the literal's own index signature is gone, so declared base members and the two #6914 keys are type-checked; type is the registry key). In-repo call sites re-checked: every plugin-dashboard test and DrillDownDrawer.tsx compile unchanged — the tests that write other spellings already carry as any.

The ceiling, stated plainly

BaseSchema still carries [key: string]: any (#5155 remains open). Anchoring inherits it, so an UNKNOWN key (visibleWhn, bogusKey) remains ACCEPTED on BOTH prop types after this PR — on the data-table it always was; on the gallery it is new (G5). The ruling accepted that cost; the pins record it as a counter-probe rather than hiding it.

Verification (on the committed heads f84c14c17 — the implementation — and the final head named below, tree clean)

Every heavy command ran under scripts/pm/os-verify-lock.sh (slot dev-6576); verdict lines quoted from the tools, not from a bare exit status.

  • Dependency closure built first (turbo run build for the three plugins' dependency closures, then every packages/*: Tasks: 39 successful, 39 total).
  • pnpm --filter @object-ui/types run buildtsc && node ../../scripts/check-dist-completeness.mjs, exit 0; dist/index.d.ts carries both new names.
  • pnpm --filter … run type-check — script echoed for each: @object-ui/types@17.6.0 type-check, @object-ui/plugin-list@17.6.0 type-check, @object-ui/plugin-dashboard@17.6.0 type-check, @object-ui/plugin-view@17.6.0 type-check — 0 errors each (each script is tsc --noEmit plus the package's test project, so every pin and both ledgers are compiled). plugin-view is the other producer of object-gallery nodes; it builds a plain node and never passes through ObjectGalleryProps, so nothing there moved.
  • pnpm --filter … run lint (types / plugin-list / plugin-dashboard): 0 errors each (warnings only, pre-existing no-explicit-any in files that already carried it; the new ObjectDataTableSchema transcribes the literal's any spellings unchanged).
  • Vitest (root-relative paths, no --), on the final head e5b24a3bb: packages/types/Test Files 88 passed (88), Tests 1434 passed (1434); packages/plugin-list/Test Files 63 passed (63), Tests 792 passed (792); the six ledger and pin files together (zod-mirror-parity, handler-keys-json-refusal-6124, base-bind-declared, widget-schema-anchors-6576, both schemaAnchor-6576 pins) → Test Files 6 passed (6), Tests 286 passed (286). packages/plugin-dashboard/ on f84c14c17 (the second commit touches only a plugin-list test file) → Test Files 86 passed (86), Tests 805 passed (805). The second commit exists because the first post-commit run of base-bind-declared went RED: its git grep scan covers tracked files, and the plugin-list pin's synthetic control spelled the pre-finding(types): two widget prop types declare their schema as a hand-rolled inline object with no BaseSchema in its ancestry #6576 literal with a bind member — the ratchet did exactly its job; the control now uses className.
  • Red-first, on origin/main with the pins added and nothing else: tsc -p tsconfig.test.json — types 17 errors (all in the new pin: TS2724 the two names do not exist, TS2344 on every type assertion), plugin-list 9 errors, plugin-dashboard 10 errors (TS2724, TS2344, four TS2578 unused directives where the literal absorbed the wrong value); vitest on the types pin: 6 failed. GREEN here on all three.
  • Ablation (each leg on the committed tree; mutation proven by blob hash equal to the origin/main blob, restore proven by blob hash equal to HEAD and git diff HEAD empty; a trap restored both files on every exit path): reverting only ObjectGallery.tsx → the types pin fails exactly one test naming packages/plugin-list/src/ObjectGallery.tsx, and plugin-list's test project reports 8 errors in ObjectGallery.schemaAnchor-6576.test.ts (plus the two gallery test literals); reverting only ObjectDataTable.tsx → the types pin fails exactly two tests naming packages/plugin-dashboard/src/ObjectDataTable.tsx (anchor shape; the finding(plugin-dashboard): ObjectDataTableProps.schema reads drillDown and onRowClick and declares neither — both absorbed by its [key: string]: any #6914 casts are back), and plugin-dashboard's test project reports 9 errors, all in ObjectDataTable.schemaAnchor-6576.test.ts.
  • Drift census on this head (read keys off schema, cast-aware, minus the members the checker resolves on the built dist declaration; both widgets in one run): ObjectGallery read-not-declared [] (10 reads, 28 declared), ObjectDataTable read-not-declared [] (7 reads, 29 declared); synthetic control with drillDown removed from the declared set reports ["drillDown"], so the instrument sees a missing key. On origin/main the same census read ['drillDown','onRowClick'] for the data-table (prior report).
  • Gates: check-changeset-presence (11 published source files changed, changeset present), check-changeset-no-major (✅ No changeset declares a major bump), check-changeset-fixed (✅ All workspace packages are in the changeset fixed group), check:readme-exports (✅ OK … 406 self-imports judged (406 real, 0 wrong-path, 0 fabricated) — the @object-ui/types README lists no object-* schema types, so no README edit is required by the gate), check:spec-symbols (exit 0 — neither new name is a spec export; control GalleryConfigSchema found in the spec api-surface), check:control-bytes OK, check:phantom-deps OK, check:self-import OK, check:esm-specifiers OK, check:published-dist OK, check:side-effects-array OK, check:element-data-source-declaration OK, check:spec-floors OK, check:doc-types OK. NOT MEASURED locally: check:sdui-registration-pins (❌ No console build to weigh at apps/console/dist/assets — a prerequisite, not a verdict; registrations are untouched) and check:eager-closure (needs the console bundle). CI owns the repo-wide farm.
  • Governed-surface check (from ../objectstack, final file list): governed-surface predicate: 0 of 15 path(s) hit the register✅ NOT governed.

Deviations from the dispatch, declared

  1. drillDown on the zod mirror: ledgered instead of mirrored. The ruling admits either route (「新类型入镜或按台账既有词表申报」); mirroring it needs a paired DrillDownConfigSchema — a third new export the ruling did not name, whose wiring into ChartSchema would move that published validator — and an inline unpaired restatement would strip nested keys silently (z.object drops unknown keys), the very drift class the ledger exists for. So the new pair is SEEDED in UnmirroredDeclared with the ChartSchema.drillDown vocabulary (LOCAL), its doc states why this is a seed and not growth on an existing pair, and the repair is filed as finding(types): DrillDownConfig has no zod mirror — drillDown is declared on ChartSchema and ObjectDataTableSchema and validated on neither #7352. The reviewer should weigh this: the ledger's shrink-only rule is written for keys that appear on registered pairs.
  2. onRowClick on the mirror is a handlerKeyRefusal arm (as the dispatch said), which also means the 6124 census and its 36 / 58 pins moved to 37 / 59 and its file list to nine. The alternative — no arm, RuntimeOnlyDeclared (the pre-finding(types): 28 zod-mirror keys are declared z.function(), which NO JSON document can satisfy — 14 in-repo corpus files author them and are silently invalid #6124 objectql policy group) — was not taken because the 2026-08-30 ruling's rationale (an undeclared key is a silent accept that forwards the value) applies to a new mirror exactly as to an old one.
  3. Fresh gallery literals: TWO test files needed the type key (cells and coverValue), not one — the dispatch assumed only cells. Both are tests; no producer changed.
  4. ObjectDataTableProps.schema is the bare named type, not NamedSchema & {…}: with a dedicated schema type there are no widget-only extras to intersect, which is the ObjectMapProps shape; PivotTable / Timeline intersect because their schemas are generic nodes.
  5. The prior report's G7 prediction (gallery data collapses to any) did not hold: it was measured on a BaseSchema & {…} intersection; an extends member overrides. Reported as UNCHANGED above and pinned.
  6. Not extended: the ObjectQLComponentSchema unions (TS and zod) and AnyComponentSchema do not include the two new types. Adding them changes what validateSchema accepts for object-gallery / object-data-table nodes — a third published surface the ruling did not name. Flagged for the reviewer rather than decided.

Not in this PR (for the PM)

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

… to exported schema types that extend BaseSchema (objectui#6576, #6914)
Two widget prop types declared their `schema` as a hand-rolled inline object
literal with no `BaseSchema` in its ancestry — the only two `Object*Props` in
the repo whose `schema` anchored to no named type. Maintainer ruling
2026-08-31 (option A): mint `ObjectGallerySchema` and `ObjectDataTableSchema`
in `@object-ui/types`, each `extends BaseSchema`, and anchor both.
- `ObjectGalleryProps.schema` (published) WIDENS to every `BaseSchema` member
(`visibleWhen` was a compile error on the literal) and NARROWS in one place:
`type` is required and pinned to `'object-gallery'`. `data` stays typed —
an interface member overrides, it does not intersect.
- `ObjectDataTableProps.schema` (not exported) loses the literal's own
`[key: string]: any` and declares the two keys the widget read behind casts,
`drillDown: DrillDownConfig` and `onRowClick` (objectui#6914); the casts go.
A wrong-typed base member and a wrong-shaped `drillDown` are refused now.
- Zod mirrors of both under `@object-ui/types/zod`, registered in the parity
ledger: `onRowClick` is a `handlerKeyRefusal` runtime-slot arm (the first on
an objectql mirror; `KnownDrift` + the 6124 census updated), `drillDown` is
seeded in `UnmirroredDeclared` — `DrillDownConfig` has no zod mirror and
minting one is outside this ruling.
- The objectui#6574 `bind` ratchet entries for both files are removed and the
ratchet's prose rewritten: the defect was #6576's, not #5155 / #6269's.
- Red-first pins: `widget-schema-anchors-6576.test.ts` (types), and one
compile-time pin beside each widget. Two gallery test literals gain the now
required `type` key.
Unchanged and stated: an unknown key still compiles on both, because
`BaseSchema`'s index signature is inherited (objectui#5155, open).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
… `bind` member (objectui#6576)
`base-bind-declared.test.ts` scans every TRACKED file for a schema-side
`bind?:` re-declaration. The new pin's "this assertion can fail" control was
written as the pre-#6576 literal shape — `{ objectName?: string; bind?: string }`
— which read as exactly such a re-declaration the moment the file was
committed (green while untracked, red on the first post-commit run). The
control keeps its job with `className` in that slot; nothing else moves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3166.7 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-CbDKJ2sq.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 (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
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)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
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.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
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)514.59KB117.40KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)194.82KB46.02KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.63KB63.90KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.15KB8.40KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
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-litant
os-litant marked this pull request as ready for review September 2, 2026 13:22
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 4ca30d0Sep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-6576-widget-schema-anchors branch September 2, 2026 13:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914) - #7355

Merged
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors
Sep 2, 2026
Merged

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914)#7355
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#6576
Fixes#6914
Clause-②: yes — contract review required before release

Maintainer ruling 2026-08-31 (director seat, batch #10, option A, verbatim 「同意」), executed as written: two new exported schema types in @object-ui/types, each extends BaseSchema, and the two widget prop types that declared their schema as a hand-rolled inline literal are anchored to them. #6914 (the two keys the data-table reads and declared nowhere) is closed in the same stroke, per the erratum: drillDown and onRowClick.

Session for this implementation: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho (kept in prose because a PR body edit strips the footer form).

What changed

No runtime behaviour changes; both widgets render exactly as before (792 plugin-list and 805 plugin-dashboard tests unchanged and green).

Contract-review pack — accept-set before / after

Real tsc --noEmit against the real prop types, one labelled statement per line, diagnostics mapped by line number (origin/main at 2956d7af8, then this head). Each row is the exact literal assigned to the prop's schema type. The origin/main column repeats the prior report's probe and re-measures it on the same instrument.

ObjectGalleryProps.schema — PUBLISHED (plugin-list/src/index.tsx exports the type)

caseliteralorigin/mainthis headmovement
G1{ objectName, visibleWhen } (no type)REJECTED — TS2353 visibleWhen does not existREJECTED — TS2741 type is missingreason changed, see G6
G6{ type: 'object-gallery', objectName, visibleWhen }REJECTED — TS2353 type does not existACCEPTEDWIDENS — a real base member is writable
G2{ type: 'object-gallery', objectName }REJECTED — TS2353 type does not existACCEPTEDWIDENS — the node's own type key can be spelled
G4{ objectName } (minimal)ACCEPTEDREJECTED — TS2741 type is missingNARROWS — type is required (the prior report's G6)
G12{ type: 'gallery', objectName }REJECTED — TS2353 type does not existREJECTED — TS2322 "gallery" not assignable to "object-gallery"NARROWS for the right reason — the literal is the registry key
G3 / G7{ [type,] objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to array of recordsREJECTED — same TS2322UNCHANGED — the prior report predicted data would collapse to any under a BaseSchema INTERSECTION; extends overrides the member instead, so the widening does not happen
G11{ type, objectName, data: [{ id: 1 }] }REJECTED — TS2353 typeACCEPTEDWIDENS (via type)
G9{ objectName, visible: 42 } (no type)REJECTED — TS2353 visible does not existREJECTED — TS2322 number not assignable to string or booleanreason changed: unknown key → wrong-typed declared member
G8{ type, objectName, visible: 42 }REJECTED — TS2353 typeREJECTED — TS2322same as G9 with type present
G10{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited bind?: string)UNCHANGED — inherited now
G5{ type, objectName, visibleWhn: 'typo' }REJECTED — TS2353 type (the unknown key would also have been refused)ACCEPTEDWIDENS — the ceiling below

Net for the published type: WIDENS to every BaseSchema member (21 members gained; visibleWhen, visible, id, hidden, disabled, testId, ariaLabel, label, style, … are writable and type-checked), NARROWS in one place (type required and pinned to 'object-gallery'). data, bind, filter, gallery, navigation, grouping, imageField, titleField keep their exact types.

ObjectDataTableProps.schema — NOT exported from plugin-dashboard/src/index.tsx (reaches consumers structurally as the exported component's prop)

caseliteralorigin/mainthis headmovement
D1{ type: 'object-data-table', objectName, bogusKey: 1 }ACCEPTEDACCEPTEDUNCHANGED — the ceiling below
D2{ type, objectName, visible: 42 }ACCEPTED (absorbed by the literal's index signature)REJECTED — TS2322 number not assignable to string or booleanNARROWS — a wrong-typed base member is refused
D3{ type, objectName, drillDown: { enabled: true, mode: 'record' } }ACCEPTED (as any)ACCEPTED (as DrillDownConfig)UNCHANGED in verdict; DECLARED now (#6914)
D4{ type, objectName, drillDown: { enabled: 'yes' } }ACCEPTED (absorbed)REJECTED — TS2322 string not assignable to booleanNARROWS — a wrong-shaped drillDown is refused
D5{ type, objectName, onRowClick: an arrow function of one untyped parameter }REJECTED — TS7006 row implicitly any (the key resolved to any, so nothing typed the parameter)ACCEPTED — row is contextually typedWIDENS in declaration — the handler can be written without an annotation (#6914)
D10{ type, objectName, onRowClick: 'not-a-function' }ACCEPTED (absorbed)REJECTED — TS2322NARROWS
D6{ type, objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to any[]REJECTED — sameUNCHANGED
D7{ type: 'data-table', objectName }ACCEPTED (type: string)REJECTED — TS2322 "data-table" not assignable to "object-data-table"NARROWS — the literal is the registry key
D8{ type: 'object-data-table' }ACCEPTEDACCEPTEDUNCHANGED
D9{ objectName } (no type)REJECTED — TS2741 type missingREJECTED — TS2741UNCHANGED (type was already required)
D11{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited)UNCHANGED
D12{ type, objectName, visibleWhen: 'ready' }ACCEPTED (absorbed as any)ACCEPTED (declared string)UNCHANGED in verdict; typed now

Net for the unpublished type: NARROWS (the literal's own index signature is gone, so declared base members and the two #6914 keys are type-checked; type is the registry key). In-repo call sites re-checked: every plugin-dashboard test and DrillDownDrawer.tsx compile unchanged — the tests that write other spellings already carry as any.

The ceiling, stated plainly

BaseSchema still carries [key: string]: any (#5155 remains open). Anchoring inherits it, so an UNKNOWN key (visibleWhn, bogusKey) remains ACCEPTED on BOTH prop types after this PR — on the data-table it always was; on the gallery it is new (G5). The ruling accepted that cost; the pins record it as a counter-probe rather than hiding it.

Verification (on the committed heads f84c14c17 — the implementation — and the final head named below, tree clean)

Every heavy command ran under scripts/pm/os-verify-lock.sh (slot dev-6576); verdict lines quoted from the tools, not from a bare exit status.

  • Dependency closure built first (turbo run build for the three plugins' dependency closures, then every packages/*: Tasks: 39 successful, 39 total).
  • pnpm --filter @object-ui/types run buildtsc && node ../../scripts/check-dist-completeness.mjs, exit 0; dist/index.d.ts carries both new names.
  • pnpm --filter … run type-check — script echoed for each: @object-ui/types@17.6.0 type-check, @object-ui/plugin-list@17.6.0 type-check, @object-ui/plugin-dashboard@17.6.0 type-check, @object-ui/plugin-view@17.6.0 type-check — 0 errors each (each script is tsc --noEmit plus the package's test project, so every pin and both ledgers are compiled). plugin-view is the other producer of object-gallery nodes; it builds a plain node and never passes through ObjectGalleryProps, so nothing there moved.
  • pnpm --filter … run lint (types / plugin-list / plugin-dashboard): 0 errors each (warnings only, pre-existing no-explicit-any in files that already carried it; the new ObjectDataTableSchema transcribes the literal's any spellings unchanged).
  • Vitest (root-relative paths, no --), on the final head e5b24a3bb: packages/types/Test Files 88 passed (88), Tests 1434 passed (1434); packages/plugin-list/Test Files 63 passed (63), Tests 792 passed (792); the six ledger and pin files together (zod-mirror-parity, handler-keys-json-refusal-6124, base-bind-declared, widget-schema-anchors-6576, both schemaAnchor-6576 pins) → Test Files 6 passed (6), Tests 286 passed (286). packages/plugin-dashboard/ on f84c14c17 (the second commit touches only a plugin-list test file) → Test Files 86 passed (86), Tests 805 passed (805). The second commit exists because the first post-commit run of base-bind-declared went RED: its git grep scan covers tracked files, and the plugin-list pin's synthetic control spelled the pre-finding(types): two widget prop types declare their schema as a hand-rolled inline object with no BaseSchema in its ancestry #6576 literal with a bind member — the ratchet did exactly its job; the control now uses className.
  • Red-first, on origin/main with the pins added and nothing else: tsc -p tsconfig.test.json — types 17 errors (all in the new pin: TS2724 the two names do not exist, TS2344 on every type assertion), plugin-list 9 errors, plugin-dashboard 10 errors (TS2724, TS2344, four TS2578 unused directives where the literal absorbed the wrong value); vitest on the types pin: 6 failed. GREEN here on all three.
  • Ablation (each leg on the committed tree; mutation proven by blob hash equal to the origin/main blob, restore proven by blob hash equal to HEAD and git diff HEAD empty; a trap restored both files on every exit path): reverting only ObjectGallery.tsx → the types pin fails exactly one test naming packages/plugin-list/src/ObjectGallery.tsx, and plugin-list's test project reports 8 errors in ObjectGallery.schemaAnchor-6576.test.ts (plus the two gallery test literals); reverting only ObjectDataTable.tsx → the types pin fails exactly two tests naming packages/plugin-dashboard/src/ObjectDataTable.tsx (anchor shape; the finding(plugin-dashboard): ObjectDataTableProps.schema reads drillDown and onRowClick and declares neither — both absorbed by its [key: string]: any #6914 casts are back), and plugin-dashboard's test project reports 9 errors, all in ObjectDataTable.schemaAnchor-6576.test.ts.
  • Drift census on this head (read keys off schema, cast-aware, minus the members the checker resolves on the built dist declaration; both widgets in one run): ObjectGallery read-not-declared [] (10 reads, 28 declared), ObjectDataTable read-not-declared [] (7 reads, 29 declared); synthetic control with drillDown removed from the declared set reports ["drillDown"], so the instrument sees a missing key. On origin/main the same census read ['drillDown','onRowClick'] for the data-table (prior report).
  • Gates: check-changeset-presence (11 published source files changed, changeset present), check-changeset-no-major (✅ No changeset declares a major bump), check-changeset-fixed (✅ All workspace packages are in the changeset fixed group), check:readme-exports (✅ OK … 406 self-imports judged (406 real, 0 wrong-path, 0 fabricated) — the @object-ui/types README lists no object-* schema types, so no README edit is required by the gate), check:spec-symbols (exit 0 — neither new name is a spec export; control GalleryConfigSchema found in the spec api-surface), check:control-bytes OK, check:phantom-deps OK, check:self-import OK, check:esm-specifiers OK, check:published-dist OK, check:side-effects-array OK, check:element-data-source-declaration OK, check:spec-floors OK, check:doc-types OK. NOT MEASURED locally: check:sdui-registration-pins (❌ No console build to weigh at apps/console/dist/assets — a prerequisite, not a verdict; registrations are untouched) and check:eager-closure (needs the console bundle). CI owns the repo-wide farm.
  • Governed-surface check (from ../objectstack, final file list): governed-surface predicate: 0 of 15 path(s) hit the register✅ NOT governed.

Deviations from the dispatch, declared

  1. drillDown on the zod mirror: ledgered instead of mirrored. The ruling admits either route (「新类型入镜或按台账既有词表申报」); mirroring it needs a paired DrillDownConfigSchema — a third new export the ruling did not name, whose wiring into ChartSchema would move that published validator — and an inline unpaired restatement would strip nested keys silently (z.object drops unknown keys), the very drift class the ledger exists for. So the new pair is SEEDED in UnmirroredDeclared with the ChartSchema.drillDown vocabulary (LOCAL), its doc states why this is a seed and not growth on an existing pair, and the repair is filed as finding(types): DrillDownConfig has no zod mirror — drillDown is declared on ChartSchema and ObjectDataTableSchema and validated on neither #7352. The reviewer should weigh this: the ledger's shrink-only rule is written for keys that appear on registered pairs.
  2. onRowClick on the mirror is a handlerKeyRefusal arm (as the dispatch said), which also means the 6124 census and its 36 / 58 pins moved to 37 / 59 and its file list to nine. The alternative — no arm, RuntimeOnlyDeclared (the pre-finding(types): 28 zod-mirror keys are declared z.function(), which NO JSON document can satisfy — 14 in-repo corpus files author them and are silently invalid #6124 objectql policy group) — was not taken because the 2026-08-30 ruling's rationale (an undeclared key is a silent accept that forwards the value) applies to a new mirror exactly as to an old one.
  3. Fresh gallery literals: TWO test files needed the type key (cells and coverValue), not one — the dispatch assumed only cells. Both are tests; no producer changed.
  4. ObjectDataTableProps.schema is the bare named type, not NamedSchema & {…}: with a dedicated schema type there are no widget-only extras to intersect, which is the ObjectMapProps shape; PivotTable / Timeline intersect because their schemas are generic nodes.
  5. The prior report's G7 prediction (gallery data collapses to any) did not hold: it was measured on a BaseSchema & {…} intersection; an extends member overrides. Reported as UNCHANGED above and pinned.
  6. Not extended: the ObjectQLComponentSchema unions (TS and zod) and AnyComponentSchema do not include the two new types. Adding them changes what validateSchema accepts for object-gallery / object-data-table nodes — a third published surface the ruling did not name. Flagged for the reviewer rather than decided.

Not in this PR (for the PM)

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

… to exported schema types that extend BaseSchema (objectui#6576, #6914)
Two widget prop types declared their `schema` as a hand-rolled inline object
literal with no `BaseSchema` in its ancestry — the only two `Object*Props` in
the repo whose `schema` anchored to no named type. Maintainer ruling
2026-08-31 (option A): mint `ObjectGallerySchema` and `ObjectDataTableSchema`
in `@object-ui/types`, each `extends BaseSchema`, and anchor both.
- `ObjectGalleryProps.schema` (published) WIDENS to every `BaseSchema` member
(`visibleWhen` was a compile error on the literal) and NARROWS in one place:
`type` is required and pinned to `'object-gallery'`. `data` stays typed —
an interface member overrides, it does not intersect.
- `ObjectDataTableProps.schema` (not exported) loses the literal's own
`[key: string]: any` and declares the two keys the widget read behind casts,
`drillDown: DrillDownConfig` and `onRowClick` (objectui#6914); the casts go.
A wrong-typed base member and a wrong-shaped `drillDown` are refused now.
- Zod mirrors of both under `@object-ui/types/zod`, registered in the parity
ledger: `onRowClick` is a `handlerKeyRefusal` runtime-slot arm (the first on
an objectql mirror; `KnownDrift` + the 6124 census updated), `drillDown` is
seeded in `UnmirroredDeclared` — `DrillDownConfig` has no zod mirror and
minting one is outside this ruling.
- The objectui#6574 `bind` ratchet entries for both files are removed and the
ratchet's prose rewritten: the defect was #6576's, not #5155 / #6269's.
- Red-first pins: `widget-schema-anchors-6576.test.ts` (types), and one
compile-time pin beside each widget. Two gallery test literals gain the now
required `type` key.
Unchanged and stated: an unknown key still compiles on both, because
`BaseSchema`'s index signature is inherited (objectui#5155, open).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
… `bind` member (objectui#6576)
`base-bind-declared.test.ts` scans every TRACKED file for a schema-side
`bind?:` re-declaration. The new pin's "this assertion can fail" control was
written as the pre-#6576 literal shape — `{ objectName?: string; bind?: string }`
— which read as exactly such a re-declaration the moment the file was
committed (green while untracked, red on the first post-commit run). The
control keeps its job with `className` in that slot; nothing else moves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3166.7 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-CbDKJ2sq.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 (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
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)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
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.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
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)514.59KB117.40KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)194.82KB46.02KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.63KB63.90KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.15KB8.40KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
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-litant
os-litant marked this pull request as ready for review September 2, 2026 13:22
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 4ca30d0Sep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-6576-widget-schema-anchors branch September 2, 2026 13:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914) - #7355

Merged
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors
Sep 2, 2026
Merged

fix(types): anchor ObjectGalleryProps / ObjectDataTableProps schema to exported schema types that extend BaseSchema (#6576, #6914)#7355
os-litant merged 2 commits into
mainfrom
claude/issue-6576-widget-schema-anchors

Conversation

@os-litant

Copy link
Copy Markdown
Collaborator

Fixes#6576
Fixes#6914
Clause-②: yes — contract review required before release

Maintainer ruling 2026-08-31 (director seat, batch #10, option A, verbatim 「同意」), executed as written: two new exported schema types in @object-ui/types, each extends BaseSchema, and the two widget prop types that declared their schema as a hand-rolled inline literal are anchored to them. #6914 (the two keys the data-table reads and declared nowhere) is closed in the same stroke, per the erratum: drillDown and onRowClick.

Session for this implementation: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho (kept in prose because a PR body edit strips the footer form).

What changed

No runtime behaviour changes; both widgets render exactly as before (792 plugin-list and 805 plugin-dashboard tests unchanged and green).

Contract-review pack — accept-set before / after

Real tsc --noEmit against the real prop types, one labelled statement per line, diagnostics mapped by line number (origin/main at 2956d7af8, then this head). Each row is the exact literal assigned to the prop's schema type. The origin/main column repeats the prior report's probe and re-measures it on the same instrument.

ObjectGalleryProps.schema — PUBLISHED (plugin-list/src/index.tsx exports the type)

caseliteralorigin/mainthis headmovement
G1{ objectName, visibleWhen } (no type)REJECTED — TS2353 visibleWhen does not existREJECTED — TS2741 type is missingreason changed, see G6
G6{ type: 'object-gallery', objectName, visibleWhen }REJECTED — TS2353 type does not existACCEPTEDWIDENS — a real base member is writable
G2{ type: 'object-gallery', objectName }REJECTED — TS2353 type does not existACCEPTEDWIDENS — the node's own type key can be spelled
G4{ objectName } (minimal)ACCEPTEDREJECTED — TS2741 type is missingNARROWS — type is required (the prior report's G6)
G12{ type: 'gallery', objectName }REJECTED — TS2353 type does not existREJECTED — TS2322 "gallery" not assignable to "object-gallery"NARROWS for the right reason — the literal is the registry key
G3 / G7{ [type,] objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to array of recordsREJECTED — same TS2322UNCHANGED — the prior report predicted data would collapse to any under a BaseSchema INTERSECTION; extends overrides the member instead, so the widening does not happen
G11{ type, objectName, data: [{ id: 1 }] }REJECTED — TS2353 typeACCEPTEDWIDENS (via type)
G9{ objectName, visible: 42 } (no type)REJECTED — TS2353 visible does not existREJECTED — TS2322 number not assignable to string or booleanreason changed: unknown key → wrong-typed declared member
G8{ type, objectName, visible: 42 }REJECTED — TS2353 typeREJECTED — TS2322same as G9 with type present
G10{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited bind?: string)UNCHANGED — inherited now
G5{ type, objectName, visibleWhn: 'typo' }REJECTED — TS2353 type (the unknown key would also have been refused)ACCEPTEDWIDENS — the ceiling below

Net for the published type: WIDENS to every BaseSchema member (21 members gained; visibleWhen, visible, id, hidden, disabled, testId, ariaLabel, label, style, … are writable and type-checked), NARROWS in one place (type required and pinned to 'object-gallery'). data, bind, filter, gallery, navigation, grouping, imageField, titleField keep their exact types.

ObjectDataTableProps.schema — NOT exported from plugin-dashboard/src/index.tsx (reaches consumers structurally as the exported component's prop)

caseliteralorigin/mainthis headmovement
D1{ type: 'object-data-table', objectName, bogusKey: 1 }ACCEPTEDACCEPTEDUNCHANGED — the ceiling below
D2{ type, objectName, visible: 42 }ACCEPTED (absorbed by the literal's index signature)REJECTED — TS2322 number not assignable to string or booleanNARROWS — a wrong-typed base member is refused
D3{ type, objectName, drillDown: { enabled: true, mode: 'record' } }ACCEPTED (as any)ACCEPTED (as DrillDownConfig)UNCHANGED in verdict; DECLARED now (#6914)
D4{ type, objectName, drillDown: { enabled: 'yes' } }ACCEPTED (absorbed)REJECTED — TS2322 string not assignable to booleanNARROWS — a wrong-shaped drillDown is refused
D5{ type, objectName, onRowClick: an arrow function of one untyped parameter }REJECTED — TS7006 row implicitly any (the key resolved to any, so nothing typed the parameter)ACCEPTED — row is contextually typedWIDENS in declaration — the handler can be written without an annotation (#6914)
D10{ type, objectName, onRowClick: 'not-a-function' }ACCEPTED (absorbed)REJECTED — TS2322NARROWS
D6{ type, objectName, data: 'not-an-array' }REJECTED — TS2322 string not assignable to any[]REJECTED — sameUNCHANGED
D7{ type: 'data-table', objectName }ACCEPTED (type: string)REJECTED — TS2322 "data-table" not assignable to "object-data-table"NARROWS — the literal is the registry key
D8{ type: 'object-data-table' }ACCEPTEDACCEPTEDUNCHANGED
D9{ objectName } (no type)REJECTED — TS2741 type missingREJECTED — TS2741UNCHANGED (type was already required)
D11{ type, objectName, bind: 42 }REJECTED — TS2322 (local bind?: string)REJECTED — TS2322 (inherited)UNCHANGED
D12{ type, objectName, visibleWhen: 'ready' }ACCEPTED (absorbed as any)ACCEPTED (declared string)UNCHANGED in verdict; typed now

Net for the unpublished type: NARROWS (the literal's own index signature is gone, so declared base members and the two #6914 keys are type-checked; type is the registry key). In-repo call sites re-checked: every plugin-dashboard test and DrillDownDrawer.tsx compile unchanged — the tests that write other spellings already carry as any.

The ceiling, stated plainly

BaseSchema still carries [key: string]: any (#5155 remains open). Anchoring inherits it, so an UNKNOWN key (visibleWhn, bogusKey) remains ACCEPTED on BOTH prop types after this PR — on the data-table it always was; on the gallery it is new (G5). The ruling accepted that cost; the pins record it as a counter-probe rather than hiding it.

Verification (on the committed heads f84c14c17 — the implementation — and the final head named below, tree clean)

Every heavy command ran under scripts/pm/os-verify-lock.sh (slot dev-6576); verdict lines quoted from the tools, not from a bare exit status.

  • Dependency closure built first (turbo run build for the three plugins' dependency closures, then every packages/*: Tasks: 39 successful, 39 total).
  • pnpm --filter @object-ui/types run buildtsc && node ../../scripts/check-dist-completeness.mjs, exit 0; dist/index.d.ts carries both new names.
  • pnpm --filter … run type-check — script echoed for each: @object-ui/types@17.6.0 type-check, @object-ui/plugin-list@17.6.0 type-check, @object-ui/plugin-dashboard@17.6.0 type-check, @object-ui/plugin-view@17.6.0 type-check — 0 errors each (each script is tsc --noEmit plus the package's test project, so every pin and both ledgers are compiled). plugin-view is the other producer of object-gallery nodes; it builds a plain node and never passes through ObjectGalleryProps, so nothing there moved.
  • pnpm --filter … run lint (types / plugin-list / plugin-dashboard): 0 errors each (warnings only, pre-existing no-explicit-any in files that already carried it; the new ObjectDataTableSchema transcribes the literal's any spellings unchanged).
  • Vitest (root-relative paths, no --), on the final head e5b24a3bb: packages/types/Test Files 88 passed (88), Tests 1434 passed (1434); packages/plugin-list/Test Files 63 passed (63), Tests 792 passed (792); the six ledger and pin files together (zod-mirror-parity, handler-keys-json-refusal-6124, base-bind-declared, widget-schema-anchors-6576, both schemaAnchor-6576 pins) → Test Files 6 passed (6), Tests 286 passed (286). packages/plugin-dashboard/ on f84c14c17 (the second commit touches only a plugin-list test file) → Test Files 86 passed (86), Tests 805 passed (805). The second commit exists because the first post-commit run of base-bind-declared went RED: its git grep scan covers tracked files, and the plugin-list pin's synthetic control spelled the pre-finding(types): two widget prop types declare their schema as a hand-rolled inline object with no BaseSchema in its ancestry #6576 literal with a bind member — the ratchet did exactly its job; the control now uses className.
  • Red-first, on origin/main with the pins added and nothing else: tsc -p tsconfig.test.json — types 17 errors (all in the new pin: TS2724 the two names do not exist, TS2344 on every type assertion), plugin-list 9 errors, plugin-dashboard 10 errors (TS2724, TS2344, four TS2578 unused directives where the literal absorbed the wrong value); vitest on the types pin: 6 failed. GREEN here on all three.
  • Ablation (each leg on the committed tree; mutation proven by blob hash equal to the origin/main blob, restore proven by blob hash equal to HEAD and git diff HEAD empty; a trap restored both files on every exit path): reverting only ObjectGallery.tsx → the types pin fails exactly one test naming packages/plugin-list/src/ObjectGallery.tsx, and plugin-list's test project reports 8 errors in ObjectGallery.schemaAnchor-6576.test.ts (plus the two gallery test literals); reverting only ObjectDataTable.tsx → the types pin fails exactly two tests naming packages/plugin-dashboard/src/ObjectDataTable.tsx (anchor shape; the finding(plugin-dashboard): ObjectDataTableProps.schema reads drillDown and onRowClick and declares neither — both absorbed by its [key: string]: any #6914 casts are back), and plugin-dashboard's test project reports 9 errors, all in ObjectDataTable.schemaAnchor-6576.test.ts.
  • Drift census on this head (read keys off schema, cast-aware, minus the members the checker resolves on the built dist declaration; both widgets in one run): ObjectGallery read-not-declared [] (10 reads, 28 declared), ObjectDataTable read-not-declared [] (7 reads, 29 declared); synthetic control with drillDown removed from the declared set reports ["drillDown"], so the instrument sees a missing key. On origin/main the same census read ['drillDown','onRowClick'] for the data-table (prior report).
  • Gates: check-changeset-presence (11 published source files changed, changeset present), check-changeset-no-major (✅ No changeset declares a major bump), check-changeset-fixed (✅ All workspace packages are in the changeset fixed group), check:readme-exports (✅ OK … 406 self-imports judged (406 real, 0 wrong-path, 0 fabricated) — the @object-ui/types README lists no object-* schema types, so no README edit is required by the gate), check:spec-symbols (exit 0 — neither new name is a spec export; control GalleryConfigSchema found in the spec api-surface), check:control-bytes OK, check:phantom-deps OK, check:self-import OK, check:esm-specifiers OK, check:published-dist OK, check:side-effects-array OK, check:element-data-source-declaration OK, check:spec-floors OK, check:doc-types OK. NOT MEASURED locally: check:sdui-registration-pins (❌ No console build to weigh at apps/console/dist/assets — a prerequisite, not a verdict; registrations are untouched) and check:eager-closure (needs the console bundle). CI owns the repo-wide farm.
  • Governed-surface check (from ../objectstack, final file list): governed-surface predicate: 0 of 15 path(s) hit the register✅ NOT governed.

Deviations from the dispatch, declared

  1. drillDown on the zod mirror: ledgered instead of mirrored. The ruling admits either route (「新类型入镜或按台账既有词表申报」); mirroring it needs a paired DrillDownConfigSchema — a third new export the ruling did not name, whose wiring into ChartSchema would move that published validator — and an inline unpaired restatement would strip nested keys silently (z.object drops unknown keys), the very drift class the ledger exists for. So the new pair is SEEDED in UnmirroredDeclared with the ChartSchema.drillDown vocabulary (LOCAL), its doc states why this is a seed and not growth on an existing pair, and the repair is filed as finding(types): DrillDownConfig has no zod mirror — drillDown is declared on ChartSchema and ObjectDataTableSchema and validated on neither #7352. The reviewer should weigh this: the ledger's shrink-only rule is written for keys that appear on registered pairs.
  2. onRowClick on the mirror is a handlerKeyRefusal arm (as the dispatch said), which also means the 6124 census and its 36 / 58 pins moved to 37 / 59 and its file list to nine. The alternative — no arm, RuntimeOnlyDeclared (the pre-finding(types): 28 zod-mirror keys are declared z.function(), which NO JSON document can satisfy — 14 in-repo corpus files author them and are silently invalid #6124 objectql policy group) — was not taken because the 2026-08-30 ruling's rationale (an undeclared key is a silent accept that forwards the value) applies to a new mirror exactly as to an old one.
  3. Fresh gallery literals: TWO test files needed the type key (cells and coverValue), not one — the dispatch assumed only cells. Both are tests; no producer changed.
  4. ObjectDataTableProps.schema is the bare named type, not NamedSchema & {…}: with a dedicated schema type there are no widget-only extras to intersect, which is the ObjectMapProps shape; PivotTable / Timeline intersect because their schemas are generic nodes.
  5. The prior report's G7 prediction (gallery data collapses to any) did not hold: it was measured on a BaseSchema & {…} intersection; an extends member overrides. Reported as UNCHANGED above and pinned.
  6. Not extended: the ObjectQLComponentSchema unions (TS and zod) and AnyComponentSchema do not include the two new types. Adding them changes what validateSchema accepts for object-gallery / object-data-table nodes — a third published surface the ruling did not name. Flagged for the reviewer rather than decided.

Not in this PR (for the PM)

🤖 Generated with Claude Code

https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho


Generated by Claude Code

… to exported schema types that extend BaseSchema (objectui#6576, #6914)
Two widget prop types declared their `schema` as a hand-rolled inline object
literal with no `BaseSchema` in its ancestry — the only two `Object*Props` in
the repo whose `schema` anchored to no named type. Maintainer ruling
2026-08-31 (option A): mint `ObjectGallerySchema` and `ObjectDataTableSchema`
in `@object-ui/types`, each `extends BaseSchema`, and anchor both.
- `ObjectGalleryProps.schema` (published) WIDENS to every `BaseSchema` member
(`visibleWhen` was a compile error on the literal) and NARROWS in one place:
`type` is required and pinned to `'object-gallery'`. `data` stays typed —
an interface member overrides, it does not intersect.
- `ObjectDataTableProps.schema` (not exported) loses the literal's own
`[key: string]: any` and declares the two keys the widget read behind casts,
`drillDown: DrillDownConfig` and `onRowClick` (objectui#6914); the casts go.
A wrong-typed base member and a wrong-shaped `drillDown` are refused now.
- Zod mirrors of both under `@object-ui/types/zod`, registered in the parity
ledger: `onRowClick` is a `handlerKeyRefusal` runtime-slot arm (the first on
an objectql mirror; `KnownDrift` + the 6124 census updated), `drillDown` is
seeded in `UnmirroredDeclared` — `DrillDownConfig` has no zod mirror and
minting one is outside this ruling.
- The objectui#6574 `bind` ratchet entries for both files are removed and the
ratchet's prose rewritten: the defect was #6576's, not #5155 / #6269's.
- Red-first pins: `widget-schema-anchors-6576.test.ts` (types), and one
compile-time pin beside each widget. Two gallery test literals gain the now
required `type` key.
Unchanged and stated: an unknown key still compiles on both, because
`BaseSchema`'s index signature is inherited (objectui#5155, open).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
… `bind` member (objectui#6576)
`base-bind-declared.test.ts` scans every TRACKED file for a schema-side
`bind?:` re-declaration. The new pin's "this assertion can fail" control was
written as the pre-#6576 literal shape — `{ objectName?: string; bind?: string }`
— which read as exactly such a re-declaration the moment the file was
committed (green while untracked, red on the first post-commit run). The
control keeps its job with `className` in that slot; nothing else moves.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 48 chunks)3166.7 KB3191.4 KB
Main entry chunk (gzip)142.7 KB350 KB
Entry fileindex-CbDKJ2sq.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 (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)15.33KB5.59KB
app-shell (runtime-config.js)20.68KB7.36KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
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)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
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.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
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)514.59KB117.40KB
core (index.js)5.80KB2.32KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)178.20KB49.60KB
fields (index.js)244.25KB61.73KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.98KB10.98KB
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)11.71KB4.29KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)6.24KB2.16KB
permissions (discardProofCache.js)1.04KB0.55KB
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)4.83KB2.27KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)47.00KB12.97KB
plugin-charts (index.js)70.02KB19.44KB
plugin-chatbot (index.js)194.82KB46.02KB
plugin-dashboard (index.js)132.63KB34.56KB
plugin-designer (index.js)212.87KB43.19KB
plugin-detail (index.js)250.63KB63.90KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)132.78KB32.58KB
plugin-gantt (index.js)166.93KB40.82KB
plugin-grid (index.js)208.92KB56.59KB
plugin-kanban (index.js)53.21KB14.66KB
plugin-list (index.js)113.51KB27.67KB
plugin-map (index.js)20.20KB6.66KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.51KB11.94KB
plugin-timeline (index.js)29.15KB8.40KB
plugin-tree (index.js)8.98KB3.08KB
plugin-view (index.js)85.90KB21.12KB
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)4.47KB1.63KB
react (SchemaRenderer.js)81.07KB26.86KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)3.11KB1.48KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)20.57KB5.88KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)10.35KB3.60KB
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)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)3.75KB1.85KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.85KB0.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-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.72KB2.24KB
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 (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
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-litant
os-litant marked this pull request as ready for review September 2, 2026 13:22
@os-litant
os-litant added this pull request to the merge queueSep 2, 2026
Merged via the queue into main with commit 4ca30d0Sep 2, 2026
32 checks passed
@os-litant
os-litant deleted the claude/issue-6576-widget-schema-anchors branch September 2, 2026 13:38
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@os-litant@claude