feat(spec): calendar in appearance.allowedVisualizations requires calendar.startDateField on list views - #14075
Conversation
…DateField (#13817) Cross-field refinement on the list-view schema, attached at all three doors (ListViewSchema, ObjectListViewSchema, the flattened runtime overlay arm): 'calendar' in appearance.allowedVisualizations with no calendar: block now refuses loudly at parse, naming calendar.startDateField and both remedies. titleField on CalendarConfigSchema moves required -> optional per the ruled 'only startDateField is load-bearing' measurement (ADR-0079 title fallback, measured in objectui ObjectCalendar.tsx). Regenerated reference docs. Ruled on #13748 (2026-08-31, director batch #19, option A); spec half of the two-half fix, objectui#7029 is the runtime half. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Mciyv38maJ6HYVMiaM26T1
📓 Docs Drift CheckThis PR changes 1 package(s): 13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 128 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 21aff86ef88caae71b975d0c0159228e21220c85 && git checkout 21aff86ef88caae71b975d0c0159228e21220c85
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a7002ce5ac9acf6a4d556713c587a770f1848c26 e5a2edeea140da8b6c13747111c4090212a30651 && git checkout -B drift-repro a7002ce5ac9acf6a4d556713c587a770f1848c26 && git merge --no-ff e5a2edeea140da8b6c13747111c4090212a30651
node scripts/docs-audit/affected-docs.mjs --json a7002ce5ac9acf6a4d556713c587a770f1848c26
|
Fixes #13817
Ruled on #13748 (2026-08-31, director batch #19, option A — fix both halves). This is the objectstack half; objectui#7029 (removing the invented
due_datedefault) is the runtime half — it remains open, lands independently, and nothing here pins its behavior.What changed
checkListViewCalendarVisualizationon the list-view schema (packages/spec/src/ui/view.zod.ts): whenappearance.allowedVisualizationscontainscalendar, thecalendar:block is required. Attached at all three doors that carrycheckListViewPageMount, for the same zod-4 derivation reason:ListViewSchema,ObjectListViewSchema(objects[].listViews.*), and the flattenedViewMetadataSchemalist-overlay arm (PUT /api/v1/meta/view— the Studio / MCP / AI-author door). The refusal is loud and specific: it namescalendar.startDateField, states why a guessed date has no truthful fallback (every record without the invented field lands on "today"), and gives both remedies (declarecalendar: { startDateField: 'DATE_FIELD' }, or dropcalendarfrom the whitelist).CalendarConfigSchema.titleFieldmoves required to optional. The ruling: "OnlystartDateFieldis load-bearing — title resolution has the ADR-0079 display-name fallback chain; do not require more than the renderer actually needs." Re-measured against the renderer before loosening: objectuiplugin-calendar/src/ObjectCalendar.tsxuses an explicittitleFieldonly when present ("1. Explicit titleField wins when present on the record. 2-4. Unified object-level resolver (ADR-0079)"). KeepingtitleFieldrequired would have made the new gate demand a key the renderer does not need — with it required, the ruled minimal shapecalendar: { startDateField }would be unauthorable, and the block schema is the intended owner of the "block present but no startDateField" refusal.calendaronly — the measured defect, per the ruling's scope clause. A scope-pin test asserts the requirement does not leak totimeline; the same-shape question for other visualizations is filed for measurement as spec: measure whethertimeline/gantt/map/ other switcher visualizations share the #13748 shape — whitelisted inappearance.allowedVisualizationswith no binding block, renderer behavior unmeasured #14074 (spec: measure whethertimeline/gantt/map/ other switcher visualizations share the #13748 shape — whitelisted inappearance.allowedVisualizationswith no binding block, renderer behavior unmeasured #14074 remains open, out of scope here).content/docs/references/ui/view.mdx,data/object.mdx,api/protocol.mdx— generated tables only) and added the minor changeset marking the accept-behavior tightening (BREAKING accept-set narrowing under the launch-window convention; adr-0087 disposition recorded in the changeset).Pre-landing census (ruling item 4 — the population, named)
Every corpus reachable from this container was grepped for
allowedVisualizationscontainingcalendar(and for calendar blocks), at objectstack@6643ba1c:examples/app-showcaseviews (task.view.ts)calendar: { startDateField: 'due_date', ... }. Stays green.packages/lintfixtures (showcase-shape.fixtures.ts)packages/spectest fixtures (view.test.ts,page.test.ts)allowedVisualizations(whitelists are grid/gallery/kanban). Green.skills/(all published SKILL.md)content/docsallowedVisualizations; no authored snippet whitelists calendar. Green.packages/create-objectstacktemplatesallowedVisualizationsoccurrences. Green.examples/app-showcasepages (task-visualizations.pages.tslines 48, 129; interface-page door)InterfacePageConfigSchemais a different schema with no calendar binding key at all. Filed as #14073 (#14073 remains open; recording, not a rider).Views flipping red in this repo: zero.
Not reached (named gaps, per the card's census note): hotcrm (separate repo, not present in this container — the #13748 measurement population there is uncounted by me), the
cloudrepo, and any external authored corpus. This is an honest partial census; the PM/CI see the rest.Verification (all readings at final commit e5a2ede; working tree clean throughout)
@objectstack/specfull suite via the verify lock: vitest "Test Files 447 passed (447) · Tests 12008 passed (12008)".viewDoorsCarryingPageMountCheckfixture: refusal with no block (message pinned to namecalendar.startDateFieldandallowedVisualizations), refusal with block-missing-startDateField (owned by the block schema; at the overlay door the truth sits nested insideinvalid_union— the test unwraps, asdiagnoseViewMetadatadocuments), acceptance ofcalendar: { startDateField }alone, acceptance without calendar in the whitelist, acceptance with noappearance, and the timeline scope pin.pnpm --filter @objectstack/spec typecheck: exit 0 —tsc --noEmitgreen,check:scripts-typecheckgreen,check:test-typecheck"OK — test layer compiles; 262 error(s) held in test-typecheck-debt.json (shrink-only)" (pre-existing pinned debt; no new entries). Coverage of the edited test file verified with--listFilesontsconfig.test.json:src/ui/view.test.tsis in the program.@objectstack/spec) — the two packages whose fixtures author calendar list views (grep-first):@objectstack/lintvitest "88 passed (88) / 2446 passed (2446)" +tsc --noEmitexit 0;@objectstack/example-showcasevitest "26 passed (26) / 364 passed (364)" +tsc --noEmitexit 0. Both after rebuilding their dependency closure.startDateFieldfrom the lint fixture's calendar block (mutation proven on disk: anchor grep old-present 1 to 0, new-present 0 to 1);@objectstack/linttsc flipped to exit 2 witherror TS2741: Property 'startDateField' is missing ... but required in type '{ startDateField: string; ... titleField?: string | undefined; ... }'— the expected type in the diagnostic is the REBUILT declaration (titleField already optional), proving the consumer reads the new dist, not a cache. Restore proven by emptygit diff HEADplus blob-hash identity (e58664a9...head == disk).node scripts/pm/dispatch-gates.mjs, derivation stamped "objectstack-ai/objectstack at commit e5a2ede"): 59 executable gates run; green ones include speccheck:authorable-surface,check:docs,check:generated(final run at e5a2ede: 15/15 artifacts fresh),check:strictness-ledger,check:liveness,check:empty-state,check:llms-txt,check:skill-refs,check:skill-examples, lintcheck:doc-formula-expressions/check:doc-security-posture(after building@objectstack/formula,@objectstack/client,@objectstack/client-react),check:changeset-gate-self-tests,check:merge-driver,check:test-source-alias,check:spec-parsed-alias, and the rest of the derived list.check-dev-prereqsandcheck:dual-build-cjs-loads(both PREREQUISITE NOT MET — they read the FULL workspace dist, ~30 packages unbuilt in this container; CI runs them after its own full build),check-test-completeness(exit 3 by design without a CI turbo log — its own text says "record this gate as NOT MEASURED"),check:react-declaration-parity(needs objectui's sdui manifest, unproducible here).eslint.config.mjs, which "never enables type-aware linting ... for ANY file" (config's own measured statement, line 325) — so this diff cannot move any untouched file's lint verdict; the restricted totality is the diff's two lintable files (the mdx/changeset files match no lint pattern in the config);--format jsoncount: 2 files linted, 0 errors, 0 warnings, exit 0.exports/dist path; the new tests import the schema source directly, and both red and green legs are live in the suite.Clause-② (ruled YES)
needs:contract-reviewgoes on this PR and on the card in the same stroke as PR creation. Accept-behavior tightening: previously-valid metadata (calendar whitelisted, no block) becomes invalid; the changeset marks it. Note for the sibling repo when it bumps spec: objectuipackages/typescarries list-view spec-parity pins that will need the titleField-optional update (named here, not touched — out of repo).Generated by Claude Code