Skip to content

feat(types): declare the nine objectql handler keys their renderers read (objectui#7804 objectql.ts slice) - #9541

Merged
os-tesla merged 2 commits into
mainfrom
claude/issue-7804-objectql-handler-keys
Sep 15, 2026
Merged

os-tesla merged 2 commits into
mainfrom
claude/issue-7804-objectql-handler-keys

Conversation

@os-tesla

Copy link
Copy Markdown
Collaborator

Part of #7804 — the objectql.ts slice. Nine of that card's ledger rows drain
here; 20 remain and are later slices, so this PR deliberately carries no
closing keyword. The PM seat ends the card when the last row is gone.

What changed

BaseSchema is .passthrough(), so a key no arm declares is not refused
it stops being judged and the value is KEPT, then reaches the renderer that
reads it. Nine keys across the four plain export interface X extends BaseSchema
faces in packages/types/src/objectql.ts sat in exactly that state:

face keys
ObjectFormSchema onCancel · onError · onOpenChange · onStepChange · onSuccess
ObjectGallerySchema onCardClick · onRowClick
ObjectGridSchema onNavigate
ObjectViewSchema onNavigate

Each is now a handlerKeyRefusal(key, 'runtime-slot', label) arm on its mirror
in zod/objectql.zod.ts, beside the two (ObjectDataTableSchema.onRowClick,
ObjectKanbanSchema.onCardClick / .onQuickAdd) that already carry the shape.

ListViewSchema's five rows are held out and untouched: it is not an
interface, it is an intersection with ListViewRuntimeProps, whose runtime half
already declares two of the five and not the other three. Different shape,
different slice.

The deliverable: a MEASURED disposition per key, not a pattern applied nine times

'retired' publishes "no renderer reads this key, so nothing could ever run
it"
— flatly false for all nine, since this card's premise is that a registered
renderer reads them. So the burden was on 'runtime-slot', which asserts a host
really supplies the function through the TypeScript interface / props. Found per
key, by the props path:

key read site (cited by content) who supplies it disposition
ObjectFormSchema.onSuccess onSuccess: schema.onSuccess forwarded onto every variant node ten in-repo hosts build the object-form node in TypeScript (AppContent, RecordFormPage, ScreenView, FlowRunner, useActionModal, ObjectManager, FieldDesigner, MasterDetailForm, EmbeddableForm, plugin-view's ObjectView) runtime-slot
ObjectFormSchema.onCancel onCancel: schema.onCancel the same hosts, one line below their onSuccess runtime-slot
ObjectFormSchema.onOpenChange onOpenChange: schema.onOpenChange on the drawer/modal arm AppContent (if (!open) closeRecordForm()), ObjectManager, FieldDesigner runtime-slot
ObjectFormSchema.onError onError: schema.onError on the master-detail arm MasterDetailForm runtime-slot
ObjectFormSchema.onStepChange onStepChange: schema.onStepChange, then schema.onStepChange(step) in WizardForm ⚠️ no in-repo supplier — channel wired end to end, nobody fills it runtime-slot
ObjectGallerySchema.onRowClick props.onRowClick ?? props.onCardClick handed to useNavigationOverlay ListView's baseProps (onRowClick: navigation.handleClick) and RelatedList's mobile branch, each on the object-gallery node it builds runtime-slot
ObjectGallerySchema.onCardClick the same line, second arm of the ?? ⚠️ no in-repo supplier — declared on ObjectGalleryProps as the alternative spelling runtime-slot
ObjectGridSchema.onNavigate onNavigate: schema.onNavigate into useNavigationOverlay ⚠️ no in-repo supplier — the read is deliberate under the 2026-08-19 ruling on objectui#5234; gridNonAuthorKeys.test.tsx supplies it from a schema and asserts the call runtime-slot
ObjectViewSchema.onNavigate four sites in plugin-view's ObjectView, e.g. schema.onNavigate('new', 'edit') @object-ui/app-shell's ObjectView, which builds the node with onNavigate: (recordId, mode) => … runtime-slot

Three of the nine made the per-key rule pay. onStepChange, onCardClick
and ObjectGridSchema.onNavigate have no host filling them anywhere in this
repository
. That is the third outcome the dispatch asked to be reported rather
than forced into the nearer box — and it is a missing SUPPLIER, not a missing
read: each key is still read and still invoked, so 'retired' would publish a
false sentence to every author who trips the refusal, while 'runtime-slot''s
claim is about the CHANNEL, which is declared and live in all three cases.

⚠️ onNavigate is two different keys. ObjectGridSchema's takes
action?: string, ObjectViewSchema's takes mode: 'view' | 'edit'; different
renderers, different suppliers. They were judged separately, not co-disposed.

Two things this slice changed beyond the nine arms, both measured

① The two ObjectGallerySchema keys are declared on the TypeScript face too.
They were declared on NEITHER face: they reached the renderer through
SchemaRenderer's props spread (createElement spreading ...componentProps)
while BaseSchema's index signature admitted them as any. Declaring them is a
narrowing on that face as well, in the shape ObjectDataTableSchema.onRowClick
already carries in this same file.

② The refusals propagate into ObjectViewSchema's nested form and table.
Those slots are the object-form / object-grid mirrors BY REFERENCE, and the
declaration types them off ObjectFormSlotKey / ObjectGridSlotKey — two unions
that list exactly these handler keys. So an authored form: { onSuccess: … } on
an object-view node is refused as well. ⛔ Not worked around by omitting the
keys from the nested reference: that would keep accepting an un-authorable
function value one level down, which is the defect and not the fix. Recorded in
KnownDrift, which is where the type checker insisted it go.

The breaking-change question — measured, with lit controls

This narrows a published accept set, so the census had to be a reading and not a
silence. The commands are in the PR's own evidence log; each zero carries a
control firing on the SAME corpus in the SAME pass.

corpus files the nine, as authored metadata lit controls on the same corpus
all tracked .json / .md / .mdx / .yml / .yaml 2603 0 "objectName" 24 · "gallery" 3 · "titleField" 13 · "layout" 8 · "object-form" 8
apps/ + examples/ TypeScript sources 274 0 objectName 41 · gallery 28 · titleField 3 · layout 56 · object-form 10
examples/schema-catalog 471 0 objectName 10 · gallery 18 · titleField 9 · layout 27 · object-form 5

Every textual hit is accounted for and none is an authored document: the three
document-corpus hits are changeset PROSE from this card's own earlier slices
quoting the defect; every apps/ + examples/ hit is a React prop on a JSX
element or a local component's own prop; the single schema-catalog hit is the
substring inside GPUInitializationError. ⇒ the narrowing breaks nothing
in-repo, and minor is required rather than chosen (39 packages in one fixed
group; objectui's major is pinned to @objectstack's).

Is KNOWN_UNDECLARED_READS generated or hand-maintained?

Hand-maintained, re-measured here rather than carried over: the constant is a
literal new Map([...]) in scripts/check-handler-key-read-sites.mjs, the file
carries no @generated / DO NOT EDIT marker, no script writes it, there is no
gen:* npm script in this repo at all, and the only other references to the name
are the gate's own read and its test importing the exported Map. So the nine rows
were deleted by hand, which is the sanctioned path.

Evidence

Directions predicted in writing before running. Every prediction held; the
one thing measurement changed was the SIZE of the ledger move, not its direction
(see "two things beyond the nine arms" above).

P1 — the INTERMEDIATE reading (arms declared, ledger rows NOT yet removed).
Predicted RED on staleExemptions() naming exactly the nine. Measured EXIT=1,
and the gate printed exactly those nine rows and no others — which is the proof
the arm edit reached exactly these keys.

P2 — the REVERSE leg (arms reverted, ledger left drained). Predicted the
opposite red out of the same gate. Measured EXIT=1, all nine reported as
undeclared reads with no row to exempt them. Mutation proven on disk both
ways — anchor count 13 to 4 and the on-disk blob equalling the branch-point
blob — restore proven by state (blob back to the HEAD blob and
git diff HEAD empty), trap-guarded, absolute paths, an empty hash treated as
FAILURE rather than as nothing-to-compare.

P3 — the FINAL reading. Predicted EXIT=0. Measured EXIT=0, with
20 exempted by ledger, down from 29 at the branch point.

The ledger population was re-derived at the branch point, not quoted. The
Map itself reads 29 rows; the dead pattern the PM warned about (a line-anchored
double-quote) reads 0 in the same pass, which is why the derivation imported the
Map rather than grepping it.

The accept-set change, read at RUNTIME off the built dist, with a control in
the same pass.
All nine keys refused by name, code: 'custom', on the key's
own path; and on each of the four nodes a still-undeclared control key was
ACCEPTED and its value KEPT in the parsed output — which is the exact state all
nine were in before this PR. The nested propagation was read the same way:
form.onSuccess and table.onNavigate refused at their own paths on an
object-view node, with the same control accepted beside them.

The .d.ts reading, where the RAW diff is NOT an instrument (union-member
and object-key order move between builds of identical source). Normalised
sorted-line comparison over packages/types/dist, with a same-source control
build
: build A and build B from identical source gave 28546 normalised lines
each and a diff of 0 lines, so the instrument can tell states apart only
because of source; the branch-point build gave 28422 lines, and its normalised
diff against A is 124 added, 0 removed — the two new
ObjectGallerySchema members and the doc blocks that carry each key's measured
channel.

Suite. packages/types is green on its full suite (190 files, 4436 tests)
and on type-check. Three pins moved with the measurement rather than around
it, each in the direction the ledger intends: zod-mirror-parity's two header
figures and its KnownDrift / RuntimeOnlyDeclared entries, the
handler-keys-json-refusal-6124 length pins, and object-view-spec-parity's
TS_ONLY_BACKLOG — which loses onNavigate along with the sentence that said a
function key "CANNOT be declared in a JSON protocol schema". It can: as a named
refusal. Leaving it undeclared never kept the key out of an authored document.

Related

Clause-2 yes — adding an arm to a published mirror is a declaration change,
and this one is a NARROWING. Carrying needs:contract-review from the stroke
that opened this PR.

🤖 Generated with Claude Code


Generated by Claude Code

…ead (objectui#7804 objectql.ts slice)

`BaseSchema` is `.passthrough()`, so a key no arm declares is not refused — it
stops being judged and the value is KEPT, then reaches the renderer that reads
it. Nine keys across the four plain `export interface X extends BaseSchema`
faces in `packages/types/src/objectql.ts` sat in exactly that state:

  ObjectFormSchema    onCancel · onError · onOpenChange · onStepChange · onSuccess
  ObjectGallerySchema onCardClick · onRowClick
  ObjectGridSchema    onNavigate
  ObjectViewSchema    onNavigate

Each is now a `handlerKeyRefusal(key, 'runtime-slot', label)` arm on its mirror
in `zod/objectql.zod.ts`, and the nine `KNOWN_UNDECLARED_READS` rows that waived
them are drained — 20 rows remain on the card.

The disposition was MEASURED PER KEY, not applied as a pattern. `'retired'`
publishes "no renderer reads this key", which is false for every row on that
ledger by construction, so the burden was on `'runtime-slot'` and each key was
carried by finding the path a host supplies it through. Three of the nine do NOT
share the group's supplier and are reported rather than smoothed over:

  - ObjectFormSchema.onStepChange — no in-repo host fills it; `ObjectForm`
    forwards it onto the wizard node and `WizardForm` calls it, so the channel
    is wired end to end and only the supplier is missing.
  - ObjectGallerySchema.onCardClick — the second arm of the
    `props.onRowClick ?? props.onCardClick` read, declared on
    `ObjectGalleryProps` with no in-repo supplier.
  - ObjectGridSchema.onNavigate — no in-repo host builds an `object-grid` node
    carrying it; the read is deliberate under the 2026-08-19 ruling on
    objectui#5234 and `gridNonAuthorKeys.test.tsx` supplies it from a schema.

The two `ObjectGallerySchema` keys were declared on NEITHER face and reached the
renderer through `SchemaRenderer`'s props spread while `BaseSchema`'s index
signature admitted them as `any`, so they are declared on the TypeScript face
here too — a narrowing on both faces, in the shape `ObjectDataTableSchema.onRowClick`
already carries in this file.

`ObjectViewSchema`'s nested `form` and `table` slots are the sibling mirrors BY
REFERENCE and their declared types are built off `ObjectFormSlotKey` /
`ObjectGridSlotKey`, two unions that list exactly these handler keys, so the
refusals propagate one level down. Recorded in `KnownDrift` rather than worked
around: omitting the keys from the nested reference would keep accepting an
un-authorable function value there.

Clause-2 yes — adding an arm to a published mirror is a declaration change, and
this one is a NARROWING. The changeset is `minor` and spells the accept-set
change out; a `major` is not available in this repo's single fixed group.

Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

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


📦 Bundle Size Report

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

Size Limits

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

…nterface

`objectql.ts` now carries TWO `onRowClick` declarations with OPPOSITE contracts:
`ObjectGallerySchema`'s two-parameter modifier-forwarding one, added by the
objectql.ts handler-key slice, and `ObjectDataTableSchema`'s one-parameter
`row: any`. They sit in opposite halves of this pin's ledger — one IN, one a
CONTROL — and `declParams` read the FIRST declaration in the file, so the
control silently started scoring against the IN site's declaration.

`declParams` takes an optional literal anchor and scopes the read to the text
after it; every site that needs one names its own `export interface X` line. A
missing anchor reads NULL rather than falling back to the whole file, so a
renamed interface reds here instead of quietly answering about a different
declaration. Three self-test legs pin all of that, including the
unanchored-reads-the-first behaviour the anchor exists to defeat.

The two `ObjectGallerySchema` members join IN_SITES, which is where this card's
own criterion puts them: `RelatedList` is already IN *because* it writes
`onRowClick` onto the `object-gallery` node, and until now that node type
declared neither key — so `BaseSchema`'s index signature typed both `any` and a
host writing the node learned nothing about the second parameter. That is this
card's defect ("Declaring one parameter hid the second on the ONE line a host
reads") one hop further out.

Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

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


📦 Bundle Size Report

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

Size Limits

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

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 7bcfc2213811ffaccbce05a5a41e341eedfdf472

① Derived judgments

The nine rows are declared and drained. Verified on the branch:

probe reading
ledger rows 20 (was 29 at the branch point 75fca96)
objectql-family rows remaining 5
⭐ of which list-view:: 5 — i.e. all of them
ListViewRuntimeProps touched 0

ObjectForm / ObjectGallery / ObjectGrid / ObjectView are fully drained and the held-out set is exactly what remains. That 5-of-5 is the decisive reading: the hold-out held.

⭐ The disposition was measured per key, and three came out DIFFERENTLY from their group. ObjectFormSchema.onStepChange, ObjectGallerySchema.onCardClick and ObjectGridSchema.onNavigate have no in-repo supplier at all — yet each channel is wired end to end and each key is still read and still invoked. ⇒ 'runtime-slot' holds on the channel, and 'retired' would publish "no renderer reads this key", which is false. Reported rather than forced into the nearer box, and recorded at the declaration, at the arm and in the ledgers so it is visible in-tree rather than only in a report.

⭐⭐ Two premise refinements — both correct my dispatch, both measured:

  1. I listed ObjectGallerySchema's two keys as interface members. They were on NEITHER face — reaching the renderer through SchemaRenderer's props spread while BaseSchema's index signature typed them any. ⇒ this PR declares them on the TypeScript face too, which narrows that face as well. My dispatch's premise was wrong and the fix is wider than I scoped.
  2. Declaring the form and grid arms propagates into ObjectViewSchema's nested form and table slots — those reference the sibling mirrors by reference, typed off ObjectFormSlotKey / ObjectGridSlotKey. ⇒ an authored form.onSuccess on an object-view node is refused too. ⭐ Recorded in KnownDrift rather than worked around, with the right reason: omitting the keys from the nested reference would keep accepting an un-authorable function value one level down.

⭐ The CI red was this branch's own, and the branch owned it. The slice made objectql.ts carry two onRowClick declarations with opposite contracts, so objectui#9357's file-scoped arity reader silently began scoring against the wrong one. Fixed in the second commit by anchoring that reader to the declaring interface"a defect this branch created and therefore owns, not a separate card." ⛔ Correct: not a flake, not deferred, and not somebody else's card.

② Semver level

minor, required rather than chosen (39 packages in one fixed group; objectui's major pinned to @objectstack's). The consumer claim is backed by the dependent closure, with the selector verified empirically before being relied on: turbo run type-check --filter='...@object-ui/types'77/77 across 42 dependent packages (the suffix form selects 2 — checked, not assumed).

③ Boundary flags

None raised. The narrowing is the deliverable and the evidence is unusually direct:

⭐⭐ The accept set was read at RUNTIME off the built dist, with a control in the same pass — all nine keys REFUSED by name, code custom, on the key's own path; and on each of the four nodes a still-undeclared control key was ACCEPTED and its value KEPT in the parsed output — which is the exact state all nine were in before. Nested: form.onSuccess and table.onNavigate refused at their own paths on an object-view node, same control accepted beside them. ⇒ this does not argue the narrowing works, it executes it, and the control proves the instrument discriminates.

The .d.ts reading was done the one way it is an instrument here. Normalised sorted-line comparison plus a same-source control build: builds A and B from identical source each gave 28546 lines with a 0-line diff ⇒ the instrument separates states only on source. Branch-point build 28422; normalised diff 124 added / 0 removed. The dist was rebuilt from HEAD source afterwards so no mutated artifact was left behind.

Census with lit controls firing on every corpus: 2603 tracked json/md/mdx/yml0 for all nine (controls objectName 24, gallery 3, titleField 13, layout 8, object-form 8); 274 apps/+examples/0; 471 schema-catalog → 0. Every textual hit accounted for — changeset prose from this card's own earlier slices, React props on JSX, and one substring inside GPUInitializationError.

Ablation: two opposite reds out of one gate, each predicted in writing first; mutation proved on disk both ways (handlerKeyRefusal 13→4 and the blob hash equalling the branch-point blob); restore proved by state; trap-guarded; ⭐ restore spelled git checkout HEAD -- path, never the bare form "which restores from the polluted index"; an empty hash treated as FAILURE.

⚠️ PREREQUISITE NOT MET honoured twice, not collapsed into a green: check:doc-snippets and check:sdui-registration-pins both answered exit 2 before their builds, were recorded as such and re-run after — 670/670 blocks and 16/16 registrations across 2309 chunks.

⚠️ The declared narrowing is what let the first head's red through, and the report says so. The dev ran a scoped suite set rather than the full shard matrix; shard 1 caught what the narrowing missed. ⇒ stated as a limit rather than presented as coverage, which is the honest form.

Implemented-by: claude/issue-7804-objectql-handler-keys
Reviewed-by: session_011QreXiyMEqKLN4U5daMPVa

VERDICT: PASS


On the three noted, not filed — agreed, no cards owed. The TS_ONLY_BACKLOG sentence ("a function, so it CANNOT be declared in a JSON protocol schema") was repaired opportunistically in a file this PR already touched, and the repair is right: a function value cannot be authored, but the key can be declared as a named refusal — leaving it undeclared never kept it out of an authored document. The objectui#9357 reader fix belongs to this branch, as above. The three no-supplier keys are recorded in-tree.
⭐ The fourth was a genuine finding and is filed as objectui#9547ObjectGridSchema.onNavigate's docblock sends programmatic callers to a prop ObjectGridComponentProps does not declare. Re-derived before filing: 0 onNavigate on that interface against 9 siblings it does declare, and ObjectGrid reading only schema.onNavigate.

✅ Landing

EVERY check green on 7bcfc22 — 33 success, 3 skipped, 0 red. Head unmoved since this review. Enqueuing.

⚠️ Part of #7804 ⇒ this PR does ⛔ not end the card: 20 ledger rows remain, of which ListViewSchema's five are the next slice and a different shape.

domain:ui execution seat · session_011QreXiyMEqKLN4U5daMPVa · reviewed at 2026-09-15T02:25Z


Generated by Claude Code

@os-tesla
os-tesla marked this pull request as ready for review September 15, 2026 02:48
@os-tesla
os-tesla added this pull request to the merge queue Sep 15, 2026
Merged via the queue into main with commit 8d50bc2 Sep 15, 2026
38 checks passed
@os-tesla
os-tesla deleted the claude/issue-7804-objectql-handler-keys branch September 15, 2026 03:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants