Skip to content

Commit 96e25a8

Browse files
claude[bot]claude
andauthored
feat(spec): calendar in allowedVisualizations requires calendar.startDateField (#13817) (#14075)
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. Claude-Session: https://claude.ai/code/session_01Mciyv38maJ6HYVMiaM26T1 Co-authored-by: Claude <noreply@anthropic.com>
1 parent afbf271 commit 96e25a8

6 files changed

Lines changed: 235 additions & 27 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec): calendar in `appearance.allowedVisualizations` now requires `calendar.startDateField` on list views (#13817)
6+
7+
**BREAKING** accept-set narrowing on `ListViewSchema` (and its two derived
8+
doors, `ObjectListViewSchema` and the flattened `ViewMetadataSchema` list
9+
overlay), shipped as `minor` under the repo's launch-window convention for
10+
breaking changes. Ruled on #13748 (2026-08-31, option A — fix both halves);
11+
this is the spec half, objectui#7029 is the runtime half.
12+
13+
A view declaring `appearance.allowedVisualizations: [... 'calendar']` with no
14+
`calendar:` block used to parse clean. Downstream (measured on #13748): the
15+
calendar toggle rendered and was clickable, objectui invented
16+
`startDateField: 'due_date'`, and the renderer landed every record without
17+
that field on "today" — a plausible-looking, fully wrong screen, with the
18+
renderer's own refusal screen unreachable because the synthesized config
19+
always looked complete. The parse now **rejects loudly**, naming
20+
`calendar.startDateField`, why the date has no truthful fallback, and both
21+
remedies (declare the block, or drop `'calendar'` from the whitelist).
22+
23+
In the same stroke `CalendarConfigSchema.titleField` moves required →
24+
**optional**: only `startDateField` is load-bearing — the renderer resolves a
25+
missing title through the ADR-0079 record display-name chain (measured in
26+
objectui `ObjectCalendar.tsx`: an explicit `titleField` wins only "when
27+
present"). Keeping it required would have made the new cross-field gate
28+
demand more than the renderer reads, which the ruling forbids. Every
29+
previously-valid calendar block stays valid.
30+
31+
Scope: `calendar` only — the measured defect. Whether `timeline` or another
32+
visualization has the same shape is a separate finding to measure first (the
33+
ruling says so in those words); a scope pin test asserts the requirement does
34+
not leak to `timeline`.
35+
36+
<!-- adr-0087: not-required (no-migration-prescription) A validity narrowing over existing keys plus an optionality widening: no key is removed, renamed or re-shaped, so there is no tombstone and nothing mechanical for `objectstack migrate meta` to rewrite. The refusal is the channel that reaches an affected author, at the parse site, naming the one missing key and both remedies; whether a calendar switcher entry meant "bind a date field" or "drop the visualization" is authoring intent no migration entry can decide. The measured in-repo population of affected sources is zero (examples, lint fixtures, spec fixtures, skills, docs snippets all either bind `calendar.startDateField` already or do not whitelist calendar). -->

content/docs/references/api/protocol.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1627,7 +1627,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
16271627
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; view?: string; preventNavigation?: boolean; openNewTab?: boolean; … }` | optional | Configuration for item click navigation (page, drawer, modal, etc.) |
16281628
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
16291629
| **kanban** | `{ groupByField: string; summarizeField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
1630-
| **calendar** | `{ startDateField: string; endDateField?: string; titleField: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
1630+
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
16311631
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … } & Record<string, any>` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
16321632
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
16331633
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline view configuration |
@@ -1712,7 +1712,7 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
17121712
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; view?: string; preventNavigation?: boolean; openNewTab?: boolean; … }` | optional | Configuration for item click navigation (page, drawer, modal, etc.) |
17131713
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
17141714
| **kanban** | `{ groupByField: string; summarizeField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
1715-
| **calendar** | `{ startDateField: string; endDateField?: string; titleField: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
1715+
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
17161716
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … } & Record<string, any>` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
17171717
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
17181718
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline view configuration |

content/docs/references/data/object.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ const result = ApiMethod.parse(data);
374374
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; view?: string; preventNavigation?: boolean; openNewTab?: boolean; … }` | optional | Configuration for item click navigation (page, drawer, modal, etc.) |
375375
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
376376
| **kanban** | `{ groupByField: string; summarizeField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
377-
| **calendar** | `{ startDateField: string; endDateField?: string; titleField: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
377+
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
378378
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … } & Record<string, any>` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
379379
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
380380
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline view configuration |

content/docs/references/ui/view.mdx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ Appearance and visualization configuration
105105
| :--- | :--- | :--- | :--- |
106106
| **startDateField** | `string` || Field providing the event start date/time |
107107
| **endDateField** | `string` | optional | Field providing the event end date/time (defaults to a single-day event) |
108-
| **titleField** | `string` | | Field displayed as the event title |
108+
| **titleField** | `string` | optional | Field displayed as the event title. Omit to fall back to the record display name (ADR-0079 resolver chain) |
109109
| **colorField** | `string` | optional | Field whose value determines the event color |
110110

111111

@@ -772,7 +772,7 @@ Map view configuration
772772
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; view?: string; preventNavigation?: boolean; openNewTab?: boolean; … }` | optional | Configuration for item click navigation (page, drawer, modal, etc.) |
773773
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
774774
| **kanban** | `{ groupByField: string; summarizeField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
775-
| **calendar** | `{ startDateField: string; endDateField?: string; titleField: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
775+
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
776776
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … } & Record<string, any>` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
777777
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
778778
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline view configuration |
@@ -914,7 +914,7 @@ View filter rule
914914
| :--- | :--- | :--- | :--- |
915915
| **startDateField** | `string` || Field providing the event start date/time |
916916
| **endDateField** | `string` | optional | Field providing the event end date/time (defaults to a single-day event) |
917-
| **titleField** | `string` | | Field displayed as the event title |
917+
| **titleField** | `string` | optional | Field displayed as the event title. Omit to fall back to the record display name (ADR-0079 resolver chain) |
918918
| **colorField** | `string` | optional | Field whose value determines the event color |
919919

920920
### Nested Shape: `ListView.gantt`
@@ -1167,7 +1167,7 @@ Tab configuration for multi-tab view interface
11671167
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; view?: string; preventNavigation?: boolean; openNewTab?: boolean; … }` | optional | Configuration for item click navigation (page, drawer, modal, etc.) |
11681168
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
11691169
| **kanban** | `{ groupByField: string; summarizeField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
1170-
| **calendar** | `{ startDateField: string; endDateField?: string; titleField: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
1170+
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
11711171
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … } & Record<string, any>` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
11721172
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
11731173
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline view configuration |
@@ -1300,7 +1300,7 @@ View filter rule
13001300
| :--- | :--- | :--- | :--- |
13011301
| **startDateField** | `string` || Field providing the event start date/time |
13021302
| **endDateField** | `string` | optional | Field providing the event end date/time (defaults to a single-day event) |
1303-
| **titleField** | `string` | | Field displayed as the event title |
1303+
| **titleField** | `string` | optional | Field displayed as the event title. Omit to fall back to the record display name (ADR-0079 resolver chain) |
13041304
| **colorField** | `string` | optional | Field whose value determines the event color |
13051305

13061306
### Nested Shape: `ObjectListView.gantt`
@@ -1753,7 +1753,7 @@ Tab configuration for multi-tab view interface
17531753
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; view?: string; preventNavigation?: boolean; openNewTab?: boolean; … }` | optional | Configuration for item click navigation (page, drawer, modal, etc.) |
17541754
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
17551755
| **kanban** | `{ groupByField: string; summarizeField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
1756-
| **calendar** | `{ startDateField: string; endDateField?: string; titleField: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
1756+
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
17571757
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … } & Record<string, any>` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
17581758
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
17591759
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline view configuration |
@@ -1838,7 +1838,7 @@ Tab configuration for multi-tab view interface
18381838
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; view?: string; preventNavigation?: boolean; openNewTab?: boolean; … }` | optional | Configuration for item click navigation (page, drawer, modal, etc.) |
18391839
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
18401840
| **kanban** | `{ groupByField: string; summarizeField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
1841-
| **calendar** | `{ startDateField: string; endDateField?: string; titleField: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
1841+
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
18421842
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … } & Record<string, any>` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
18431843
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
18441844
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline view configuration |
@@ -2079,7 +2079,7 @@ This schema accepts one of the following structures:
20792079
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; view?: string; preventNavigation?: boolean; openNewTab?: boolean; … }` | optional | Configuration for item click navigation (page, drawer, modal, etc.) |
20802080
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
20812081
| **kanban** | `{ groupByField: string; summarizeField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
2082-
| **calendar** | `{ startDateField: string; endDateField?: string; titleField: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
2082+
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
20832083
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … } & Record<string, any>` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
20842084
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
20852085
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline view configuration |
@@ -2255,7 +2255,7 @@ This schema accepts one of the following structures:
22552255
| **navigation** | `{ mode?: Enum<'page' \| 'drawer' \| 'modal' \| 'split' \| 'popover' \| 'new_window' \| 'none'>; view?: string; preventNavigation?: boolean; openNewTab?: boolean; … }` | optional | Configuration for item click navigation (page, drawer, modal, etc.) |
22562256
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
22572257
| **kanban** | `{ groupByField: string; summarizeField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
2258-
| **calendar** | `{ startDateField: string; endDateField?: string; titleField: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
2258+
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
22592259
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … } & Record<string, any>` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
22602260
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
22612261
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline view configuration |

0 commit comments

Comments
 (0)