You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
finding(plugin-dashboard,components): date-only strings parsed as UTC midnight outside toDisplayDate — the dashboard custom range and the form date-picker show the previous day west of UTC (the date-only zone family's open closure card) #10844
Filing-gate category: ① a product defect with named sites, class (a), in the date-only zone family (objectui#10110 → objectui#10183 → PR objectui#10266, all closed). Reader: triage first (route and grade). ⛔ Not graded here.
Filed by the domain:ui seat 4 (session_01MCg3z5cGrV51xEddpGcxEY) from the objectui#10799 dev report (5859155332: out_of_scope_findings[1] and open_questions[0], answered 「land as-is, file for the family」) and from the contract review of PR objectui#10838 (5859296505, item 5). The family has no open card: objectui#10110 and objectui#10183 are closed. So this card is its closure card, and any later read site that parses a date-only string with new Date(…) or date-fns rides here.
Sites
packages/plugin-dashboard/src/DashboardFilterBar.tsx, the custom range. It stores toIsoDate(range.from) (local yyyy-MM-dd) and reads it back with new Date(value.from) / new Date(value.to), which is UTC midnight. PR objectui#10838 moves these two expressions, unchanged, into rangeFrom / rangeTo.
Measured by the dev in a forks probe with process.env.TZ = 'America/Los_Angeles' (offset 420), the popover passed through: clicking 15 Sep commits { from: '2026-09-15', to: '2026-09-15' } (correct), but the calendar highlights 2026-09-14. Under UTC the control highlights 2026-09-15.
packages/components/src/renderers/form/date-picker.tsx, an authored ISO-string value. An authored value: '2024-01-15' reaches the component unparsed through SchemaRenderer, and date-fns parses a date-only string as UTC. The reviewer probed startOfMonth / isSameDay in node under TZ=America/Los_Angeles, and reported that the trigger label reads 「January 14th, 2024」 for 2024-01-15 there. That was at the merge-base, before PR objectui#10838.
Why
A stored value names a calendar day. West of UTC these faces show and highlight the day before, so an author reads the wrong day back from a correct stored value. The family's repair convention exists: toDisplayDate from @object-ui/core builds a local-midnight Date from a date-only string, and scripts/__tests__/date-only-zone-pins-10183.test.ts drives each read site under a non-UTC zone in a forks child.
Direction (for triage to confirm)
Route each site's date-only read through toDisplayDate.
Register each site in the zone-pin driver's PINS, so the family's census stays enumerated.
Census the rest: any other new Date(dateOnly) or date-fns parse of a stored date-only value in packages/** rides here.
Dedupe
Listed the 1000 most recently updated objectui issues and PRs (open and closed) and grepped them locally for date-only|toDisplayDate|10183|west of UTC|UTC midnight. Every family hit is closed: objectui#10110, #10183, #10301, #10567, #10625, #10474, #10442, #10722 and their PRs. No open card covers these two sites.
Dedupe words: date-only UTC midnight dashboard range · toDisplayDate DashboardFilterBar · date-picker ISO string previous day west of UTC
Path: business objects, records and views | 缺项 (no item reads a stored date back west of UTC on the dashboard range or an authored date-picker value) | P2
Triage: first grade — bug · priority:p2 · domain:ui · area:records · pm:queue. The closure card for the date-only zone family
Triage: lands in packages/plugin-dashboard/src/DashboardFilterBar.tsx (rangeFrom / rangeTo, new Date(yyyy-MM-dd)) and packages/components/src/renderers/form/date-picker.tsx (an authored ISO date parsed by date-fns) ⇒ domain:ui. Rationale: a stored day is shown and highlighted as the day before for viewers west of UTC (measured under TZ=America/Los_Angeles). The family grade is p2 (objectui#10110, objectui#10183, both p2 and closed) ⇒ p2.
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01W89enF2dYV7K4N2Fbfj33f · 2026-09-27T21:27Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card (no comments), objectui#10110 and objectui#10183 (closed), and PR objectui#10838 (merged).
This card is now the family's closure card. The earlier carriers are closed. Any read site that parses a stored date-only value with new Date(…) or date-fns rides here.
Execution notes.
Route each site's date-only read through toDisplayDate (@object-ui/core).
Register each in scripts/__tests__/date-only-zone-pins-10183.test.ts's PINS, so it is driven under a non-UTC zone in a forks child.
Census packages/** for other date-only parses, and fix or record each in ## Acceptance notes.
packages/plugin-dashboard/src/DashboardFilterBar.tsx: the rangeFrom / rangeTo date-only parse only (⛔ not the custom-range select, which is objectui#10843, serial after this card);
packages/components/src/renderers/form/date-picker.tsx: the authored ISO date-only value read;
scripts/__tests__/date-only-zone-pins-10183.test.ts (PINS registration) and any further date-only read site the census finds in packages/** (fixed here, or recorded in ## Acceptance notes);
tests beside them; .changeset/10844-…md (patch for each touched package).
Stop on breach; explain in the report.
Container & model: M, mode:subagent, model: opus (TIER_DEFAULT, scripts/pm/dispatch-gates.mjs:12534 on objectstack 17bd3187; --tier refuses objectui paths).
Clause-②: no
Thread-read: 5859979628
Serial constraints cleared: open-PR file lists read 2026-09-27T21:52Z at objectui origin/main4aebea0 (9 open PRs): none touches these files. No in-flight lane claim names them (the pm:dispatched cards' newest claims checked).
Clause-②: no: each date-only read goes through the existing toDisplayDate. No declared key, export or accept set moves.
{
"issue": 10844,
"status": "done",
"branch": "claude/issue-10844-date-only-zone-closure",
"pr": "https://github.com/objectstack-ai/objectui/pull/10861",
"session": "session_01MCg3z5cGrV51xEddpGcxEY — mode:subagent, the parent PM session's harness-stamped id (commit Claude-Session trailer)",
"premise_still_valid": true,
"summary": "Both named sites were measured as the card describes, and both are fixed. DashboardFilterBar rangeFrom/rangeTo now read the stored yyyy-MM-dd bounds through toDisplayDate. The components date-picker renderer decides at its read (toPickerDay): only a date-only string that names a real day (isRealCalendarDate) goes through toDisplayDate, and a Date, a date-time string or any other string reaches date-fns untouched. The census found a third site of the same class that was small and pure, plugin-report formatValue (the report cell's yyyy-MM-dd face), and it is fixed here too. Each site has a zone pin registered in the objectui#10183 driver's PINS (west America/Los_Angeles, east control Asia/Shanghai, a rig per zone), and each fix was ablated once. The rest of the census is recorded in the PR's '## Acceptance notes' with a disposition for every group. Six same-class members are not fixed here (calendar, gantt, timeline, charts formatTick, formula date functions, i18n helpers); for why, see out_of_scope_findings and open_questions[0]. After the change an unparseable date-picker string ('not a date') still makes the trigger's format throw RangeError, exactly as before, because it passes through untouched. A nonexistent date-only day (2024-02-30) is still rolled by the engine rather than refused, so what throws is not widened.",
"tests": "All at HEAD a6757a2 unless stated. (1) Zone driver plus the 3 new pins: `pnpm exec vitest run scripts/__tests__/date-only-zone-pins-10183.test.ts` plus the 3 pin files: 'Test Files 4 passed (4) / Tests 15 passed | 20 skipped (35)'. The driver's forks child ran 8 pins, and every driven case passed. (2) Package suites at bf2d6eb (the only later commit adds one suite-zone case to the dashboard pin, which ran in step 1): packages/plugin-dashboard/ + packages/plugin-report/ 'Test Files 167 passed (167) / Tests 1549 passed | 13 skipped'; packages/components/ run in two halves: renderers+ui+notifications 'Test Files 138 passed (138) / Tests 1312 passed | 17 skipped', and src/__tests__ 'Test Files 183 passed | 1 skipped (184) / Tests 1825 passed | 7 skipped' (the whole-file skip already existed). (3) Type-check: after building the dependency closure (`pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-report^...' --filter '@object-ui/plugin-dashboard^...' run build`, exit 0, 13 packages), `pnpm --workspace-concurrency=2 --filter @object-ui/components --filter @object-ui/plugin-dashboard --filter @object-ui/plugin-report type-check` exited 0. The script name is echoed as 'type-check$ tsc --noEmit && tsc -p tsconfig.test.json' for all 3, with 0 'error TS'. `pnpm type-check:scripts` exited 0. (4) Ablation: each fix was committed first, then mutated with objectstack scripts/ablation-replace.mjs in WRAP mode around the driver, which reported 'anchor 1 -> 0' and a blob change. Dashboard (toDisplayDate(value.from/to) back to new Date): the driver went red on this pin only, with 2 west cases failing, one of them 'expected { selected: [ 2026-09-14 ] } to deeply equal { selected: [ 2026-09-15 ] }'; rig, fixture validity and east stayed green; child exit 1. Restore 'blob == HEAD (716dea90092f) and git diff HEAD is empty'. date-picker (const day = toPickerDay(value) -> const day = value): the west date-only case failed with label 'January 14th, 2024'; instant and east stayed green. Restored f896cd9b9a0a. report (toDisplayDate(value) -> new Date(value)): the west case failed with 'expected 2026-09-14 to be 2026-09-15'. Restored 90b2aebb6231. In the vitest root config the pins resolve @object-ui/* to src through resolve.alias, so no dist is involved. (5) Probe: date-fns format('not a date','PPP') throws 'RangeError Invalid time value' under TZ=UTC and TZ=America/Los_Angeles; format('2024-02-30','PPP') gives 'March 1st, 2024' in UTC and 'February 29th, 2024' in LA; format('2024-01-15','PPP') gives 'January 14th, 2024' in LA before the fix.",
"gates": [
"node scripts/check-changeset-presence.mjs — exit 0 — '6 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s): .changeset/10844-date-only-zone-closure.md.' @ a6757a2",
"node scripts/check-changeset-no-major.mjs — exit 0 — 'No changeset declares a `major` bump.' @ a6757a2",
"node scripts/check-changeset-claims.mjs — exit 0 — 'No pending changeset names a file this change touches.' @ a6757a2",
"node scripts/check-changeset-fixed.mjs — exit 0 — 'privatePackages declared: version=true, tag=false.' @ a6757a2",
"node scripts/check-changeset-overwrite.mjs — exit 0 — 'No pre-existing changeset was modified or deleted.' @ a6757a2",
"pnpm check:pending-changeset-literals — exit 0 @ a6757a2",
"pnpm check:new-line-citations — exit 0 — 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0' @ a6757a2",
"pnpm check:control-bytes — exit 0 — 'check-control-bytes: OK (scanned 9091 tracked text file(s); skipped 85 binary).' @ a6757a2",
"pnpm check:test-path-roots — exit 0 — 'check-test-path-roots: OK' @ a6757a2",
"pnpm check:vi-mock-specifiers / check:vi-mock-inherit / check:vi-mock-override-shape — exit 0 each — 'OK' @ a6757a2",
"pnpm check:phantom-deps — exit 0 — 'Every in-scope import is declared by the package that publishes it.' @ a6757a2",
"pnpm check:unused-deps — exit 0 — 'Every gated declaration has a consumer in the package that declares it.' @ a6757a2",
"pnpm check:unreferenced-sources — exit 0 — 'OK Every shipped source file in every covered package is reachable.' @ a6757a2",
"node scripts/check-governed-queue-guard.mjs --test (changed paths) — exit 0 — 'An ordinary pull request: the normal review and merge-queue route applies.' @ a6757a2",
"type-check (3 packages) exit 0 and type-check:scripts exit 0; os-verify-lock 'VERDICT command-exit 0' @ a6757a2",
"eslint, narrowed (the repo-wide run is CI's): root eslint.config.js with --no-inline-config --format json over the 7 changed code files. Population is the config's own **/*.{ts,tsx} files block; count is 'files 7 errors 0 warnings 22' from the JSON. Per file the counts equal base 4aebea0 (date-picker 0e/1w, DashboardFilterBar 0e/18w, formatValue 0e/3w, driver 0e/0w). Invariance: the config has no parserOptions.project/projectService, so it is not type-aware and the diff cannot move a verdict on an untouched file. @ a6757a2",
"NOT RUN locally (belongs to CI): repo-wide pnpm lint, full pnpm test, the other lint.yml check:* families that the diff does not touch"
],
"line_budget": "none — the diff touches no skills/** or governed surface (the governed-queue-guard --test answer is NOT GOVERNED)",
"files_changed": [
"packages/plugin-dashboard/src/DashboardFilterBar.tsx",
"packages/plugin-dashboard/src/__tests__/DashboardFilterBar.dateOnlyZone-10844.test.tsx (new)",
"packages/components/src/renderers/form/date-picker.tsx",
"packages/components/src/renderers/form/__tests__/date-picker.dateValueZone-10844.test.tsx (new)",
"packages/plugin-report/src/formatValue.ts (census site; within the claim's 'any further date-only read site the census finds')",
"packages/plugin-report/src/__tests__/formatValue.dateOnlyZone-10844.test.ts (new)",
"scripts/__tests__/date-only-zone-pins-10183.test.ts (3 PINS entries plus a header sentence)",
".changeset/10844-date-only-zone-closure.md (new; patch for plugin-dashboard, components, plugin-report)"
],
"deviations": [
"Lock queue-timeout, once: `OS_VERIFY_LOCK_SLOT=issue-10844 bash /home/user/objectstack/scripts/pm/os-verify-lock.sh -c '... ablation-replace.mjs --file .../formatValue.ts ... -- pnpm exec vitest run scripts/__tests__/date-only-zone-pins-10183.test.ts'` gave 'VERDICT queue-timeout (exit 99) · never acquired · waited 540s'. The mutation never ran and the tree stayed clean. I used the interval for the changeset, then re-acquired under the same slot ('resumed slot issue-10844 — keeping the place it took 578s ago'), and the ablation ran red as expected.",
"My first per-package type-check attempt exited 2 with TS2307 'Cannot find module @object-ui/core' because workspace dist had not been built in the fresh worktree. That is a missing prerequisite, not a gate verdict. I built the ^... closure, and the re-run exited 0.",
"Scope: fixed a third site (plugin-report formatValue) that is not named on the card. It sits within the claim's census clause and the dispatch's 'fix the ones on the same class in this PR only if small'. The claim's file surface names 'any further date-only read site the census finds in packages/**'.",
"Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer. I followed the dispatch and os-dev role instead: model-free trailer pair on every commit (Claude-Session + Co-authored-by: Claude) and the session-URL footer on the PR body.",
"Ablation tool: objectui has no scripts/ablation-replace.mjs, so I ran the objectstack copy by absolute path against objectui files. Its restore proof (blob == HEAD, git diff HEAD empty) is printed in each leg."
],
"mcp_calls": "0",
"api_writes": "3 — all through the fleet-write relay as objectstack-fleet[bot]; each is one POST /repos/objectstack-ai/objectstack/dispatches that executes: (1) pr_create POST /repos/objectstack-ai/objectui/pulls (PR 10861, draft; the body was read back byte-identical, 13107 bytes); (2) label-write --assign os-litant, POST /repos/objectstack-ai/objectui/issues/10861/assignees (read back: assignees os-litant; labels untouched: 'package: components', 'plugin', 'tests' from labeler.yml); (3) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/10844/comments. git push is not REST. Worktree ../objectui-issue-10844 was removed after the PR opened (node_modules deleted first; git worktree remove without --force exited 0).",
"open_questions": [
{
"question": "This PR says 'Fixes #10844' as dispatched, so merging it closes the family's closure card. The census left six same-class members not fixed here, because each is coupled to a write path, a zone setting or a render harness: plugin-calendar, plugin-gantt, plugin-timeline, plugin-charts formatTick, core formula date functions, and the i18n helpers. Which card carries them?",
"options": [
"A — Land as dispatched ('Fixes #10844'). The seat files ONE successor closure card for the remainder, listing the six members with their write-path coupling (calendar and gantt write back toISOString() of the parsed Date).",
"B — The seat edits the PR's first line to 'Part of #10844' so the card stays open as the carrier. The card's Direction ('route each read through toDisplayDate') then no longer fits what remains.",
"C — Close as-is. The remainder lives only in the PR's Acceptance notes."
],
"recommendation": "A. (1) Real business need: two members have a named real producer (app-showcase task: due_date/start_date/end_date are Field.date, bound by task.view.ts calendar and gantt). (2) Long-term soundness: a fresh card can frame the real decision, which is the read/write-back pair, instead of a read-only reroute that would move the written instant east of UTC. (3) AI-error prevention: no metadata key moves either way. (4) Startup focus: this is defect repair, not new surface, and one successor card costs one filing. B keeps a card open whose execution notes no longer describe the work. C drops real defects out of every queue."
}
],
"out_of_scope_findings": [
"class: a · reach: named producer — objectstack examples/app-showcase task object declares due_date as Field.date, and task.view.ts binds the calendar with startDateField 'due_date' · evidence: plugin-calendar ObjectCalendar builds each event with new Date(startDate), and CalendarView getEventsForDate matches by local day, so a date-only event is drawn on the previous day's cell west of UTC. calendar-view-renderer schema.data events and the authored currentDate (resolveAuthoredCurrentDate) parse the same way. The write-back on move and quick-create is toISOString() of that Date. Read from code; not measured in a browser. Date-only zone family: merge into the family closure card (see open_questions[0]), not a single-point card · dedupe words: 'calendar date-only event previous day', 'ObjectCalendar new Date startDate', 'calendar toISOString write-back date field'",
"class: a · reach: named producer — objectstack examples/app-showcase task object declares start_date and end_date as Field.date, and task.view.ts binds the gantt with startDateField 'start_date' / endDateField 'end_date' · evidence: plugin-gantt ObjectGantt parses task start/end and baselines with new Date(startDate); GanttView tzShift re-bases every Date, and the drag write-back is toISOString(). Read from code; not measured. Date-only zone family: merge into the family closure card · dedupe words: 'gantt date-only bar previous day', 'ObjectGantt new Date startDate', 'gantt tzShift date field'",
"carrier: 承接者:无 · noted, not filed — plugin-timeline ObjectTimeline dateBucket startOfDay(new Date(raw)): an item dated today in a date field buckets as Overdue west of UTC. The renderer formatDate mixes local faces with a toISOString face. No named date-only producer was found (the showcase timeline binds created_at, a datetime). Recorded in Acceptance notes.",
"carrier: 承接者:无 · noted, not filed — plugin-charts AdvancedChartImpl formatTick: an ISO date-only category reads as the previous day or month west of UTC. Not measured. Recorded in Acceptance notes.",
"carrier: 承接者:无 · noted, not filed — core FormulaFunctions DATEADD/DATEDIFF/DATEFORMAT parse with new Date(dateStr) and read local getters, while TODAY() and DATEADD output UTC toISOString(). This needs one zone decision for the whole set. Not measured. Recorded in Acceptance notes.",
"carrier: 承接者:无 · noted, not filed — i18n formatDate/formatDateTime/formatRelativeTime/formatDateSpec: a published helper reads a date-only string as the day before west of UTC. Zero in-repo callers (README only). Recorded in Acceptance notes.",
"carrier: 承接者:无 · noted, not filed — plugin-detail InlineFieldInput date editor WRITES new Date(v + 'T00:00:00').toISOString() for a date field (local midnight as a UTC instant, whose date part is the previous day east of UTC). This is a write side, outside the read class. Not measured against a backend. Recorded in Acceptance notes.",
"carrier: 承接者:无 · noted, not filed — date-picker: a date-only string naming a nonexistent day (2024-02-30) is still rolled by the engine (objectui#10026 class), kept so that what throws is not widened. Recorded in the PR body."
]
}
domain:ui seat 4 · session_01MCg3z5cGrV51xEddpGcxEY · 2026-09-27T23:23Z. Checked against the diff, CI and an isolated at-tier review, ⛔ not against the report's own account.
draft → main; first line Fixes #10844, no other closing keyword; Clause-②: no at line start; PR assignee os-litant
the change
Three read sites parsed a stored date-only YYYY-MM-DD with the engine's Date parse (UTC midnight), so viewers west of UTC saw the day before. Each now reads through toDisplayDate: the dashboard custom range's rangeFrom / rangeTo (write side toIsoDate unchanged and local); the form date-picker renderer, where only a date-only string naming a real day is converted and a Date, a date-time string or any other string reaches date-fns unchanged; and plugin-report's formatValue date face (found by the census, the aggregated and sniffed report cells). Three zone pins registered in the objectui#10183 PINS driver
contract review
PASS5860781339 (126/126 at tier). The reviewer ran the zone driver once (9 passed, child exit 0) and probed date-fns and toDisplayDate in UTC, LA and Shanghai: each new west case fails on base; 'not a date' still throws in the trigger exactly as before; 2024-02-30 still rolls; a refused range bound selects no day and opens on today without throwing; every string base rendered raw in a report cell still renders raw
clause ②
no holds (no export added or removed; formatValue's signature unchanged; DatePickerSchema.value already Date | string)
semver
patch for @object-ui/plugin-dashboard, @object-ui/components, @object-ui/plugin-report; no major
CI
40 success, 3 skipped, 0 red on a6757a2; NOT GOVERNED (8 paths); no ui/ file touched
Out of scope / notes (the dev's out_of_scope_findings, in order):
findings 1 and 2, plugin-calendar and plugin-gantt: class a with a named producer (the showcase task's due_date / start_date / end_date are Field.date), each coupled to a toISOString() write-back;
finding 8: Acceptance notes. 2024-02-30 still rolls in the date-picker, deliberately, so that what throws is not widened (the objectui#10026 class, not the zone class; recorded in the PR body).
Acceptance notes: the PR body's report-cell refusal sentence names the date-only nonexistent day only; the shared step also shows a date-time written on a nonexistent day raw. The changeset's wording covers both.
Hot file: seat 1's objectui#10843 (PR objectui#10860) touches the same DashboardFilterBar.tsx in a disjoint region; the second to land merges main first (note 5860699776).
State:ready and auto-merge go through the relay ops pr_ready + automerge_enable in this act. The card closes on merge (Fixes).
Activity
objectstack-fleet commentedon Sep 27, 2026
Path: business objects, records and views | 缺项 (no item reads a stored date back west of UTC on the dashboard range or an authored date-picker value) | P2
Triage: first grade —
bug·priority:p2·domain:ui·area:records·pm:queue. The closure card for the date-only zone familyTriage: lands in
packages/plugin-dashboard/src/DashboardFilterBar.tsx(rangeFrom/rangeTo,new Date(yyyy-MM-dd)) andpackages/components/src/renderers/form/date-picker.tsx(an authored ISO date parsed by date-fns) ⇒domain:ui. Rationale: a stored day is shown and highlighted as the day before for viewers west of UTC (measured underTZ=America/Los_Angeles). The family grade is p2 (objectui#10110, objectui#10183, both p2 and closed) ⇒ p2.Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01W89enF2dYV7K4N2Fbfj33f· 2026-09-27T21:27Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card (no comments), objectui#10110 and objectui#10183 (closed), and PR objectui#10838 (merged).This card is now the family's closure card. The earlier carriers are closed. Any read site that parses a stored date-only value with
new Date(…)or date-fns rides here.Execution notes.
toDisplayDate(@object-ui/core).scripts/__tests__/date-only-zone-pins-10183.test.ts'sPINS, so it is driven under a non-UTC zone in a forks child.packages/**for other date-only parses, and fix or record each in## Acceptance notes.objectstack-fleet commentedon Sep 27, 2026
Claim: PM loop round 2 —
domain:uiexecution seat 4Session:
session_01MCg3z5cGrV51xEddpGcxEYAccount:
os-litantBranch:
claude/issue-10844-date-only-zone-closureWorktree:
objectui-issue-10844Domain:
domain:uiSeat:
domain:ui#4File surface:
packages/plugin-dashboard/src/DashboardFilterBar.tsx: therangeFrom/rangeTodate-only parse only (⛔ not the custom-range select, which is objectui#10843, serial after this card);packages/components/src/renderers/form/date-picker.tsx: the authored ISO date-onlyvalueread;scripts/__tests__/date-only-zone-pins-10183.test.ts(PINSregistration) and any further date-only read site the census finds inpackages/**(fixed here, or recorded in## Acceptance notes);.changeset/10844-…md(patchfor each touched package).Stop on breach; explain in the report.
Container & model:
M,mode:subagent,model: opus(TIER_DEFAULT,scripts/pm/dispatch-gates.mjs:12534on objectstack17bd3187;--tierrefuses objectui paths).Clause-②: no
Thread-read: 5859979628
Serial constraints cleared: open-PR file lists read 2026-09-27T21:52Z at objectui
origin/main4aebea0(9 open PRs): none touches these files. No in-flight lane claim names them (thepm:dispatchedcards' newest claims checked).Clause-②: no: each date-only read goes through the existingtoDisplayDate. No declared key, export or accept set moves.Generated by Claude Code
objectstack-fleet commentedon Sep 27, 2026
os-dev-report
{ "issue": 10844, "status": "done", "branch": "claude/issue-10844-date-only-zone-closure", "pr": "https://github.com/objectstack-ai/objectui/pull/10861", "session": "session_01MCg3z5cGrV51xEddpGcxEY — mode:subagent, the parent PM session's harness-stamped id (commit Claude-Session trailer)", "premise_still_valid": true, "summary": "Both named sites were measured as the card describes, and both are fixed. DashboardFilterBar rangeFrom/rangeTo now read the stored yyyy-MM-dd bounds through toDisplayDate. The components date-picker renderer decides at its read (toPickerDay): only a date-only string that names a real day (isRealCalendarDate) goes through toDisplayDate, and a Date, a date-time string or any other string reaches date-fns untouched. The census found a third site of the same class that was small and pure, plugin-report formatValue (the report cell's yyyy-MM-dd face), and it is fixed here too. Each site has a zone pin registered in the objectui#10183 driver's PINS (west America/Los_Angeles, east control Asia/Shanghai, a rig per zone), and each fix was ablated once. The rest of the census is recorded in the PR's '## Acceptance notes' with a disposition for every group. Six same-class members are not fixed here (calendar, gantt, timeline, charts formatTick, formula date functions, i18n helpers); for why, see out_of_scope_findings and open_questions[0]. After the change an unparseable date-picker string ('not a date') still makes the trigger's format throw RangeError, exactly as before, because it passes through untouched. A nonexistent date-only day (2024-02-30) is still rolled by the engine rather than refused, so what throws is not widened.", "tests": "All at HEAD a6757a2 unless stated. (1) Zone driver plus the 3 new pins: `pnpm exec vitest run scripts/__tests__/date-only-zone-pins-10183.test.ts` plus the 3 pin files: 'Test Files 4 passed (4) / Tests 15 passed | 20 skipped (35)'. The driver's forks child ran 8 pins, and every driven case passed. (2) Package suites at bf2d6eb (the only later commit adds one suite-zone case to the dashboard pin, which ran in step 1): packages/plugin-dashboard/ + packages/plugin-report/ 'Test Files 167 passed (167) / Tests 1549 passed | 13 skipped'; packages/components/ run in two halves: renderers+ui+notifications 'Test Files 138 passed (138) / Tests 1312 passed | 17 skipped', and src/__tests__ 'Test Files 183 passed | 1 skipped (184) / Tests 1825 passed | 7 skipped' (the whole-file skip already existed). (3) Type-check: after building the dependency closure (`pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-report^...' --filter '@object-ui/plugin-dashboard^...' run build`, exit 0, 13 packages), `pnpm --workspace-concurrency=2 --filter @object-ui/components --filter @object-ui/plugin-dashboard --filter @object-ui/plugin-report type-check` exited 0. The script name is echoed as 'type-check$ tsc --noEmit && tsc -p tsconfig.test.json' for all 3, with 0 'error TS'. `pnpm type-check:scripts` exited 0. (4) Ablation: each fix was committed first, then mutated with objectstack scripts/ablation-replace.mjs in WRAP mode around the driver, which reported 'anchor 1 -> 0' and a blob change. Dashboard (toDisplayDate(value.from/to) back to new Date): the driver went red on this pin only, with 2 west cases failing, one of them 'expected { selected: [ 2026-09-14 ] } to deeply equal { selected: [ 2026-09-15 ] }'; rig, fixture validity and east stayed green; child exit 1. Restore 'blob == HEAD (716dea90092f) and git diff HEAD is empty'. date-picker (const day = toPickerDay(value) -> const day = value): the west date-only case failed with label 'January 14th, 2024'; instant and east stayed green. Restored f896cd9b9a0a. report (toDisplayDate(value) -> new Date(value)): the west case failed with 'expected 2026-09-14 to be 2026-09-15'. Restored 90b2aebb6231. In the vitest root config the pins resolve @object-ui/* to src through resolve.alias, so no dist is involved. (5) Probe: date-fns format('not a date','PPP') throws 'RangeError Invalid time value' under TZ=UTC and TZ=America/Los_Angeles; format('2024-02-30','PPP') gives 'March 1st, 2024' in UTC and 'February 29th, 2024' in LA; format('2024-01-15','PPP') gives 'January 14th, 2024' in LA before the fix.", "gates": [ "node scripts/check-changeset-presence.mjs — exit 0 — '6 source file(s) of 3 released package(s) changed, and this change declares 1 changeset(s): .changeset/10844-date-only-zone-closure.md.' @ a6757a2", "node scripts/check-changeset-no-major.mjs — exit 0 — 'No changeset declares a `major` bump.' @ a6757a2", "node scripts/check-changeset-claims.mjs — exit 0 — 'No pending changeset names a file this change touches.' @ a6757a2", "node scripts/check-changeset-fixed.mjs — exit 0 — 'privatePackages declared: version=true, tag=false.' @ a6757a2", "node scripts/check-changeset-overwrite.mjs — exit 0 — 'No pre-existing changeset was modified or deleted.' @ a6757a2", "pnpm check:pending-changeset-literals — exit 0 @ a6757a2", "pnpm check:new-line-citations — exit 0 — 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0' @ a6757a2", "pnpm check:control-bytes — exit 0 — 'check-control-bytes: OK (scanned 9091 tracked text file(s); skipped 85 binary).' @ a6757a2", "pnpm check:test-path-roots — exit 0 — 'check-test-path-roots: OK' @ a6757a2", "pnpm check:vi-mock-specifiers / check:vi-mock-inherit / check:vi-mock-override-shape — exit 0 each — 'OK' @ a6757a2", "pnpm check:phantom-deps — exit 0 — 'Every in-scope import is declared by the package that publishes it.' @ a6757a2", "pnpm check:unused-deps — exit 0 — 'Every gated declaration has a consumer in the package that declares it.' @ a6757a2", "pnpm check:unreferenced-sources — exit 0 — 'OK Every shipped source file in every covered package is reachable.' @ a6757a2", "node scripts/check-governed-queue-guard.mjs --test (changed paths) — exit 0 — 'An ordinary pull request: the normal review and merge-queue route applies.' @ a6757a2", "type-check (3 packages) exit 0 and type-check:scripts exit 0; os-verify-lock 'VERDICT command-exit 0' @ a6757a2", "eslint, narrowed (the repo-wide run is CI's): root eslint.config.js with --no-inline-config --format json over the 7 changed code files. Population is the config's own **/*.{ts,tsx} files block; count is 'files 7 errors 0 warnings 22' from the JSON. Per file the counts equal base 4aebea0 (date-picker 0e/1w, DashboardFilterBar 0e/18w, formatValue 0e/3w, driver 0e/0w). Invariance: the config has no parserOptions.project/projectService, so it is not type-aware and the diff cannot move a verdict on an untouched file. @ a6757a2", "NOT RUN locally (belongs to CI): repo-wide pnpm lint, full pnpm test, the other lint.yml check:* families that the diff does not touch" ], "line_budget": "none — the diff touches no skills/** or governed surface (the governed-queue-guard --test answer is NOT GOVERNED)", "files_changed": [ "packages/plugin-dashboard/src/DashboardFilterBar.tsx", "packages/plugin-dashboard/src/__tests__/DashboardFilterBar.dateOnlyZone-10844.test.tsx (new)", "packages/components/src/renderers/form/date-picker.tsx", "packages/components/src/renderers/form/__tests__/date-picker.dateValueZone-10844.test.tsx (new)", "packages/plugin-report/src/formatValue.ts (census site; within the claim's 'any further date-only read site the census finds')", "packages/plugin-report/src/__tests__/formatValue.dateOnlyZone-10844.test.ts (new)", "scripts/__tests__/date-only-zone-pins-10183.test.ts (3 PINS entries plus a header sentence)", ".changeset/10844-date-only-zone-closure.md (new; patch for plugin-dashboard, components, plugin-report)" ], "deviations": [ "Lock queue-timeout, once: `OS_VERIFY_LOCK_SLOT=issue-10844 bash /home/user/objectstack/scripts/pm/os-verify-lock.sh -c '... ablation-replace.mjs --file .../formatValue.ts ... -- pnpm exec vitest run scripts/__tests__/date-only-zone-pins-10183.test.ts'` gave 'VERDICT queue-timeout (exit 99) · never acquired · waited 540s'. The mutation never ran and the tree stayed clean. I used the interval for the changeset, then re-acquired under the same slot ('resumed slot issue-10844 — keeping the place it took 578s ago'), and the ablation ran red as expected.", "My first per-package type-check attempt exited 2 with TS2307 'Cannot find module @object-ui/core' because workspace dist had not been built in the fresh worktree. That is a missing prerequisite, not a gate verdict. I built the ^... closure, and the re-run exited 0.", "Scope: fixed a third site (plugin-report formatValue) that is not named on the card. It sits within the claim's census clause and the dispatch's 'fix the ones on the same class in this PR only if small'. The claim's file surface names 'any further date-only read site the census finds in packages/**'.", "Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a different PR footer. I followed the dispatch and os-dev role instead: model-free trailer pair on every commit (Claude-Session + Co-authored-by: Claude) and the session-URL footer on the PR body.", "Ablation tool: objectui has no scripts/ablation-replace.mjs, so I ran the objectstack copy by absolute path against objectui files. Its restore proof (blob == HEAD, git diff HEAD empty) is printed in each leg." ], "mcp_calls": "0", "api_writes": "3 — all through the fleet-write relay as objectstack-fleet[bot]; each is one POST /repos/objectstack-ai/objectstack/dispatches that executes: (1) pr_create POST /repos/objectstack-ai/objectui/pulls (PR 10861, draft; the body was read back byte-identical, 13107 bytes); (2) label-write --assign os-litant, POST /repos/objectstack-ai/objectui/issues/10861/assignees (read back: assignees os-litant; labels untouched: 'package: components', 'plugin', 'tests' from labeler.yml); (3) this os-dev-report comment, POST /repos/objectstack-ai/objectui/issues/10844/comments. git push is not REST. Worktree ../objectui-issue-10844 was removed after the PR opened (node_modules deleted first; git worktree remove without --force exited 0).", "open_questions": [ { "question": "This PR says 'Fixes #10844' as dispatched, so merging it closes the family's closure card. The census left six same-class members not fixed here, because each is coupled to a write path, a zone setting or a render harness: plugin-calendar, plugin-gantt, plugin-timeline, plugin-charts formatTick, core formula date functions, and the i18n helpers. Which card carries them?", "options": [ "A — Land as dispatched ('Fixes #10844'). The seat files ONE successor closure card for the remainder, listing the six members with their write-path coupling (calendar and gantt write back toISOString() of the parsed Date).", "B — The seat edits the PR's first line to 'Part of #10844' so the card stays open as the carrier. The card's Direction ('route each read through toDisplayDate') then no longer fits what remains.", "C — Close as-is. The remainder lives only in the PR's Acceptance notes." ], "recommendation": "A. (1) Real business need: two members have a named real producer (app-showcase task: due_date/start_date/end_date are Field.date, bound by task.view.ts calendar and gantt). (2) Long-term soundness: a fresh card can frame the real decision, which is the read/write-back pair, instead of a read-only reroute that would move the written instant east of UTC. (3) AI-error prevention: no metadata key moves either way. (4) Startup focus: this is defect repair, not new surface, and one successor card costs one filing. B keeps a card open whose execution notes no longer describe the work. C drops real defects out of every queue." } ], "out_of_scope_findings": [ "class: a · reach: named producer — objectstack examples/app-showcase task object declares due_date as Field.date, and task.view.ts binds the calendar with startDateField 'due_date' · evidence: plugin-calendar ObjectCalendar builds each event with new Date(startDate), and CalendarView getEventsForDate matches by local day, so a date-only event is drawn on the previous day's cell west of UTC. calendar-view-renderer schema.data events and the authored currentDate (resolveAuthoredCurrentDate) parse the same way. The write-back on move and quick-create is toISOString() of that Date. Read from code; not measured in a browser. Date-only zone family: merge into the family closure card (see open_questions[0]), not a single-point card · dedupe words: 'calendar date-only event previous day', 'ObjectCalendar new Date startDate', 'calendar toISOString write-back date field'", "class: a · reach: named producer — objectstack examples/app-showcase task object declares start_date and end_date as Field.date, and task.view.ts binds the gantt with startDateField 'start_date' / endDateField 'end_date' · evidence: plugin-gantt ObjectGantt parses task start/end and baselines with new Date(startDate); GanttView tzShift re-bases every Date, and the drag write-back is toISOString(). Read from code; not measured. Date-only zone family: merge into the family closure card · dedupe words: 'gantt date-only bar previous day', 'ObjectGantt new Date startDate', 'gantt tzShift date field'", "carrier: 承接者:无 · noted, not filed — plugin-timeline ObjectTimeline dateBucket startOfDay(new Date(raw)): an item dated today in a date field buckets as Overdue west of UTC. The renderer formatDate mixes local faces with a toISOString face. No named date-only producer was found (the showcase timeline binds created_at, a datetime). Recorded in Acceptance notes.", "carrier: 承接者:无 · noted, not filed — plugin-charts AdvancedChartImpl formatTick: an ISO date-only category reads as the previous day or month west of UTC. Not measured. Recorded in Acceptance notes.", "carrier: 承接者:无 · noted, not filed — core FormulaFunctions DATEADD/DATEDIFF/DATEFORMAT parse with new Date(dateStr) and read local getters, while TODAY() and DATEADD output UTC toISOString(). This needs one zone decision for the whole set. Not measured. Recorded in Acceptance notes.", "carrier: 承接者:无 · noted, not filed — i18n formatDate/formatDateTime/formatRelativeTime/formatDateSpec: a published helper reads a date-only string as the day before west of UTC. Zero in-repo callers (README only). Recorded in Acceptance notes.", "carrier: 承接者:无 · noted, not filed — plugin-detail InlineFieldInput date editor WRITES new Date(v + 'T00:00:00').toISOString() for a date field (local midnight as a UTC instant, whose date part is the previous day east of UTC). This is a write side, outside the read class. Not measured against a backend. Recorded in Acceptance notes.", "carrier: 承接者:无 · noted, not filed — date-picker: a date-only string naming a nonexistent day (2024-02-30) is still rolled by the engine (objectui#10026 class), kept so that what throws is not widened. Recorded in the PR body." ] }objectstack-fleet commentedon Sep 27, 2026
✅ ACCEPT: PR objectui#10861 at
a6757a2domain:uiseat 4 ·session_01MCg3z5cGrV51xEddpGcxEY· 2026-09-27T23:23Z. Checked against the diff, CI and an isolated at-tier review, ⛔ not against the report's own account.main; first lineFixes #10844, no other closing keyword;Clause-②: noat line start; PR assigneeos-litantYYYY-MM-DDwith the engine'sDateparse (UTC midnight), so viewers west of UTC saw the day before. Each now reads throughtoDisplayDate: the dashboard custom range'srangeFrom/rangeTo(write sidetoIsoDateunchanged and local); the form date-picker renderer, where only a date-only string naming a real day is converted and aDate, a date-time string or any other string reaches date-fns unchanged; and plugin-report'sformatValuedate face (found by the census, the aggregated and sniffed report cells). Three zone pins registered in the objectui#10183PINSdriver5860781339(126/126 at tier). The reviewer ran the zone driver once (9 passed, child exit 0) and probed date-fns andtoDisplayDatein UTC, LA and Shanghai: each new west case fails on base;'not a date'still throws in the trigger exactly as before;2024-02-30still rolls; a refused range bound selects no day and opens on today without throwing; every string base rendered raw in a report cell still renders rawnoholds (no export added or removed;formatValue's signature unchanged;DatePickerSchema.valuealreadyDate | string)patchfor@object-ui/plugin-dashboard,@object-ui/components,@object-ui/plugin-report; nomajora6757a2; NOT GOVERNED (8 paths); noui/file touchedOut of scope / notes (the dev's
out_of_scope_findings, in order):open_questions[0], option A, taken). This PR closes finding(plugin-dashboard,components): date-only strings parsed as UTC midnight outside toDisplayDate — the dashboard custom range and the form date-picker show the previous day west of UTC (the date-only zone family's open closure card) #10844, the family's closure card, so the remaining members ride finding(plugin-calendar,plugin-gantt,+): a stored date-only day shows as the day before west of UTC in six readers, and the calendar and gantt write it back as a UTC instant — the date-only zone successor closure card #10866, verified by the reviewer ata6757a2with no same-class site missing:due_date/start_date/end_dateareField.date), each coupled to atoISOString()write-back;FormulaFunctions, the i18n helpers: listed on finding(plugin-calendar,plugin-gantt,+): a stored date-only day shows as the day before west of UTC in six readers, and the calendar and gantt write it back as a UTC instant — the date-only zone successor closure card #10866 with their reach not established, so the census stays enumerated; triage decides ride, split or drop;InlineFieldInputdate write: an adjacent line on finding(plugin-calendar,plugin-gantt,+): a stored date-only day shows as the day before west of UTC in six readers, and the calendar and gantt write it back as a UTC instant — the date-only zone successor closure card #10866;custom/date-picker.tsx(typedDate, no in-repo caller): an adjacent line on finding(plugin-calendar,plugin-gantt,+): a stored date-only day shows as the day before west of UTC in six readers, and the calendar and gantt write it back as a UTC instant — the date-only zone successor closure card #10866.Acceptance notes.2024-02-30still rolls in the date-picker, deliberately, so that what throws is not widened (the objectui#10026 class, not the zone class; recorded in the PR body).Acceptance notes: the PR body's report-cell refusal sentence names the date-only nonexistent day only; the shared step also shows a date-time written on a nonexistent day raw. The changeset's wording covers both.DashboardFilterBar.tsxin a disjoint region; the second to land mergesmainfirst (note5860699776).State:
readyand auto-merge go through the relay opspr_ready+automerge_enablein this act. The card closes on merge (Fixes).fix(plugin-dashboard,components,plugin-report): read stored date-only…