Skip to content

fix(plugin-calendar,app-shell): quick-create hands an object it cannot satisfy to the full create form with the clicked day (objectui#12291) - #12315

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-12291-calendar-quick-create-handoff
Oct 13, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-12291-calendar-quick-create-handoff

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #12291
Clause-②: yes

What changed

The calendar (@object-ui/plugin-calendar, ObjectCalendar). Before the quick-create dialog is offered, the calendar checks the object's required fields against what the dialog's write can fill. That set used to be stated only inside the write's auto-fill loop. It is now one function, quickCreateFill, and both the write and the check (requiredFieldsQuickCreateCannotFill) call it, so the two cannot drift. A required field the server fills on insert does not count: one declared readonly or system, or of type autonumber. These are the exemptions objectql's insert walk (validateRecordInScope, isCallerWritableField) makes before its required check.

  • When every required field can be filled, nothing changes: the dialog opens as before (the stock crm_activity).
  • When one cannot be filled (the stock showcase_task, required project lookup), the empty-day click and the week/day range drag no longer offer the dialog. They call the declared onEdit prop with a new record (no id) that carries the date values the dialog would have written, for example { due_date: '2026-10-20' }. The host then opens its full create form pre-filled with them.
  • With no onEdit, the gesture offers nothing on such an object. It is withheld the way a closed calendarQuickCreate grant withholds it.
  • A caller-supplied onDateClick still takes precedence. The dialog gets no lookup picker, and no lookup value is guessed (both ruled out by the triage direction).

The console (@object-ui/app-shell). The console's record-form handler (AppContent's handleEdit) already opened a create form for a record with no id; the list's New calls it with null. It now opens that create form pre-filled with the values such a record carries:

  • the record-form overlay (?form=new) gets them as initialValues, carried in the state of the history entry that opens it (RECORD_FORM_CREATE_VALUES_STATE);
  • a page-mode object (editMode: 'page') gets them as the query-string prefill its create page already reads (/new?due_date=2026-10-20).

A record with an id still opens the edit form, and its other keys are ignored.

How the hand-off route was chosen (measured, not assumed)

  • The calendar had no create path of its own. ObjectCalendarComponentProps.onEdit was declared, and the registered renderer forwards it (HOST_CALLBACKS), but ObjectCalendar never read it. Its renderer's own doc called it inert.
  • ListView forwards its props to the view component through a trailing spread. In the console that includes onEdit, whose chain is plugin-view's handleEdit, then the console ObjectView's onEdit, then AppContent's handleEdit. That is the same handler the header New reaches through useObjectActions' create (onEdit(null)). So the console's full create form was already reachable from the calendar through a declared prop, and no new callback is needed.
  • What was missing was a way for a value to reach that form. The overlay's only prefill channel was formLink (a child task's parent link). The values now travel in history state rather than in a new reserved URL param. This follows the precedent AppContent already sets for its post-create from origin: no addition to the urlParams.ts contract, while Back and a refresh of that entry keep the values.
  • The other ListView hosts (InterfaceListPage, ObjectDataPage) pass no onEdit to their list. On those hosts, an object quick-create cannot satisfy offers no empty-day gesture, where it used to offer a dialog that could not succeed.

Pins (three boundaries)

  1. On showcase_task, the click opens the full form with due_date set. At the calendar boundary, ObjectCalendar.quickCreateHandoff-12291.test.tsx asserts that onEdit is called once with exactly { due_date: '2026-10-20' } and that no dialog opens and nothing is created. The same file drives it through the registered object-calendar renderer with onEdit as a trailing prop, which is how ListView hands it down. At the console boundary, AppContent.createSeed-12291.test.tsx asserts ?form=new, mode: 'create' and initialValues: { due_date: '2026-10-20' }.
  2. An object with no unfillable required field keeps the dialog. On a crm_activity-shaped object, the dialog opens and its write carries the day and the auto-filled selects. A table runs one required field per row through both the check and the write, including the server-filled exemptions.
  3. A row created from the hand-off carries the clicked date. Asserted at the console's create form: the real ModalForm, opened through the hand-off, submits { title, due_date: '2026-10-20' } to the adapter's create. The server is replaced by an adapter double.

Verification (HEAD 457ea5d)

  • Closure build pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build (29 packages, @object-ui/plugin-calendar among them): exit 0, under the shared verify lock (VERDICT command-exit 0).
  • pnpm --filter @object-ui/plugin-calendar type-check exit 0 and pnpm --filter @object-ui/app-shell type-check exit 0. Each script name was echoed, and each runs tsc -p tsconfig.test.json, whose include covers the new test files.
  • pnpm exec vitest run --maxWorkers=2 packages/plugin-calendar/ packages/app-shell/src/console/__tests__/ packages/app-shell/src/utils/__tests__/recordFormNavigation.test.ts: Test Files 90 passed (90), Tests 700 passed | 95 skipped (795).
  • The tests that read the edited README and docs page (calendar-doc-key-set-8830, object-calendar-record-source-7313, calendar-flat-color-allday-8466, readme-calendar-view-schema): 4 passed, 48 passed.
  • node scripts/check-changeset-presence.mjs exit 0; check-changeset-no-major exit 0; pnpm check:control-bytes exit 0; pnpm check:new-line-citations VERDICT ... 0 new citation(s); pnpm check:doc-fences and pnpm check:doc-json exit 0.
  • node scripts/check-governed-queue-guard.mjs --test over the 10 changed paths: NOT GOVERNED.
  • ESLint, a declared narrowing to the 7 changed .ts / .tsx files (--format json: 7 files, 0 errors). The population comes from eslint.config.js: its **/*.{ts,tsx} blocks cover them. The config is not type-aware (no parserOptions.project), and its one custom-rule directory reads no files, so this diff cannot change the verdict on any untouched file. Repo-wide pnpm lint is CI's.
  • Ablations (each through ablation-replace.mjs, the mutation proven on disk and restored to the HEAD blob, git diff HEAD empty):
    • in ObjectCalendar, forcing quickCreateCanSucceed to true turns exactly the seven hand-off cases red, and the ten dialog-keeping cases stay green;
    • in AppContent, dropping the history-state values turns the overlay initialValues case red, and the real-ModalForm case too (due_date received undefined); the page-mode case and the two unchanged callers stay green.
  • pnpm check:readme-exports exits 1 only on self-imports it could not judge, all in READMEs of packages outside the built closure (app-shell, plugin-gantt, plugin-timeline and four more). The plugin-calendar README is judged, and its edit adds no import.
  • NOT MEASURED, live browser: the objectstack backend is not installed in this container. The hand-off was measured at the component and console boundaries above.

Acceptance notes

  • Scope beyond the file named in the claim, and why. The claim's surface lists ObjectCalendar.tsx, "the console's own create path" and tests and changeset. This PR also touches plugin-calendar's index.tsx, but only the doc comment that called onEdit inert. It touches the plugin's README and content/docs/plugins/plugin-calendar.mdx to document the hand-off (AGENTS.md commandment 2, docs-driven). It touches app-shell's recordFormNavigation.ts, where the create-values helpers sit beside resolveRecordFormTarget, which handleEdit already calls.
  • Not measured: required-ness in the served schema. The stock objects' declared fields have no required field the server fills. Whether the served schema of a stock object adds required system columns (id, created_at) that are not marked system or readonly was not read: the backend is not installed here. The server skips those names by name, and the calendar's check does not copy that list. If one does read required, the object hands off to the full form, which still creates the row; it is not a dead end.
  • Residual race. A click made before the object schema settles still gets the dialog: the check reads the field definitions, and until they arrive it has nothing to judge.
  • Moved unchanged. The auto-fill's options || choices and value ?? id reads moved verbatim into quickCreateFill. They are dialect reads in the sense of AGENTS.md commandment 0.1; not touched here. One behaviour moved with them: a null first option no longer throws.
  • Observation, not measured. In the console, a list view that declares addRecord: { enabled: true } draws ListView's own Add record button. That button calls onAddRecord, and the console's ObjectView passes ListView no onAddRecord. It was read in source only, not reproduced. Carrier: none.
  • ObjectCalendar's unused handleCreate is pre-existing dead code and is left alone.

Generated by Claude Code

…ct to the full create form with the clicked day (objectui#12291)

Before the quick-create dialog is offered, the calendar checks the object's
required fields against what the dialog's write can fill, read from one
statement of that set (`quickCreateFill`) that the write itself now uses.
Where a required field is outside it (the stock showcase_task's required
`project` lookup), the empty-day click and the week/day range drag call the
declared `onEdit` prop with a new record carrying the clicked day instead of
offering a dialog the server would refuse. With no `onEdit`, the gesture
offers nothing on such an object.

The console's record-form handler already read a record with no id as a new
one; it now opens the create form with the values such a record carries: the
overlay through the history entry that opens it, a page-mode object through
the query-string prefill its create page reads.

Claude-Session: https://claude.ai/code/session_01RZYixBnXPWvcm2YenWo6eJ
Co-authored-by: Claude <noreply@anthropic.com>
… any (objectui#12291)

Claude-Session: https://claude.ai/code/session_01RZYixBnXPWvcm2YenWo6eJ
Co-authored-by: Claude <noreply@anthropic.com>
…y its title textbox (objectui#12291)

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 72 chunks) 3133.7 KB 3204.6 KB
Main entry chunk (gzip) 73.8 KB 350 KB
Entry file index-CtryoV3a.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) 20.69KB 7.62KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 588.92KB 141.82KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 244.49KB 68.27KB
fields (index.js) 279.77KB 71.34KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
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.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.26KB 11.71KB
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) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 55.05KB 16.09KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 143.63KB 38.79KB
plugin-designer (index.js) 233.77KB 50.02KB
plugin-detail (index.js) 258.42KB 68.17KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 178.42KB 46.26KB
plugin-gantt (index.js) 178.63KB 45.19KB
plugin-grid (index.js) 258.10KB 71.90KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 123.39KB 31.55KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 45.65KB 12.81KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
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 (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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: 457ea5d2f92190543c127c5b610bb3a5dd15353a
Local-runs: none

Inputs: card objectui#12291 (body, triage 6137205429, claim 6139306452, os-dev-report 6140477085), PR objectui#12315 (body, the 10-file list, the net diff against main), the 32 check-runs on the head, and the source at this head for the published surface of @object-ui/plugin-calendar and @object-ui/app-shell (each package.json exports and the entry module it reaches), plus the wiring the PR body claims (ListView, plugin-view ObjectView, app-shell ObjectView, AppContent, RecordFormPage, InterfaceListPage, ObjectDataPage). Nothing built, run or re-run.

① Derived judgments

  1. @object-ui/plugin-calendar published surface: unchanged. exports["."] reaches src/index.tsx, which exports ObjectCalendar, ObjectCalendarComponentProps (alias ObjectCalendarProps), CalendarView and the object-calendar renderer with its HOST_CALLBACKS forward. The diff adds no export, prop or type member; onEdit keeps its declared shape and HOST_CALLBACKS keeps its list — only doc comments move. RIGHT.
  2. Behaviour contract on the published onEdit prop: widened, and rightly. The component never called onEdit before this head; now, on an object where requiredFieldsQuickCreateCannotFill is non-empty, the empty-day click and the week/day range drag call it with a new record (no id) carrying the start field and, when bound, the end field in stored form (quickCreateDateValues). The same calendarQuickCreate grant gates it, a caller onDateClick still wins, and with no onEdit no handler is handed to the grid, so the affordance is withheld rather than a silent click. A host handler written for existing records now receives a record with no id: that is a new emission on a declared callback, so the true answer to 「本卡放宽接受集或扩大公开面吗」 is yes for this package. The direction matches triage: no lookup picker, no guessed lookup; showcase_task hands off, crm_activity keeps the dialog. RIGHT.
  3. One statement of the fill set. quickCreateFill is read by both requiredFieldsQuickCreateCannotFill and submitQuickCreate; the row table pins each required type through both halves. The exemptions readonly / system / autonumber match objectql: isCallerWritableField skips system and readonly (and five lifecycle names by name, which the calendar deliberately does not copy), and autonumber is the runtime-owned type exempt from the missing-value check on insert. If a served schema marks one of the name-skipped columns required without a flag, the object hands off instead of dead-ending. RIGHT, with the residual declared.
  4. @object-ui/app-shell published surface: unchanged. exports["."] reaches src/index.ts, which re-exports AppContent as DefaultAppContent and picks named utilities from utils/index.js. resolveRecordFormTarget is exported by utils/index.ts but NOT re-exported by the package entry (no star export), and the four new module exports (recordFormCreateValues, readRecordFormCreateValues, RECORD_FORM_CREATE_VALUES_STATE, RecordFormRecord) are re-exported by neither. So the widened record parameter on resolveRecordFormTarget is internal, and the changeset's sentence "No export, prop or type member is added" holds. RIGHT.
  5. DefaultAppContent behaviour: widened, and rightly. handleEdit — the onEdit it hands its ObjectView, and through plugin-view's handleEdit and app-shell's renderListView the one ListView spreads onto the calendar — now seeds a create from an id-less record: the overlay through history state under RECORD_FORM_CREATE_VALUES_STATE, read into initialValues (a formLink still wins, and the two cannot coexist because handleEdit deletes the link param on the same push); page mode through the query string RecordFormPage already reads for every pair in create mode. onEdit(null) and a record with an id are unchanged and pinned. A published component gains a documented capability: yes for this package as well. RIGHT.
  6. The chain the PR body claims holds at this head. Calendar from ListView's trailing props spread; ListView from app-shell renderListView's onEdit={editHandler}; that from plugin-view handleEdit, which calls onEditProp when present; that from app-shell ObjectView's onEdit, which is AppContent.handleEdit. InterfaceListPage and ObjectDataPage hand their ListView no onEdit (their no-op onEdit sits on the drawer's RecordDetailView), so on those hosts the gesture is withheld, as the body says. RIGHT.
  7. Pre-schema race. Until objectSchema settles the check sees no fields and the dialog is offered; its write then runs the old auto-fill and the server refuses as before. Declared by the dev; a bounded residual, not a new dead end. ACCEPTED.
  8. A derived behaviour the PR did not measure: plugin-view's fallback. With no onEditProp, plugin-view's handleEdit routes a record to schema.onNavigate(recordId, 'edit') on a page surface or opens its own form in edit mode with selectedRecord; for the id-less hand-off record that is an edit surface with no record id. Reachable only by a host that composes plugin-view's ObjectView through the renderListView slot without passing onEdit (the console always passes it; the Studio design surface's renderStudioGridList is a slot user). Outside the claim's file surface and the console path this card is about. FLAGGED in ③, not a blocker.
  9. Moved behaviour. quickCreateFill on a select whose first option is null now returns a fill of null where the old loop threw; malformed metadata only. Named, no action.
  10. Pins and docs. The three pins triage asked for are present at the component boundary, the console boundary and the adapter boundary, with the ablations reported; README and the plugin docs page document the hand-off; no locale pack and no t(...) call changed, so the i18n gates are not owed. RIGHT.

② Semver level

③ Boundary flags

  • Deviation 1 (unlocked targeted runs, then the authoritative build / type-check / test chain under the lock at this head): accepted; the check-runs on the head are the gate.
  • Deviation 2 (file surface beyond the claim: index.tsx doc comment, README, the plugin docs page, recordFormNavigation.ts and its test): each is the hand-off's own producer, consumer or docs, inside the claim's "console's own create path" and the docs-driven commandment. Accepted.
  • Deviation 3 (gates beyond the dispatch list): accepted.
  • Deviation 4 (model-free trailer pair on the commits, session-URL footer on the PR body): what objectui AGENTS.md prescribes for this repository. Accepted.
  • open_questions[0] (history state vs a new reserved URL param): answered A — keep history state. objectui AGENTS.md feat: add live playground for interactive schema demonstration #8 sorts state by what must survive: Back closes the overlay with its entry, and a reload of that entry restores its history.state, so both survive; what a URL would add is a shareable seeded create, which nobody asked for, and the formLink param exists for a parent link that must survive a share. Precedent: the post-create from origin already rides history state in handleCrudSuccess. This is the reviewing seat's answer, not a maintainer ruling; a shareable seed would be a new card.
  • out_of_scope_findings[0] (ListView's own Add record button with no console onAddRecord; source-read, not reproduced): carrier none. ESCALATED to the owning seat to file as a card or dedupe; it does not ride this PR.
  • New flag from ① item 8 (plugin-view's fallback handleEdit receiving an id-less record): outside the claim's surface; ESCALATED to the owning seat as a candidate card on @object-ui/plugin-view (route an id-less record to its create surface, or pin the current behaviour). Not a blocker for this card.
  • NOT MEASURED, as declared: served-schema required-ness and the live browser (backend not installed in the dev container); the pre-schema race. Accepted — each failure mode is a hand-off, not a dead end.
  • Check-runs on the head at the time of this record: 26 success, 3 skipped (the two coverage matrices and dependabot), 3 still in_progress (Spec Main Shape Gate, Test (shard 3/12), Test (shard 7/12)); no failure. The PR is a draft and behind main; neither bears on this verdict. The verdict below rests on ② alone.

Implemented-by: claude/issue-12291-calendar-quick-create-handoff
Reviewed-by: session_01RZYixBnXPWvcm2YenWo6eJ

VERDICT: FAIL


Generated by Claude Code

…lendar-quick-create-handoff

Claude-Session: https://claude.ai/code/session_01RZYixBnXPWvcm2YenWo6eJ
Co-authored-by: Claude <noreply@anthropic.com>
… (objectui#12291)

The published calendar newly calls its declared `onEdit` with an id-less
record, and the console's record-form handler newly honours a seeded create:
a Clause-② `yes` widening, so both packages take `minor`.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 72 chunks) 3133.8 KB 3204.6 KB
Main entry chunk (gzip) 73.8 KB 350 KB
Entry file index-CBRpLsGT.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) 20.69KB 7.62KB
app-shell (runtime-config.js) 22.59KB 7.89KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 588.92KB 141.82KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 245.03KB 68.47KB
fields (index.js) 279.77KB 71.34KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
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.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.26KB 11.71KB
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) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 55.05KB 16.09KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 143.63KB 38.79KB
plugin-designer (index.js) 233.77KB 50.02KB
plugin-detail (index.js) 258.42KB 68.17KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 178.42KB 46.26KB
plugin-gantt (index.js) 178.63KB 45.19KB
plugin-grid (index.js) 258.10KB 71.90KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 123.39KB 31.55KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 45.65KB 12.81KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 92.11KB 23.27KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
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 (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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: 82128ab04c1f0cac10fb8fa7eb6cb8058700a55c
Local-runs: none

Round 2, after the patch round for record 6140680761 (FAIL on ② alone). Inputs: card objectui#12291 (body and all four comments: triage 6137205429, claim 6139306452, os-dev-report 6140477085, patch-round os-dev-report 6140723169), PR objectui#12315 (body, the 10-file list, the net diff against main at this head), the 32 check-runs on this head, the head-to-head compare 457ea5d…82128ab and the source at this head where a judgment needed it (ListView.tsx, the changeset, both packages' package.json exports and entry modules). Nothing built, run or re-run.

What moved between the heads, measured. The compare is 6 commits ahead, 0 behind: the merge of origin/main (base moved 3092aca → 068a540, bringing objectui#12309, #12312, #12311 and #12300 — 40 files) plus one changeset commit. Of the PR's 10 files, exactly one differs between the heads: the changeset (patch → minor on both packages, two lines). The merge touched none of the PR's other nine files, so their net diff against main is byte-for-byte the one record 6140680761 judged. On the onEdit hand-off chain (calendar ← ListView trailing props spread ← app-shell renderListView ← plugin-view handleEdit ← app-shell ObjectView ← AppContent.handleEdit; RecordFormPage; useObjectActions; urlParams), the merge touched one file, packages/plugin-list/src/ListView.tsx (+17/−1): a rowsCarryConcurrencyToken memo, an updated_at projection for the inline-edit If-Match save, and that memo added to the fetch effect's dependencies. None of the three hunks is near the {...props} spread or the onEdit prop, and at this head ListView still forwards onEdit through that spread. The other merge-brought mentions of the calendar are inert (a lucide Calendar icon import in ObjectGrid, the unchanged (schema as any).calendar dependency in ListView's effect); the affordance-grant map's test moved two listInlineEdit write-site rows, the map itself did not move, so the calendarQuickCreate row the hand-off is gated by is unchanged.

① Derived judgments

  1. @object-ui/plugin-calendar published surface: unchanged. exports["."] reaches src/index.tsx (ObjectCalendar, ObjectCalendarComponentProps and its alias, CalendarView, the object-calendar renderer with HOST_CALLBACKS). No export, prop or type member is added or changed; onEdit keeps its declared shape; only doc comments move. RIGHT.
  2. Behaviour contract on the published onEdit prop: widened, and rightly. On an object where requiredFieldsQuickCreateCannotFill is non-empty, the empty-day click and the week/day range drag now call onEdit with a new record (no id) carrying the start field and, when bound, the end field in stored form (quickCreateDateValues); the calendarQuickCreate grant gates it, a caller onDateClick wins, and with no onEdit no handler reaches the grid, so the affordance is withheld rather than a silent click. A host handler written for existing records now receives a record with no id — a new emission on a declared callback, so the true answer to 「本卡放宽接受集或扩大公开面吗」 is yes for this package. Direction matches triage: no lookup picker, no guessed lookup; showcase_task hands off, crm_activity keeps the dialog. RIGHT.
  3. One statement of the fill set. quickCreateFill is read by both the check and submitQuickCreate; the row table pins each required type through both halves. The exemptions readonly / system / autonumber match objectql (isCallerWritableField skips system and readonly; autonumber is the runtime-owned type exempt from the missing-value check on insert); the five name-skipped lifecycle columns are deliberately not copied, and a served schema marking one required without a flag hands off instead of dead-ending. RIGHT, residual declared.
  4. @object-ui/app-shell published surface: unchanged. exports["."] reaches src/index.ts, which re-exports AppContent as DefaultAppContent and picks named utilities from utils/index.js with no star export; resolveRecordFormTarget is exported by utils/index.ts but not by the package entry, and the four new module exports (recordFormCreateValues, readRecordFormCreateValues, RECORD_FORM_CREATE_VALUES_STATE, RecordFormRecord) by neither. The widened record parameter is therefore internal, and the changeset's "No export, prop or type member is added" holds. RIGHT.
  5. DefaultAppContent behaviour: widened, and rightly. handleEdit now seeds a create from an id-less record: the overlay through history state under RECORD_FORM_CREATE_VALUES_STATE read into initialValues (a formLink still wins; the two cannot coexist because handleEdit deletes the link param on the same push); page mode through the query string RecordFormPage already reads for every pair in create mode. onEdit(null) and a record with an id are unchanged and pinned. yes for this package too. RIGHT.
  6. The chain holds at this head (the sequence named above), with the merge-brought ListView hunks read and found off the spread. InterfaceListPage and ObjectDataPage hand their ListView no onEdit (their no-op onEdit sits on the drawer's RecordDetailView), so on those hosts the gesture is withheld, as the PR body says. RIGHT.
  7. Pre-schema race. Until objectSchema settles the dialog is offered and its write runs the old auto-fill; declared, bounded, not a new dead end. ACCEPTED.
  8. Derived behaviour the PR did not measure: plugin-view's fallback. With no onEditProp, plugin-view's handleEdit routes a record to schema.onNavigate(recordId, 'edit') on a page surface or opens its own form in edit mode; for the id-less hand-off record that is an edit surface with no record id. Reachable only by a host composing plugin-view's ObjectView through the renderListView slot without its own onEdit (the console always passes one). Outside the claim's surface; carried in ③. Not a blocker.
  9. Moved behaviour. quickCreateFill on a select whose first option is null returns a fill of null where the old loop threw; malformed metadata only. Named, no action.
  10. Pins and docs. The three pins triage asked for stand at the component, console and adapter boundaries with their ablations; README and the plugin docs page document the hand-off; no locale pack or t(...) call changed. RIGHT.
  11. The merge-brought delta (40 files: grid inline-edit saves with If-Match, Setup › Apps honest writes, the OAuth consent public-client name, the related-list dataSource strict face) touches no accept set this PR declares and no line of the hand-off; each carries its own landed record and changeset. Nothing for this record to judge beyond the chain reading above. RIGHT.

② Semver level

  • The PR body declares Clause-②: yes with no arm — well-formed, TRUE (items 2 and 5), unchanged since round 1 (stored body 9475 bytes, no sentence names a level).
  • .changeset/12291-calendar-quick-create-handoff.md now declares '@object-ui/plugin-calendar': minor and '@object-ui/app-shell': minor; the body is unchanged and consistent with minor (it describes a new capability on each package and names no level). The changeset commit's message states the widening it levels for. RIGHT.
  • Rule: a yes takes at least minor; the fixed group forbids major (objectui AGENTS.md 版本号策略, check-changeset-no-major.mjs), so minor is both floor and ceiling. Precedent on this repository, measured in round 1: the 24 most recent merged Clause-②: yes PRs (objectui#12309 through feat(app-shell): metadata-admin reads /meta/types allowEnvironmentOverlay; the read-only banners name OS_METADATA_WRITABLE (objectui#12141) #12168) every one carry minor on at least the widened package, none patch-only. This head follows it. The Changeset Bump Policy, Changeset Declaration, Changeset Fixed Group Check and Changeset Overwrite Report checks on this head are all success.
  • The round-1 defect — the two halves of one declaration disagreeing — is closed on this head.

③ Boundary flags

  • Patch-round deviation (the merge commit amended locally before its first push to carry the model-free trailer pair; nothing pushed was rewritten; fast-forward 457ea5d..82128ab, no force): what objectui AGENTS.md prescribes. Accepted.
  • No pin re-run after the merge: the dev's measurement (merge delta ∩ PR files = ∅) is confirmed by the compare, and the one chain file it touched was read above. The PR body's "Verification (HEAD 457ea5d)" section therefore still describes this head's PR-owned files, but not the merged tree — for that the check-runs on this head are the authority, see below. Accepted.
  • Round-1 deviations 1–4 (unlocked targeted runs then the locked chain; file surface beyond the claim; extra gates; attribution): accepted as before.
  • open_questions: none this round; round 1's answer stands — A, keep history state (objectui AGENTS.md feat: add live playground for interactive schema demonstration #8: Back and reload both survive with history.state; a shareable seed was not asked for; precedent is the post-create from origin in handleCrudSuccess). The reviewing seat's answer, not a maintainer ruling.
  • Still escalated to the owning seat, not riding this PR: (a) the out-of-scope ListView Add record button with no console onAddRecord (source-read, not reproduced) — file as a card or dedupe; (b) ① item 8, plugin-view's fallback handleEdit receiving an id-less record — a candidate @object-ui/plugin-view card (route an id-less record to its create surface, or pin the current behaviour).
  • NOT MEASURED, as declared: served-schema required-ness and the live browser (backend not installed); the pre-schema race. Accepted — each failure mode is a hand-off, not a dead end.
  • Check-runs on this head at the time of this record: 15 success (Build & E2E, Build Docs, Bundle Analysis, the four Changeset * checks, Governed Surface Queue Guard, Line Citation Gate, Lint, Live E2E (informational), Test (dist pins), Type Check, label), 3 skipped (the two coverage matrices and dependabot), 14 still in_progress (Test (shard 1/12) through Test (shard 12/12), Repo Gates, Spec Main Shape Gate); no failure. The PR is a draft and mergeable_state reads blocked while checks run; neither bears on the contract verdict. This record judges the contract; the owning seat lands only once every check on this head is green, and a check concluding failure reopens the question for a fresh record.

Implemented-by: claude/issue-12291-calendar-quick-create-handoff
Reviewed-by: session_01RZYixBnXPWvcm2YenWo6eJ

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation package: app-shell plugin tests

Projects

None yet

2 participants