Skip to content

feat(plugin-calendar): declare the three record-source keys object-calendar already honours - #8340

Merged
os-justin merged 2 commits into
mainfrom
claude/issue-8314-calendar-data-staticdata-loading
Sep 7, 2026
Merged

feat(plugin-calendar): declare the three record-source keys object-calendar already honours#8340
os-justin merged 2 commits into
mainfrom
claude/issue-8314-calendar-data-staticdata-loading

Conversation

@os-justin

@os-justin os-justin commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Fixes #8314

Slice 2b of objectui#8201, and the one that empties object-calendar's reverse-parity backlog: data, staticData and loading are now DECLARED on both plugin-calendar registrations, so the html tier stops reporting working metadata as unknown-prop. Every key @objectstack/spec declares for this block is discoverable.

Measured per key, at its own sink — and the sinks are not the same

key declared arm read site member pin asks
data array PROPS channel — resolveExternalData(rest.data) in index.tsx, forwarded as the component's data prop, where Array.isArray makes hasExternalData which keys are read INSIDE a record
staticData array SCHEMA channel — the shared record-source ladder's rung 2 inside ObjectCalendar the same record keys, plus the ladder's two POSITION claims
loading boolean PROPS channel — resolveExternalLoading(rest.loading), applied only under hasExternalData scalar — no member pin owed

loading's coupling to data was slice 1's reasoning; it is now a measurement. Authored loading: true replaces a data-fed calendar with its loading placeholder and does nothing at all to a staticData-fed one — one controlled pair, only the row-carrier differs, opposite verdicts. So it ships declared on exactly those terms, with the coupling named in its published description.

⛔ The false green this PR was written around — measured, not inherited

objectui#8313 found object-kanban's data doubly guarded. The same two-carrier arrangement exists here and produces a different false green, so it was re-derived rather than ported:

SchemaRenderer spreads every non-metadata node key as a React prop, so an authored data array arrives BOTH as rest.data at the renderer boundary AND as schema.data inside the component, where the ladder's rung 1 returns it verbatim as a record-source config. A bare array carries no provider, so it matches no fetch branch and no query is issued either way. "Authoring data skips the internal fetch" is therefore true of a tree where the boundary drops data entirely — an assertion resting on it cannot fail.

Confirmed by ablation: with data={externalData} deleted from the boundary, expect(dataSource.find).not.toHaveBeenCalled() passed and the very next line (the authored rows are drawn) reddened. The pin rests on the rows; the query row is kept, ordered query-first so the ablation reads off the failure line, and labelled as a companion rather than as the claim.

The three-part obligation (objectui#8212), paid for both array keys

  1. the inputs entries;
  2. the three matching UNPUBLISHED_EXEMPTIONS entries deleted in the same change;
  3. a MEMBER_PINS entry apiece — never an exemption, so MEMBER_PIN_EXEMPTION_CEILING stays 62.

Both halves of the shrink-only backlog ceiling move together, as the ratchet requires.

⚠️ The number was re-derived twice, and the second time by a real conflict. This branch was cut from d8ab2dc0c, where the ledger read toBe(8) — not the 4 the dispatch brief predicted, because #8313 (object-kanban's four array keys) had not landed yet. The first commit here therefore moved 8 → 5. #8313 then merged while this branch was open, main moved to 614d85b19, and the merge conflicted on exactly those lines. Resolved by re-deriving rather than by picking a side: the ceiling now reads 1, and the whole remaining backlog is object-kanban.quickAdd — the escalated key, untouched here. Both the history table and the prose carry both steps.

Neither array key declares an of, and that is derived

ComponentPropsMap['object-calendar'] (installed @objectstack/spec 17.3.0) rows both keys as z.array(z.unknown()).optional(). Every coarse member kind parses — ['a', 1, true, [], {}, null] is accepted whole — so no single-kind of is available (objectui#8067's rule), and the READ SITE is the whole of the member contract there is. Same reading the filter and sort entries on this block already record.

Clause ② — measured, not asserted

All three parse together on one strict safeParse call while a probe key on the same call draws unrecognized_keys:

THREE-TOGETHER   success=true
CONTROL-PROBE    success=false  [{"code":"unrecognized_keys","keys":["bogusProbeKey"]}]

So this restores declared = enforced rather than publishing anything new — the grounds objectui#8186 and objectui#8223 cleared on, re-measured because objectui#8172 falsified exactly this assumption for limit.

Descriptions name the POSITION they are true about

Each published description says when the key is honoured, not only what it means, and a pinned row keeps those sentences in place — a description recommending a write the renderer would drop is this gate's own failure mode one layer in. The negative half is pinned too: no description may teach the { provider, items } config spelling, because the contract refuses it by kind (invalid_type, expected: 'array') and recommending it would harden a second dialect (AGENTS.md #0.1).

One rider, in the file that owns it

staticData joins SUPERSEDES_BINDING in apps/console/src/__tests__/public-block-binding-reach.test.tsx. That probe fills EVERY declared input and then asserts the block queried its objectName; staticData is rung 2 of the ladder, one above objectName, so filling it is the author telling the block not to fetch. Without the entry the probe manufactures its own finding — which it did, as a red, the moment the declaration landed. Added with the guard quoted, exactly as that ledger's own docblock requires, and the file's instance count moved with it.

Verification — every verdict quoted, all of it re-run after the merge

apps/console/                           Test Files  89 passed (89)    Tests 1069 passed (1069)
plugin-calendar/ + plugin-kanban/       Test Files  60 passed (60)    Tests  420 passed (420)
@object-ui/plugin-calendar type-check   EXIT=0   (tsc --noEmit && tsc -p tsconfig.test.json)
@object-ui/console      type-check      EXIT=0   (tsc --noEmit && tsc -b tsconfig.node.json --force)
@object-ui/plugin-calendar lint         EXIT=0   ✖ 219 problems (0 errors, 219 warnings)
@object-ui/console      lint            EXIT=0   ✖ 211 problems (0 errors, 211 warnings)
check-control-bytes                     ✅ OK (scanned 6636 tracked text file(s))
check-changeset-presence                ✅ 5 source file(s) of 2 released package(s); 1 changeset
check-changeset-no-major                ✅ No changeset declares a `major` bump
check-sdui-registration-pins            ✅ All 16 registration(s) present (518 chunks weighed)
check-element-data-source-declaration   ✅ OK — 13 gate-consuming file(s)
check-side-effects-array / vi-mock-*    ✅ OK

Type-check ran only after pnpm --filter '@object-ui/console^...' build, twice — a stale dist reports TS2307/TS2882 across untouched files and is not a verdict.

Every ablation ran from the committed implementation, mutated a READ SITE (never a pin), proved the mutation reached disk by git hash-object differing from git rev-parse HEAD:PATH, and restored by state (git diff HEAD empty and the hash equal again):

ablation result
delete data={externalData} at the boundary 7 failed / 4 passed — every rows-are-drawn row red; the query row passed on the line above the failure
delete the ladder's staticData rung (packages/core) 3 failed / 8 passed — exactly the three staticData-dependent rows
delete loading={externalLoading} at the boundary 1 failed / 10 passed — exactly the loading-honoured row
re-add the deleted object-calendar.data exemption 2 failedcarries no stale unpublished-key exemption and the backlog ceiling
delete the object-calendar.staticData MEMBER_PINS entry 2 failed — the member-shape census and object-calendar pins the member shape of every array/object input it declares
strip loading's POSITION sentence 2 failed — the description row, on both tags

Under vitest every @object-ui/* specifier resolves to that package's src (vitest.config.mts resolve.alias), so no build stands between a source mutation and the measurement.

What is NOT here

  • object-kanban.quickAdd is untouched — its disposition is the product ruling objectui#8285 owns.
  • getCalendarConfig's calendar: {} behaviour is the ruled objectui#7071 one and was not "fixed".
  • One finding is reported to the PM rather than acted on: ObjectCalendar also honours an off-spec { provider, items } config object under data (measured — rows drawn, no query issued), a spelling ComponentPropsMap rejects by kind. Whether the renderer's extra dialect is retired or the contract widened is a product/contract ruling, not a measurement.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S

…lendar already honours

`ComponentPropsMap['object-calendar']` declares nine top-level keys; after
objectui#8201's first slice the registrations published six. These are the
remaining three — `data`, `staticData` and `loading` — and with them this
block's reverse-parity backlog is empty.

Each was measured at its own sink, and they are not the same sink: `data` and
`loading` are read off the PROPS channel at the renderer boundary
(`resolveExternalData` / `resolveExternalLoading` in `index.tsx`), `staticData`
off the SCHEMA by the shared record-source ladder's rung 2. `loading`'s
coupling to `data` was slice 1's reasoning and is now a measurement: authored
`loading: true` replaces a `data`-fed calendar with its loading placeholder and
does nothing at all to a `staticData`-fed one.

Each declared description NAMES the position it is true about, and a pinned row
keeps those sentences in place — a description recommending a write the
renderer would drop is this gate's own failure mode one layer in.

objectui#8212's three-part obligation is paid for both array-armed keys: the
`inputs` entry, deletion of the `UNPUBLISHED_EXEMPTIONS` entry, and a
`MEMBER_PINS` entry apiece — never an exemption, so
`MEMBER_PIN_EXEMPTION_CEILING` does not move. Both halves of the shrink-only
backlog ceiling move 8 -> 5.

Two findings recorded rather than assumed. "An authored `data` skips the
internal fetch" is true on BOTH carriers — `SchemaRenderer` spreads the node
key as a prop while the ladder also reads `schema.data`, and a bare array
carries no `provider`, so no query is issued either way — which makes it an
assertion that cannot fail; the pin rests on the authored ROWS BEING DRAWN
instead, and the companion row is labelled as such. And `staticData` joins
`SUPERSEDES_BINDING` in the console's binding-reach probe, with its ladder rung
quoted: filling it is the author telling the block not to fetch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
…endar-data-staticdata-loading

# Conflicts:
#	apps/console/src/__tests__/registry-inputs-spec-parity.test.ts
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3472.1 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-QIQABAX-.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) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 498.43KB 114.00KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 189.15KB 52.56KB
fields (index.js) 243.15KB 61.40KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
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) 15.16KB 3.68KB
plugin-calendar (index.js) 49.00KB 13.91KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 193.54KB 46.04KB
plugin-dashboard (index.js) 131.48KB 34.45KB
plugin-designer (index.js) 213.21KB 43.63KB
plugin-detail (index.js) 248.56KB 63.89KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.58KB 56.63KB
plugin-kanban (index.js) 55.38KB 15.72KB
plugin-list (index.js) 113.38KB 27.73KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.46KB 20.80KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@os-justin
os-justin marked this pull request as ready for review September 7, 2026 13:55
@os-justin
os-justin enabled auto-merge September 7, 2026 13:56
@os-justin
os-justin added this pull request to the merge queue Sep 7, 2026
Merged via the queue into main with commit 289d146 Sep 7, 2026
34 checks passed
@os-justin
os-justin deleted the claude/issue-8314-calendar-data-staticdata-loading branch September 7, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[plugin-calendar] declare the remaining spec keys object-calendar reads — data · staticData · loading (objectui#8201 slice 2b)

2 participants