Skip to content

feat(types): retire 'agenda' from defaultView on all three declaration faces - #5806

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-5784-default-view-agenda-retire
Aug 23, 2026
Merged

feat(types): retire 'agenda' from defaultView on all three declaration faces#5806
os-sam merged 1 commit into
mainfrom
claude/issue-5784-default-view-agenda-retire

Conversation

@os-sam

@os-samos-sam commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

Fixes#5784

Clause-②: yes — accept-set narrowing on a published validator surface: an author who writes defaultView: 'agenda' will now be refused at validation, where it previously parsed green.

What

Narrows defaultView to ['month', 'week', 'day'] on all three declaration faces, in lockstep (Option A by the inherited ruling in the issue's triage comment, basis transferring from objectui#5740 — same renderer, same retired view):

  1. packages/types/src/objectql.ts — the ObjectCalendarSchema TS interface's inline union (not a CalendarViewMode reference, which is exactly why objectui#5740's narrowing did not move it)
  2. packages/types/src/zod/objectql.zod.ts — zod ObjectCalendarSchema.defaultView
  3. packages/types/src/zod/objectql.zod.ts — the list-view calendar config (CalendarConfig), the objectui-only defaultView extension of the spec's CalendarConfigSchema

Plus a retirement pin test (packages/types/src/__tests__/default-view-agenda-retired.test.ts, mirroring objectui#5740's pin) and a changeset that states the new rejection loudly (minor per this repo's version-alignment policy — majors track @objectstack; check:changeset-no-major verified locally, exit 0).

The runtime boundary is deliberately unchanged: ObjectCalendar still resolves an off-union raw value to its 'month' default. objectui#5740 remains not addressed beyond what already landed in PR #5782; objectui#5667 remains the parent ruling and stays as it is.

Why

The three faces admitted a value nothing enforced: ObjectCalendar's props declare defaultView?: 'month' | 'week' | 'day', its schema read casts to the same three, and CalendarView renders only those three — no agenda view exists. An author writing the zod-valid defaultView: 'agenda' got a month calendar with no error (declared ≠ enforced, ADR-0049). The spec side already agrees: @objectstack/spec's ObjectCalendarProps.defaultView is z.enum(['month', 'week', 'day']) (packages/spec/src/ui/component.zod.ts:2353), so this also removes a spec-alignment gap (commandment #0).

Authorship sweep (with positive controls)

Measured against origin/main of both repos at implementation time (objectui 641543f69, objectstack 918988a):

  • objectui: defaultView + agenda value pattern — 0 hits. Positive control: the same grep shape against month|week|day hits 3 real authored defaultView: 'week' fixtures, so the pattern is proven live on this corpus.
  • objectstack: same pattern — 0 hits. The shape-control was 0 there too (no calendar defaultView authors at all), so the broader census is the control: the entire tree contains exactly one agenda token, and it is the Agenda npm job-scheduler mention in packages/core/src/fallbacks/memory-job.ts — not a calendar value.
  • Producer check (who writes the key, not just who reads it): app-shell/ObjectView.tsx:2170 and plugin-list/ListView.tsx:2061 forward calendar.defaultView from authored metadata — pass-throughs, not producers of 'agenda'. No synth or generator writes this key.
  • Reachability: stored customer metadata is not reachable from this seat (objectui#5741), so this is a "no reachable usage" claim, not "no usage".

Verification

  • pnpm exec vitest run packages/types/ packages/plugin-calendar/ (repo root, per AGENTS.md): 57 files / 598 tests passed, re-run on final HEAD bec062d53 with a clean tree — sha quoted from that run's own output.
  • pnpm --filter @object-ui/types type-check: green (includes tsconfig.test.json, which is what makes the @ts-expect-error pin real enforcement).
  • Reverse verification (trap-guarded ablation, committed-state): restored the pre-narrowing faces from base 641543f69, proved the mutation on disk by anchored grep (four-value zod enums: 2, four-value TS union: 1), re-ran the pin file → exactly the 2 refusal pins failed, 4 passed; trap restored HEAD, restoration proven by the same anchors reading 0/0, re-run 6/6 green. No build step is involved in either leg — the pins import the schemas by source-relative specifiers, so no dist/ participates in resolution. The TS face is self-verifying: the green type-check with the @ts-expect-error present proves tsc flags 'agenda' on that line (an unused directive is itself an error).
  • Targeted gates, each read from its own verdict line: check:spec-symbols ✅, check:control-bytes ✅, scripts/check-changeset-presence.mjs ✅, scripts/check-changeset-no-major.mjs ✅.
  • Declared narrowing: repo-wide lint and the remaining check:* farm are left to CI, which runs them in full; locally only the families implicated by this diff were run (listed above).
  • CI note for review: read gates by name — Type Check carries check:spec-symbols; Build Docs is red on main (objectui#5668, inherited) and its ~10s cached greens are not evidence either way, so it is excluded with cause.

Fixture triage

Repo-wide sweep of the narrowed enums' consumption radius: zero fixtures author defaultView: 'agenda'. The only agenda-valued fixture anywhere is calendar-view-renderer.propsContract.test.tsx (view: 'agenda'), which is objectui#5740's own pin on a different key and passes because rejection is what it asserts. list-view-spec-parity.test.ts keeps local: ['defaultView'] — the key survives; only its value set narrowed.

Out of scope

The i18n calendar.agenda label key exists in all 10 locales with zero consumers (CalendarView's selector reads only calendar.day/week/month) — dead residue this run re-confirmed independently. Already on file as #5783 (open, finding), so no new issue was created; #5783 remains open and is not addressed here.

Generated by Claude Code

…n faces
Narrow `defaultView` to `['month', 'week', 'day']` in lockstep (objectui#5784,
the `defaultView` sibling of objectui#5740; ADR-0049 enforce-or-remove):
- `ObjectCalendarSchema` TS interface — an inline union, which is why #5740's
`CalendarViewMode` narrowing did not move it
- zod `ObjectCalendarSchema.defaultView`
- the list-view `calendar` config's objectui-only `defaultView` extension
The enforcement points read three values (`ObjectCalendar` props and its
schema read; `CalendarView` renders no agenda view), and the spec side
already declares `['month', 'week', 'day']` on `ObjectCalendarProps`.
Declared keys are validated even under `.passthrough()`, so
`defaultView: 'agenda'` is now an `invalid_value` refusal where it
previously parsed green — stated in the changeset; pinned by the refusal
envelope, survivor full-green parses, a passthrough control, and a
`@ts-expect-error` on the TS twin.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E7snar5mwF7qoXJazqKhys
@os-samClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — verified against the tree, not the report

Re-derived the load-bearing claims independently. All hold, and one is stronger than the card assumed.

1. The spec-alignment finding is real, and it upgrades the ruling. The dev measured that @objectstack/spec's ObjectCalendarProps.defaultView is alreadyz.enum(['month','week','day']). Confirmed in the objectstack tree at my own checkout (52a41b7, line 2314 — the dev cited 2353 from base 918988a; different base, same fact). I did not brief this. It means objectui's four-value faces were the outlier, so Option A is not merely an accept-set narrowing under ADR-0049 — it closes a spec-alignment gap under commandment #0. The inherited ruling turns out to rest on firmer ground than the basis it inherited from.

2. No fourth face was missed. I did not trust my local checkout — it was stale (pre-#5782), which would have made any grep on it worthless. Re-ran at the PR's actual base 6ce89daae: exactly threedefaultView declarations carry 'agenda', and they are precisely the three this PR narrows. #5740's own surfaces (complex.ts, complex.zod.ts) are present and already narrowed there — confirming the branch really does carry #5782, and that defaultView was genuinely the residue #5740's CalendarViewMode narrowing could not reach.

3. The @ts-expect-error pin is real enforcement, not decoration. This was worth checking because a @ts-expect-error in a file no tsc invocation reads is a no-op that looks like a test. packages/types declares:

type-check: tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json

tsconfig.test.json is chained, so the directive is compiled — and since an unused@ts-expect-error is itself an error, a green type-check proves tsc rejects 'agenda' on that line. Self-verifying, exactly as claimed.

4. The producer check holds.app-shell/ObjectView.tsx:2170 and plugin-list/ListView.tsx:2061 both forward authored calendar.defaultView verbatim; neither manufactures a value. No synth or generator writes this key. I asked for this explicitly because last round's card (#5494) was written as a consumer-side retirement and objectui's own synth turned out to be a live producer of the retired key.

5. Test construction is better than the bar. The zod faces are pinned by the refusal envelope — one issue, on the right path, coded invalid_value, with the retired value absent from the offered vocabulary and every survivor present — rather than a bare success === false, which would also pass if the node were rejected for an unrelated reason. The .passthrough() control keeps declared-key validation distinct from a strictness change. Survivors are asserted as full green parses, so a value-level shrink that quietly invalidated one would not slip through.

Observed, not measured, not filed

plugin-view/src/index.tsx:113 declares a differentdefaultView (type: 'string', default 'grid') read by ViewSwitcher.tsx:179 — same key name, disjoint vocabulary from the calendar view-mode enum, both authorable in SDUI JSON. That is the same-name/different-vocabulary hazard shape object-strictness-batch20.test.ts:410 exists to catch. It is pre-existing, untouched by this diff, and on another lane's surface (plugin-view), and I have not measured whether the two can ever meet on one node. Recording it here rather than filing a card I would be guessing at, or burying it.

Merge posture — deliberately different from last round

Gates read by name. Type Check (which carries check:spec-symbols, ci.yml:238) and the four Test (shard n/4) jobs are still running.

I am not arming auto-merge. Last round I armed it on #5794 and told the card it was holding for the test shards; it was not — Test (shard n/4) is not a required check here, and the PR merged with two shards still in flight. They passed, but the assumption was wrong and I corrected it publicly. So this one waits for the full farm and gets merged deliberately.

Build Docs reports success in 10 seconds — a turbo cache hit, not a build. Not counted as evidence in either direction; it is red on main (#5668) and inherited.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3226.6 KB3990.2 KB
Main entry chunk (gzip)152.7 KB350 KB
Entry fileindex-1VGv_5fn.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.04KB3.72KB
app-shell (runtime-config.js)12.80KB4.47KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)33.99KB8.57KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)503.96KB113.94KB
core (index.js)4.92KB1.97KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)164.55KB45.67KB
fields (index.js)238.40KB59.89KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)186.10KB44.03KB
plugin-dashboard (index.js)128.41KB32.95KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)242.34KB60.99KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.63KB30.64KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.80KB27.20KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.61KB20.74KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)44.39KB14.99KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.33KB0.69KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.88KB1.85KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)3.40KB1.68KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

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

Projects

None yet

2 participants

@os-sam@claude