Skip to content

Commit 8255a51

Browse files
fix(spec): correct GanttConfig.timeZone describe — date fields don't persist as real instants (#20495)
Fixes #20466 Clause-②: no ## What changed `packages/spec/src/ui/view.zod.ts`, the `GanttConfig` `timeZone` member's `.describe()`: replaced only the false clause "persisted data stays real instants" with the card's true wording — a datetime value is still written as the real instant, and a date value as the calendar day it was dropped on in this zone's calendar (`YYYY-MM-DD`). Nothing else in the describe moved. Verified against the spec's own storage rule (`packages/core/src/utils/temporal-storage-form.ts`, ADR-0053): `datetime` stores canonical UTC ISO text; `date` stores a timezone-naive `YYYY-MM-DD` calendar day. The new sentence is true of the contract, not of one renderer. No mention of objectui's DST shim defect (objectstack-ai/objectui#10866 slice 5) — that stays a renderer bug, per triage. Regenerated the reference docs that carried the old sentence verbatim (`content/docs/references/ui/view.mdx` ×3, `component.mdx` ×1) via `pnpm --filter @objectstack/spec gen:schema && gen:docs` — never by hand. Grepped the exact phrase across the whole repo; the only other copies were those four generated doc occurrences (plus the JSON-Schema manifest, which only ratchets schema names, not prose). No hand-written copy found, no test pins the old text. ## Verification record - `pnpm --filter @objectstack/spec build` — green (36/36 declared `.d.ts`, 130 declaration files, 394/394 relative references resolved). - `pnpm --filter @objectstack/spec check:generated` — 15/15 generated artifacts green (only `check:docs` was stale before `gen:docs`, now current). - `pnpm --filter @objectstack/spec typecheck` (`tsc --noEmit` + scripts + test-layer) — green. - `pnpm --filter @objectstack/spec test` — 572 test files, 16789 tests passed, 1 todo (unrelated pre-existing todo). - Targeted first: `view.test.ts` + `view-gantt-tree-config-closed-15469.test.ts` — 502 tests passed. No test pins the old describe text; none needed updating. - `packages/spec`'s own build closure (`pnpm --filter '@objectstack/spec^...' build`) is EMPTY — nothing in this repo depends on `@objectstack/spec` as a workspace package the way `turbo`'s graph tracks it here, so ① has nothing to build. - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived 102 gate commands from the diff (4 files, +25/-5, 30 changed lines). Ran all 102: - **99/102 green** on the real command (exit 0), including `check:doc-authoring` (no tracker number in the describe), `check:nul-bytes`, `check:merge-driver`, `check:changeset-no-major`, `check:empty-changeset`, `check:issue-citations`/`check:closing-keyword-parity` (the `Fixes #20466` line), and every `packages/spec` `check:*` gate the diff touches. - **3/102 NOT MEASURED** — `PREREQUISITE NOT MET` (exit 3, not a finding): `check:skill-examples` (client SDK surface needs `@objectstack/client-react`+`@objectstack/client` built), `check:dual-build-cjs-loads` (needs ~80 packages' `dist/`, effectively a whole-repo build) and `check:lean-entry-closure` (needs `@objectstack/objectql` built). None is in `packages/spec`'s own build closure (which is empty); building them locally would mean rebuilding most of the monorepo for a single-clause prose fix. Deferred to CI's full build, which measures all three. - 3 of the original 6 exit-3s (`check:doc-formula-expressions`, `check:doc-security-posture`, `check:docs-transcript-drift`) needed only `@objectstack/formula` + `@objectstack/lint` built — built those two directly and reran: all green. - Merged `origin/main` (view.zod.ts carried seat 4's landed #20474 `view-console-round-trip-keys` change past my region) via `bash scripts/pm/os-regen-merge.sh`: the driver deferred `content/docs/references/ui/view.mdx` (both sides changed it), regenerated with `gen:schema && gen:docs` on the merged tree, verified `#20474`'s symbols and prose survived byte-for-byte (`git grep` against `origin/main`), then committed the regeneration as its own commit per the runbook. `git diff origin/main HEAD -- packages/spec/src/ui/view.zod.ts` shows exactly the one-clause change, nothing else. - Reverified no further origin/main commits touch this file surface after the merge (`git log HEAD..origin/main -- <the 3 touched paths>` — empty). ## Acceptance notes - objectui's `@object-ui/types` zod mirror (`stripImportedDefaults(SpecGanttConfigSchema).shape.timeZone`) is a spec-derived reuse of this describe. Per triage's direction, objectui is not edited here — its mirror picks up the corrected text at its next spec bump. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2b24b8b commit 8255a51

4 files changed

Lines changed: 25 additions & 5 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
fix(spec): correct `GanttConfig.timeZone`'s describe — persisted gantt drops on a `date` field are not "real instants" (#20466)
6+
7+
Clause-②: no
8+
9+
The `GanttConfig` `timeZone` member's `.describe()` said "persisted data stays real
10+
instants" for every field. That is false for a `Field.date` column: per the spec's own
11+
storage rule (`temporalStorageForm` / ADR-0053), a gantt drop on a `date` field writes the
12+
calendar day it landed on, as a timezone-naive `YYYY-MM-DD`, while a `datetime` field
13+
still writes the real instant. Only the false clause is replaced — "a datetime value is
14+
still written as the real instant, and a date value as the calendar day it was dropped on
15+
in this zone's calendar (`YYYY-MM-DD`)" — the rest of the describe, and every other
16+
member, is unchanged.
17+
18+
No key moves and no verdict moves: this corrects a published describe's prose to match
19+
the contract it already had, it does not add, remove or re-scope anything authorable. The
20+
JSON Schema and reference docs regenerate from the corrected source.

‎content/docs/references/ui/component.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ Sort field and direction pair
676676
| **summaryExtent** | `Enum<'children' \| 'self'>` | optional | How a summary bar's span is computed. 'children' (renderer default) rolls the bar up from its children — min start, max end, duration-weighted progress — and ignores the record's own dates; 'self' renders the record's OWN start, end and progress and falls back to rollup only for records without dates (use it when the parent's schedule is authoritative, e.g. a shift plan whose work-order children are locked history) |
677677
| **defaultCollapsedDepth** | `integer` | optional | Auto-collapse tree nodes at or below this 0-indexed depth on first render (roots are depth 0): every node at that depth or deeper that has children starts folded; the user can still expand them. Omit to start fully expanded |
678678
| **dependencyTypes** | `boolean` | optional | Whether the backing store persists dependency link TYPES (fs, ss, ff, sf); renderer default true. Set false when dependencies are bare predecessor ids: the link menu hides the type switcher (a switch would be silently reverted on refetch) and drag-created links are always finish-to-start |
679-
| **timeZone** | `string` | optional | Business time zone, an IANA name such as 'Asia/Shanghai': the chart's calendar — shift bands, day columns, snapping, the today line, date labels — renders in this zone's wall time for every viewer instead of the browser's zone; persisted data stays real instants. An invalid name falls back to the browser zone with a console warning |
679+
| **timeZone** | `string` | optional | Business time zone, an IANA name such as 'Asia/Shanghai': the chart's calendar — shift bands, day columns, snapping, the today line, date labels — renders in this zone's wall time for every viewer instead of the browser's zone; a datetime value is still written as the real instant, and a date value as the calendar day it was dropped on in this zone's calendar (YYYY-MM-DD). An invalid name falls back to the browser zone with a console warning |
680680
| **exportFileName** | `string` | optional | Base name for exported PNG and PDF files (e.g. the view's display label — the host's view schema often reaches the renderer stripped of label); falls back to the object schema label, then the object API name. A timestamp suffix is always appended |
681681
| **interactions** | `{ move?: boolean; resize?: boolean; progress?: boolean; link?: boolean }` | optional | Per-interaction switches, each defaulting to true: allow bar moves but pin durations (resize: false), or keep the dependency UI read-only (link: false). They only narrow what readOnly and row locks already allow |
682682
| **timeSegments** | `{ dayStart?: string; bands: object[]; showMidnight?: boolean }` | optional | Shift segmentation for the day-mode timeline: splits each shift-day (starting at dayStart) into the configured bands — a two-tier header (date over band), per-band tints and drag/resize snapping to band boundaries. No shift concept is hardcoded; bands are pure config. Off when omitted |

‎content/docs/references/ui/view.mdx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ Gallery/card view configuration
581581
| **summaryExtent** | `Enum<'children' \| 'self'>` | optional | How a summary bar's span is computed. 'children' (renderer default) rolls the bar up from its children — min start, max end, duration-weighted progress — and ignores the record's own dates; 'self' renders the record's OWN start, end and progress and falls back to rollup only for records without dates (use it when the parent's schedule is authoritative, e.g. a shift plan whose work-order children are locked history) |
582582
| **defaultCollapsedDepth** | `integer` | optional | Auto-collapse tree nodes at or below this 0-indexed depth on first render (roots are depth 0): every node at that depth or deeper that has children starts folded; the user can still expand them. Omit to start fully expanded |
583583
| **dependencyTypes** | `boolean` | optional | Whether the backing store persists dependency link TYPES (fs, ss, ff, sf); renderer default true. Set false when dependencies are bare predecessor ids: the link menu hides the type switcher (a switch would be silently reverted on refetch) and drag-created links are always finish-to-start |
584-
| **timeZone** | `string` | optional | Business time zone, an IANA name such as 'Asia/Shanghai': the chart's calendar — shift bands, day columns, snapping, the today line, date labels — renders in this zone's wall time for every viewer instead of the browser's zone; persisted data stays real instants. An invalid name falls back to the browser zone with a console warning |
584+
| **timeZone** | `string` | optional | Business time zone, an IANA name such as 'Asia/Shanghai': the chart's calendar — shift bands, day columns, snapping, the today line, date labels — renders in this zone's wall time for every viewer instead of the browser's zone; a datetime value is still written as the real instant, and a date value as the calendar day it was dropped on in this zone's calendar (YYYY-MM-DD). An invalid name falls back to the browser zone with a console warning |
585585
| **exportFileName** | `string` | optional | Base name for exported PNG and PDF files (e.g. the view's display label — the host's view schema often reaches the renderer stripped of label); falls back to the object schema label, then the object API name. A timestamp suffix is always appended |
586586
| **interactions** | `{ move?: boolean; resize?: boolean; progress?: boolean; link?: boolean }` | optional | Per-interaction switches, each defaulting to true: allow bar moves but pin durations (resize: false), or keep the dependency UI read-only (link: false). They only narrow what readOnly and row locks already allow |
587587
| **timeSegments** | `{ dayStart?: string; bands: object[]; showMidnight?: boolean }` | optional | Shift segmentation for the day-mode timeline: splits each shift-day (starting at dayStart) into the configured bands — a two-tier header (date over band), per-band tints and drag/resize snapping to band boundaries. No shift concept is hardcoded; bands are pure config. Off when omitted |
@@ -979,7 +979,7 @@ View filter rule
979979
| **summaryExtent** | `Enum<'children' \| 'self'>` | optional | How a summary bar's span is computed. 'children' (renderer default) rolls the bar up from its children — min start, max end, duration-weighted progress — and ignores the record's own dates; 'self' renders the record's OWN start, end and progress and falls back to rollup only for records without dates (use it when the parent's schedule is authoritative, e.g. a shift plan whose work-order children are locked history) |
980980
| **defaultCollapsedDepth** | `integer` | optional | Auto-collapse tree nodes at or below this 0-indexed depth on first render (roots are depth 0): every node at that depth or deeper that has children starts folded; the user can still expand them. Omit to start fully expanded |
981981
| **dependencyTypes** | `boolean` | optional | Whether the backing store persists dependency link TYPES (fs, ss, ff, sf); renderer default true. Set false when dependencies are bare predecessor ids: the link menu hides the type switcher (a switch would be silently reverted on refetch) and drag-created links are always finish-to-start |
982-
| **timeZone** | `string` | optional | Business time zone, an IANA name such as 'Asia/Shanghai': the chart's calendar — shift bands, day columns, snapping, the today line, date labels — renders in this zone's wall time for every viewer instead of the browser's zone; persisted data stays real instants. An invalid name falls back to the browser zone with a console warning |
982+
| **timeZone** | `string` | optional | Business time zone, an IANA name such as 'Asia/Shanghai': the chart's calendar — shift bands, day columns, snapping, the today line, date labels — renders in this zone's wall time for every viewer instead of the browser's zone; a datetime value is still written as the real instant, and a date value as the calendar day it was dropped on in this zone's calendar (YYYY-MM-DD). An invalid name falls back to the browser zone with a console warning |
983983
| **exportFileName** | `string` | optional | Base name for exported PNG and PDF files (e.g. the view's display label — the host's view schema often reaches the renderer stripped of label); falls back to the object schema label, then the object API name. A timestamp suffix is always appended |
984984
| **interactions** | `{ move?: boolean; resize?: boolean; progress?: boolean; link?: boolean }` | optional | Per-interaction switches, each defaulting to true: allow bar moves but pin durations (resize: false), or keep the dependency UI read-only (link: false). They only narrow what readOnly and row locks already allow |
985985
| **timeSegments** | `{ dayStart?: string; bands: object[]; showMidnight?: boolean }` | optional | Shift segmentation for the day-mode timeline: splits each shift-day (starting at dayStart) into the configured bands — a two-tier header (date over band), per-band tints and drag/resize snapping to band boundaries. No shift concept is hardcoded; bands are pure config. Off when omitted |
@@ -1362,7 +1362,7 @@ View filter rule
13621362
| **summaryExtent** | `Enum<'children' \| 'self'>` | optional | How a summary bar's span is computed. 'children' (renderer default) rolls the bar up from its children — min start, max end, duration-weighted progress — and ignores the record's own dates; 'self' renders the record's OWN start, end and progress and falls back to rollup only for records without dates (use it when the parent's schedule is authoritative, e.g. a shift plan whose work-order children are locked history) |
13631363
| **defaultCollapsedDepth** | `integer` | optional | Auto-collapse tree nodes at or below this 0-indexed depth on first render (roots are depth 0): every node at that depth or deeper that has children starts folded; the user can still expand them. Omit to start fully expanded |
13641364
| **dependencyTypes** | `boolean` | optional | Whether the backing store persists dependency link TYPES (fs, ss, ff, sf); renderer default true. Set false when dependencies are bare predecessor ids: the link menu hides the type switcher (a switch would be silently reverted on refetch) and drag-created links are always finish-to-start |
1365-
| **timeZone** | `string` | optional | Business time zone, an IANA name such as 'Asia/Shanghai': the chart's calendar — shift bands, day columns, snapping, the today line, date labels — renders in this zone's wall time for every viewer instead of the browser's zone; persisted data stays real instants. An invalid name falls back to the browser zone with a console warning |
1365+
| **timeZone** | `string` | optional | Business time zone, an IANA name such as 'Asia/Shanghai': the chart's calendar — shift bands, day columns, snapping, the today line, date labels — renders in this zone's wall time for every viewer instead of the browser's zone; a datetime value is still written as the real instant, and a date value as the calendar day it was dropped on in this zone's calendar (YYYY-MM-DD). An invalid name falls back to the browser zone with a console warning |
13661366
| **exportFileName** | `string` | optional | Base name for exported PNG and PDF files (e.g. the view's display label — the host's view schema often reaches the renderer stripped of label); falls back to the object schema label, then the object API name. A timestamp suffix is always appended |
13671367
| **interactions** | `{ move?: boolean; resize?: boolean; progress?: boolean; link?: boolean }` | optional | Per-interaction switches, each defaulting to true: allow bar moves but pin durations (resize: false), or keep the dependency UI read-only (link: false). They only narrow what readOnly and row locks already allow |
13681368
| **timeSegments** | `{ dayStart?: string; bands: object[]; showMidnight?: boolean }` | optional | Shift segmentation for the day-mode timeline: splits each shift-day (starting at dayStart) into the configured bands — a two-tier header (date over band), per-band tints and drag/resize snapping to band boundaries. No shift concept is hardcoded; bands are pure config. Off when omitted |

‎packages/spec/src/ui/view.zod.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1917,7 +1917,7 @@ export const GanttConfigSchema = lazySchema(() => strictObject({
19171917
summaryExtent: z.enum(['children', 'self']).optional().describe("How a summary bar's span is computed. 'children' (renderer default) rolls the bar up from its children — min start, max end, duration-weighted progress — and ignores the record's own dates; 'self' renders the record's OWN start, end and progress and falls back to rollup only for records without dates (use it when the parent's schedule is authoritative, e.g. a shift plan whose work-order children are locked history)"),
19181918
defaultCollapsedDepth: z.number().int().min(0).optional().describe('Auto-collapse tree nodes at or below this 0-indexed depth on first render (roots are depth 0): every node at that depth or deeper that has children starts folded; the user can still expand them. Omit to start fully expanded'),
19191919
dependencyTypes: z.boolean().optional().describe('Whether the backing store persists dependency link TYPES (fs, ss, ff, sf); renderer default true. Set false when dependencies are bare predecessor ids: the link menu hides the type switcher (a switch would be silently reverted on refetch) and drag-created links are always finish-to-start'),
1920-
timeZone: z.string().optional().describe("Business time zone, an IANA name such as 'Asia/Shanghai': the chart's calendar — shift bands, day columns, snapping, the today line, date labels — renders in this zone's wall time for every viewer instead of the browser's zone; persisted data stays real instants. An invalid name falls back to the browser zone with a console warning"),
1920+
timeZone: z.string().optional().describe("Business time zone, an IANA name such as 'Asia/Shanghai': the chart's calendar — shift bands, day columns, snapping, the today line, date labels — renders in this zone's wall time for every viewer instead of the browser's zone; a datetime value is still written as the real instant, and a date value as the calendar day it was dropped on in this zone's calendar (YYYY-MM-DD). An invalid name falls back to the browser zone with a console warning"),
19211921
exportFileName: z.string().optional().describe("Base name for exported PNG and PDF files (e.g. the view's display label — the host's view schema often reaches the renderer stripped of label); falls back to the object schema label, then the object API name. A timestamp suffix is always appended"),
19221922
interactions: strictObject({
19231923
surface: 'this gantt interactions block',

0 commit comments

Comments
 (0)