Skip to content

fix(spec): correct GanttConfig.timeZone describe — date fields don't persist as real instants - #20495

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-20466-gantt-timezone-describe
Sep 28, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-20466-gantt-timezone-describe

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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 feat(spec): declare the console's round-trip keys on the stored view wire (#20456) #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

…persist as real instants

The `GanttConfig` `timeZone` member's `.describe()` said "persisted data stays
real instants" unconditionally. That is false for a `Field.date` column: per
the spec's own storage rule (`temporalStorageForm` / ADR-0053), a gantt drop
on a `date` field writes the calendar day it landed on as a timezone-naive
`YYYY-MM-DD`, while a `datetime` field still writes the real instant. Only
the false clause is replaced with the true wording; the rest of the describe
is unchanged.

Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Co-authored-by: Claude <noreply@anthropic.com>
…scribe fix

pnpm --filter @objectstack/spec gen:docs — content/docs/references/ui/view.mdx
(x3) and component.mdx (x1), the four generated occurrences of the corrected
describe. check:generated is green (15/15).

Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Co-authored-by: Claude <noreply@anthropic.com>
…ing origin/main

os-regen-merge.sh step 3: the merge driver deferred this generated path (both
sides changed it) and kept main's side; pnpm --filter @objectstack/spec
gen:schema && gen:docs re-derives it on the merged tree, carrying forward both
this branch's timeZone describe fix and main's #20474 round-trip-key docs.

Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/releases/v17/17-1.mdx (via GanttConfigSchema (symbol, a top-level const))
  • content/docs/releases/v17/17-4.mdx (via GanttConfigSchema (symbol, a top-level const))

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 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 137 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 fc0db22bcfdbdb778945317fc4de6dc46aab966d → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 9d2ff7252a2528a00c9644dbc5dc9b6629bcc4ee — the merge of head 192aba113686efc4293a8844fdce27df856d7789 into base fc0db22bcfdbdb778945317fc4de6dc46aab966d, 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 9d2ff7252a2528a00c9644dbc5dc9b6629bcc4ee && git checkout 9d2ff7252a2528a00c9644dbc5dc9b6629bcc4ee
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin fc0db22bcfdbdb778945317fc4de6dc46aab966d 192aba113686efc4293a8844fdce27df856d7789 && git checkout -B drift-repro fc0db22bcfdbdb778945317fc4de6dc46aab966d && git merge --no-ff 192aba113686efc4293a8844fdce27df856d7789

node scripts/docs-audit/affected-docs.mjs --json fc0db22bcfdbdb778945317fc4de6dc46aab966d

⚠️ 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 fc0db22bcfdbdb778945317fc4de6dc46aab966d → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:ui tooling labels Sep 28, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 192aba113686efc4293a8844fdce27df856d7789
Local-runs: none

Inputs read: card #20466 (body; comments 5873772768 triage, 5874985593 claim, 5876538219 os-dev-report), PR #20495 (body, its one comment 5876509376, file list, net diff origin/main...head at fc0db22bcf), the check-runs on the head, and the read-only trees of this repo and the sibling objectui checkout. Nothing built, run or re-run.

① Derived judgments

  • Accept set: unchanged — RIGHT. The diff's one source hunk is the .describe() string of GanttConfig.timeZone (packages/spec/src/ui/view.zod.ts:1920); the member stays z.string().optional(), no key is added, removed, renamed, aliased, defaulted or refined, and no other member moves. authorable-surface/, api-surface/ and json-schema.manifest/ are untouched, and the manifest carries names, not prose (zero hits for the describe's text in any of the three). Spec property liveness is success on the head.
  • Public surface: no export moves — RIGHT. What changes in the published package is the description the spec's zod and JSON-Schema output carry for this one member — shipped bytes, not the published accept set (the contract-review reference's own distinction).
  • The new sentence is true of the CONTRACT, not of one renderer — RIGHT. ADR-0053 (Accepted, Phase 1 implemented) fixes date as a timezone-naive YYYY-MM-DD calendar day that is never converted to an instant and never timezone-shifted, and datetime as an instant stored in UTC. The one rule is temporalStorageForm in packages/core/src/utils/temporal-storage-form.ts (date → canonicalCalendarDay, a string's leading YYYY-MM-DD; datetime → canonical UTC ISO text), and it sits on the write path a gantt drop reaches: driver-sql's formatInput runs every dateFields member through toDateOnly, whose body is temporalStorageForm(value, 'date') (sql-driver.ts), and driver-memory's write path runs coerceTemporalValue (memory-temporal.ts), the same rule. So whatever the renderer sends for a date column, what persists is a calendar day, and for a datetime column the same instant re-spelled in UTC. The old clause "persisted data stays real instants" was false for date; the replacement states the rule the platform already had.
  • "the calendar day it was dropped on in this zone's calendar" is a contract statement, not a renderer description — RIGHT. The same describe's first clause already defines the chart's day columns and snapping as this zone's calendar; a drop lands on one of those columns; and a date column persists a calendar day with no instant to shift (ADR-0053), so the day that persists is that column's day. It follows from the contract alone. Cross-checked, read-only, against the only shipped renderer: objectui packages/plugin-gantt/src/ObjectGantt.tsx toStoredDateValue writes toDateInputValue(chartZone.to(date)) for a declared date and date.toISOString() otherwise (objectui#10866 sites 1 and 2, landed as objectui#10886 in the checkout) — the renderer already does what the sentence says, so it is also a fair statement of what persists today.
  • The DST shim defect is correctly left out — RIGHT. objectui#10866 slice 5 is a defect in objectui's makeTzShift near a DST change (a day's midnight placed an hour early); the contract states the rule, not a renderer's bug. Triage directed the omission; the describe carries no tracker number (check:doc-authoring is inside Lint & Repo Gates).
  • The four generated doc copies are the generator's output — RIGHT. The rows at content/docs/references/ui/view.mdx :584 (### Properties), :982 (ListView.gantt), :1365 (ObjectListView.gantt) and component.mdx :679 (ObjectGanttProps.gantt) change exactly the describe cell and are byte-identical elsewhere; the old phrase has zero hits on the head tree; the new describe contains no {, <, backslash or pipe, so escapeMdxDescription and the cell escaping in schema-section.ts pass it through verbatim. The machine proof is Type Check · source gates — the job carrying check:docs / check:generated — which is success on this head.
  • The net diff against current main is only this PR's change — RIGHT. origin/main...head = 4 files, +25/−5, matching the PR's file list. The branch is fix (38bce2f) → regen (a6aeb19) → merge of origin/main at 9801da1 (ad11217) → driver-deferred regen of view.mdx only (192aba1), per os-regen-merge.sh. Seat 4's feat(spec): declare the console's round-trip keys on the stored view wire (#20456) #20474 view-console-round-trip-keys symbols count the same on head and on main in view.zod.ts; the five commits main gained after the merge base touch none of the three content paths.
  • objectui's zod mirror is not a landing concern — RIGHT. packages/types/src/zod/objectql.zod.ts:2113 takes stripImportedDefaults(SpecGanttConfigSchema).shape.timeZone by reference, so it corrects itself at the next spec bump; objectui pins no test on the old text (zero hits). Nothing is removed, so the pinned .objectui-sha (dd3f7e1) imports nothing this diff drops and Console Pin Gate (skipped by path filter) is not owed. objectui's own TS-side prose (packages/types/src/objectql.ts GanttConfig.timeZone, makeTzShift's docblock) is that repo's card (objectui#10994), not this PR's.
  • Governed surfaces: none — RIGHT. None of the four paths is on the register; Governed Surface Queue Guard is success. The tier owed is the clause-② path limb packages/spec/src/**, which this record serves.
  • Docs Drift Check advisory (comment 5876509376): the two release-owned pages it names (releases/v17/17-1.mdx, 17-4.mdx) name the GanttConfigSchema symbol only — neither carries timeZone or the old clause — so nothing there is falsified and nothing is owed.

② Semver level

  • .changeset/20466-gantt-timezone-describe.md: "@objectstack/spec": patch, body carries Clause-②: no with no direction arm, and no ADR-0087 marker — RIGHT. A released package's bytes change (the corrected description in the spec's zod and JSON-Schema output), so skip-changeset would be wrong; nothing authorable is added, removed or re-scoped and no accept set widens or narrows, so patch is the level and neither arm applies. No migration text is owed. Check Changeset is success.
  • The PR body's Clause-②: no and the changeset's agree with the diff.
  • Clause-②: no — confirmed against the diff, not the prose.

③ Boundary flags

  • Dev flag — 3/102 derived gates NOT MEASURED locally (check:skill-examples, check:dual-build-cjs-loads, check:lean-entry-closure, each needing other packages' dist/): answered by the head's check-runs, read off the workflow files rather than the dev's prose — check:skill-examples runs in Type Check · consumer gates (lint.yml), check:dual-build-cjs-loads and check:lean-entry-closure in Build Core (ci.yml); both jobs are success on 192aba1. check:doc-authoring, the gate over the describe's own prose, runs in Lint & Repo Gates: success on 192aba1 (completed 2026-09-28T19:14Z).
  • Dev flag — process correction (a hand git merge origin/main, reset to the last-pushed commit before any push, redone through scripts/pm/os-regen-merge.sh): nothing from the hand merge reached the branch — the pushed history is the four commits above, and the post-merge regen commit touches view.mdx only. Answered; no action.
  • Dev acceptance note — objectui's mirror not edited, per triage: answered in ① (reference reuse, corrects itself at the next bump).
  • open_questions: [] and out_of_scope_findings: [] — none to answer or escalate.
  • Check-runs on 192aba1, one read for this record (read 2026-09-28T19:16Z; 34 runs): 30 success — including every one of the seven required contexts that has concluded: Lint & Repo Gates, TypeScript Type Check (with Type Check · source gates, · consumer gates, · debt ledger, · workspace), Dogfood Regression Gate (rollup and 3/3 shards), Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard — plus Check Changeset, Spec property liveness, Build Docs, Dogfood Verify CLI, Flag docs affected by code changes, Check Documentation Links, Check PR Size, Auto Label, filter and the four claim/closing-parity checks; 2 skipped by design (Console Pin Gate — path filter, no objectui pin or removal in this diff; Packed-tarball smoke (opt-in)); 0 failures; 2 NOT CONCLUDED at the read: Test Core (1/6) and Test Core (5/6) (both in_progress) — the Test Core required context is therefore not yet green on this head and is not presumed so here; shards 2, 3, 4 and 6 are success. The landing seat reads CI again before enqueue (landing-operations, pre-check ②); this verdict is on the contract and does not stand in for that read.

Implemented-by: claude/issue-20466-gantt-timezone-describe
Reviewed-by: session_014EJ1ED8X4MMrT18BhVx4tx

VERDICT: PASS


Generated by Claude Code

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/s tooling

Projects

None yet

2 participants