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 }}
Repository navigation
[finding] Date macros resolve on two clocks: an authored view filter in the browser, a panel filter in the workspace timezone — they can disagree about today #29117
Filed by the record-page epic seat under epic #26676. An independent reviewer assessed this and the seat re-read the evidence. The maintainer: 「三点都按 A:相对日期立独立卡,在 #28971 第一版立卡前合入,包含早于/晚于的相对点;仪表盘拼法另立低优先级卡;两只钟另立 finding。」 (2026-11-02). Code readings below are on maina4015ab47b. No domain:* label is set: triage routes this card.
The maintainer asked for this as a finding, so triage grades it. The difference is read from code. No wrong result has been reproduced.
Reading
Authored view filter, resolved in the browser's clock:
That reaches ui/packages/core/src/utils/filter-tokens.ts, resolveFilterPlaceholders, then resolveDateMacros.
ui/packages/core/src/utils/date-macros.ts:46-67 uses new Date() with local getFullYear, getDate and setHours, and takes no timezone.
Reader's filter panel, resolved on the server:
ui/packages/plugin-list/src/ListView.tsx:1033 sends the triples with the macro unresolved.
packages/core/src/utils/filter-tokens.ts:149-158 takes a timezone, defaulting to UTC. The request context carries the workspace localization.timezone.
What this could mean
Workspace in Asia/Taipei, reader's browser in New York. Around the date boundary, a view declaring due_date < {today} and a panel filter due_date before {today} could use different days. The list may show one rule on the browser's today and another on the workspace's. #29115 adds more panel macros, so this becomes easier to reach, but that card doesn't cause it.
date-macros.ts also describes itself as a temporary duplicate of the spec's vocabulary.
To measure
Set the workspace timezone and emulate a browser in a timezone where the two dates differ.
Open a view with an authored {today} filter and compare the request with a panel filter on the same field.
Report the requests and row counts.
If they agree, close this with the reading. If they differ, decide which clock is right; the seat's starting point is that the workspace timezone should decide, because that's what the console's timezone prompt promises.
Read fresh. The browser-clock side is ui/packages/core/src/utils/date-macros.ts (resolveDateMacros uses new Date() with no timezone, reached from objectViewListRenderer.tsx), while the server already resolves panel macros in the workspace timezone, so a fix lands in ui and the card is domain:ui. It is p2 because, if measured, it is a user-visible wrong-day result in a list. The maintainer ruled it a finding and it names no reader, so it keeps finding and gets only domain and priority.
Claim: PM loop round 6 · 2026-11-02T11:38Z
Session: session_01QsR2jUHSADt5g4prJd5Rte (card thread, os-dev); dispatched by seat session session_01QsR2jRFuJz9QBZqPVHYVhJ
Account: zhuangjianguo
Branch: claude/project-thread-bg6tw3 (platform-assigned to the card thread; it can push only this branch)
Worktree: objectstack-issue-29117
Domain: domain:ui
Seat: domain:ui#2
File surface: none in phase 1 (measure and read only, no commits); the phase 2 surface is set by a claim amendment after the measurement, before any code (stop on breach; explain in the report)
Container & model: S, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: default
Clause-②: no
Metadata-protocol: none
Responsibility: an authored view filter's date macro may resolve in the browser's clock while a panel filter resolves in the workspace timezone, so near a date boundary one list could use two different days | a wrong-day list result, easier to reach once #29115 adds panel macros | readers whose browser zone differs from the workspace's
Thread-read: none
Premise: unmeasured by design (the maintainer ruled it a finding; the card's difference is read from code at a4015ab47b): phase 1 measures it live (workspace and browser in different zones, authored {today} vs panel {today} on one field) and either closes the card with the reading or proposes the fix
Serial constraints cleared: phase 1 writes nothing; a phase 2 edit to objectViewListRenderer.tsx serializes after open PR #29266 (#28950); #29115 (seat 3) is filter-builder.tsx, disjoint
Claim amended · 2026-11-02T12:12Z · phase 1 measured (run record /mnt/project-files/issue-29117/phase1-run-record.md, mainf2bf2938); phase 2 opens. Branch, session and size are unchanged.
Clause-②: no
Metadata-protocol: none
Measured: the two clocks differ. Workspace Pacific/Auckland, browser America/New_York, real clock 12:00Z (NY 11-02, Auckland 11-03), closed_won rows on 11-02 and 11-03: the authored list-view filter close_date between [{today},{today}] was sent resolved as ["2026-11-02","2026-11-02"] (1 row, 11-02); the reader's panel filter was sent as ["{today}","{today}"] and the server answered 1 row (11-03); both on one view sent both days and drew 0 rows. The reverse pair flips; same-zone controls agree. The page already had the zone (/auth/me/localization answered Auckland); resolveDateMacros never reads one. Every other client caller of resolveDateMacros (useResolvedFilter family, ObjectDataPage URL filters, grid, plugin-view, charts, dashboard widgets, compare-to, calendar initialDate) uses the browser clock by reading.
Ruling (seat): the workspace timezone decides, as the console's timezone prompt promises and as the server already resolves. resolveDateMacros resolves calendar tokens (today, week/month/quarter/year bounds, N_days/weeks/months/years) on the wall clock of @object-ui/core's getDisplayTimeZone() (set by LocalizationProvider from the same localization.timezone cascade); instant tokens (now, N_minutes/hours) stay on the real instant; no zone known ⇒ the browser clock, as today. No signature change, so Clause-② stays no; an explicit timeZone parameter is not taken.
File surface (phase 2):ui/packages/core/src/utils/date-macros.ts and its tests; ui/packages/app-shell/src/views/objectViewListRenderer.tsx (the authored-filter substituteFilterTokens call only: read useLocalization().timezone so the filter re-resolves when the zone lands); a root .changeset (@object-ui/core, @object-ui/app-shell patch). Not filter-tokens.ts, not the other callers (they gain the fix through resolveDateMacros; their re-resolve-on-zone-arrival is out of scope and named in the PR). Stop on breach.
{
"issue": 29117,
"status": "done",
"branch": "claude/project-thread-bg6tw3",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29406",
"session": "session_01QsR2jUHSADt5g4prJd5Rte",
"premise_still_valid": true,
"summary": "Phase 1 measured the two clocks live (main f2bf2938, app-crm --fresh, Playwright, workspace Pacific/Auckland vs browser America/New_York at 12:00Z): an authored view's close_date between [today,today] macro pair was sent resolved as 2026-11-02 (browser day) while the panel's unresolved macro was answered by the server on 2026-11-03; both on one view matched 0 rows; reverse pair flipped, same-zone controls agreed. Phase 2 (claim amendment 6576830395): resolveDateMacros reads calendar tokens on the wall-clock day of core getDisplayTimeZone() (instant tokens stay on now; no zone = browser clock, unchanged), and objectViewListRenderer adds useLocalization().timezone to renderListView deps so the authored filter re-resolves when the zone arrives. Live re-measure after the fix: authored and panel land on the same day in both zone orders; the combined request returns the row.",
"files_changed": [
"ui/packages/core/src/utils/date-macros.ts",
"ui/packages/core/src/utils/__tests__/date-macros.workspaceZone-29117.test.ts",
"ui/packages/app-shell/src/views/objectViewListRenderer.tsx",
".changeset/29117-date-macros-workspace-zone.md"
],
"line_budget": "4 files, 239 insertions, 30 deletions vs merge base (git diff --shortstat); every file inside the amended surface",
"tests": "Final head 86b71aeb: core+app-shell type-check exit 0; vitest packages/core/ 232 files, 4288 passed / 27 skipped; app-shell 126 files naming ObjectView/objectViewListRenderer in 3 locked batches 38+44+44 files, 364+358+362 passed. New file 8 cases green. Ablation at 530d6f50 via scripts/ablation-replace.mjs (anchor 1 -> 0, restore blob == HEAD, git diff HEAD empty): zone read cut -> 7 failed / 9 passed across new+old date-macros files; now moved onto the calendar day -> instant-token case red, 1 failed / 7 passed. Core imports src relatively, no dist leg.",
"gates": "dispatch-gates --commands derived 34 families at 86b71aeb (same list as at 530d6f50); all 34 ran exit 0 incl. check-sdui-manifest (exit 0), check:type-check-debt (locked, exit 0), vitest run scripts/ (169 passed, 2 skipped); dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. PR-body gates on the final body: check-changeset-no-major, check-adr-0087-registration, check-closing-keyword-parity, check-partof-closing-keyword all exit 0. CI: in_progress at report time.",
"dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm",
"deviations": [
"Phase 1 panel leg overlaid PR #29263 head e1926801 filter-builder.tsx + filterRelativeDates.ts in the Vite source console because main could not author a today macro on a date column; #29263 merged during the run and the after-fix re-measure used main's own builder.",
"No app-shell unit test pins the zone-arrival re-resolve; the seat's test list named core cases only. It is covered by the live run, not by a test.",
"Merged origin/main (eef116e4) before pr_create; rebuilt the app-shell closure and re-ran type-check, core suite, the 126 app-shell files and all 34 gates on the merged head."
],
"mcp_calls": "0",
"api_writes": "2 — fleet-write pr_create (PR 29406); this post-stamped report comment on issue 29117. git push x2 (not REST).",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: seat 2 · the other resolveDateMacros callers (useResolvedFilter family, ObjectDataPage URL filters, grid, plugin-view, charts, dashboard widgets, compare-to, calendar initialDate) take the workspace day when it is declared at render but do not re-render when the zone arrives late; named in the PR acceptance notes, not filed (read, not reproduced)."
]
}
Landed · 2026-11-02T14:54Z · PR #29406 merged through the queue as 0e0ddacd; the merge commit carries all 4 files of the accepted head 86b71aeb (calendarDayIn and the calendar/instant token split in date-macros.ts, the useLocalization().timezone dependency in objectViewListRenderer.tsx, its test, the changeset). Delivered in full: nothing remains on this card; pm:dispatched removed. Noted, not filed: other resolveDateMacros callers do not re-resolve when the workspace zone arrives after the first render.
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: records-forms.adhoc-filter-sort-builder
Triage:
domain:ui· p2 · 2026-11-02T08:25Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ
Claim: PM loop round 6 · 2026-11-02T11:38Z
Session:
session_01QsR2jUHSADt5g4prJd5Rte(card thread, os-dev); dispatched by seat sessionsession_01QsR2jRFuJz9QBZqPVHYVhJAccount:
zhuangjianguoBranch:
claude/project-thread-bg6tw3(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-29117Domain:
domain:uiSeat:
domain:ui#2File surface: none in phase 1 (measure and read only, no commits); the phase 2 surface is set by a claim amendment after the measurement, before any code (stop on breach; explain in the report)
Container & model: S,
mode:thread,model: default tier (dispatch-gates --tier: no path-derived mandate),effort: defaultClause-②: no
Metadata-protocol: none
Responsibility:
an authored view filter's date macro may resolve in the browser's clock while a panel filter resolves in the workspace timezone, so near a date boundary one list could use two different days | a wrong-day list result, easier to reach once #29115 adds panel macros | readers whose browser zone differs from the workspace'sThread-read: none
Premise: unmeasured by design (the maintainer ruled it a finding; the card's difference is read from code at
a4015ab47b): phase 1 measures it live (workspace and browser in different zones, authored{today}vs panel{today}on one field) and either closes the card with the reading or proposes the fixSerial constraints cleared: phase 1 writes nothing; a phase 2 edit to
objectViewListRenderer.tsxserializes after open PR #29266 (#28950); #29115 (seat 3) isfilter-builder.tsx, disjointobjectstack-fleet commentedon Nov 2, 2026
domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ
Claim amended · 2026-11-02T12:12Z · phase 1 measured (run record
/mnt/project-files/issue-29117/phase1-run-record.md,mainf2bf2938); phase 2 opens. Branch, session and size are unchanged.Clause-②: no
Metadata-protocol: none
closed_wonrows on 11-02 and 11-03: the authored list-view filterclose_date between [{today},{today}]was sent resolved as["2026-11-02","2026-11-02"](1 row, 11-02); the reader's panel filter was sent as["{today}","{today}"]and the server answered 1 row (11-03); both on one view sent both days and drew 0 rows. The reverse pair flips; same-zone controls agree. The page already had the zone (/auth/me/localizationanswered Auckland);resolveDateMacrosnever reads one. Every other client caller ofresolveDateMacros(useResolvedFilter family, ObjectDataPage URL filters, grid, plugin-view, charts, dashboard widgets, compare-to, calendar initialDate) uses the browser clock by reading.resolveDateMacrosresolves calendar tokens (today, week/month/quarter/year bounds,N_days/weeks/months/years) on the wall clock of@object-ui/core'sgetDisplayTimeZone()(set byLocalizationProviderfrom the samelocalization.timezonecascade); instant tokens (now,N_minutes/hours) stay on the real instant; no zone known ⇒ the browser clock, as today. No signature change, so Clause-② staysno; an explicittimeZoneparameter is not taken.ui/packages/core/src/utils/date-macros.tsand its tests;ui/packages/app-shell/src/views/objectViewListRenderer.tsx(the authored-filtersubstituteFilterTokenscall only: readuseLocalization().timezoneso the filter re-resolves when the zone lands); a root.changeset(@object-ui/core,@object-ui/app-shellpatch). Notfilter-tokens.ts, not the other callers (they gain the fix throughresolveDateMacros; their re-resolve-on-zone-arrival is out of scope and named in the PR). Stop on breach.objectViewListRenderer.tsxhas landed; PR feat(components): the filter panel offers relative dates on date rows #29263 (List filter panel: offer relative dates on date rows — 本周 and 今天 today store a fixed date, so a saved filter silently goes wrong #29115, seat 3) ownsfilter-builder.tsx/filterRelativeDates.ts, disjoint (phase 1 overlaid them in Vite only, restored).objectstack-fleet commentedon Nov 2, 2026
domain:ui#2 · zhuangjianguo · session_01QsR2jUHSADt5g4prJd5Rte
os-dev-report
{ "issue": 29117, "status": "done", "branch": "claude/project-thread-bg6tw3", "pr": "https://github.com/objectstack-ai/objectstack/pull/29406", "session": "session_01QsR2jUHSADt5g4prJd5Rte", "premise_still_valid": true, "summary": "Phase 1 measured the two clocks live (main f2bf2938, app-crm --fresh, Playwright, workspace Pacific/Auckland vs browser America/New_York at 12:00Z): an authored view's close_date between [today,today] macro pair was sent resolved as 2026-11-02 (browser day) while the panel's unresolved macro was answered by the server on 2026-11-03; both on one view matched 0 rows; reverse pair flipped, same-zone controls agreed. Phase 2 (claim amendment 6576830395): resolveDateMacros reads calendar tokens on the wall-clock day of core getDisplayTimeZone() (instant tokens stay on now; no zone = browser clock, unchanged), and objectViewListRenderer adds useLocalization().timezone to renderListView deps so the authored filter re-resolves when the zone arrives. Live re-measure after the fix: authored and panel land on the same day in both zone orders; the combined request returns the row.", "files_changed": [ "ui/packages/core/src/utils/date-macros.ts", "ui/packages/core/src/utils/__tests__/date-macros.workspaceZone-29117.test.ts", "ui/packages/app-shell/src/views/objectViewListRenderer.tsx", ".changeset/29117-date-macros-workspace-zone.md" ], "line_budget": "4 files, 239 insertions, 30 deletions vs merge base (git diff --shortstat); every file inside the amended surface", "tests": "Final head 86b71aeb: core+app-shell type-check exit 0; vitest packages/core/ 232 files, 4288 passed / 27 skipped; app-shell 126 files naming ObjectView/objectViewListRenderer in 3 locked batches 38+44+44 files, 364+358+362 passed. New file 8 cases green. Ablation at 530d6f50 via scripts/ablation-replace.mjs (anchor 1 -> 0, restore blob == HEAD, git diff HEAD empty): zone read cut -> 7 failed / 9 passed across new+old date-macros files; now moved onto the calendar day -> instant-token case red, 1 failed / 7 passed. Core imports src relatively, no dist leg.", "gates": "dispatch-gates --commands derived 34 families at 86b71aeb (same list as at 530d6f50); all 34 ran exit 0 incl. check-sdui-manifest (exit 0), check:type-check-debt (locked, exit 0), vitest run scripts/ (169 passed, 2 skipped); dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. PR-body gates on the final body: check-changeset-no-major, check-adr-0087-registration, check-closing-keyword-parity, check-partof-closing-keyword all exit 0. CI: in_progress at report time.", "dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm", "deviations": [ "Phase 1 panel leg overlaid PR #29263 head e1926801 filter-builder.tsx + filterRelativeDates.ts in the Vite source console because main could not author a today macro on a date column; #29263 merged during the run and the after-fix re-measure used main's own builder.", "No app-shell unit test pins the zone-arrival re-resolve; the seat's test list named core cases only. It is covered by the live run, not by a test.", "Merged origin/main (eef116e4) before pr_create; rebuilt the app-shell closure and re-ran type-check, core suite, the 126 app-shell files and all 34 gates on the merged head." ], "mcp_calls": "0", "api_writes": "2 — fleet-write pr_create (PR 29406); this post-stamped report comment on issue 29117. git push x2 (not REST).", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "carrier: seat 2 · the other resolveDateMacros callers (useResolvedFilter family, ObjectDataPage URL filters, grid, plugin-view, charts, dashboard widgets, compare-to, calendar initialDate) take the workspace day when it is declared at render but do not re-render when the zone arrives late; named in the PR acceptance notes, not filed (read, not reproduced)." ] }objectstack-fleet commentedon Nov 2, 2026
domain:ui#2 · zhuangjianguo · session_01QsR2jRFuJz9QBZqPVHYVhJ
Landed · 2026-11-02T14:54Z · PR #29406 merged through the queue as
0e0ddacd; the merge commit carries all 4 files of the accepted head86b71aeb(calendarDayInand the calendar/instant token split indate-macros.ts, theuseLocalization().timezonedependency inobjectViewListRenderer.tsx, its test, the changeset). Delivered in full: nothing remains on this card;pm:dispatchedremoved. Noted, not filed: otherresolveDateMacroscallers do not re-resolve when the workspace zone arrives after the first render.