Skip to content

fix(plugin-view): an authored event-name onChange / onViewChange no longer crashes filter-ui, sort-ui and view-switcher through SchemaRenderer (objectui#10616) - #10646

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-10616-view-handler-event-names
Sep 25, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-10616-view-handler-event-names

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10616
Clause-②: no

Implemented by the os-dev run that the domain:ui seat 2 dispatched, session https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN. Readings were taken at BASE eca276070, the origin/main commit the worktree was cut from. Every gate below ran at HEAD 528b56d6e, which is this branch's head when the PR opens.

What changed

filter-ui, sort-ui and view-switcher declare onChange / onViewChange as a window event NAME (objectui#6124). SchemaRenderer passes every non-metadata node key to the component as a React prop, and then spreads the host's own props last. With no host prop, the authored string therefore lands in the component's callback prop of the same name. Each control called that prop with onChange?.(next) before dispatching. The first interaction threw TypeError: onChange is not a function (onViewChange on the switcher), and the window event never fired.

  • New packages/plugin-view/src/viewHandlerChannels.ts holds one helper, notifyViewHandlerChannels, and all three controls call it from their notifyChange. It calls the prop only when typeof says it is a function. Then, if the schema names an event, it dispatches new CustomEvent(schema.KEY, { detail }). The event name always comes from schema, never from the prop slot. One helper means the three controls cannot drift apart.
  • In FilterUI.tsx, SortUI.tsx and ViewSwitcher.tsx, notifyChange now calls the helper with the documented detail: { values }, { sort } or { view }. The exported prop types, the zod mirror and the registrations do not change.
  • The spread in SchemaRenderer is not touched.
  • Order and precedence: the host function runs first and the window event second, as before. If a host function and an authored name are both present, both are delivered. The host function takes the prop slot because SchemaRenderer spreads the host's props last, and the authored name is still on schema.
  • handlerEventNameLiveness.6124.test.tsx: only the header comment changed. It had three cross-file line addresses into lines this diff moves. They are now symbol citations, and the header adds a note that the file mocks SchemaRenderer plus a pointer to the new pin. No assertion changed.
  • .changeset/10616-view-handler-event-names-through-renderer.md: '@object-ui/plugin-view': patch.

Pins: through the real SchemaRenderer and registry, with no mock

packages/plugin-view/src/__tests__/handlerEventNameThroughRenderer-10616.test.tsx has six tests, two for each control:

  • Authored string, no host prop: the named event fires once with the documented detail, and nothing is reported. The test reads the window error event, console.error and any thrown error, because React 19 reports a handler error rather than rethrowing it out of fireEvent.
  • CONTROL: a host function passed through SchemaRenderer next to the same string is called once with the new value, and the event still fires.

Red on base, green on head. All five legs ran in one locked run at HEAD 528b56d6e. The base leg checks out the three components at BASE and keeps the pins from HEAD.

leg result
HEAD: new pin and the objectui#6124 pin Tests 14 passed (14)
BASE: all three components at BASE Tests 3 failed / 11 passed (14). Each authored-string leg fails: reported holds TypeError: onChange is not a function for filter-ui and sort-ui, and TypeError: onViewChange is not a function for view-switcher. The three controls and all eight objectui#6124 tests pass.
ablation: only FilterUI.tsx at BASE 1 failed / 5 passed (6): only the filter-ui authored leg fails
ablation: only SortUI.tsx at BASE 1 failed / 5 passed (6): only the sort-ui authored leg fails
ablation: only ViewSwitcher.tsx at BASE 1 failed / 5 passed (6): only the view-switcher authored leg fails
  • Before each run, the mutation was checked on disk: notifyViewHandlerChannels occurs 0 times and a bare onChange?.( or onViewChange?.( occurs once.
  • Each restore ran from an EXIT INT TERM trap. It was proven by comparing each file's hash with its HEAD blob and by an empty git diff HEAD.
  • The tests import source through the repo's vitest aliases, so no build sits between a mutation and its run.

The objectui#6124 pins stay. They complement the new pins rather than duplicate them:

  • They own the declared half (.shape and the describe() text).
  • They render the components directly, so the callback prop is always undefined there.
  • The new pins own the renderer entry, where the prop holds the authored string.

Gates (HEAD 528b56d6e)

  • pnpm exec vitest run packages/plugin-view/: Test Files 50 passed (50), Tests 483 passed (483).
  • pnpm --filter @object-ui/plugin-view type-check (tsc --noEmit && tsc -p tsconfig.test.json) ran after turbo run build --filter='@object-ui/plugin-view^...' --concurrency=2, which reported Tasks: 15 successful, 15 total. Result: exit 0. tsc -p tsconfig.test.json --listFilesOnly confirms that the test program contains the new pin, the objectui#6124 pin and viewHandlerChannels.ts.
  • ESLint was narrowed to the six touched TS files: eslint --no-inline-config --format json reports 6 files, 0 errors and 11 warnings. All 11 warnings sit on lines this diff does not touch (no-explicit-any, set-state-in-effect), and lint sets no --max-warnings. Three facts support the narrowing:
    • The population comes from eslint.config.js: the files: ['**/*.{ts,tsx}'] block covers all six files, and none is ignored.
    • The file count is 6, read from the JSON output.
    • Nothing outside the touched files can change verdict. The config sets no parserOptions or projectService, so linting is not type-aware. No rule in eslint-rules/*.js reads the filesystem (0 hits for readFileSync, existsSync, readdirSync or statSync; control: 9 of 9 rule files match create).
  • These gates all exited 0: check-changeset-presence, check:new-line-citations (0 new citation(s)), check:control-bytes, check-changeset-no-major, check-changeset-overwrite, check-changeset-fixed, check-changeset-claims, check:handler-key-reads, check:unreferenced-sources, check:esm-specifiers, check:phantom-deps, check:self-import, check-vi-mock-override-shape, check-vi-mock-specifiers, check-vi-mock-inherit and check-test-path-roots.
  • The diff reaches no doc or readme gate: it touches no content/ or README file.
  • check-governed-queue-guard --test on the seven paths: NOT GOVERNED.
  • git merge-tree --write-tree HEAD origin/main at f99f9cde7: clean. origin/main has not changed packages/plugin-view or the objectui#6124 changeset since BASE.

The pending objectui#6124 changeset

.changeset/6124-view-handler-keys-are-event-names.md publishes the authored form as a working example. At this change every sentence in it is true: the renderer dispatches new CustomEvent(schema.onViewChange, …) and its two siblings, and the sort-ui example and its listener now work through SchemaRenderer. The file is therefore unchanged: its md5 is ccd92197c632d2b200478176ac6d1a95 at BASE and at HEAD.

check-changeset-claims reports that changeset's last paragraph because it names the objectui#6124 test file, which this diff edits. That report is report-only. The edit only touches the header comment and the pin still holds both halves, so the paragraph stays true.

Acceptance notes

  • content/docs/api/schema-reference.md, ViewSwitcherSchema table: the onViewChange row reads "Expression or callback invoked on view change." The objectui#6124 describe() text refuses exactly that reading. The row was false before this diff and is still false after it. It is outside the claimed file surface, so it is not changed here and is reported to the seat instead.
  • The docs rows onChange?: string; (filter-ui.mdx, sort-ui.mdx) and onViewChange?: string; (view-switcher.mdx) are true at this change. The plugin-view README names neither key.
  • No other key in packages/plugin-view or views.zod.ts has this shape: views.zod.ts carries exactly these three event-name keys. The one other Event name describe under packages/types/src/zod is ActionSchema.tracking.event in crud.zod.ts. It is nested under tracking, so it is never spread as a prop, and no component calls a same-named prop.

Generated by Claude Code

… a function, so an authored event name no longer crashes through SchemaRenderer (objectui#10616)

filter-ui, sort-ui and view-switcher declare onChange / onViewChange as a
window event NAME (objectui#6124). SchemaRenderer spreads node keys as React
props, so the authored string also reached the same-named callback prop, and
`onChange?.(next)` threw "onChange is not a function" before the event was
dispatched. One shared helper now calls the prop only when it is a function
and dispatches the event named on the schema; a host function is still
called first.

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

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 1 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/6124-view-handler-keys-are-event-names.md

  • names plugin-view/src/__tests__/handlerEventNameLiveness.6124.test.tsx → packages/plugin-view/src/__tests__/handlerEventNameLiveness.6124.test.tsx — edited by this change

    The correction also protects the capability. A handler-key census that buckets by declared TYPE cannot tell an event name from the unsupported handler-expression dialect, and on that reading these three had been swept in for retirement, which would have deleted working behaviour. A new pin (plugin-view/src/__tests__/handlerEventNameLiveness.6124.test.tsx) now holds both halves — that each key is DECLARED on the authorable surface, and that the authored string reaches new CustomEvent(...).

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with f99f9cde7 (merge-base with origin/main): 6 file(s) changed outside .changeset/, read against 1500 pending declaration(s) that publish a body (2084 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3051.3 KB 3104.5 KB
Main entry chunk (gzip) 148.0 KB 350 KB
Entry file index-Dr0Vb9ec.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.57KB 6.15KB
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) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
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.39KB 1.21KB
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.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
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) 546.99KB 130.83KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 260.06KB 65.86KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.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.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.49KB 14.64KB
plugin-charts (index.js) 74.94KB 20.89KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.60KB 35.41KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.96KB 61.65KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 152.20KB 39.05KB
plugin-gantt (index.js) 169.64KB 41.91KB
plugin-grid (index.js) 215.37KB 58.92KB
plugin-kanban (index.js) 48.35KB 15.08KB
plugin-list (index.js) 114.58KB 28.31KB
plugin-map (index.js) 22.42KB 7.38KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.67KB 8.95KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.77KB 21.99KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
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) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 528b56d6e7cf62f8d1fdf00a67a6f0569a593a36

① Derived judgments

  • Event contract. viewHandlerChannels.ts takes eventName straight from schema.onChange / schema.onViewChange at the three call sites (FilterUI.tsx:101, SortUI.tsx:95, ViewSwitcher.tsx:277) and dispatches window.dispatchEvent(new CustomEvent(eventName, { detail })). Details are { values: nextValues }, { sort: nextSort }, { view: nextView }, matching views.zod.ts describe text at lines 368, 402, 327 (detail: { values }, { sort }, { view }, "dispatched on window"). Absent or empty-string schema.KEY: the if (eventName && typeof window !== 'undefined') guard skips dispatch, no throw. A non-string truthy value (not authorable: the mirror is z.string(), and the 6124 pin asserts a function fails parse) is coerced by the CustomEvent constructor and dispatched without throwing, identical to base. The authored string is never called: the prop slot is called only under typeof hostCallback === 'function'. One nuance, not a regression: a function placed on schema.KEY by a TS host would also be spread into the prop slot and called, exactly as base did.
  • Both channels. True at source. SchemaRenderer.tsx destructures ({ schema, ...props }) (line 900); the metadata strip (lines 1959 to 2001) removes type, children, body, schema, visibility and enablement keys, dataSource, testId, objectFields, responsiveStyles, and leaves onChange / onViewChange in componentProps; createElement (lines 2194 to 2228) spreads schema: schemaForComponent, then ...outgoingComponentProps, ...outgoingPropsBag, aria, debug, then ...props LAST. So a host function wins the prop slot and the authored name stays on schema. Host-function-only path (for example ObjectView.tsx:2602 renders ViewSwitcher directly with onViewChange={handleViewTypeChange}): function called, no window event unless the schema names one, same as base. Order host-then-event is unchanged from base (onChange?.(next) preceded the dispatch in all three).
  • One helper, no drift. The only dispatchEvent / new CustomEvent in plugin-view non-test source is viewHandlerChannels.ts:52; each component reads schema.KEY and the prop exactly once, inside notifyChange. views.zod.ts has exactly three string-typed on* keys (326, 367, 401); onBack, onNavigate, onAddComment are handlerKeyRefusal runtime slots. The only other "Event name" describe under packages/types/src/zod is crud.zod.ts:139 tracking.event, nested and never spread. No other component has the shape.
  • Pins. handlerEventNameThroughRenderer-10616.test.tsx imports the real SchemaRenderer and the real registry (import '../index'), no vi.mock. Re-run in a scratch worktree (offline install, vitest source aliases): HEAD Tests 14 passed (14); all three components at BASE 3 failed / 11 passed, reported holding TypeError: onChange is not a function twice and TypeError: onViewChange is not a function once; ablations FilterUI, SortUI, ViewSwitcher each 1 failed / 13 passed, the matching authored leg only. Mutation proven on disk before each run (helper count 0, bare ?.( count 1) and restore proven (git diff HEAD empty). The 6124 pin change is comment-only: zero non-comment changed lines, vi.mock('@object-ui/react') and all 8 tests untouched.
  • Docs finding. Real: content/docs/api/schema-reference.md:1302 reads "| onViewChange | string | Expression or callback invoked on view change. |" at HEAD and at current main; it contradicts the views.zod.ts:327 describe. The PR's changeset and the pending 6124 changeset both say the key is the NAME of a CustomEvent, so neither repeats it. It predates this card (a 6124 leftover), is outside the claimed surface and reaches the doc gates, so it is not in scope here. A card is owed: the dev noted it in the PR body only and filed nothing. Related drift the PR did not mention: packages/types/src/views.ts JSDoc for all three keys (lines 1034, 1155, 1260) cites "Read at plugin-view/src/ViewSwitcher.tsx (FilterUI, SortUI) as new CustomEvent(schema.KEY, …)"; after this PR that expression lives only in viewHandlerChannels.ts. These are symbol citations, not banned line addresses, and the file is in @object-ui/types, so a follow-up (fold into the docs card) rather than a blocker.

② Semver level

patch and Clause-②: no hold. packages/plugin-view/src/index.tsx does not export viewHandlerChannels or notifyViewHandlerChannels; package.json exports only . to dist/index; the helper is imported by the three components only. Exported prop types, zod mirror and registrations are unchanged. New changeset: every sentence true against the diff and the re-run legs, with one overstatement: "SchemaRenderer also passes every node key to the component as a React prop" (it strips the metadata set listed above; the load-bearing clause, that the authored string reaches the same-named prop, is true). Pending 6124 changeset: md5 ccd92197c632d2b200478176ac6d1a95 at BASE, HEAD and main; its sentences (the string is the name of a CustomEvent the renderer dispatches on window; nothing an author writes breaks; the pin holds both halves) are now true through the renderer.

③ Boundary flags

  • CI on the head: 43 check runs, 40 success, 3 skipped (coverage, coverage shard, dependabot), none red, none in progress at final poll. Test (shard 2/8) is success, so the objectui#10645 intermittent did not fire on this head. Type Check, Lint, Line Citation Gate, Changeset Claim Re-read all success.
  • git merge-tree --write-tree main f99f9cde7 against the head: clean (tree b588210e1). Main has 3 commits since merge-base eca276070, none touching plugin-view, SchemaRenderer, views.zod.ts, views.ts, schema-reference.md or the 6124 changeset. GitHub reports mergeable, state "behind".
  • Open PRs: of 11, only chore(deps): lucide-react 1.31.0 -> 1.43.0, with the one retired spelling repaired #8941 (draft, lucide bump) touches content/docs/api/schema-reference.md and packages/plugin-view/package.json; no overlap with this PR's seven files.
  • The PR is still a Draft with no assignee (the dev's assignee write was refused by the classifier); the seat must set os-elon-musk and mark it ready before merge. Nothing here should stop landing.

Implemented-by: claude/issue-10616-view-handler-event-names
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: PASS


Generated by Claude Code

…d, so the 10616 changeset says it passes these keys through, not every node key

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3051.3 KB 3104.5 KB
Main entry chunk (gzip) 148.0 KB 350 KB
Entry file index-Dr0Vb9ec.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.57KB 6.15KB
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) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.17KB 10.58KB
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.39KB 1.21KB
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.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.13KB 7.95KB
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) 546.99KB 130.83KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.91KB 62.28KB
fields (index.js) 260.06KB 65.86KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.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.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.93KB
plugin-calendar (index.js) 51.49KB 14.64KB
plugin-charts (index.js) 74.94KB 20.89KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.60KB 35.41KB
plugin-designer (index.js) 216.25KB 44.39KB
plugin-detail (index.js) 232.96KB 61.65KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 152.20KB 39.05KB
plugin-gantt (index.js) 169.64KB 41.91KB
plugin-grid (index.js) 215.37KB 58.92KB
plugin-kanban (index.js) 48.35KB 15.08KB
plugin-list (index.js) 114.58KB 28.31KB
plugin-map (index.js) 22.42KB 7.38KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.67KB 8.95KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.77KB 21.99KB
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) 116.21KB 38.14KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
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) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 19efdc426f5d322d7119b5e17f65892804dcad9c

① Derived judgments

  1. Diff 528b56d6e..19efdc426 is exactly the expected one. One file, .changeset/10616-view-handler-event-names-through-renderer.md, 2 insertions / 2 deletions, all inside the one sentence. git diff --stat confirms no other path.
  2. The new sentence is true at source.
    • Antecedent: "these keys" resolves through the preceding "These keys hold the NAME of a CustomEvent…" to the first paragraph's "event-name key (onChange, or onViewChange on the switcher)". Unambiguous.
    • Strip set at head (packages/react/src/SchemaRenderer.tsx, the destructure of evaluatedSchema that yields componentProps): type, children, body, schema, visible, visibleWhen, visibleOn, visibility, hidden, hiddenOn, disabled, disabledOn, dataSource, testId, objectFields, _hidden, _disabled, responsiveStyles. Neither onChange nor onViewChange is in it.
    • Nothing else drops them before createElement: withoutAuthoredDataKey removes only data (view-data arm only), withoutAuthoredObjectFields only objectFields, propsWithoutCanonicalKeys narrows only the legacy props alias bag.
    • Spread order at React.createElement(Component, …): schema, ...outgoingComponentProps, ...outgoingPropsBag, ...ariaProps, ...debugAttrs, disabled, className, data-obj-id, data-obj-type, conditional data-testid, conditional invalid marker, then the host's ...props last. With no host prop, the authored string lands on the same-named callback prop. "reached the component's callback prop of the same name" holds.
    • Empirical corroboration: the round-1 pin packages/plugin-view/src/__tests__/handlerEventNameThroughRenderer-10616.test.tsx renders through the real SchemaRenderer (no vi.mock in the file, imported from @object-ui/react), and its BASE leg fails with TypeError: onChange is not a function, which only happens if the string arrived in the prop.
  3. Rest of the changeset unchanged and still true. No source file moved between the two heads. At head, viewHandlerChannels.ts calls the prop only when typeof hostCallback === 'function', then dispatches new CustomEvent(eventName, { detail }) with eventName taken from schema.onChange / schema.onViewChange; details are { values }, { sort }, { view }, matching e.detail.values / e.detail.sort / e.detail.view. Host function first, event second. A host function passed through SchemaRenderer overrides the prop slot (...props last) while the name stays on schema, so "unaffected … called first … then dispatched as before" holds.
  4. Frontmatter md5 of the first three lines is 4d3ab4fd127d2f0feaabd7840d888656 at both 528b56d6e and 19efdc426.
  5. No other file changed between the heads. Branch vs merge-base eca276070 is still the same 7 files as round 1.
  6. CI on the head: 42 check runs; at the second poll 29 completed (all success or skipped), 13 in_progress (Test shards 1–8, Lint, Type Check, Doc Snippet Type Check, README Export Check, Skill Example Check, Spec Main Shape Gate), 0 failed. Test (shard 2/8) was in progress, not red, so there is no log line to name.
  7. Merge: git merge-tree --write-tree refs/review/main refs/review/pr-10646 wrote tree 3d8a59a3e22f288234f798189338d42e8b1536a4, exit 0, no conflicts, against main f99f9cde75bc563b18b39a37b867214bc1866fc7.

② Semver level

patch on @object-ui/plugin-view, as declared. Behaviour fix only; viewHandlerChannels.ts is internal (imported solely by FilterUI.tsx, SortUI.tsx, ViewSwitcher.tsx; not exported from any package entry). Exported prop types, zod mirror and registrations unchanged. Changeset Bump Policy and Changeset Declaration checks are green.

③ Boundary flags

  • CI was still running when this record was written: all eight test shards, Lint and Type Check were in_progress. Nothing red at either poll. If Test (shard 2/8) later goes red at apps/console/src/pages/developer/ApiConsolePage.requestPreset-10591.test.tsx:299, that is the known objectui#10645 intermittent and not attributable to this diff.
  • Branch is behind main (merge-base eca276070, main f99f9cde7; GitHub mergeable_state: behind). Merge is clean per merge-tree; no rebase required for correctness.
  • PR is still marked draft; it must be marked ready before it can land.

Implemented-by: claude/issue-10616-view-handler-event-names
Reviewed-by: session_014mXUNuFomfj24w7s1pZzhN

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 25, 2026 14:42
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit 3d17a97 Sep 25, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10616-view-handler-event-names branch September 25, 2026 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant