Skip to content

feat(formatters): surface account timezone + today's local date on list responses - #151

Merged
devondragon merged 1 commit into
mainfrom
feat/account-date-context
Aug 24, 2026
Merged

feat(formatters): surface account timezone + today's local date on list responses#151
devondragon merged 1 commit into
mainfrom
feat/account-date-context

Conversation

@devondragon

Copy link
Copy Markdown
Owner

Closes #150.

Why

Date-relative reasoning ("what's due this week?", "when am I free today?") had nothing in the payload stating the account's timezone. The model knows the wall-clock date from its own context but not that the account is, say, America/Denver — so in live testing fresh agents inferred "now" from the newest task updatedTime stamp and sometimes reported the wrong weekday ("Sunday" on a Monday). Relative filters the model delegates to the server (dueDate:'today') already resolve correctly; the gap is the date reasoning the model does itself.

What

  • New formatAccountDateContext(timeZone, now?) in dateFormat.ts returns one line: Account timezone: America/Denver | Today: 2026-08-24 (Monday). Built from Intl.formatToParts (order-independent, workerd-safe); now is injectable for deterministic tests; falls back to a UTC-labelled reading when no usable zone is available.
  • Prepended to formatTaskList (used by list and list_all_uncompleted) and formatScheduleList (zone resolved from the schedules via resolveDisplayTimeZone).

The zone is already resolved for rendering local times, so this is one line and no extra API calls. Shared code, so it lands on both the stdio and Worker entry points.

What it helps

Correct weekday and "this week / next week / end of month" window math in the right zone; "when am I free" anchored to the real current time and correct weekday; eliminates the wrong-day-of-week class of errors.

Tests / review

New tests/date-context-formatter.spec.ts: pinned-instant zone+weekday rendering, midnight local-vs-UTC day rollover, no-zone and invalid-IANA-zone fallbacks, and header presence in both formatters. Full suite (693) green; type-check, worker:type-check, and test:types all clean. A code-review pass found no issues at confidence ≥80 (verified timezone math, no positional-parse break, header stays out of structuredContent).

Note on the related free/busy limitation

Separately researched whether a real "when am I free" (meeting-aware) tool is feasible: the public Motion API exposes no calendar-events, free/busy, availability, or booking endpoint. Task scheduledStart/scheduledEnd + /schedules working hours are the only signals a personal API key can reach; a true free/busy lives only in Motion's undocumented internal app API. So that limitation is not addressable on this API surface and is intentionally out of scope here.

https://claude.ai/code/session_01HbZLaLAKnrR3ZoZkZUk1Dn

…st responses

Date-relative reasoning (this week, weekday, "now") had nothing in the payload
stating the account's timezone, so the model guessed: fresh agents inferred
"now" from the newest updatedTime stamp and sometimes reported the wrong day of
week. The wall-clock date the model has from its own context is in an unknown
zone; relative filters it delegates to the server already resolve correctly, but
reasoning it does itself did not.

Add formatAccountDateContext(timeZone, now?) and prepend a one-line header to
task-list responses (formatTaskList, used by list and list_all_uncompleted) and
schedule responses (formatScheduleList, zone resolved from the schedules):

  Account timezone: America/Denver | Today: 2026-08-24 (Monday)

The zone is already resolved for rendering local times, so this is one line and
no extra API calls. now is injectable for deterministic tests; falls back to a
UTC-labelled reading when no usable zone is available. Shared code, so it lands
on both the stdio and Worker entry points.

Closes #150.

Claude-Session: https://claude.ai/code/session_01HbZLaLAKnrR3ZoZkZUk1Dn
Copilot AI lite review requested due to automatic review settings August 24, 2026 21:53
@claude

claude Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review

Small, well-scoped change that does exactly what the description says. Went through dateFormat.ts, responseFormatters.ts, and the new spec file in detail.

Code quality

  • formatAccountDateContext is clean: Intl.formatToParts reduced into a record makes it order-independent across ICU implementations (a real concern given the Node vs. workerd split this repo cares about), and the try/catch fallback to a raw UTC ISO slice is a sensible last resort even though isValidTimeZone already filters bad zones before this runs, so that catch branch should rarely fire in practice — fine as defensive belt-and-suspenders.
  • Threading timeZone/resolveDisplayTimeZone through TaskHandler into formatTaskList and formatScheduleList follows the existing pattern used for formatDateOnly/formatTimestamp elsewhere in the file, so it's consistent with the rest of the module rather than a new convention.
  • formatScheduleList was refactored off the shared formatListResponse helper to build the response text inline. That's a necessary trade-off to prepend the context line, and the pre-existing empty-schedules early return (No schedules found.) still short-circuits before the new code, so an empty schedule list doesn't get a misleading/zone-less context line. Good catch keeping that ordering intact.

Correctness

  • Verified the day-rollover logic by hand: 2026-08-24T04:00:00Z is 2026-08-23T22:00 in America/Denver (UTC-6, MDT in effect), matching the new test's expected 2026-08-23 (Sunday). The date math checks out.
  • Confirmed formatMcpSuccess only attaches structuredContent when explicitly passed (src/utils/errors.ts:201-214), and neither formatTaskList nor formatScheduleList puts the context line into structured/structuredContent — the PR's claim that the header stays out of structured output holds.
  • formatTaskList doesn't special-case zero tasks, so the header still renders for an empty list — that's actually useful here (a "0 tasks due this week" answer still needs the account's "today" to be meaningful).

Scope / consistency

  • Only formatTaskList and formatScheduleList get the header, per the PR description; formatTaskDetail and other single-item formatters don't. That's a reasonable scope boundary (single-item responses lean less on "what week/day is it" reasoning than list/filter responses do), just flagging it as an intentional asymmetry rather than an oversight, in case future PRs want to extend this to more formatters.

Tests

  • New tests/date-context-formatter.spec.ts covers the cases that matter most: pinned instant zone+weekday, midnight local-vs-UTC rollover, missing zone, and invalid IANA zone — plus header-presence checks for both formatters. That's solid coverage for a one-line-output helper.
  • No test exercises the catch fallback branch inside formatAccountDateContext (i.e., Intl.DateTimeFormat/formatToParts throwing after isValidTimeZone already passed) — understandable since it's hard to trigger given isValidTimeZone already gates it, but worth knowing it's currently unverified by tests if that ever becomes reachable (e.g. a runtime with a zone DB mismatch between the two Intl calls).

Security / performance

  • No new API calls — the zone is derived from data already being fetched (schedules cache) or passed through from resolved timezone, matching the "no extra API calls" claim.
  • No injection or sanitization concerns; output is a fixed-format string built from Intl parts and a timeZone string that's already validated via isValidTimeZone before use.

Overall this looks correct, minimal, and consistent with the codebase's existing timezone-handling conventions (resolveDisplayTimeZone, renderInZone). No blocking issues found.

@devondragon
devondragon merged commit eac7ebf into main Aug 24, 2026
3 checks passed
@devondragon
devondragon deleted the feat/account-date-context branch August 24, 2026 21:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The UTC fallback path in formatAccountDateContext can produce a UTC date while still labeling the account timezone as a valid zone, which can mislead consumers about what “Today” is relative to.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds an account “date context” header to key list-format responses so downstream date-relative reasoning can anchor to the account timezone and the correct local “today” (instead of guessing from timestamps).

Changes:

  • Added formatAccountDateContext(timeZone, now?) to compute a one-line Account timezone … | Today: YYYY-MM-DD (Weekday) context string.
  • Prepended the context line to formatTaskList and formatScheduleList text responses (while keeping task structuredContent unchanged).
  • Added Vitest coverage for pinned instants, near-midnight rollover, fallbacks, and header presence in both formatters.
File summaries
File Description
tests/date-context-formatter.spec.ts Adds tests for the new account date context helper and verifies header presence in task/schedule list text output.
src/utils/responseFormatters.ts Prepends the account date context line to task and schedule list response text.
src/utils/dateFormat.ts Introduces formatAccountDateContext implemented via Intl.DateTimeFormat(...).formatToParts with fallbacks.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/utils/dateFormat.ts
} catch {
// fall through to a minimal UTC reading
}
return `Account timezone: ${usableZone ?? 'unknown (times shown in UTC)'} | Today: ${now.toISOString().slice(0, 10)}`;
devondragon added a commit that referenced this pull request Aug 24, 2026
Update the CHANGELOG [Unreleased] section and README for the changes
shipped since the #132-#141 wave:

- #151: account timezone + today's local date header on list/schedule
  responses
- #149: list_all_uncompleted honors dueDate/priority; list gains
  completedAfter/completedBefore; schedule working hours; motion_statuses
  in the essential tier
- #148: dueDate filter reduced to account-zone calendar date
- wrangler observability logs enabled

README: add list_all_uncompleted to motion_tasks operations, describe the
new dueDate/completion filters and the timezone header, and correct the
motion_schedules description to match the tool (working-hour templates,
not calendar events).

Claude-Session: https://claude.ai/code/session_01HbZLaLAKnrR3ZoZkZUk1Dn
devondragon added a commit that referenced this pull request Aug 24, 2026
Release 2.9.0. Stamps the [Unreleased] section (the #132-#151 wave:
Worker auth hardening, timezone-aware date handling, list/schedule
filters, and the structured-content and CORS fixes) with the release
date, and bumps package.json/package-lock.json.

Claude-Session: https://claude.ai/code/session_01HbZLaLAKnrR3ZoZkZUk1Dn
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Surface account timezone + today's local date so date reasoning stops guessing

2 participants