Files
social-app/docs/plans/2026-03-16-calendar-timezone-unification.md
T

286 lines
10 KiB
Markdown

# Calendar Timezone Unification Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Eliminate calendar time mismatches by enforcing one end-to-end timezone policy across App input, Agent runtime context, tool execution, and UTC database storage.
**Architecture:** Keep database schema unchanged (`start_at/end_at TIMESTAMPTZ + timezone`) and enforce strict runtime normalization. Device timezone is injected from `RunAgentInput.forwardedProps`, resolved into a single `effective_timezone`, then written explicitly into tool arguments and persisted as event timezone while timestamps are stored in UTC. Calendar read responses include deterministic event-timezone-rendered values so frontend rendering is stable and no implicit `toLocal()` conversion remains.
**Tech Stack:** FastAPI, Pydantic v2, AgentScope runtime/tooling, Flutter (Dart), PostgreSQL TIMESTAMPTZ, pytest, Flutter test.
---
## Chunk 1: Protocol and Backend Runtime Normalization
### Task 1: Freeze protocol and timezone precedence contract
**Files:**
- Modify: `docs/protocols/agent/run-agent-input.md`
- Create: `docs/protocols/calendar/timezone-policy.md`
- [ ] **Step 1: Write protocol delta checklist in docs first**
Document the exact policy:
- `event_timezone > device_timezone > profile.timezone > UTC`
- `event_timezone` must be present in final tool call
- `start_at/end_at` must be timezone-aware
- DB stores UTC timestamps and IANA timezone string
- [ ] **Step 2: Update RunAgentInput protocol with forwardedProps contract**
Add canonical payload example:
```json
{
"forwardedProps": {
"client_time": {
"device_timezone": "America/Los_Angeles",
"client_now_iso": "2026-03-16T09:12:33-07:00",
"client_epoch_ms": 1773658353000
}
}
}
```
- [ ] **Step 3: Add calendar timezone policy protocol doc**
Include:
- accepted datetime formats
- explicit error codes
- write/read response semantics
- DST handling rule
- [ ] **Step 4: Verify docs consistency**
Run: `cd backend && uv run python -m pytest tests/unit/core/agentscope/test_system_prompt.py -q`
Expected: PASS (no protocol-breaking prompt assumptions)
### Task 2: Parse forwarded device time and compute effective timezone
**Files:**
- Modify: `backend/src/core/agentscope/schemas/agui_input.py`
- Modify: `backend/src/core/agentscope/runtime/runner.py`
- Modify: `backend/src/core/agentscope/prompts/system_prompt.py`
- Test: `backend/tests/unit/core/agentscope/test_system_prompt.py`
- [ ] **Step 1: Write failing tests for effective timezone resolution**
Add tests covering:
- forwarded `device_timezone` present -> selected
- missing forwarded timezone -> fallback profile timezone
- invalid forwarded timezone -> fallback profile timezone
- [ ] **Step 2: Run tests to confirm RED**
Run: `cd backend && uv run pytest tests/unit/core/agentscope/test_system_prompt.py -k timezone -v`
Expected: FAIL on new assertions
- [ ] **Step 3: Implement minimal runtime context extraction**
Implement a typed helper in runner path to read:
- `run_input.forwarded_props.client_time.device_timezone`
- `client_now_iso`
- `client_epoch_ms`
Compute `effective_timezone` using fixed precedence and pass it into `build_system_prompt(...)`.
- [ ] **Step 4: Inject effective_timezone into ENV section**
Update `build_system_prompt` env payload to include:
- `timezone_profile`
- `timezone_device`
- `timezone_effective`
Update guidance sentence to resolve ambiguous time with `timezone_effective`.
- [ ] **Step 5: Run tests to confirm GREEN**
Run: `cd backend && uv run pytest tests/unit/core/agentscope/test_system_prompt.py -v`
Expected: PASS
### Task 3: Remove timezone ambiguity and hidden fallbacks from calendar write
**Files:**
- Modify: `backend/src/core/agentscope/tools/utils/calendar_domain.py`
- Modify: `backend/src/core/agentscope/tools/custom/calendar.py`
- Modify: `backend/src/v1/schedule_items/schemas.py`
- Modify: `backend/src/v1/schedule_items/service.py`
- Test: `backend/tests/unit/core/agentscope/test_calendar_tools.py`
- Test: `backend/tests/unit/v1/schedule_items/test_schemas.py`
- Test: `backend/tests/unit/v1/schedule_items/test_service.py`
- [ ] **Step 1: Write failing tests for forbidden naive datetime and required timezone**
Add tests for:
- naive `start_at` rejected
- missing `event_timezone` rejected in tool path
- parse failure does not fallback to `now + 1h`
- [ ] **Step 2: Run tests to confirm RED**
Run: `cd backend && uv run pytest tests/unit/core/agentscope/test_calendar_tools.py tests/unit/v1/schedule_items/test_schemas.py -v`
Expected: FAIL on new constraints
- [ ] **Step 3: Implement strict parsing and normalization**
Implementation requirements:
- `parse_iso_datetime` rejects naive input
- remove default `Asia/Shanghai` in tool
- remove fallback auto-generated start time
- validate IANA timezone and normalize `start_at/end_at` to UTC before persistence
- [ ] **Step 4: Enforce service-level invariants**
Service invariant set:
- timezone non-empty and valid IANA
- `end_at is None or end_at >= start_at`
- [ ] **Step 5: Run backend tests**
Run: `cd backend && uv run pytest tests/unit/core/agentscope/test_calendar_tools.py tests/unit/v1/schedule_items/test_schemas.py tests/unit/v1/schedule_items/test_service.py tests/integration/test_schedule_items_routes.py -v`
Expected: PASS
### Task 4: Keep DB schema, add non-breaking constraint migration only
**Files:**
- Create: `backend/alembic/versions/20260316_000x_schedule_items_time_constraints.py`
- Test: `backend/tests/integration/test_schedule_items_routes.py`
- [ ] **Step 1: Write migration test expectation first**
Add/extend integration assertion for invalid `end_at < start_at` returning 422.
- [ ] **Step 2: Run integration test to confirm RED**
Run: `cd backend && uv run pytest tests/integration/test_schedule_items_routes.py -k end_at -v`
Expected: FAIL
- [ ] **Step 3: Implement migration with CHECK only (no new columns)**
Migration includes:
- `CHECK (end_at IS NULL OR end_at >= start_at)`
- [ ] **Step 4: Run migration + integration test**
Run: `cd backend && uv run alembic upgrade head && uv run pytest tests/integration/test_schedule_items_routes.py -v`
Expected: PASS
---
## Chunk 2: Frontend Deterministic Display and Agent Input Wiring
### Task 5: Wire device timezone into RunAgentInput forwardedProps
**Files:**
- Modify: `apps/lib/features/chat/data/services/ag_ui_service.dart`
- Modify: `apps/lib/features/chat/data/models/ag_ui_event.dart` (only if serialization helper is needed)
- Test: `apps/test/features/chat/ag_ui_event_test.dart`
- [ ] **Step 1: Write failing test for forwarded client_time payload**
Assert outgoing run request contains:
- `forwardedProps.client_time.device_timezone`
- `client_now_iso`
- `client_epoch_ms`
- [ ] **Step 2: Run test to confirm RED**
Run: `cd apps && flutter test test/features/chat/ag_ui_event_test.dart`
Expected: FAIL
- [ ] **Step 3: Implement payload injection in one place**
Add a single helper to build client time context and attach it to run input requests.
- [ ] **Step 4: Run test to confirm GREEN**
Run: `cd apps && flutter test test/features/chat/ag_ui_event_test.dart`
Expected: PASS
### Task 6: Remove implicit local-time rendering and render by event timezone
**Files:**
- Modify: `apps/lib/features/calendar/data/models/schedule_item_model.dart`
- Modify: `apps/lib/features/calendar/ui/screens/calendar_event_detail_screen.dart`
- Modify: `apps/lib/features/calendar/ui/screens/calendar_dayweek_screen.dart`
- Modify: `apps/lib/features/calendar/ui/screens/calendar_month_screen.dart`
- Modify: `apps/lib/features/messages/ui/widgets/calendar_message_card.dart`
- Modify: `apps/lib/features/calendar/ui/widgets/create_event_sheet.dart`
- Test: `apps/test/features/calendar/ui/calendar_time_utils_test.dart`
- Test: `apps/test/features/calendar/ui/create_event_sheet_time_align_test.dart`
- [ ] **Step 1: Write failing tests for timezone-specific rendering**
Cover cases:
- same UTC event shows different local clock time under different `event.timezone`
- list/day/week/month are consistent for one event
- create sheet sends explicit timezone in payload
- [ ] **Step 2: Run tests to confirm RED**
Run: `cd apps && flutter test test/features/calendar/ui/calendar_time_utils_test.dart test/features/calendar/ui/create_event_sheet_time_align_test.dart`
Expected: FAIL
- [ ] **Step 3: Implement deterministic time conversion utility**
Implement one utility used by all calendar UI surfaces:
- input: UTC datetime + IANA timezone
- output: event-local datetime
Replace direct `.toLocal()` usage in calendar model/view with this utility.
- [ ] **Step 4: Enforce explicit timezone on create/update payload**
Create/update must always include `timezone` field from selected event timezone.
- [ ] **Step 5: Run Flutter tests**
Run: `cd apps && flutter test test/features/calendar/ui/calendar_time_utils_test.dart test/features/calendar/ui/create_event_sheet_time_align_test.dart`
Expected: PASS
### Task 7: End-to-end verification matrix and release checklist
**Files:**
- Modify: `docs/plans/timezone-e2e-checklist.md`
- Test: `backend/tests/integration/test_schedule_items_routes.py`
- Test: `apps/test/features/calendar/ui/create_event_sheet_time_align_test.dart`
- [ ] **Step 1: Add reproducible matrix**
Matrix axes:
- device timezone: `America/Los_Angeles`, `Asia/Shanghai`
- profile timezone: `Asia/Shanghai`, `Europe/Paris`
- explicit event timezone: `Asia/Tokyo`
- [ ] **Step 2: Run backend + frontend verification commands**
Run:
- `cd backend && uv run pytest tests/unit/core/agentscope/test_system_prompt.py tests/unit/core/agentscope/test_calendar_tools.py tests/integration/test_schedule_items_routes.py -v`
- `cd apps && flutter test test/features/chat/ag_ui_event_test.dart test/features/calendar/ui/calendar_time_utils_test.dart test/features/calendar/ui/create_event_sheet_time_align_test.dart`
Expected: all PASS
- [ ] **Step 3: Manual scenario check**
Manual script:
1. device timezone set to Los Angeles
2. profile timezone set to Shanghai
3. ask agent create "明天上午9点开会"
4. verify assistant text, tool card, DB UTC value, and calendar detail all align to chosen event timezone semantics
- [ ] **Step 4: Capture release notes**
Record:
- removed hidden timezone defaults
- deterministic precedence
- no schema expansion
---
Plan complete and saved to `docs/superpowers/plans/2026-03-16-calendar-timezone-unification.md`. Ready to execute?