Time Compass — Data Flow Timing¶
Summary¶
- Internal backend datetime baseline remains
Asia/Taipei (UTC+8). - Frontend planner payload is now normalized to
camelCaseat ingress (api.js). - External API contract remains
snake_case(/api/planner/*,/api/context/fetch). - Recurring master events now keep original start/end anchor timestamps.
08:00heuristic for all-day detection is removed.
Google Calendar Event Semantics¶
1. Backend Datetime Retention (No Early String Casting)¶
File: src/time_compass/integrations/google_calendar/models/models_read.py - start and end are retained as native datetime objects (with Asia/Taipei timezone) in GoogleEventRead. - Why: The frontend planner components and intermediate normalization layers (like planner_utils.py -> _to_taipei_datetime()) expect robust input processing. Eagerly casting dictionaries ({'dateTime': ...}) to strings (str({'dateTime': ...})) silently breaks the downstream parsers. - String serialization (like format_for_llm()) is strictly deferred to the final TOON encoding layer (safe_encode()) or the final API Response Payload output.
2. All-day detection (backend)¶
File: src/time_compass/integrations/google_calendar/models/models_read.py - start.date / end.date present in the original raw dict => all-day event. - start.dateTime / end.dateTime only => timed event. - endTimeUnspecified=true => treated as all-day by project policy.
Notes: - endTimeUnspecified => all_day=True is an explicit product decision for planner rendering consistency. - Previous 08:00 => all_day inference has been completely removed to avoid accidental mutation of midnight events.
TOON conversion alignment¶
File: src/time_compass/integrations/google_calendar/models/models_toon.py - TOON conversion now trusts event.all_day directly. - No secondary 08:00 fallback exists anymore.
Google Tasks Event Semantics¶
1. Eager String Formatting (Discrepancy)¶
File: src/time_compass/integrations/google_tasks/models/models_read.py - Unlike GoogleEventRead, GoogleTaskRead still immediately casts due and completed native datetime objects into strings using format_for_llm(dt) during from_dict() parsing. - Risk: If downstream modules need to perform date math on Tasks, they must re-parse the string. - Note: This is acceptable for the current architecture because Google Tasks lack precise "time" tracking (only days/midnight UTC), but it creates a structural mismatch with Calendar models.
2. All-day detection (backend)¶
GoogleTaskRead.all_dayis hardcoded toTrue. Because Google Tasks API'sduetimestamp is always sent atT00:00:00.000Z(UTC Midnight) which translates to08:00UTC+8, no precise duration exists.
Recurrence Anchor Policy¶
File: src/time_compass/utils/planner_utils.py - Removed logic that re-anchored recurring master start/end to view_date. - Master events retain original source anchor timestamps. - Exceptions/cancellations are still modeled via recurring_event_id, original_start, status, and exdate aggregation.
Impact: - Frontend no longer receives rewritten master start times. - Prevents planner start-time drift introduced by synthetic re-anchoring.
Frontend Normalization & Naming¶
Element cache naming¶
Files: - src/time_compass/mcp/ui/js/core/state.js - src/time_compass/mcp/ui/js/services/api.js - src/time_compass/mcp/ui/js/features/view.js - src/time_compass/mcp/ui/js/features/zoom.js - src/time_compass/mcp/ui/js/debug/payload_test_page.js
Changes: - el renamed to elements across UI modules. - Internal helper names expanded for clarity (getFixedCalendarEvents, getDraftCalendarEvents, calculateIso8601Duration, etc.).
Ingress mapper (snake_case -> camelCase)¶
File: src/time_compass/mcp/ui/js/services/api.js - _validatePlannerPayload validates raw payload via Zod schema (snake_case). - normalizePlannerPayload maps to internal camelCase state: - session_id -> sessionId - calendar_events -> calendarEvents - draft_variants -> draftVariants - writable_calendars -> writableCalendars - apply_state.applied_at -> applyState.appliedAt - etc.
Outbound API request bodies stay snake_case.
FullCalendar Integration Policy¶
File: src/time_compass/mcp/ui/js/features/view.js - Calendar option explicitly sets timeZone: "local". - Recurring fixed events are emitted with structured recurrence fields: - rrule: { dtstart, ...ruleParts } - exdate: [...] - duration supplied for master recurrence rendering.
This keeps browser-local display stable while preserving RFC3339 offsets from backend payload timestamps.
Verification Checklist¶
Static¶
rg -n "\bel\b|el\." src/time_compass/mcp/ui/jsrg -n "_durationInput|fixedCalendarEvents|draftCalendarEvents|pad2|formatHm|overlap\(" src/time_compass/mcp/ui/js
Runtime / unit¶
uv run pytest tests/unit/integrations/google_calendar/test_event_read_model.py -vuv run pytest tests/unit/test_planner_utils.py -vuv run pytest tests/unit/planner/test_planner_web.py -v
UI smoke¶
- Open Planner Studio and verify:
- recurring start anchor no longer drifts to
view_date - apply/toggle/zoom/date navigation still function
- event times match browser local timezone display expectations
Version¶
- v2.0 (2026-02-21)
- Removed 08:00 all-day heuristic.
- Removed recurrence master re-anchor behavior.
- Added frontend ingress normalization and naming standardization.
- Explicit FullCalendar local timezone configuration.