Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .changeset/gantt-tree-config-close-passthrough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
"@objectstack/spec": minor
---

feat(spec)!: `GanttConfigSchema` / `TreeConfigSchema` refuse undeclared keys — both `.passthrough()` windows are closed and the ten gantt members plugin-gantt read through the window are declared (#15469)

<!-- adr-0087: not-required (no-migration-prescription) No authorable key is renamed, retired or re-typed: every key either block accepted by declaration still parses, and the ten gantt keys that used to ride through `.passthrough()` are now DECLARED at the types the renderer reads, so an author who wrote them keeps parsing byte-identically. The only newly refused input is a key no renderer ever read — a misspelling or an invention — for which there is no rewrite to prescribe. -->

**BREAKING** accept-set narrowing on two published authorable config blocks —
`ListView.gantt` (`GanttConfigSchema`) and `ListView.tree` (`TreeConfigSchema`)
in `@objectstack/spec/ui`, reached through every view door (`defineView`,
`objects[].listViews`, the `view` metadata type): an UNDECLARED key inside
either block is now **refused** at parse with the `strictObject` named error
(`unrecognized_keys`; surface named, key echoed, closest declared key
suggested), where it used to pass through silently. Shipped as `minor` under
the repo's launch-window convention for breaking changes. Maintainer ruling
2026-09-05 on #15469 (director decision batch #41 item 2, verbatim 「同意」):
option A for both sites.

Both blocks were `strictObject(…).passthrough()` — the campaign's own helper
applied and immediately undone, so `colourField` on a gantt block parsed green
and rendered an uncoloured bar while the same typo on a calendar or timeline
block got a named refusal. One `strictObject` applied and then undone is two
contracts on one surface (Prime Directive #12); the renderer-ahead window it
kept open is shut, and a renderer knob is declared in the spec before it is
read.

**Newly declared on `GanttConfigSchema`** — all optional, types measured from
objectui's `GanttConfigExtensionFields` (`@object-ui/types/zod`) at pin
`a472b07`, each with a describe saying what plugin-gantt does with it:

- `borderColorField: string` — field carrying a per-task alert stroke color
- `lockField: string` — field marking a row view-only (truthy = locked)
- `objectField: string` — field carrying the row's own object API name (mixed-object trees)
- `summaryExtent: 'children' | 'self'` — how a summary bar's span is computed
- `defaultCollapsedDepth: integer ≥ 0` — auto-collapse nodes at or below this depth
- `dependencyTypes: boolean` — whether the store persists dependency link types
- `timeZone: string` — IANA business time zone the calendar renders in
- `exportFileName: string` — base name for exported PNG / PDF files
- `interactions: { move?, resize?, progress?, link? : boolean }` — per-interaction switches (closed sub-object)
- `timeSegments: { dayStart?: string, bands: [{ key?, label, start, end, color? }], showMidnight?: boolean }` — shift segmentation for the day-mode timeline (closed sub-objects)

**`TreeConfigSchema` declares nothing new.** plugin-tree's `getTreeConfig`
(objectui `a472b07`) reads exactly the four keys already declared —
`parentField`, `labelField`, `fields`, `defaultExpandedDepth` — from the `tree`
block, so the close refuses only what no renderer ever read.

**Who is affected (measured, objectstack `f7db8f4fd`):** zero gantt or tree
blocks under `examples/**`, `content/docs/**`, `skills/**` or any package
fixture author one of the ten keys or any undeclared key; objectui's own gantt
fixtures author the ten and keep parsing because the keys are now declared. A
block carrying a key outside the declared set — a misspelling such as
`colourField`, or a renderer knob authored ahead of its declaration — is refused
on upgrade with the key named; fix the spelling, or declare the knob in the spec
first.
8 changes: 4 additions & 4 deletions content/docs/references/api/protocol.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1629,12 +1629,12 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
| **kanban** | `{ groupByField: string; summarizeField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
| **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 |
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … }` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline view configuration |
| **chart** | `{ chartType?: Enum<'bar' \| 'line' \| 'pie' \| 'area' \| 'scatter'>; dataset: string; dimensions?: string[]; values: string[] }` | optional | List chart view configuration |
| **map** | `{ latitudeField?: string; longitudeField?: string; locationField?: string; titleField?: string; … }` | optional | Map configuration — applies when the view renders as a map layout |
| **tree** | `{ parentField?: string; labelField?: string; fields?: string[]; defaultExpandedDepth?: integer } & Record<string, any>` | optional | Tree/hierarchy configuration — applies when the view renders as a tree layout |
| **tree** | `{ parentField?: string; labelField?: string; fields?: string[]; defaultExpandedDepth?: integer }` | optional | Tree/hierarchy configuration — applies when the view renders as a tree layout |
| **pageName** | `string` | optional | Published page this view mounts — required when `type: 'page'`, and refused on every other view type. Rendering is delegated to the existing page renderer; the page keeps its own `assignedProfiles` audience. |
| **description** | `string \| Record<string, string>` | optional | View description for documentation/tooltips |
| **sharing** | `{ type?: Enum<'personal' \| 'collaborative'>; lockedBy?: string }` | optional | View sharing and access configuration |
Expand Down Expand Up @@ -1714,12 +1714,12 @@ The published metadata item body, opaque by ruling (1C). Shape is the item's own
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
| **kanban** | `{ groupByField: string; summarizeField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
| **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 |
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … }` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline view configuration |
| **chart** | `{ chartType?: Enum<'bar' \| 'line' \| 'pie' \| 'area' \| 'scatter'>; dataset: string; dimensions?: string[]; values: string[] }` | optional | List chart view configuration |
| **map** | `{ latitudeField?: string; longitudeField?: string; locationField?: string; titleField?: string; … }` | optional | Map configuration — applies when the view renders as a map layout |
| **tree** | `{ parentField?: string; labelField?: string; fields?: string[]; defaultExpandedDepth?: integer } & Record<string, any>` | optional | Tree/hierarchy configuration — applies when the view renders as a tree layout |
| **tree** | `{ parentField?: string; labelField?: string; fields?: string[]; defaultExpandedDepth?: integer }` | optional | Tree/hierarchy configuration — applies when the view renders as a tree layout |
| **pageName** | `string` | optional | Published page this view mounts — required when `type: 'page'`, and refused on every other view type. Rendering is delegated to the existing page renderer; the page keeps its own `assignedProfiles` audience. |
| **description** | `string \| Record<string, string>` | optional | View description for documentation/tooltips |
| **sharing** | `{ type?: Enum<'personal' \| 'collaborative'>; lockedBy?: string }` | optional | View sharing and access configuration |
Expand Down
4 changes: 2 additions & 2 deletions content/docs/references/data/object.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -376,12 +376,12 @@ const result = ApiMethod.parse(data);
| **pagination** | `{ pageSize?: integer; pageSizeOptions?: integer[] }` | optional | Pagination configuration |
| **kanban** | `{ groupByField: string; summarizeField?: string; columns: string[] }` | optional | Kanban-board configuration — applies when the view renders as a kanban layout |
| **calendar** | `{ startDateField: string; endDateField?: string; titleField?: string; colorField?: string }` | optional | Calendar configuration — applies when the view renders as a calendar layout |
| **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 |
| **gantt** | `{ startDateField: string; endDateField: string; titleField: string; progressField?: string; … }` | optional | Gantt-timeline configuration — applies when the view renders as a gantt layout |
| **gallery** | `{ coverField?: string; coverFit?: Enum<'cover' \| 'contain'>; cardSize?: Enum<'small' \| 'medium' \| 'large'>; titleField?: string; … }` | optional | Gallery/card view configuration |
| **timeline** | `{ startDateField: string; endDateField?: string; titleField: string; groupByField?: string; … }` | optional | Timeline view configuration |
| **chart** | `{ chartType?: Enum<'bar' \| 'line' \| 'pie' \| 'area' \| 'scatter'>; dataset: string; dimensions?: string[]; values: string[] }` | optional | List chart view configuration |
| **map** | `{ latitudeField?: string; longitudeField?: string; locationField?: string; titleField?: string; … }` | optional | Map configuration — applies when the view renders as a map layout |
| **tree** | `{ parentField?: string; labelField?: string; fields?: string[]; defaultExpandedDepth?: integer } & Record<string, any>` | optional | Tree/hierarchy configuration — applies when the view renders as a tree layout |
| **tree** | `{ parentField?: string; labelField?: string; fields?: string[]; defaultExpandedDepth?: integer }` | optional | Tree/hierarchy configuration — applies when the view renders as a tree layout |
| **pageName** | `string` | optional | Published page this view mounts — required when `type: 'page'`, and refused on every other view type. Rendering is delegated to the existing page renderer; the page keeps its own `assignedProfiles` audience. |
| **description** | `string \| Record<string, string>` | optional | View description for documentation/tooltips |
| **sharing** | `{ type?: Enum<'personal' \| 'collaborative'>; lockedBy?: string }` | optional | View sharing and access configuration |
Expand Down
Loading
Loading