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

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 28, 2026 19:39
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 28, 2026
Merged via the queue into main with commit 8255a51 Sep 28, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20466-gantt-timezone-describe branch September 28, 2026 20:05
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