Skip to content

fix(plugin-calendar): allowCreate works and colorMapping is retired — no declared-but-inert inputs left (#4454, #4493) - #4516

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-4454-4493-calendar-inert-inputs
Aug 13, 2026
Merged

fix(plugin-calendar): allowCreate works and colorMapping is retired — no declared-but-inert inputs left (#4454, #4493)#4516
yinlianghui merged 1 commit into
mainfrom
claude/issue-4454-4493-calendar-inert-inputs

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Closes#4454
Closes#4493

calendar-view had two registry inputs that were declared and read by nobody — the one state ADR-0049's enforce-or-remove framing says must not persist. Per the ruling on #4454, measurement decided them in opposite directions.

#4454allowCreate: ENFORCE

Measured first

What CalendarView renders with and without onAddClick (CalendarView.tsx:333):

{onAddClick && ( … a Button with onClick={onAddClick}, a PlusIcon, and {t('calendar.newEvent')} … )}

(JSX tags elided above — GitHub's body sanitizer eats a bare < followed by a letter.)

Present, the header renders a New event button (accessible name New event, the English default of calendar.newEvent). Absent, the button does not exist at all — undefined is literally what suppresses the affordance.

What handleAddClick does, and whether it survived the #4453/#4494 consume-or-declare refactor. It is still correct — not stale. It dispatches through dispatchAction, the narrowedonAction the refactor introduced, exactly like its sibling handleEventClick:

const dispatchAction =
typeof onAction === 'function' ? (onAction as (a: { type: string; payload: unknown }) => void) : undefined;
const handleAddClick = () => { dispatchAction?.({ type: 'create', payload: {} }); };

So it dispatches { type: 'create', payload: {} } on the widget's own action channel, and an authored onAction: 'NOT-A-FUNCTION' cannot turn the newly-live affordance back into #4453's uncaught handler crash. No STOP condition.

The fix

The wiring goes through the declared onAddClick hatch, not around it via a second prop. onAddClick is already one of the renderer's function-typed HOST_CALLBACKS (#4453), so a React host can switch the affordance on today; allowCreate supplies that hatch's value for the SDUI path, where an author writing JSON can never produce a function.

onAddClick={hostCallbacks.onAddClick ?? (allowCreate ? handleAddClick : undefined)}

Same precedence rule as the onEventClick line directly above it: a host handler replaces the action dispatch rather than running alongside it.

Only the boolean true turns it on. Absent, false, and the off-type spellings JSON invites ('true', 1, an object) all resolve to undefined — the absent-key answer every other resolver at this boundary gives, and on this prop the thing that makes the button not render. That is what keeps today's behaviour byte-identical for every node that never authored the key. Off-type input is not coerced (AGENTS.md #0.1).

#4493colorMapping: REMOVE

No read site anywhere, confirmed by the filing and by #4492's census: the renderer's event mapping takes the colour straight off the record (color: record[colorField]) and CalendarView resolves a colour from event.color. An author writing the documented colorMapping: { meeting: 'blue' } got no mapping, no warning and no error — the raw field value was used as the colour, which for a picklist value like meeting is not a colour at all.

Retired rather than implemented: no measured app authors it, and a declared capability with no pull behind it is not worth building.

Swept in this PR:

SiteAction
packages/plugin-calendar/src/calendar-view-renderer.tsx — registry inputs declarationremoved (replaced by a comment recording why)
content/docs/plugins/plugin-calendar.mdx — "CalendarView Schema API" lineremoved
packages/plugin-calendar/README.mdno mention — nothing to remove
examples/schema-catalog/src/schemas/plugin-calendar/*.json fixturesneither fixture authors the key — nothing to drop

A repo-wide grep -rn colorMapping now returns only the retirement comment, the new pin file, and the open-tail canary in calendar-view-renderer.propsContract.test.tsx (where the key now legitimately belongs — it is an ordinary unknown authored key).

Red-first

New pin file packages/plugin-calendar/src/calendar-view-renderer.inertInputs.test.tsx, run before the fix — 4 failed, 8 passed:

 ❯ |dom| .../calendar-view-renderer.inertInputs.test.tsx (12 tests | 4 failed) 647ms
× authored `allowCreate: true` on the NODE renders the add affordance, and clicking it dispatches `create` 146ms
× authored `allowCreate: true` in the `props` CONTAINER does the same 60ms
× an authored `onAddClick` STRING still cannot reach the component (objectui#4453 stays closed) 41ms
× the registry no longer declares a `colorMapping` input 2ms
AssertionError: expected null not to be null
❯ .../calendar-view-renderer.inertInputs.test.tsx:111:26
110| const button = addButton();
111| expect(button).not.toBeNull();
AssertionError: expected [ 'data', 'titleField', …(9) ] to not include 'colorMapping'
❯ .../calendar-view-renderer.inertInputs.test.tsx:338:26
338| expect(declared).not.toContain('colorMapping');
Test Files 1 failed (1)
Tests 4 failed | 8 passed (12)

The 8 green pre-fix are the must-not-change cases, green on both sides: absent allowCreate, explicit false, the three off-type spellings, the pre-existing host onAddClick path, host-replaces-dispatch precedence, and "an authored colorMapping changes nothing" (already true — the key never had a read site to lose, which is exactly why retiring it is not a behaviour change).

After the fix, 12/12 green:

 Test Files 1 passed (1)
Tests 12 passed (12)

Green

pnpm exec vitest run packages/plugin-calendar/ --maxWorkers=2
Test Files 9 passed (9)
Tests 77 passed (77)

Sibling pins from #4433 / #4452 / #4453 / #4492 all stay green inside that run (eventsCollision, currentDate, propsContract, object-calendar-renderer.propsContract).

pnpm --filter @object-ui/plugin-calendar type-check # tsc --noEmit && tsc -p tsconfig.test.json → exit 0
pnpm --filter @object-ui/plugin-calendar lint # 0 errors (108 pre-existing warnings)
node scripts/check-control-bytes.mjs # OK, 4229 tracked text files
node scripts/check-changeset-presence.mjs # OK, 1 changeset declared
node scripts/check-changeset-no-major.mjs # OK
node scripts/check-doc-links.mjs # Links are valid across 13 scan roots
check:spec-symbols / check:action-forward-parity / check:phantom-deps / check:i18n-keys / check:i18n-drift # all exit 0

Out of surface but verified unaffected, because turning an affordance on changes what reaches the DOM:

pnpm exec vitest run packages/app-shell/src/__tests__/widget-dom-leak-sweep.test.tsx
Test Files 1 passed (1)
Tests 37 passed (37)

Changeset grade: minor

Measured both ways by building @object-ui/plugin-calendar on this branch and again with the renderer reverted to origin/main:

  • Emitted .d.ts: byte-identical.dist/index.d.ts does not move — it types ObjectCalendarRenderer as React.FC< { schema: any; [key: string]: any } > and never expresses calendar-view's authorable vocabulary. So "the .d.ts moved" is false, and saying otherwise would have been the easy wrong answer here.

  • Published registry surface: it moved. The vocabulary lives in the runtime registry metadata in dist/index.js, and calendar-view's inputs array loses a member — colorMapping goes from 1 emitted declaration to 0:

    before: {name:"colorField",…},{name:"colorMapping",type:"object",label:"Color Mapping",…},{name:"view",…}
    after : {name:"colorField",…},{name:"view",…}
    

    onAddClick occurrences go 3 → 4 (the new wiring line).

A declared input removed narrows the published authorable vocabulary, and a second declared input starts producing a user-visible affordance — minor, matching the #4403 / #4177 precedent. Never major (AGENTS.md §版本号策略: objectui's major follows @objectstack, so even breaking changes here are minor).

One changeset: .changeset/calendar-inert-inputs-4454-4493.md, '@object-ui/plugin-calendar': minor, describing both changes.

Surface

packages/plugin-calendar/** + the one colorMapping docs mention + one changeset. Untouched: the DOM-leak sweep file, core/fields, console/**, content/docs/releases/. No overlap with the in-flight siblings (#4497, #4468, #4419, #4446, #4475).


Generated by Claude Code

…4454, #4493)
`calendar-view` had two registry inputs declared and read by nobody — the one
state ADR-0049's enforce-or-remove framing says must not persist. Measurement
answered them in opposite directions.
ENFORCE `allowCreate` (#4454). The handler it would gate was already built in
the renderer (`handleAddClick`, dispatching `{ type: 'create', payload: {} }` on
the widget's own `onAction` channel) and simply never passed, so `CalendarView`
— which renders its "New event" button behind `onAddClick` — never saw a handler
and the button never existed. An authored `allowCreate: true` now supplies the
handler through the DECLARED `onAddClick` hatch rather than around it via a
second prop, so the pre-existing React-host path keeps working and keeps its
precedence (a host handler replaces the action dispatch). Only the boolean
`true` turns it on: absent, `false` and off-type spellings all get the
absent-key answer, which on this prop is what makes the button not render.
REMOVE `colorMapping` (#4493). No read site anywhere — the renderer takes the
colour straight off the record (`color: record[colorField]`) and `CalendarView`
resolves it from `event.color` — and no measured app authors it, so the
declaration is retired rather than given an implementation nobody pulled on.
The docs line publishing it goes with it.
Verified red-first: on the pre-fix tree the four new assertions that need the
wiring/removal fail (no add affordance for an authored `allowCreate: true`;
the registry still declares `colorMapping`), and the must-not-change cases are
green on both sides.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 13, 2026 3:15am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)24.7 KB350 KB
Entry fileindex-yc5x7YcA.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)9.56KB3.59KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)8.92KB3.41KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)25.13KB5.40KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)38.46KB10.17KB
auth (createAuthenticatedFetch.js)6.34KB2.43KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.02KB0.88KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.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)489.32KB108.45KB
core (index.js)3.37KB1.34KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)153.79KB41.35KB
fields (index.js)230.14KB57.12KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)3.35KB1.38KB
i18n (pickLocalized.js)3.69KB1.73KB
i18n (provider.js)23.12KB7.62KB
i18n (useDisplayLocale.js)2.84KB1.45KB
i18n (useObjectLabel.js)27.59KB6.63KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.98KB10.85KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.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.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.86KB12.91KB
plugin-charts (index.js)62.07KB17.65KB
plugin-chatbot (index.js)181.21KB43.14KB
plugin-dashboard (index.js)120.95KB31.53KB
plugin-designer (index.js)212.58KB42.83KB
plugin-detail (index.js)239.03KB59.77KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)114.58KB27.68KB
plugin-gantt (index.js)164.14KB39.98KB
plugin-grid (index.js)188.40KB50.10KB
plugin-kanban (index.js)48.62KB13.42KB
plugin-list (index.js)111.13KB27.12KB
plugin-map (index.js)18.16KB5.81KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)41.16KB10.96KB
plugin-timeline (index.js)26.21KB7.52KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.08KB20.55KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)23.73KB7.96KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.23KB0.66KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
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)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.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-retry.js)4.32KB2.02KB
types (index.js)3.05KB1.52KB
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 (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
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

@yinlianghui
yinlianghui marked this pull request as ready for review August 13, 2026 03:25
@yinlianghui
yinlianghui added this pull request to the merge queueAug 13, 2026
Merged via the queue into main with commit 515328fAug 13, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-4454-4493-calendar-inert-inputs branch August 13, 2026 03:25
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@yinlianghui@claude