Skip to content

fix(plugin-list,plugin-calendar): a list's calendar view fetches the days it shows, walked in fetch batches (objectui#12081 item 2) - #12116

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-12081-calendar-date-window
Oct 11, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-12081-calendar-date-window

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Part of #12081 (item 2)
Clause-②: yes

A calendar view inside a list now draws every record of the days it shows. Before, it drew only the first 100 records of the object. Items 3, 4 and 7 of the card stay open, and #12081 is not addressed beyond item 2 here.

What was measured on main (5330afd)

  • Where the 100 comes from (PM assumption 1: holds). ListView makes one unpaged fetch for every view except the flat grid. Its $top is effectivePageSize, which falls back to DEFAULT_LIST_FETCH_BATCH_SIZE (100), and it carries no date condition. The rows are handed to the object-calendar node as data. The renderer forwards an array data, and ObjectCalendar then skips its own fetch. That fetch is the one that carries the ruling a′ ceiling (2,001 with the probe row) and its footnote. So the hotclm reading top=100&select=…,end_date is this fetch, and its only sign was the list's "Showing first 100 records" note.
  • Reproduced red on main. New pin ListView.calendarDateWindow-12081, case (a), with only the test file present: AssertionError: expected [ 'sep-0', 'sep-1', 'sep-2', …(97) ] to include 'oct-60'. The calendar was handed the first 100 rows of the object, mostly from the month before.
  • Where the visible range lives (PM assumption 3). It is ObjectCalendar state (currentDate, view), which CalendarView mirrors. The month grid is 42 days from the display locale's first day of the week, and on a phone the view switches to day by itself. ListView received none of it: it passed neither onNavigate nor onViewChange, and neither one fires for the state the calendar opens on.
  • The three texts are compatible (PM assumption 2). Windowing and paging need no authorable key. The range reaches the fetch through one new host prop and nothing else.

What changed

  • One fetch owner: ListView (calendarWindowed). With a start binding, the calendar view's fetch selects the visible days on startDateField. With an endDateField bound, it selects every span that touches them. It also selects the records with no start date, so ObjectCalendar's Unscheduled area keeps its records. The window node joins the effective filter under one and, so the view filter, panel, user filters and search still narrow it. The bounds are local YYYY-MM-DD dates, widened by one day on each side, so a UTC-stored instant always lands inside the window.
  • Walked in fetch-batch steps (fetchCalendarWindow in the new plugin-list/src/calendarWindow.ts). Every request has $top at most 100, with $skip advancing. The walk stops at the first short batch, or one probe row past NON_GRID_ROW_CEILING. applyNonGridRowCeiling then cuts the result, and ListView draws NonGridRowCeilingNote under the calendar naming N and M. ⛔ No bigger cap. dataLimitReached stays off for a windowed calendar. The rows-per-page picker is withheld there, because a page size changes nothing the calendar draws.
  • The calendar reports its days: ObjectCalendarComponentProps.onVisibleRangeChange({ start, end }). It fires on mount and whenever navigation or a view change moves the range. The range is half-open over local midnights. It is computed by getVisibleDateRange, built on the same getMonthDays and getWeekStart the grids draw with. Those two helpers moved verbatim from CalendarView.tsx into the new plugin-calendar/src/visibleDays.ts, so the report also survives the tests that stub the CalendarView module. The effect is keyed on the two instants, and the callback is read through a ref, so an unchanged range is never reported twice. The object-calendar renderer forwards it through its declared host-callback list, as it does onNavigate.
  • Navigation. Until the first report, the window covers any month grid today can open on (initialCalendarWindow). So the first fetch does not wait for the calendar, and a host-registered calendar that never reports still draws today's month. The calendar's first report falls inside that window, so it asks for nothing more. A range the held window covers (a week of the same month, or the phone's day view) asks for nothing; one it does not cover fetches that window. A windowed calendar is not swapped for the list's loading skeleton, so it keeps the month it moved to across the refetch. It keeps drawing while the next window loads, under the existing refresh bar. Leaving the calendar view drops its window. This is done during render, so a calendar shown again opens on today's window.
  • Route note. The other suggested route was "a hosted calendar fetches its own window". It would have taken the list's toolbar search off the calendar, because ObjectCalendar's query has no search channel. It would also have broken the toolbar Refresh, the client-side export and the user-filter counts, all of which read this view's rows, and each would have needed another prop. So the fetch stays with ListView. The standalone ObjectCalendar fetch (no host data) is unchanged.
  • File surface. The claimed surface was ListView.tsx and ObjectCalendar.tsx with their tests, plus the changeset. Named extensions: two new pure modules beside them (calendarWindow.ts, visibleDays.ts), the renderer's host-callback list in plugin-calendar/src/index.tsx, the calendar row of ListView.pageSizeAndFetchBatch-9853 (it now asserts the walk: every step is the batch, never the page size), the cross-package pin in app-shell (the only package depending on both), and the docs that AGENTS.md's docs-driven rule asks for (plugin-list README's paging paragraph, which this change made false; plugin-calendar README; content/docs/plugins/plugin-calendar.mdx). ListView's calendar branch keeps its own reads of the two bindings, which the calendarUnionReads-8651 source pin reads. The window reads the same two expressions at component scope, with a comment tying the pair together.

Clause-② (yes): what the published surface gains

  • Props: one. ObjectCalendarComponentProps.onVisibleRangeChange?: (range: { start: Date; end: Date }) => void, optional.
  • Exports: none. The plugin-list package entry is not in the diff. In plugin-calendar's entry, only the HOST_CALLBACKS array changes. The new modules are not re-exported, and no export * exists in either entry.
  • Locale keys: none. The footnote reuses common.rowCeilingNote / common.rowCeilingNoteUnknownTotal.
  • Authorable keys: none. Nothing on @object-ui/types changes, and the view schema is unchanged.

Tests (head 25e515d)

  • ListView.calendarDateWindow-12081 (11 cases). (a): 122 October records with 40 August records listed ahead are all handed over; every step has $top at most 100; the walk takes more than one step; the filter names the field the calendar draws by; no out-of-grid month is handed over; no data-limit-warning appears. (b) twice: an uncovered range refetches December whole, and a covered range sends zero requests. (c): 2,050 records hand 2,000 rows, the walk asks for 2,001 in total, and the role=note footnote names 2000 and 2050. Also: a fitting window draws no note; undated records ride along; a span opening before the window is fetched; the view filter still narrows. CONTROLS: the paged grid's requests are a literal equal to before ($top = the spec display default, then $skip on page 2, no window); the board is still one batch with no filter; a calendar with no start binding keeps its single batch and gets no callback.
  • ObjectCalendar.visibleRange-12081 (6 cases): the month grid under en-US and en-GB, Next period, the week view, the day view, and no re-report on a fresh handler.
  • app-shell listCalendarDateWindow-12081: the REAL ListView over the REAL ObjectCalendar. All 122 October chips are drawn. Next period draws all 110 November chips, and the header still reads November 2026.
  • Reverse check from committed 25e515d. The four product files were restored to 5330afd, with each blob verified on disk. The predicted direction was red. Observed: Tests 15 failed | 14 passed (29) across the four pin files. The 14 passing are the CONTROL rows, the 9853 rows of the other views, the filter case, and the no-re-report case, which passes vacuously on main. The restore was git checkout HEAD -- …, with every blob equal to HEAD and git diff HEAD 0 bytes.
  • Package suites. pnpm exec vitest run packages/plugin-list/ packages/plugin-calendar/ …listCalendarDateWindow-12081.test.tsx gave Test Files 188 passed (188), Tests 1837 passed | 95 skipped. That run was at da0bab9. 25e515d changes only the window-state plumbing in ListView.tsx and the ref write in ObjectCalendar.tsx. After it, the 6 directly affected files are green (56 passed). The union re-run at 25e515d is in progress, and its verdict lands in the os-dev-report comment on console (17.7.0): seven UI defects measured in one full browser pass of a metadata app — missing Edit, capped calendar, page-only footer sum, ignored password, and three affordances shown to users who cannot use them #12081.

Gates (head 25e515d)

  • pnpm --filter @object-ui/plugin-list type-check and pnpm --filter @object-ui/plugin-calendar type-check: both scripts echoed tsc --noEmit && tsc -p tsconfig.test.json, exit 0.
  • Exit 0 each: check:new-line-citations (VERDICT 0 new citations), check:control-bytes, check-changeset-presence (10 source files of 3 released packages, 1 changeset), check-changeset-no-major, check:changeset-claims, check:pending-changeset-literals, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:esm-specifiers (specifiers only), check:self-import, check:phantom-deps, check:handler-key-reads, check:spec-symbols, check:doc-fences, check:doc-example-ids. A raw control-byte self-scan of the changed files found 0.
  • Locale gates not owed: no locale string changed.
  • NOT MEASURED: check:readme-exports. Reason: it needs every package built (population floor 25), and this tree built only the closure. The README edits add no fenced block or import.
  • NOT MEASURED yet: check:eager-closure. It needs a base and a head console build. The reading lands in the report.
  • Lint, narrowed, three parts. ① The population is eslint.config.js's **/*.{ts,tsx} blocks; the 10 touched .ts/.tsx files all fall in them. ② --format json read 10 files, 0 errors. ③ Invariance: the config has no projectService, parserOptions.project or TypeChecked preset, so linting is not type-aware and this diff cannot move any untouched file's verdict. On added lines, the only non-any warnings sit on the two pre-existing dependency-array lines. Both exist on main: the fetch effect's are suppressed by its inline directive under eslint ., and the view memo's (onColumnStateChange) is a standing warning. Repo-wide pnpm lint is declared to CI.

Acceptance notes (observed, not filed)

  • The standalone ObjectCalendar (no host data) still fetches its whole filtered set up to the a′ ceiling, with its footnote, rather than its visible window. It is loud, not silent, so it is not a defect. It could reuse getVisibleDateRange. Carrier: none.
  • Walking by $skip relies on the backend answering the view's $orderby, or no order, stably between requests. The paged grid's pager relies on the same property. Carrier: none.
  • Resolved in this PR by b5a5d1b: content/docs/plugins/plugin-calendar.mdx used to list "Date Filtering: Automatically filters records by date range" as an ObjectCalendar feature. The bullet now names the visible-day report, links the Visible days section, and says that the standalone component fetches its whole filtered set up to the row ceiling.
  • The Tests and Gates sections above were read on 25e515d. The landing head b5a5d1b adds only a main merge and prose: the DEFAULT_LIST_FETCH_BATCH_SIZE docblock, the 9853 pin's header, two changeset bullets and the mdx bullet. No code or assertion changed. Contract review 6105341715 is on b5a5d1b.

Generated by Claude Code

…it draws, walked in fetch batches

A calendar under a list view drew the rows of ListView's one unpaged
fetch: the first 100 records of the object, with no date condition. The
fetch now selects the calendar's visible days on its start (and end)
field, plus the records with no start date, and walks them in steps of
the fetch batch up to the non-grid ceiling, whose footnote ListView
draws. ObjectCalendar reports the days it draws through a new
onVisibleRangeChange host callback.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AswpQDLCKiZos2jCXknwKz
…s date window, its walk, its ceiling and the calendar's range report

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AswpQDLCKiZos2jCXknwKz
…wn module; docs, changeset and the 9853 calendar row

The grid's week arithmetic moves to visibleDays.ts so the calendar's range
report survives a test that stubs the CalendarView module. ListView's
calendar branch keeps its own binding reads. The 9853 pin's calendar row
now asserts the walk: every step is the fetch batch.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AswpQDLCKiZos2jCXknwKz
…etState in an effect, for the calendar window

The calendar refreshes its callback ref in an effect. ListView holds the
window and its truncation in one state and drops it during render when the
view leaves the calendar.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 290 chunks) 3171.0 KB 3204.6 KB
Main entry chunk (gzip) 74.1 KB 350 KB
Entry file index-BXJb6JSo.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) 19.75KB 7.29KB
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.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
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) 586.66KB 141.23KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 269.55KB 68.24KB
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) 14.32KB 5.17KB
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) 54.31KB 15.86KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.52KB 47.99KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 249.19KB 65.68KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.59KB 46.00KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.43KB 69.15KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 122.77KB 31.19KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.06KB 11.80KB
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.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
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: 25e515dd415c451134be11180083d0314bf4d242
Local-runs: none

① Derived judgments

Inputs: the card (objectui#12081, body and every comment through the os-dev-report 6105106125), the PR body, its 14-file list and the net diff against main at this head, the 43 check-runs on the head, and the two rulings the thread cites (objectui#7210 comment 5508048888, ruling a′; objectui#9853 comment 5824040487, the fetch-batch ruling). Published entries read at the head where a judgment needed them: the plugin-calendar and plugin-list entries, pickHostCallbacks, mergeFilterNodes, applyNonGridRowCeiling, ValueDataSource, the data-objectstack filter translation, and the spec's FilterArraySchema.

Gate verdicts on the head: 40 check-runs success, 3 skipped (the coverage matrix and dependabot), 0 failures. Governed Surface Queue Guard success, and the file list touches none of .claude/**, skills/**, docs/adr/**, AGENTS.md, CLAUDE.md: NOT GOVERNED. Head repo equals base repo (not a fork). 1,233 changed lines. Draft, Part of #12081 (item 2) on line one, no closing keyword near a card number.

  1. New published prop, ObjectCalendarComponentProps.onVisibleRangeChange (optional). The interface is exported from the plugin-calendar entry under two names (ObjectCalendarComponentProps, ObjectCalendarProps), so this is a public-surface widening. RIGHT: optional, additive, typed on the component props (a host hand-off), not on the authoring face; @object-ui/types is untouched and the standalone component's behaviour is unchanged.
  2. object-calendar renderer accept set: HOST_CALLBACKS gains onVisibleRangeChange. pickHostCallbacks forwards a declared key only when the value is a function, so an authored JSON value cannot reach it, and the HostCallbacks type is picked off the interface, so the list and the declaration cannot drift. RIGHT, and it is the same channel onRowClick and onNavigate already travel from ListView's calendar node. Not an authorable key; check:handler-key-reads and the zod-mirror parity pin are green on the head.
  3. ListView wire change: the hosted calendar's $filter joins a window node under one and through mergeFilterNodes; $top is DEFAULT_LIST_FETCH_BATCH_SIZE; $skip advances. mergeFilterNodes drops an undefined source and wraps only when two or more survive, so a view with no filter sends the window alone. The window node is an or of the in-window arm and [startField, 'is_null']; the two-element null predicate is the form the spec's FilterArraySchema declares ("real and deliberate", accepted at every door), ValueDataSource evaluates it, and the data-objectstack translation leaves a comparison tuple untouched inside a recognised or head. RIGHT. The span arm (start before the window's end, and start or end at or after its beginning) selects exactly the spans that touch the window; a span with no end falls to the start arm. RIGHT. Bounds are local YYYY-MM-DD days widened by one day each side, the spelling core's date macros already resolve to.
  4. Walk semantics against ruling a′ and the fetch-batch ruling. Every step asks at most the batch (100), never a bigger $top; the walk stops at the first short batch or one probe row past NON_GRID_ROW_CEILING; applyNonGridRowCeiling cuts and decides truncation from the probe row, never from total; NonGridRowCeilingNote is drawn under the calendar naming N and M. RIGHT on both rulings (a′: a loud footnote, never silent truncation; 5824040487: the step is the batch, not the page size). Pinned: case (c) asks 2,001 rows in total and the note names 2000 and 2050.
  5. dataLimitReached off and the rows-per-page picker withheld on a windowed calendar; a declared pagination.pageSize no longer sizes the calendar's step. RIGHT: the window is walked to its end, so the "Showing first 100 records" note would be false, and a page size changes nothing drawn, so a picker would advertise an inert affordance. The 9853 CONTROL (grid and kanban) still holds. Noted: the header prose of ListView.pageSizeAndFetchBatch-9853 ("A declared pagination.pageSize still sizes the window on every view") is now untrue for the calendar and was not amended; a same-file comment no gate reads, left for a follow-up touch of that file.
  6. Visible-range report. getVisibleDateRange reads the same getMonthDays and getWeekStart the grids draw with, under firstDayOfWeek(dialogLocale), and CalendarView's effectiveLocale resolves the locale prop by the same rule (an explicit tag wins, else the display locale), so the reported days are the drawn days. The effect is keyed on two instants and the callback read through a ref: AGENTS.md [WIP] Enhance every detail of the designer #10 honoured, pinned by the fresh-handler case. RIGHT. The two helpers moved verbatim into visibleDays.ts; neither new module is re-exported from either entry.
  7. Host-side window state. initialCalendarWindow covers any month grid today can open on (six leading and fourteen trailing days, then one day each side), so the calendar's first report asks for nothing; a covered range refetches nothing unless the held window was truncated; leaving the calendar view resets the window during render. The list skeleton no longer unmounts a windowed calendar, and ObjectCalendar syncs external data and loading through its own effects, so the calendar keeps its month across a refetch. RIGHT, and the app-shell pin mounts the real pair: 122 October chips drawn, Next period draws all 110 November chips with the header still on November.
  8. Controls. The paged grid's request literal is unchanged (spec display default, $skip on page 2, no window); kanban stays one batch with no filter; a calendar with no start binding keeps one batch and receives no callback. RIGHT.

② Semver level

Changeset .changeset/12081-calendar-date-window.md: @object-ui/plugin-calendar: minor, @object-ui/plugin-list: patch. It matches what the diff publishes: the one widening is on plugin-calendar's exported props type, so minor there; plugin-list publishes no new export (its entry is not in the diff, has no export *, and calendarWindow.ts is not re-exported), so patch for a behaviour fix. app-shell's change is test-only and publishes nothing, so no entry is owed. No major (Changeset Bump Policy, Changeset Declaration and Changeset Claim Re-read green). The body states the FROM and TO behaviour, what is unchanged, and the new member's contract. Clause-②: yes sits on the PR's second line, and the changeset carries Clause-②: yes (widening): one arm, not breaking, so no ADR-0087 disposition is owed. Gap noted, not blocking: the changeset body does not say that a declared pagination.pageSize no longer sizes the calendar's fetch, nor that the rows-per-page picker is withheld there; the plugin-list README states the first and the PR body states both.

③ Boundary flags

open_questions: none declared.

Deviations (five), each answered:

  • File surface extended (two new pure modules, the renderer's HOST_CALLBACKS, the 9853 calendar row, the app-shell cross-package pin, three docs): ACCEPTED. Each is named in the PR and tied to item 2, and the docs are what AGENTS.md's docs-driven rule asks for.
  • Route (ListView windows its own fetch rather than the hosted calendar fetching its own window): ACCEPTED. One fetch owner keeps toolbar search, Refresh, export and the user-filter counts on the calendar's rows.
  • A declared pagination.pageSize no longer sizes the calendar's step, and the picker is withheld: ACCEPTED, per ① item 5.
  • The PR was opened before the eager-closure and union re-run at this head finished: ANSWERED by the head's check-runs. Bundle Analysis is green with the Console Performance Budget at PASS (3171.0 KB against a 3204.6 KB budget), the eight test shards are green, and README Export Check is green, which covers the dev's NOT MEASURED item.
  • Commit trailers in the model-free pair: ACCEPTED. No model identifier appears in the PR title, body, changeset or this thread.

Out-of-scope findings (three, carrier none), each answered:

  • The standalone ObjectCalendar still fetches its whole filtered set up to the a′ ceiling with its footnote: ACCEPTED as noted. Loud, not silent, and outside item 2's claim.
  • The $skip walk relies on a stable order between requests, as the grid's pager does: ACCEPTED as noted. An inference, not a measured defect.
  • content/docs/plugins/plugin-calendar.mdx lists "Date Filtering: Automatically filters records by date range" under the standalone component's features while that fetch is not windowed: ESCALATED to the card's owner as a docs-only correction (trim the bullet, or point it at the hosted path the new "Visible days" section describes). Pre-existing prose not introduced by this diff, so not a defect of it.

Also noted by this review: the 9853 pin's header sentence named in ① item 5.

Implemented-by: claude/issue-12081-calendar-date-window
Reviewed-by: session_01AswpQDLCKiZos2jCXknwKz

VERDICT: PASS


Generated by Claude Code

…es a windowed calendar's fetch, said where it was said otherwise

The fetch-batch docblock and the 9853 pin's header said a declared
pagination.pageSize sizes every unpaged view's fetch; a calendar with a
start date bound now walks its window in batch steps whatever is
declared. The changeset names that and the withheld rows-per-page
picker. The calendar docs' "Date Filtering" bullet described a standalone
behaviour that does not exist; it now points at the hosted path.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 290 chunks) 3171.0 KB 3204.6 KB
Main entry chunk (gzip) 74.1 KB 350 KB
Entry file index-BAEznqDB.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) 19.75KB 7.29KB
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.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
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) 586.66KB 141.23KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 240.39KB 67.09KB
fields (index.js) 269.55KB 68.24KB
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) 14.32KB 5.17KB
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) 54.31KB 15.86KB
plugin-charts (index.js) 84.72KB 23.27KB
plugin-chatbot (index.js) 201.52KB 47.99KB
plugin-dashboard (index.js) 144.20KB 38.95KB
plugin-designer (index.js) 233.53KB 49.80KB
plugin-detail (index.js) 249.19KB 65.68KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.59KB 46.00KB
plugin-gantt (index.js) 179.17KB 45.07KB
plugin-grid (index.js) 249.43KB 69.15KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 122.77KB 31.19KB
plugin-map (index.js) 27.24KB 9.03KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 39.06KB 11.80KB
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.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
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: b5a5d1b520c6e501a78297d7e9812e1a543c732d
Local-runs: none

① Derived judgments

Inputs: the card (objectui#12081, body and every comment through the patch-round os-dev-report 6105245333), the PR body, its 14-file list, the net diff against main at this head, the compare 25e515dd41...b5a5d1b5 (the delta since the previous record), the check-runs on this head, and the two rulings the thread cites (objectui#7210 comment 5508048888, ruling a′; objectui#9853 comment 5824040487, the fetch-batch ruling). Read at this head where a judgment needed them: the plugin-list and plugin-calendar entries, ObjectCalendar.tsx, CalendarView.tsx, ListView.tsx, core's mergeFilterNodes and applyNonGridRowCeiling, the data-objectstack filter translation and both find() routes, the plugin-calendar docs, and the 9853 pin.

What moved since record 6105204384 (head 25e515dd41): three commits, ahead 3 and behind 0. One is origin/main a21ff9a (objectui#12097, PR objectui#12115: three app-shell marketplace files and their changeset, none on this PR's surface; they are not in the net diff). One is the merge. One, b5a5d1b, is prose only, and I read each of its four patches: two bullets added to the changeset; one feature bullet replaced in content/docs/plugins/plugin-calendar.mdx; five comment lines for two in the DEFAULT_LIST_FETCH_BATCH_SIZE docblock of ListView.tsx; five comment lines for two in the header of ListView.pageSizeAndFetchBatch-9853.test.tsx. No code line, test assertion or export changed, so the judgments below are on the same code the earlier record judged, re-derived here rather than inherited.

Gate verdicts on this head, read at 2026-10-11T04:11Z: 42 check-runs, 31 success, 3 skipped (the coverage matrix and dependabot), 8 in_progress, all eight the Test (shard N/8) jobs. An honest reading, not a pass: the same eight were success on 25e515dd41, and the delta touched no code or assertion, but the seat reads them green before the queue. Type Check, Lint, README Export Check, Spec Main Shape Gate, Internal Docs Link Check, every Changeset * gate and Bundle Analysis (Console Performance Budget PASS, 3171.0 KB against 3204.6 KB, comment 6105256545 on this head's entry) are success. Governed Surface Queue Guard success, and the file list touches none of .claude/**, skills/**, docs/adr/**, AGENTS.md, CLAUDE.md: NOT GOVERNED. Head repo equals base repo (not a fork). 1,251 changed lines. Draft, Part of #12081 (item 2) on line one, Clause-②: yes on line two, no closing keyword near a card number.

  1. Public surface: ObjectCalendarComponentProps gains the optional member onVisibleRangeChange, a function of { start: Date; end: Date } returning nothing. The interface is exported from the plugin-calendar entry under two names (ObjectCalendarComponentProps and the deprecated alias ObjectCalendarProps), so this is a published widening. RIGHT: optional, additive, on the component props face (a host hand-off), not on the authoring face; @object-ui/types is untouched, and the standalone component's own fetch is unchanged.
  2. Renderer accept set: HOST_CALLBACKS in plugin-calendar/src/index.tsx gains onVisibleRangeChange, serving both object-calendar and view:calendar. pickHostCallbacks forwards a declared key only when the value is a function, so an authored JSON value cannot reach it, and HostCallbacks is Picked off the interface, so the list cannot name a key the interface does not declare. RIGHT, and it is the channel onRowClick and onNavigate already travel from ListView's calendar node; no authorable input is declared for it. Noted, not blocking: the same file's boundary docblock still counts the interface at "eight callbacks and a locale" (now nine); it narrates the objectui#4492 measurement, no gate reads it.
  3. ListView wire change for a self-fetching calendar view with a start binding (calendarWindowed): $filter becomes mergeFilterNodes(finalFilter, windowNode). Verified at this head: mergeFilterNodes maps each source through toFilterNode, drops the ones that normalise to nothing, returns one survivor as-is and otherwise ['and', ...nodes], so a view with no filter sends the window alone and every panel, user and search narrowing still applies. The window node is an or of the in-window arm and the 2-tuple [start, 'is_null']; with an end field bound the arm is "start before to AND (start at or after from OR end at or after from)", which selects exactly the spans that touch the window, and a span with no end falls to the start arm. On the wire, data-objectstack's translateFilterArray passes and/or heads through, recurses into array children and leaves a comparison tuple or 2-tuple untouched, on both find() routes. The valueless 2-tuple is the form filter-converter.ts already lowers a value-less ViewFilterRule to (VALUELESS_VIEW_OPERATORS: the spec takes the direction from the operator name), so this is an established shape, not a new dialect. Bounds are local YYYY-MM-DD days widened by one day each side, which covers any UTC offset of a stored instant. RIGHT.
  4. Walk against the two rulings. fetchCalendarWindow asks $top of at most the batch (the smaller of 100 and the rows still wanted up to the ceiling plus one probe row), advances $skip by the rows in hand, and stops at the first short batch or at 2,001 rows. applyNonGridRowCeiling (verified at this head) decides truncated from the probe row, never from total, and carries a reported total for the note; NonGridRowCeilingNote is drawn under the calendar. So the step is the batch, never a bigger $top (ruling 5824040487), and the ceiling is crossed loudly, never silently (ruling a′). Pinned: case (c) asks 2,001 rows in total and the role=note names 2000 and 2050; case (a) has every $top at most 100 and more than one step. RIGHT. Named as a reliance, not a defect: the short-batch stop assumes the server answers the requested $top in full until the set ends; a server that capped $top below the batch would end the walk early with truncated false. The single $top: 100 fetch this replaces and the grid's pager rest on the same property. Carrier none.
  5. A declared pagination.pageSize no longer sizes a windowed calendar's step, dataLimitReached is forced off there, and the rows-per-page picker is withheld there. RIGHT: the window is walked to its end, so "Showing first 100 records" would be false; a page size changes nothing drawn, so the picker would be an inert affordance; and structure B says a page size means one page only where there is a pager, which the calendar does not have. The 9853 pin at this head has the calendar row out of UNPAGED_VIEWS and in its own row asserting the walk (one call per batch of the total, every $top the batch, never the display default); its CONTROL (grid and kanban under a declared size) is unchanged; and its header prose now says "every view but a windowed calendar". The behaviour is stated in the changeset, the plugin-list README and the DEFAULT_LIST_FETCH_BATCH_SIZE docblock. RIGHT.
  6. Visible-range report. ObjectCalendar computes getVisibleDateRange(currentDate, view, firstDayOfWeek(dialogLocale)) with dialogLocale = the locale prop when set and not 'default', else the display locale. CalendarView receives locale={locale}, defaults it to "default", and resolves effectiveLocale by the same rule before weekStart = firstDayOfWeek(effectiveLocale), so the reported days are the drawn days. getMonthDays and getWeekStart moved into visibleDays.ts with identical bodies (both hunks read), and CalendarView imports them back; neither new module is re-exported (both entries read: no export *, no mention). The report effect is keyed on two instants and the callback is read through a ref refreshed by a dependency-less effect declared ahead of it, so an unchanged range is never reported twice and a fresh handler does not re-report. Pinned under en-US and en-GB, Next period, week, day, fresh handler. RIGHT.
  7. Host-side window state. initialCalendarWindow covers six days before the 1st through fourteen after the month's last day, then one more each side, so any month grid today can open on falls inside it and the calendar's first report asks for nothing; a covered range keeps the held window unless the last fetch was truncated; leaving the calendar view resets the window during render, and the view pane is keyed on currentView (ListView.tsx line 5567), so a calendar shown again opens on today. fetchSkip is 0 off the paged grid (paginate is grid-only), so the walk's $skip is the only skip. isCurrent is asked after every step and a superseded walk returns null and sets nothing. The list skeleton is not shown over a windowed calendar, loading reaches it only when it has nothing to draw, and ObjectCalendar's loading placeholder is an in-component early return under state it keeps (currentDate, view), with external data and loading synced by effect, so the month survives a refetch. The app-shell pin mounts the real pair: 122 October chips, then Next period draws all 110 November chips with the header on November. RIGHT.
  8. Controls. The paged grid's request literal is unchanged (the spec display default, $skip on page two, no window, no callback); kanban is one batch with no filter; a calendar with no start binding keeps one batch, no filter and receives no callback. RIGHT.
  9. Docs. The plugin-list README paging paragraph carves the calendar out and says a declared size sizes "every view but the calendar"; the plugin-calendar README gains "Reporting the visible days"; the mdx gains a "Visible days" section, and its feature bullet now says the component reports its days to a host and on its own fetches the whole filtered set up to the row ceiling, replacing the false "Date Filtering" bullet. The mdx's "CalendarView Schema API" block lists the calendar-view element, not ObjectCalendar's props, so it owes nothing for the new prop. Internal Docs Link Check is green on the new #visible-days anchor. RIGHT.

② Semver level

Changeset .changeset/12081-calendar-date-window.md: @object-ui/plugin-calendar: minor, @object-ui/plugin-list: patch. It matches what the diff publishes. The one widening is on plugin-calendar's exported props interface, so minor there. plugin-list publishes nothing new: its entry is not in the diff and has no export *, calendarWindow.ts is not re-exported, and DEFAULT_LIST_FETCH_BATCH_SIZE was already exported from ListView.tsx and still not from the entry; a declared page size that no longer sizes the calendar's step removes no key, refuses nothing and sizes every other view as before, so it is a behaviour fix at patch. app-shell's change is test-only and owes no entry. No major (Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Fixed Group Check green). Clause-②: yes sits on the PR's second line and the changeset carries Clause-②: yes (widening): one arm, consistent, not breaking, so no ADR-0087 disposition is owed. The body states the FROM and TO behaviour, what is unchanged, the new member's contract, and, since b5a5d1b, both user-visible changes the earlier record found missing (a declared page size no longer sizes a windowed calendar's fetch; the rows-per-page picker is not offered there). The 12097 changeset that arrived through the main merge is main's, not this PR's. RIGHT.

③ Boundary flags

open_questions: none declared, in either os-dev-report (6105106125, 6105245333).

The REWORK 6105214842 (four prose items): each RESOLVED at this head, read in the compare patches. (1) the DEFAULT_LIST_FETCH_BATCH_SIZE docblock; (2) the 9853 pin's header; (3) the changeset's two bullets; (4) the mdx "Date Filtering" bullet.

Deviations from the first report (five), each answered:

  • File surface extended (two new pure modules, the renderer's HOST_CALLBACKS, the 9853 calendar row, the app-shell cross-package pin, three docs): ACCEPTED. Each is named in the PR and tied to item 2; the docs are what the docs-driven rule asks for.
  • Route (ListView windows its own fetch rather than the hosted calendar fetching its own window): ACCEPTED. One fetch owner keeps toolbar search, Refresh, export and the user-filter counts on the calendar's rows.
  • A declared pagination.pageSize no longer sizes the calendar's step, and the picker is withheld: ACCEPTED per ① item 5, and now stated where the changelog reader and the pin reader look.
  • The PR was opened before the eager-closure and union re-run finished: ANSWERED. The report gave the union (221 files, 2393 passed), and this head's Bundle Analysis is green with the budget at PASS.
  • Commit trailers in the model-free pair: ACCEPTED. No model identifier appears in the PR title, body, changeset or this thread.

Deviations from the patch round (two), each answered:

  • The PR body's third Acceptance note (the mdx "Date Filtering" bullet) is resolved by b5a5d1b and its "Tests" and "Gates" headings still name 25e515d: ESCALATED to the owning seat as a body edit. The body is accurate about the head it names, and the delta is a main merge plus prose, so this is not a defect of the diff.
  • The local merge commit was amended before its push: ACCEPTED. The compare is a plain fast-forward (ahead 3, behind 0), so no pushed history was rewritten.

Out-of-scope findings from the first report (three, carrier none), each answered:

  • The standalone ObjectCalendar still fetches its whole filtered set up to the a′ ceiling with its footnote: ACCEPTED as noted. Loud, not silent, and outside item 2's claim; the mdx bullet now says so.
  • The $skip walk relies on a stable order between requests, as the grid's pager does: ACCEPTED as noted. An inference, not a measured defect.
  • The mdx listed "Date Filtering" as a standalone feature: RESOLVED at this head, closing the earlier record's escalation.

Noted by this review, carrier none: the renderer docblock's "eight callbacks" count (① item 2) and the short-batch stop's reliance on a server that answers $top in full (① item 4). Neither blocks.

Implemented-by: claude/issue-12081-calendar-date-window
Reviewed-by: session_01AswpQDLCKiZos2jCXknwKz

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 11, 2026 04:20
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 11, 2026 04:20
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 11, 2026
Merged via the queue into main with commit 16c8810 Oct 11, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12081-calendar-date-window branch October 11, 2026 04:39
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

Development

Successfully merging this pull request may close these issues.

2 participants