Skip to content

[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

Closed

Description

@objectstack-fleet

epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff

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 main a4015ab47b. 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:

  • ui/packages/app-shell/src/views/objectViewListRenderer.tsx:521-527 calls substituteFilterTokens.
  • 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

  1. Set the workspace timezone and emulate a browser in a timezone where the two dates differ.
  2. Open a view with an authored {today} filter and compare the request with a panel filter on the same field.
  3. 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.

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    domain:uiDomain lane — seat card indexed by label:pm:seat
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: records-forms.adhoc-filter-sort-builder

    Triage: domain:ui · p2 · 2026-11-02T08:25Z

    ⛔ Not a claim.

    • 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.

    Generated by Claude Code

  3. added
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    pm:queueReady for the PM dispatch loop
    on Nov 2, 2026
  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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 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

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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, main f2bf2938); 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.
    • Serial constraints cleared: PR feat(ui): one Personalized marker beside the list title holds the one reset to the view #29266 (List personalization: one visible 「已个性化」 marker and one reset that restores the whole list — today two reset entries cover different parts and neither says what changed #28950) on objectViewListRenderer.tsx has 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) owns filter-builder.tsx/filterRelativeDates.ts, disjoint (phase 1 overlaid them in Vite only, restored).
  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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)."
      ]
    }
  7. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:uiDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions