Skip to content

feat(spec): calendar in appearance.allowedVisualizations requires calendar.startDateField on list views - #14075

Merged
os-support-ai merged 1 commit into
mainfrom
claude/issue-13817-calendar-crossfield-validation
Sep 1, 2026
Merged

feat(spec): calendar in appearance.allowedVisualizations requires calendar.startDateField on list views#14075
os-support-ai merged 1 commit into
mainfrom
claude/issue-13817-calendar-crossfield-validation

Conversation

@claude

@claude claude Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

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_date default) is the runtime half — it remains open, lands independently, and nothing here pins its behavior.

What changed

  • Cross-field refinement checkListViewCalendarVisualization on the list-view schema (packages/spec/src/ui/view.zod.ts): when appearance.allowedVisualizations contains calendar, the calendar: block is required. Attached at all three doors that carry checkListViewPageMount, for the same zod-4 derivation reason: ListViewSchema, ObjectListViewSchema (objects[].listViews.*), and the flattened ViewMetadataSchema list-overlay arm (PUT /api/v1/meta/view — the Studio / MCP / AI-author door). The refusal is loud and specific: it names calendar.startDateField, states why a guessed date has no truthful fallback (every record without the invented field lands on "today"), and gives both remedies (declare calendar: { startDateField: 'DATE_FIELD' }, or drop calendar from the whitelist).
  • CalendarConfigSchema.titleField moves required to optional. The ruling: "Only startDateField is 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: objectui plugin-calendar/src/ObjectCalendar.tsx uses an explicit titleField only when present ("1. Explicit titleField wins when present on the record. 2-4. Unified object-level resolver (ADR-0079)"). Keeping titleField required would have made the new gate demand a key the renderer does not need — with it required, the ruled minimal shape calendar: { startDateField } would be unauthorable, and the block schema is the intended owner of the "block present but no startDateField" refusal.
  • Scope: calendar only — the measured defect, per the ruling's scope clause. A scope-pin test asserts the requirement does not leak to timeline; the same-shape question for other visualizations is filed for measurement as spec: measure whether timeline / gantt / map / other switcher visualizations share the #13748 shape — whitelisted in appearance.allowedVisualizations with no binding block, renderer behavior unmeasured #14074 (spec: measure whether timeline / gantt / map / other switcher visualizations share the #13748 shape — whitelisted in appearance.allowedVisualizations with no binding block, renderer behavior unmeasured #14074 remains open, out of scope here).
  • Regenerated reference docs (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 allowedVisualizations containing calendar (and for calendar blocks), at objectstack@6643ba1c:

Corpus Result
examples/app-showcase views (task.view.ts) 1 view whitelists calendar — has calendar: { startDateField: 'due_date', ... }. Stays green.
packages/lint fixtures (showcase-shape.fixtures.ts) 1 view whitelists calendar — has the block. Stays green.
packages/spec test fixtures (view.test.ts, page.test.ts) No fixture whitelists calendar in allowedVisualizations (whitelists are grid/gallery/kanban). Green.
skills/ (all published SKILL.md) View examples whitelist grid/kanban/gallery only. Green.
content/docs Only generated reference tables mention allowedVisualizations; no authored snippet whitelists calendar. Green.
packages/create-objectstack templates Zero allowedVisualizations occurrences. Green.
examples/app-showcase pages (task-visualizations.pages.ts lines 48, 129; interface-page door) Whitelists calendar on the PAGE surface — not gated by this PR: InterfacePageConfigSchema is 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 cloud repo, 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/spec full suite via the verify lock: vitest "Test Files 447 passed (447) · Tests 12008 passed (12008)".
  • New rejection/acceptance tests run at all three doors via the shared viewDoorsCarryingPageMountCheck fixture: refusal with no block (message pinned to name calendar.startDateField and allowedVisualizations), refusal with block-missing-startDateField (owned by the block schema; at the overlay door the truth sits nested inside invalid_union — the test unwraps, as diagnoseViewMetadata documents), acceptance of calendar: { startDateField } alone, acceptance without calendar in the whitelist, acceptance with no appearance, and the timeline scope pin.
  • pnpm --filter @objectstack/spec typecheck: exit 0 — tsc --noEmit green, check:scripts-typecheck green, 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 --listFiles on tsconfig.test.json: src/ui/view.test.ts is in the program.
  • Consumer sweep (direction: downstream consumers of @objectstack/spec) — the two packages whose fixtures author calendar list views (grep-first): @objectstack/lint vitest "88 passed (88) / 2446 passed (2446)" + tsc --noEmit exit 0; @objectstack/example-showcase vitest "26 passed (26) / 364 passed (364)" + tsc --noEmit exit 0. Both after rebuilding their dependency closure.
  • Reverse verification (rebuilt-dts proof), committed-state, expected direction declared before the run (red): removed startDateField from the lint fixture's calendar block (mutation proven on disk: anchor grep old-present 1 to 0, new-present 0 to 1); @objectstack/lint tsc flipped to exit 2 with error 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 empty git diff HEAD plus blob-hash identity (e58664a9... head == disk).
  • Derived gate battery (node scripts/pm/dispatch-gates.mjs, derivation stamped "objectstack-ai/objectstack at commit e5a2ede"): 59 executable gates run; green ones include spec check: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, lint check: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.
  • NOT MEASURED locally (declared, not green): check-dev-prereqs and check: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, narrowed with evidence: config is the single root 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 json count: 2 files linted, 0 errors, 0 warnings, exit 0.
  • Ablation/dist-preflight: not applicable — no test in this change resolves the subject through a package 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-review goes 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: objectui packages/types carries list-view spec-parity pins that will need the titleField-optional update (named here, not touched — out of repo).


Generated by Claude Code

…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
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 7 documentable anchor(s).

13 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/error-handling-server.mdx (via due_date (literal, a string literal on a changed line))
  • content/docs/api/wire-format.mdx (via due_date (literal, a string literal on a changed line))
  • content/docs/automation/flows.mdx (via due_date (literal, a string literal on a changed line))
  • content/docs/concepts/metadata-driven.mdx (via due_date (literal, a string literal on a changed line))
  • content/docs/data-modeling/field-type-decision-tree.mdx (via due_date (literal, a string literal on a changed line))
  • content/docs/data-modeling/objects.mdx (via due_date (literal, a string literal on a changed line))
  • content/docs/getting-started/common-patterns.mdx (via due_date (literal, a string literal on a changed line))
  • content/docs/getting-started/examples.mdx (via due_date (literal, a string literal on a changed line))
  • content/docs/kernel/contracts/data-engine.mdx (via due_date (literal, a string literal on a changed line))
  • content/docs/protocol/objectui/index.mdx (via due_date (literal, a string literal on a changed line))
  • content/docs/protocol/objectui/layout-dsl.mdx (via due_date (literal, a string literal on a changed line))
  • content/docs/ui/actions.mdx (via due_date (literal, a string literal on a changed line))
  • content/docs/ui/views.mdx (via due_date (literal, a string literal on a changed line))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx (via ObjectListViewSchema (symbol, a top-level const))
  • content/docs/releases/v17.mdx (via ListViewSchema (symbol, a top-level const), due_date (literal, a string literal on a changed line))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a7002ce5ac9acf6a4d556713c587a770f1848c26packageMentionDocs.

Which tree this was computed on

This run read content/docs from 21aff86ef88caae71b975d0c0159228e21220c85 — the merge of head e5a2edeea140da8b6c13747111c4090212a30651 into base a7002ce5ac9acf6a4d556713c587a770f1848c26, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a7002ce5ac9acf6a4d556713c587a770f1848c26 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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 protocol:ui size/m tests tooling

Projects

None yet

2 participants