Orchestrata Ultimate / Specs / 017-calendar-integration
017-calendar-integration: Calendar Integration
The Calendar Integration is a VizInt Command Center gadget that provides calendar visibility and event management within the Orchestrata dashboard. It displays today's events with time blocks, supports creating events from the command center, and automatically syncs orchestration session start/close times to the user's calendar. The gadget supports both Google Calendar (via OAuth2) and CalDAV-compatible servers (via app password).
At a glance
| Item | Value |
|---|---|
| Spec directory | specs/017-calendar-integration/ |
| Status line in the spec | Draft |
| Requirement IDs defined | 70 |
With a @traces citation in src/ |
23 (32.9%) |
With a @traces citation in tests/ |
0 (0.0%) |
| Tracked files | 6 |
| RTM page | 017-calendar-integration |
Reading this spec
The Calendar Integration is a VizInt Command Center gadget that shows calendar events and manages them inside the Orchestrata dashboard. It displays today's events as time blocks, creates events from the command center, and pushes orchestration session start and close times to the user's calendar. Before it, session times were recorded in the session context file but invisible in the user's calendar, and time blocking for orchestration work was manual.
The scope is five user stories (view today's events, connect calendar accounts, create events, sync session times, navigate between days) and five functional groups: calendar data fetching, Google Calendar OAuth2, CalDAV integration, event creation and session calendar sync. Requirement IDs use the REQ-017- prefix.
The design is a browser-side JavaScript gadget with no Python backend. It supports Google Calendar through OAuth2 and CalDAV-compatible servers through a proxied endpoint, renders a day-view timeline, has an event creation form with title, times, description and calendar selector, and registers as a Command Center gadget through a GadgetManifest with the category productivity. It learns about session lifecycle from other gadgets through the event bus.
Because the gadget is browser-side JavaScript, the Python trace table is small and its entries are in other packages: orchestrata_ultimate/memory/cold_store, orchestrata_ultimate/memory/embedding, orchestrata_ultimate/memory/compressor and orchestrata_ultimate/llm/client. The per-requirement view is on the RTM page.
The spec's status line reads Draft. It tracks 6 files and defines 70 requirement IDs. The full text follows below.
Measured coverage is 23 of 70 requirements cited by at least one @traces in src/ (32.9%), spread over 6 modules; the module citing the most of them is orchestrata_ultimate/memory/cold_store (6 requirements). In tests/, 0 requirements are cited by a test-side @traces.
Coverage gaps: 47 requirements have no citing symbol. The largest untraced groups are REQ-017-* (47). Untraced means no @traces names the ID; the behaviour may still exist in code that was never annotated. Test-side citations are thinner than code-side ones (0 against 23).
Requirements
Checkbox is the state recorded in the requirements checklist; Traced by is the count of src/ symbols whose @traces names the ID.
| ID | Done | Requirement | Traced by |
|---|---|---|---|
REQ-017-001 |
no | Gadget displays a vertical day-view timeline for the current date | none |
REQ-017-002 |
no | Events are rendered as colored time blocks at their correct time positions | none |
REQ-017-003 |
no | Each time block shows the event title and time range | none |
REQ-017-004 |
no | All-day events are displayed in a separate section above the hourly timeline | none |
REQ-017-005 |
no | Multi-calendar events are color-coded by source calendar | none |
REQ-017-006 |
no | Current time is indicated by a horizontal red marker line | none |
REQ-017-007 |
no | Time marker updates its position every 60 seconds | none |
REQ-017-008 |
no | Timeline auto-scrolls to center the current time on first load | none |
REQ-017-009 |
no | Clicking an event shows a detail popover | none |
REQ-017-010 |
no | Detail popover includes: full title, description, location, attendees, calendar name | none |
REQ-017-011 |
no | Detail popover includes a link to the event in the source calendar app | none |
REQ-017-012 |
no | Popover dismisses on click outside or Escape key | 1 |
REQ-017-013 |
no | Event data auto-refreshes every 5 minutes (configurable) | 1 |
REQ-017-014 |
no | Manual refresh button in gadget header triggers immediate re-fetch | 1 |
REQ-017-015 |
no | Last sync timestamp displayed in the gadget footer | 2 |
REQ-017-016 |
no | Settings panel provides "Connect Google Calendar" button | 1 |
REQ-017-017 |
no | Button initiates OAuth2 implicit grant flow in a popup window | none |
REQ-017-018 |
no | OAuth2 access token stored in localStorage encrypted via Web Crypto API | 1 |
REQ-017-019 |
no | Token expiration detected on 401 response, re-authentication prompted | none |
REQ-017-020 |
no | Calendar list fetched via calendar.list after successful authentication |
none |
REQ-017-021 |
no | Settings panel provides CalDAV connection form | none |
REQ-017-022 |
no | Form fields: server URL, username, app password | none |
REQ-017-023 |
no | Credentials stored in localStorage encrypted with AES-GCM | none |
REQ-017-024 |
no | Available calendars discovered via CalDAV PROPFIND | none |
REQ-017-025 |
no | Connection validated on form submission with test PROPFIND | none |
REQ-017-026 |
no | Multiple calendar accounts connectable simultaneously | none |
REQ-017-027 |
no | Individual calendars toggleable on/off within each account | none |
REQ-017-028 |
no | Connection status indicator: green (connected), yellow (expiring), red (error) | none |
REQ-017-029 |
no | "Disconnect" button removes credentials and cached events | none |
REQ-017-030 |
no | Account list persisted in localStorage | none |
REQ-017-031 |
no | "New Event" button in gadget header opens creation form | 1 |
REQ-017-032 |
no | Form fields: title, start time, end time, description, calendar selector | 1 |
REQ-017-033 |
no | Start time defaults to next 30-minute boundary | 1 |
REQ-017-034 |
no | End time defaults to 30 minutes after start time | none |
REQ-017-035 |
no | Calendar selector shows all writable calendars from connected accounts | none |
REQ-017-036 |
no | Empty title rejected with inline error message | none |
REQ-017-037 |
no | End time before start time rejected with inline error message | 1 |
REQ-017-038 |
no | Duration exceeding 24 hours rejected with inline error message | 1 |
REQ-017-039 |
no | Invalid date/time format rejected with inline error message | 1 |
REQ-017-040 |
no | Google Calendar events created via events.insert endpoint |
none |
REQ-017-041 |
no | CalDAV events created via PUT with VEVENT body | 1 |
REQ-017-042 |
no | Created event appears in timeline immediately on success | 1 |
REQ-017-043 |
no | API error displayed with retry button (form data preserved) | 1 |
REQ-017-044 |
no | calendar.event.created emitted on ctx.bus after success |
1 |
REQ-017-045 |
no | VEVENT generated with RFC 5545 compliant properties | 1 |
REQ-017-046 |
no | GadgetBridge subscription to session.events for event_type: "opened" |
none |
REQ-017-047 |
no | Calendar event created with title "Orchestrata Session {number}" | none |
REQ-017-048 |
no | Event description includes session purpose/goals | none |
REQ-017-049 |
no | Event start time matches session open timestamp | none |
REQ-017-050 |
no | Event created in configurable target calendar (default: primary) | none |
REQ-017-051 |
no | GadgetBridge subscription to session.events for event_type: "closed" |
none |
REQ-017-052 |
no | Corresponding calendar event end time updated to session close timestamp | none |
REQ-017-053 |
no | Session-to-calendar-event mapping stored in localStorage | none |
REQ-017-054 |
no | If matching calendar event not found, new event created with estimated duration | 1 |
REQ-017-055 |
no | Session sync enable/disable toggle in gadget settings | 1 |
REQ-017-056 |
no | Target calendar selector in gadget settings | 1 |
REQ-017-057 |
no | Session event includes a dashboard link in description | 1 |
REQ-017-058 |
no | Failed sync retried with exponential backoff (1s, 2s, 4s, max 30s) | 1 |
REQ-017-059 |
no | Maximum 5 retry attempts before logging failure | none |
REQ-017-060 |
no | Pending syncs persisted in localStorage for retry on page reload | none |
REQ-017-061 |
no | Previous day button (left arrow) in gadget header | none |
REQ-017-062 |
no | Next day button (right arrow) in gadget header | none |
REQ-017-063 |
no | "Today" button returns to current date | none |
REQ-017-064 |
no | Date picker allows jumping to any date | none |
REQ-017-065 |
no | Current displayed date shown in gadget header | none |
REQ-017-066 |
no | Events for navigated date fetched on demand | none |
REQ-017-067 |
no | Fetched events cached with 5-minute TTL | 1 |
REQ-017-068 |
no | Loading spinner shown during fetch | none |
REQ-017-069 |
no | Cache hit renders immediately without spinner | none |
REQ-017-070 |
no | Navigation preserves scroll position relative to time of day | none |
Code that traces this spec
| Module | Symbols | Requirements |
|---|---|---|
orchestrata_ultimate/llm/client |
5 | 4 |
orchestrata_ultimate/llm/exceptions |
1 | 2 |
orchestrata_ultimate/memory/cold_store |
6 | 6 |
orchestrata_ultimate/memory/compressor |
5 | 5 |
orchestrata_ultimate/memory/embedding |
5 | 5 |
orchestrata_ultimate/memory/memory_engine |
1 | 1 |
Checklists
checklists/README.md
Calendar Integration Checklists
Project: Orchestrata Calendar Integration (Spec 017) Component: Calendar Gadget Status: Planning Phase Owner: Architecture Team
Overview
The Calendar Integration is a VizInt Command Center gadget that provides calendar visibility and event management within the Orchestrata dashboard. It supports Google Calendar (OAuth2) and CalDAV servers (app password), displays today's events as time blocks, enables event creation, and automatically syncs session start/close times to the user's calendar.
Checklist Summary
| Checklist | Total Items | Completed | Progress |
|---|---|---|---|
| Requirements | 70+ | 0 | 0% |
| Constitutional Compliance | 55+ | 0 | 0% |
| Testing Plan | 120+ | 0 | 0% |
| TOTAL | 245+ | 0 | 0% |
Key Responsibilities
1. Calendar Event Display
- Fetch events from Google Calendar API and CalDAV servers
- Render day-view timeline with colored time blocks
- Display event details in popover on click
- Support date navigation (previous, next, today, date picker)
2. Calendar Account Management
- Google Calendar OAuth2 implicit flow authentication
- CalDAV server connection with app password
- Multi-account support with per-calendar toggle
- Secure credential storage (Web Crypto API encryption)
3. Event Creation
- Form-based event creation with validation
- Google Calendar and CalDAV API event insertion
- RFC 5545 compliant VEVENT generation for CalDAV
4. Session Calendar Sync
- Automatic calendar event creation on session open
- Calendar event end time update on session close
- Configurable target calendar and enable/disable toggle
- Retry mechanism for failed API calls
Performance Targets
| Metric | Target | Critical Threshold |
|---|---|---|
| Google API Fetch | < 1s P95 | < 2s P99 |
| CalDAV Fetch | < 2s P95 | < 3s P99 |
| Timeline Render (20 events) | < 100ms | < 200ms |
| Event Creation | < 2s P95 | < 3s P99 |
| Cache Hit | < 10ms | < 50ms |
| Memory Usage | < 10MB | < 20MB |
Checklist Files
1. requirements.md
Functional requirements organized by user stories:
- US-001: View Today's Calendar Events
- US-002: Connect Calendar Accounts
- US-003: Create Calendar Events
- US-004: Sync Session Times to Calendar
- US-005: Navigate Between Days
2. constitutional.md
Compliance with all 11 constitutional articles covering agent autonomy, Prolog governance, session isolation, extensibility, testing standards, observability, graceful degradation, and versioning.
3. testing.md
Comprehensive testing strategy:
- Unit Tests (50+ tests): Timeline rendering, event creation, cache, session sync, iCalendar parsing
- Integration Tests (35+ tests): Google Calendar API, CalDAV, session sync, credentials
- E2E Tests (20+ tests): Full browser workflows via Playwright
- Performance Tests (15+ tests): API latency, render time, memory
Integration Points
Upstream Dependencies
- Google Calendar API v3 (event listing, creation, update)
- CalDAV protocol (PROPFIND, REPORT, PUT)
- SPEC-016 VizInt Command Center (GadgetBridge, ctx.bus)
- SPEC-014 WebSocket Gateway (session event subscriptions)
Downstream Consumers
- Session Manager gadget (session-to-calendar event correlation)
- Notification Center (calendar sync failure alerts)
Dependencies
Required APIs
- Google Calendar API v3 (OAuth2)
- CalDAV protocol (RFC 4791)
Required Libraries
- VizInt portal framework (gadget lifecycle)
- Web Crypto API (credential encryption)
Optional Services
- CalDAV server (Nextcloud, Radicale, Baikal)
References
- SPEC-014: WebSocket Gateway
- SPEC-016: VizInt Command Center
- Google Calendar API Documentation
- RFC 4791: CalDAV
- RFC 5545: iCalendar
Last Updated: 2026-03-28 Version: 1.0.0 Maintained By: Architecture Team
checklists/constitutional.md
Calendar Integration - Constitutional Compliance
Component: Calendar Gadget Spec: 017-calendar-integration Status: Compliance Review Total Compliance Items: 50+
Overview
This document tracks compliance of the Calendar Integration gadget with the Orchestrata Constitution. As a browser-side productivity gadget, the calendar integration has a lighter constitutional footprint than core system components but must still meet security, observability, and extensibility requirements.
Article I: Agent Autonomy
Principle: Agents retain autonomy; gadgets observe and assist without interference.
Compliance Requirements
- [ ] CONST-I-001: Calendar gadget does not inject or modify CXP messages
- [ ] CONST-I-002: Session sync creates calendar events but does not alter session state
- [ ] CONST-I-003: Calendar data does not influence Volk routing or task assignment
- [ ] CONST-I-004: Gadget operates independently of agent decision-making
- [ ] CONST-I-005: Session event observation is read-only (subscribe, not publish)
Priority: Medium Verification: Code review + integration tests
Article II: Prolog as Single Source of Truth
Principle: Governance rules defined in Prolog.
Compliance Requirements
- [ ] CONST-II-001: Calendar gadget respects gateway-level Prolog authorization for session events
- [ ] CONST-II-002: No hardcoded access control logic in the gadget
- [ ] CONST-II-003: Gadget defers to gateway for all authorization decisions
- [ ] CONST-II-004: Calendar data access does not bypass Prolog governance
- [ ] CONST-II-005: Session sync respects session ownership rules from gateway
Priority: Medium Verification: Integration tests
Article III: Session Isolation
Principle: Sessions are isolated; state does not leak between sessions.
Compliance Requirements
- [ ] CONST-III-001: Session sync creates separate calendar events per session (no merging)
- [ ] CONST-III-002: Calendar events do not expose cross-session data
- [ ] CONST-III-003: Session-to-calendar mapping keyed by session_id (no ambiguity)
- [ ] CONST-III-004: Calendar event descriptions do not leak data from other sessions
- [ ] CONST-III-005: Session event subscriptions scoped appropriately
Priority: High Verification: Integration tests
Article IV: Hooks for Extensibility
Principle: Extension points for customization without core modification.
Compliance Requirements
- [ ] CONST-IV-001: Calendar provider interface allows adding new backends (e.g., Outlook)
- [ ] CONST-IV-002: Session sync event format is extensible (additional metadata fields)
- [ ] CONST-IV-003: Gadget configuration schema allows future property additions
- [ ] CONST-IV-004: Calendar event creation hooks for pre/post processing
- [ ] CONST-IV-005: Gadget manifest allows future capability declarations
Priority: Medium Verification: Architecture review
Article V: Minimal Core, Maximal Extension
Principle: Core is minimal; features added via extensions.
Compliance Requirements
- [ ] CONST-V-001: Calendar gadget is a standalone extension, not embedded in core
- [ ] CONST-V-002: Calendar providers are pluggable (Google, CalDAV, future providers)
- [ ] CONST-V-003: Session sync is an optional feature (toggleable)
- [ ] CONST-V-004: No calendar logic in core Command Center code
- [ ] CONST-V-005: Gadget loadable/unloadable without affecting other gadgets
Priority: High Verification: Architecture review
Article VI: Implementation Standards
Principle: JavaScript ES2024 with modern async patterns (browser-side gadget).
Compliance Requirements
- [ ] CONST-VI-001: ES2024 module syntax (import/export)
- [ ] CONST-VI-002: Async/await for all API calls (no callback nesting)
- [ ] CONST-VI-003: JSDoc comments for all public functions
- [ ] CONST-VI-004: No global mutable state (module-scoped only)
- [ ] CONST-VI-005: Clean error handling with try/catch (no swallowed errors)
Priority: High Verification: Code review + linting
Article VII: Testing Without Mocks
Principle: Tests use real implementations.
Compliance Requirements
- [ ] CONST-VII-001: Tests use real DOM via JSDOM or Playwright
- [ ] CONST-VII-002: Calendar API tests use real API endpoints (sandbox accounts)
- [ ] CONST-VII-003: No mock objects for WebSocket, fetch, or localStorage
- [ ] CONST-VII-004: Integration tests use real gateway for session events
- [ ] CONST-VII-005: E2E tests use real browser via Playwright
Priority: Critical Verification: Test code review
Article VIII: Metrics and Observability
Principle: All operations observable and measurable.
Compliance Requirements
- [ ] CONST-VIII-001: API call latency logged for each calendar provider
- [ ] CONST-VIII-002: Cache hit/miss ratio trackable
- [ ] CONST-VIII-003: Session sync success/failure events logged
- [ ] CONST-VIII-004: Connection status changes logged
- [ ] CONST-VIII-005: Error events include sufficient context for debugging
Priority: Medium Verification: Log review + observability tests
Article IX: Graceful Degradation
Principle: System degrades gracefully under failure.
Compliance Requirements
- [ ] CONST-IX-001: Cached events visible when calendar API is unreachable
- [ ] CONST-IX-002: Session sync queues retries on API failure
- [ ] CONST-IX-003: OAuth2 token expiration handled without gadget crash
- [ ] CONST-IX-004: CalDAV server unavailability does not block Google Calendar display
- [ ] CONST-IX-005: localStorage unavailability handled with in-memory fallback
Priority: High Verification: Failure injection tests
Article X: Version Compatibility
Principle: APIs are versioned; breaking changes avoided.
Compliance Requirements
- [ ] CONST-X-001: Gadget manifest declares min_api_version
- [ ] CONST-X-002: Calendar provider interface versioned
- [ ] CONST-X-003: State sync data format backward compatible
- [ ] CONST-X-004: Gadget settings schema additive-only for new versions
- [ ] CONST-X-005: Session sync mapping format extensible without breaking
Priority: Medium Verification: Compatibility tests
Article XI: Documentation First
Principle: Documentation precedes implementation.
Compliance Requirements
- [ ] CONST-XI-001: Specification written before implementation
- [ ] CONST-XI-002: Calendar provider API documented
- [ ] CONST-XI-003: Configuration options documented
- [ ] CONST-XI-004: Session sync behavior documented
- [ ] CONST-XI-005: Error conditions documented
Priority: High Verification: Documentation review
Summary Statistics
| Article | Total Items | Completed | Percentage |
|---|---|---|---|
| Article I (Autonomy) | 5 | 0 | 0% |
| Article II (Prolog) | 5 | 0 | 0% |
| Article III (Isolation) | 5 | 0 | 0% |
| Article IV (Hooks) | 5 | 0 | 0% |
| Article V (Minimal Core) | 5 | 0 | 0% |
| Article VI (Implementation) | 5 | 0 | 0% |
| Article VII (No Mocks) | 5 | 0 | 0% |
| Article VIII (Observability) | 5 | 0 | 0% |
| Article IX (Degradation) | 5 | 0 | 0% |
| Article X (Versioning) | 5 | 0 | 0% |
| Article XI (Documentation) | 5 | 0 | 0% |
| TOTAL | 55 | 0 | 0% |
Last Updated: 2026-03-28 Version: 1.0.0 Maintained By: Architecture Team
checklists/requirements.md
The requirement list above comes from this file; 0 of 70 checkbox items are ticked.
checklists/testing.md
Calendar Integration - Testing Plan
Component: Calendar Gadget Spec: 017-calendar-integration Status: Test Planning Phase Total Test Cases: 120+
Overview
This document defines the testing strategy for the Calendar Integration gadget. All tests follow the strict "no mocking" policy. Unit tests use JSDOM for real DOM interaction, integration tests use real calendar APIs (sandbox accounts), and E2E tests use Playwright with a real browser.
Test Categories Summary
| Category | Test Count | Purpose | Priority |
|---|---|---|---|
| Unit Tests | 50+ | Core logic validation | Critical |
| Integration Tests | 35+ | API and system integration | Critical |
| E2E Tests | 20+ | Full browser workflow | High |
| Performance Tests | 15+ | Latency and rendering | High |
| TOTAL | 120+ |
Unit Tests (50+)
Timeline Rendering (15 tests)
- [ ] TEST-UNIT-TL-001: Timeline displays 24-hour vertical grid
- [ ] TEST-UNIT-TL-002: Events positioned at correct vertical offset based on start time
- [ ] TEST-UNIT-TL-003: Event block height proportional to duration
- [ ] TEST-UNIT-TL-004: Overlapping events displayed side-by-side
- [ ] TEST-UNIT-TL-005: All-day events displayed in separate header section
- [ ] TEST-UNIT-TL-006: Current time marker positioned correctly
- [ ] TEST-UNIT-TL-007: Time marker updates position on interval
- [ ] TEST-UNIT-TL-008: Auto-scroll centers current time on load
- [ ] TEST-UNIT-TL-009: Event detail popover displays on click
- [ ] TEST-UNIT-TL-010: Popover dismisses on outside click
- [ ] TEST-UNIT-TL-011: Popover dismisses on Escape key
- [ ] TEST-UNIT-TL-012: Multi-calendar events color-coded correctly
- [ ] TEST-UNIT-TL-013: Empty day shows "No events" message
- [ ] TEST-UNIT-TL-014: Cross-midnight events display visible portion
- [ ] TEST-UNIT-TL-015: Timeline handles 50+ events without layout breakage
Event Creation Form (10 tests)
- [ ] TEST-UNIT-EC-001: Form opens with default start time (next 30-min boundary)
- [ ] TEST-UNIT-EC-002: Form opens with default end time (start + 30 minutes)
- [ ] TEST-UNIT-EC-003: Calendar selector populated with writable calendars
- [ ] TEST-UNIT-EC-004: Empty title validation error displayed
- [ ] TEST-UNIT-EC-005: End-before-start validation error displayed
- [ ] TEST-UNIT-EC-006: Duration > 24h validation error displayed
- [ ] TEST-UNIT-EC-007: Valid form submission calls provider createEvent
- [ ] TEST-UNIT-EC-008: Successful creation closes form and updates timeline
- [ ] TEST-UNIT-EC-009: API error displays error message with retry button
- [ ] TEST-UNIT-EC-010: Form data preserved on API error
Event Cache (10 tests)
- [ ] TEST-UNIT-CACHE-001: Fetched events stored in cache with TTL
- [ ] TEST-UNIT-CACHE-002: Cache hit returns events without API call
- [ ] TEST-UNIT-CACHE-003: Cache miss triggers API fetch
- [ ] TEST-UNIT-CACHE-004: Cache expires after TTL
- [ ] TEST-UNIT-CACHE-005: Manual refresh bypasses cache
- [ ] TEST-UNIT-CACHE-006: Cache cleared on account disconnect
- [ ] TEST-UNIT-CACHE-007: Cache keyed by date and calendar ID
- [ ] TEST-UNIT-CACHE-008: Cache size bounded (no memory leak)
- [ ] TEST-UNIT-CACHE-009: Concurrent fetches for same date coalesced
- [ ] TEST-UNIT-CACHE-010: Cache returns deep copies (no mutation)
Session Sync Engine (10 tests)
- [ ] TEST-UNIT-SS-001: Session open event creates calendar event
- [ ] TEST-UNIT-SS-002: Session close event updates calendar event end time
- [ ] TEST-UNIT-SS-003: Session-event mapping stored in localStorage
- [ ] TEST-UNIT-SS-004: Failed sync retried with exponential backoff
- [ ] TEST-UNIT-SS-005: Maximum 5 retries before abandoning
- [ ] TEST-UNIT-SS-006: Sync disabled when toggle is off
- [ ] TEST-UNIT-SS-007: Target calendar configurable
- [ ] TEST-UNIT-SS-008: Missing session-open mapping creates new event on close
- [ ] TEST-UNIT-SS-009: Pending syncs survived page reload (localStorage)
- [ ] TEST-UNIT-SS-010: Duplicate session open events create only one calendar event
iCalendar Parser (5 tests)
- [ ] TEST-UNIT-ICAL-001: VEVENT parsed to CalendarEvent object
- [ ] TEST-UNIT-ICAL-002: DTSTART and DTEND extracted correctly
- [ ] TEST-UNIT-ICAL-003: All-day events detected (DATE vs DATETIME)
- [ ] TEST-UNIT-ICAL-004: SUMMARY, DESCRIPTION, LOCATION extracted
- [ ] TEST-UNIT-ICAL-005: ATTENDEE email addresses extracted
Integration Tests (35+)
Google Calendar API (10 tests)
- [ ] TEST-INTEG-GCAL-001: OAuth2 token accepted by Google Calendar API
- [ ] TEST-INTEG-GCAL-002: Calendar list fetched with correct calendars
- [ ] TEST-INTEG-GCAL-003: Events fetched for today with correct date range
- [ ] TEST-INTEG-GCAL-004: Event creation returns valid event ID
- [ ] TEST-INTEG-GCAL-005: Event update modifies end time correctly
- [ ] TEST-INTEG-GCAL-006: Expired token triggers re-auth prompt
- [ ] TEST-INTEG-GCAL-007: Rate limit response handled gracefully
- [ ] TEST-INTEG-GCAL-008: Pagination handled for large event sets
- [ ] TEST-INTEG-GCAL-009: Event deletion reflected on refresh
- [ ] TEST-INTEG-GCAL-010: Multi-calendar fetching returns merged results
CalDAV Integration (10 tests)
- [ ] TEST-INTEG-CALDAV-001: PROPFIND discovers available calendars
- [ ] TEST-INTEG-CALDAV-002: REPORT fetches events for date range
- [ ] TEST-INTEG-CALDAV-003: PUT creates new VEVENT successfully
- [ ] TEST-INTEG-CALDAV-004: VEVENT body is RFC 5545 compliant
- [ ] TEST-INTEG-CALDAV-005: Basic auth with app password succeeds
- [ ] TEST-INTEG-CALDAV-006: Invalid credentials return clear error
- [ ] TEST-INTEG-CALDAV-007: Server unavailability handled gracefully
- [ ] TEST-INTEG-CALDAV-008: Large calendar (100+ events) paginated
- [ ] TEST-INTEG-CALDAV-009: Special characters in event titles handled
- [ ] TEST-INTEG-CALDAV-010: Timezone conversions correct for DTSTART/DTEND
Session Sync Integration (10 tests)
- [ ] TEST-INTEG-SYNC-001: Session open event from GadgetBridge creates calendar event
- [ ] TEST-INTEG-SYNC-002: Session close event updates calendar event end time
- [ ] TEST-INTEG-SYNC-003: Calendar event title matches "Orchestrata Session {number}"
- [ ] TEST-INTEG-SYNC-004: Calendar event description includes session purpose
- [ ] TEST-INTEG-SYNC-005: Session-calendar mapping persists across page reload
- [ ] TEST-INTEG-SYNC-006: API failure triggers retry mechanism
- [ ] TEST-INTEG-SYNC-007: ctx.bus event emitted after successful sync
- [ ] TEST-INTEG-SYNC-008: Sync works with Google Calendar provider
- [ ] TEST-INTEG-SYNC-009: Sync works with CalDAV provider
- [ ] TEST-INTEG-SYNC-010: Concurrent session events handled without race conditions
Credential Management (5 tests)
- [ ] TEST-INTEG-CRED-001: Google OAuth2 token encrypted in localStorage
- [ ] TEST-INTEG-CRED-002: CalDAV password encrypted with AES-GCM
- [ ] TEST-INTEG-CRED-003: Encrypted credentials decryptable on next load
- [ ] TEST-INTEG-CRED-004: Key derivation produces consistent keys
- [ ] TEST-INTEG-CRED-005: Disconnect removes all credentials for account
E2E Tests (20+)
- [ ] TEST-E2E-001: Full flow: connect Google Calendar, view events, create event
- [ ] TEST-E2E-002: Full flow: connect CalDAV, view events, navigate days
- [ ] TEST-E2E-003: Session sync: open session, verify calendar event created
- [ ] TEST-E2E-004: Session sync: close session, verify event end time updated
- [ ] TEST-E2E-005: Multi-account: Google + CalDAV events merged in timeline
- [ ] TEST-E2E-006: Offline: disconnect network, verify cached events visible
- [ ] TEST-E2E-007: Reconnect: restore network, verify sync catches up
- [ ] TEST-E2E-008: Theme toggle: dark/light mode applies to calendar gadget
- [ ] TEST-E2E-009: Resize: gadget resizes without layout breakage
- [ ] TEST-E2E-010: Page reload: settings and connected accounts persist
- [ ] TEST-E2E-011: OAuth2 popup: blocked popup detected with user guidance
- [ ] TEST-E2E-012: Event creation: full form submission and timeline update
- [ ] TEST-E2E-013: Date navigation: previous, next, today buttons work
- [ ] TEST-E2E-014: Date picker: jump to specific date loads events
- [ ] TEST-E2E-015: Multiple gadgets: calendar coexists with CXP feed and session manager
- [ ] TEST-E2E-016: Error handling: invalid OAuth2 callback handled
- [ ] TEST-E2E-017: Error handling: CalDAV server error shows user feedback
- [ ] TEST-E2E-018: Event detail: popover shows full event information
- [ ] TEST-E2E-019: Calendar toggle: disabling a calendar hides its events
- [ ] TEST-E2E-020: Settings persistence: account list survives gadget unmount/remount
Performance Tests (15+)
- [ ] TEST-PERF-001: Google Calendar API fetch < 1s P95 for day events
- [ ] TEST-PERF-002: CalDAV fetch < 2s P95 for day events
- [ ] TEST-PERF-003: Timeline render with 20 events < 100ms
- [ ] TEST-PERF-004: Timeline render with 50 events < 200ms
- [ ] TEST-PERF-005: Event creation API round-trip < 2s P95
- [ ] TEST-PERF-006: Cache hit retrieval < 10ms
- [ ] TEST-PERF-007: Date navigation (cached) < 200ms
- [ ] TEST-PERF-008: Date navigation (uncached) < 1.5s
- [ ] TEST-PERF-009: Gadget memory usage < 10MB
- [ ] TEST-PERF-010: No memory leaks after 100 date navigation cycles
- [ ] TEST-PERF-011: Session sync event-to-calendar < 3s P95
- [ ] TEST-PERF-012: Form validation < 50ms
- [ ] TEST-PERF-013: Theme toggle applies to calendar < 100ms
- [ ] TEST-PERF-014: Popover render < 50ms
- [ ] TEST-PERF-015: Auto-refresh does not cause visible jank
Summary Statistics
| Category | Test Count | Completed | Percentage |
|---|---|---|---|
| Unit Tests | 50 | 0 | 0% |
| Integration Tests | 35 | 0 | 0% |
| E2E Tests | 20 | 0 | 0% |
| Performance Tests | 15 | 0 | 0% |
| TOTAL | 120 | 0 | 0% |
Last Updated: 2026-03-28 Version: 1.0.0 Maintained By: Architecture Team
Other tracked files
specs/017-calendar-integration/compliance-audit.md
Full spec text
Verbatim from specs/017-calendar-integration/spec.md; headings are pushed down two levels.
SPEC-017: Calendar Integration
Metadata
- Spec ID: 017-calendar-integration
- Title: Calendar Integration
- Version: 1.0.0
- Author: Mohamed
- Status: Draft
- Created: 2026-03-28
- Last Updated: 2026-03-28
- Priority: P2
- Extends: (none)
- Components: CalendarGadget, CalDAVProvider, GoogleCalendarProvider, SessionCalendarSync
- Dependencies: VizInt portal framework, SPEC-016 VizInt Command Center, JavaScript ES2024, Google Calendar API, CalDAV protocol
- API Version: 1.0.0
- Contract Version: 1.0.0
- Backward Compatibility: Initial release — no backward compatibility constraints
Overview
What
The Calendar Integration is a VizInt Command Center gadget that provides calendar visibility and event management within the Orchestrata dashboard. It displays today's events with time blocks, supports creating events from the command center, and automatically syncs orchestration session start/close times to the user's calendar. The gadget supports both Google Calendar (via OAuth2) and CalDAV-compatible servers (via app password).
Why
- Orchestrata sessions have no calendar representation — session start/close times are tracked in context.json but invisible in the user's calendar workflow
- Context-switching between the Orchestrata dashboard and a separate calendar app breaks flow during deep work sessions
- Time blocking for orchestration tasks (spikes, iterations, code review) requires manual calendar entry, which is error-prone and often skipped
- Prayer times (already displayed via Chronus) and calendar events are complementary — seeing both in the same dashboard enables better daily planning
- Session duration tracking in context.json is valuable but not actionable without calendar integration that shows availability windows
How
- Browser-side JavaScript gadget — no Python backend required
- Connects to Google Calendar via OAuth2 (browser-side implicit flow) or CalDAV via app password (proxied through CORS-friendly endpoint)
- Renders today's events as time blocks in a day-view timeline
- Event creation form with title, start/end time, description, and calendar selector
- Session start/close events are automatically pushed to the selected calendar as events
- Registers as a Command Center gadget via GadgetManifest with
category: "productivity" - Communicates with other gadgets via
ctx.busfor session lifecycle awareness
User Scenarios
US-001: View Today's Calendar Events
As a developer using the Command Center I want to see today's calendar events in a timeline view So that I can plan my orchestration work around existing commitments
Priority: P0
Acceptance Criteria:
- [ ] Calendar gadget displays a day-view timeline showing all events for the current date
- [ ] Events are rendered as colored time blocks with title and time range visible
- [ ] All-day events are displayed in a separate section above the timeline
- [ ] Multi-calendar events are color-coded by source calendar
- [ ] Current time is indicated by a horizontal marker line that updates every minute
- [ ] Clicking an event shows a detail popover with full title, description, location, and attendees
- [ ] The timeline scrolls to center the current time on first load
US-002: Connect Calendar Accounts
As a developer I want to connect my Google Calendar and CalDAV accounts So that the calendar gadget can display my events
Priority: P0
Acceptance Criteria:
- [ ] Settings panel provides "Connect Google Calendar" button that initiates OAuth2 implicit flow
- [ ] Google OAuth2 token is stored in localStorage with automatic refresh handling
- [ ] Settings panel provides CalDAV connection form with server URL, username, and app password fields
- [ ] CalDAV credentials are stored in localStorage (encrypted with Web Crypto API)
- [ ] Multiple calendar accounts can be connected simultaneously
- [ ] Individual calendars within an account can be toggled on/off
- [ ] Connection status indicator shows green/yellow/red for each account
- [ ] Disconnecting an account removes all stored credentials and cached events
US-003: Create Calendar Events
As a developer I want to create calendar events from the Command Center So that I can block time for orchestration tasks without switching apps
Priority: P1
Acceptance Criteria:
- [ ] "New Event" button in the gadget header opens a creation form
- [ ] Form includes: title, start time, end time, description, calendar selector
- [ ] Start and end time default to the next available 30-minute slot
- [ ] Calendar selector shows all writable calendars from connected accounts
- [ ] Created event appears in the timeline immediately upon successful API response
- [ ] Validation prevents: blank title, end before start, duration > 24 hours
- [ ] Error feedback displayed if API call fails (with retry button)
US-004: Sync Session Times to Calendar
As a developer I want session start and close times automatically recorded in my calendar So that I have a permanent record of my orchestration work sessions
Priority: P1
Acceptance Criteria:
- [ ] When a session is opened (via
session.eventssubscription), a calendar event is created with title "Orchestrata Session {number}" - [ ] Session event includes the session purpose/goals in the description field
- [ ] When a session is closed, the calendar event end time is updated to the actual close time
- [ ] Session calendar events are created in a configurable target calendar (default: primary)
- [ ] Session sync can be enabled/disabled in gadget settings
- [ ] If calendar API is unavailable during session open/close, the sync is retried on reconnection
- [ ] Session events include a link back to the Command Center session view
US-005: Navigate Between Days
As a developer I want to view calendar events for other days So that I can plan ahead or review past sessions
Priority: P2
Acceptance Criteria:
- [ ] Previous/next day navigation buttons in the gadget header
- [ ] Date picker allows jumping to any specific date
- [ ] "Today" button returns to the current date
- [ ] Navigation preserves scroll position relative to time of day
- [ ] Event data for navigated dates is fetched on demand and cached for 5 minutes
Standardized Acceptance Criteria
SAC-US1: View Today's Calendar Events
SAC-US1: View Today's Calendar Events
| # | Criterion | Verification |
|---|---|---|
| SAC-US1-01 | Calendar gadget renders a day-view timeline with all events for the current date within 2 seconds of mount | Playwright E2E: mount gadget with test calendar, assert timeline container and event blocks present within 2s |
| SAC-US1-02 | Events are rendered as colored time blocks with title and time range visible at default zoom | Vitest DOM: render 5 events, assert each block has .event-title and .event-time text content |
| SAC-US1-03 | All-day events are displayed in a dedicated section above the hourly timeline | Vitest DOM: render mix of timed and all-day events, assert all-day events in .allday-section above .timeline-section |
| SAC-US1-04 | Events from different calendars are color-coded by their source calendar color | Vitest DOM: render events from 2 calendars with different colors, assert background-color matches calendar color |
| SAC-US1-05 | A horizontal "now" marker line is rendered at the current time and updates every 60 seconds | Vitest DOM: render timeline, assert .now-marker position matches current hour; advance fake timer 60s, assert position updated |
| SAC-US1-06 | Clicking an event opens a detail popover with title, description, location, and attendees | Playwright E2E: click event block, assert popover visible with all 4 fields populated |
| SAC-US1-07 | On first load, the timeline scroll position centers the current time in the viewport | Vitest DOM: mount at 14:00, assert scrollTop positions 14:00 within the middle third of the viewport |
SAC-US2: Connect Calendar Accounts
| # | Criterion | Verification |
|---|---|---|
| SAC-US2-01 | "Connect Google Calendar" button opens an OAuth2 popup with correct client_id and scopes | Playwright E2E: click button, assert popup URL contains accounts.google.com/o/oauth2 with expected client_id and scope params |
| SAC-US2-02 | After OAuth2 callback, access token is stored in localStorage under commandcenter.state.calendar.google.token |
Playwright E2E: complete OAuth2 flow with sandbox account, assert localStorage key exists with valid token JSON |
| SAC-US2-03 | CalDAV connection form accepts server URL, username, and app password and validates URL format | Vitest DOM: submit form with invalid URL, assert validation error; submit with valid URL, assert no error |
| SAC-US2-04 | CalDAV credentials are encrypted with AES-GCM via Web Crypto API before localStorage write | Integration test: connect CalDAV, read raw localStorage value, assert it is not plaintext and round-trip decrypts correctly |
| SAC-US2-05 | Multiple accounts (Google + CalDAV) can be connected and display events simultaneously | Playwright E2E: connect 1 Google + 1 CalDAV account, assert events from both appear in timeline with distinct colors |
| SAC-US2-06 | Individual calendars within an account can be toggled on/off and events update immediately | Playwright E2E: toggle off a calendar, assert its events disappear; toggle on, assert they reappear |
| SAC-US2-07 | Connection status indicator shows green (connected), yellow (token expiring), or red (error) per account | Vitest DOM: set account status to each state, assert .status-indicator has correct CSS class |
| SAC-US2-08 | Disconnecting an account removes all stored credentials, cached events, and associated calendars from the UI | Playwright E2E: disconnect account, assert localStorage keys removed and events gone from timeline |
SAC-US3: Create Calendar Events
| # | Criterion | Verification |
|---|---|---|
| SAC-US3-01 | "New Event" button opens a creation form with title, start, end, description, and calendar selector fields | Vitest DOM: click button, assert form with all 5 fields is rendered |
| SAC-US3-02 | Start and end time default to the next available 30-minute slot from current time | Vitest DOM: mount at 14:17, assert start defaults to 14:30 and end to 15:00 |
| SAC-US3-03 | Calendar selector lists only writable calendars from all connected accounts | Vitest DOM: connect 2 accounts (1 read-only, 2 writable calendars), assert selector shows only 2 writable options |
| SAC-US3-04 | Created event appears in the timeline immediately upon successful API response | Playwright E2E: create event via form, assert new event block appears in timeline without manual refresh |
| SAC-US3-05 | Validation rejects blank title, end time before start time, and duration exceeding 24 hours | Vitest DOM: submit each invalid case, assert corresponding validation error message displayed |
| SAC-US3-06 | Failed API call displays error message with retry button; form data is preserved | Vitest DOM: simulate API failure, assert error banner visible with retry button and form fields unchanged |
| SAC-US3-07 | calendar.event.created event is emitted on ctx.bus with the created CalendarEvent payload |
Integration test: listen on ctx.bus, create event, assert bus event received with matching event ID and title |
SAC-US4: Sync Session Times to Calendar
| # | Criterion | Verification |
|---|---|---|
| SAC-US4-01 | When session.events emits event_type: "opened", a calendar event is created with title "Orchestrata Session {N}" |
Integration test: emit session opened event, assert Google/CalDAV API received events.insert with correct title |
| SAC-US4-02 | Session calendar event description contains the session purpose/goals text | Integration test: emit session opened with purpose "Fix auth bug", assert created event description contains that text |
| SAC-US4-03 | When session.events emits event_type: "closed", the matching calendar event end time is updated |
Integration test: emit session closed, assert events.update called with correct event ID and new end time |
| SAC-US4-04 | Session sync targets a configurable calendar (default: primary), changeable in gadget settings | Vitest: change session_sync_calendar setting, emit session opened, assert event created in the configured calendar |
| SAC-US4-05 | Session sync can be disabled in settings and no calendar events are created when disabled | Vitest: set session_sync_enabled: false, emit session opened, assert no API call made |
| SAC-US4-06 | If calendar API is unavailable, sync is queued in localStorage and retried with exponential backoff up to 5 times | Integration test: simulate API failure, assert localStorage queue entry; restore API, assert retry succeeds and queue cleared |
| SAC-US4-07 | Session calendar events include a dashboard link in the description | Integration test: emit session opened, assert created event description contains http://localhost:8080/#session-{N} |
SAC-US5: Navigate Between Days
| # | Criterion | Verification |
|---|---|---|
| SAC-US5-01 | Previous/next day buttons navigate the timeline to the adjacent date | Playwright E2E: click next, assert displayed date is tomorrow; click previous twice, assert displayed date is yesterday |
| SAC-US5-02 | Date picker allows jumping to any specific date and the timeline updates | Playwright E2E: select a date 7 days ahead, assert timeline header shows that date and events are fetched |
| SAC-US5-03 | "Today" button returns to the current date from any navigated date | Playwright E2E: navigate to another date, click Today, assert current date displayed with now-marker visible |
| SAC-US5-04 | Navigation preserves scroll position relative to the same time of day | Vitest DOM: scroll to 14:00, navigate to next day, assert scroll position still shows 14:00 region |
| SAC-US5-05 | Event data for non-today dates is fetched on demand and cached for 5 minutes | Integration test: navigate to tomorrow (assert API call), navigate away and back within 5min (assert no new API call), wait 5min+ and navigate back (assert new API call) |
Functional Requirements
FR-001: Calendar Data Fetching
User Story: US-001, US-005
- System SHALL fetch calendar events from Google Calendar API using the
events.listendpoint withtimeMinandtimeMaxparameters for the displayed date - System SHALL fetch calendar events from CalDAV servers using the
REPORTmethod withcalendar-queryfilter for the displayed date range - System SHALL cache fetched events in memory with a configurable TTL (default: 5 minutes)
- System SHALL refresh event data on returning to the current date after navigation
- System SHALL handle pagination for Google Calendar API responses with
nextPageToken
FR-002: Google Calendar OAuth2
User Story: US-002
- System SHALL initiate OAuth2 implicit grant flow using
https://accounts.google.com/o/oauth2/v2/authwithscope=https://www.googleapis.com/auth/calendar - System SHALL store the access token in localStorage with key
commandcenter.state.calendar.google.token - System SHALL detect token expiration and prompt re-authentication when receiving 401 responses
- System SHALL request
calendar.listto enumerate available calendars after authentication - System SHALL support multiple Google accounts by storing tokens keyed by account email
FR-003: CalDAV Integration
User Story: US-002
- System SHALL connect to CalDAV servers using HTTP Basic authentication with app password
- System SHALL discover available calendars via CalDAV
PROPFINDon the calendar home set - System SHALL parse iCalendar (RFC 5545) VEVENT components from CalDAV responses
- System SHALL support CalDAV servers including Nextcloud, Radicale, Baikal, and macOS Calendar Server
- System SHALL proxy CalDAV requests through a CORS-friendly endpoint or use the
no-corsfetch mode with server-side CORS headers
FR-004: Event Creation
User Story: US-003
- System SHALL create events via Google Calendar API
events.insertendpoint for Google calendars - System SHALL create events via CalDAV
PUTmethod with iCalendar VEVENT body for CalDAV calendars - System SHALL generate RFC 5545 compliant VEVENT components for CalDAV event creation
- System SHALL validate event data client-side before API submission
- System SHALL emit a
calendar.event.createdevent onctx.busafter successful creation
FR-005: Session Calendar Sync
User Story: US-004
- System SHALL listen for
session.eventswithevent_type: "opened"via the GadgetBridge subscription - System SHALL create a calendar event with title format "Orchestrata Session {session_number}" and the session purpose as description
- System SHALL listen for
session.eventswithevent_type: "closed"and update the corresponding calendar event's end time - System SHALL store the mapping between session IDs and calendar event IDs in localStorage for update correlation
- System SHALL retry failed calendar API calls with exponential backoff (1s, 2s, 4s, max 30s)
Non-Functional Requirements
Performance Requirements
NFR-001: Event Fetching
- Calendar event fetch for a single day: < 1s (P95) for Google Calendar API
- Calendar event fetch for a single day: < 2s (P95) for CalDAV servers
- Event cache hit response: < 10ms
- Timeline render with 20 events: < 100ms
NFR-002: Event Creation
- Event creation API round-trip: < 2s (P95) for Google Calendar
- Event creation API round-trip: < 3s (P95) for CalDAV
- Form validation: < 50ms
Reliability
NFR-003: Offline Behavior
- Cached events remain visible when calendar API is unreachable
- Event creation queued locally when offline, synced on reconnection
- OAuth2 token refresh failure does not crash the gadget
NFR-004: Data Integrity
- Session sync creates exactly one calendar event per session (no duplicates)
- Failed session sync retried up to 5 times before logging and abandoning
- Calendar event IDs stored persistently for session-to-event correlation
- Form validation: < 50ms
NFR-007: Navigation
- Date navigation (day change): < 200ms if cached, < 1.5s if fetch required
- Session sync (session event to calendar event): < 3s (P95)
NFR-008: Resource Usage
- Browser memory consumption: < 10MB
Observability
Logging
| Event | Level | Fields |
|---|---|---|
| Google OAuth2 flow initiated | INFO | account_email (if re-auth), scopes_requested |
| Google OAuth2 token acquired | INFO | account_email, expires_at, calendars_discovered_count |
| Google OAuth2 token expired | WARNING | account_email, expired_at, cached_events_count |
| CalDAV connection established | INFO | server_url, username, calendars_discovered_count |
| CalDAV connection failed | ERROR | server_url, username, http_status, error_message |
| Calendar events fetched | INFO | provider, calendar_id, date, event_count, latency_ms, cache_hit |
| Calendar event fetch failed | ERROR | provider, calendar_id, date, http_status, error_message, retry_count |
| Event created | INFO | provider, calendar_id, event_id, title, duration_minutes |
| Event creation failed | ERROR | provider, calendar_id, title, http_status, error_message |
| Event updated | INFO | provider, calendar_id, event_id, updated_fields |
| Session sync: event created | INFO | session_number, calendar_event_id, calendar_id, provider |
| Session sync: event updated (close) | INFO | session_number, calendar_event_id, session_duration_minutes |
| Session sync: queued for retry | WARNING | session_number, attempt_count, next_retry_ms, error_message |
| Session sync: abandoned after max retries | ERROR | session_number, total_attempts, last_error |
| Credential encryption failed | ERROR | operation (encrypt/decrypt), error_type |
| Google API rate limit hit | WARNING | calendar_id, retry_after_ms, requests_in_window |
| Cache eviction | DEBUG | provider, calendar_id, date, reason (ttl_expired/manual_refresh) |
Metrics (Browser Performance API)
| Metric | Type | Labels | Description |
|---|---|---|---|
calendar_events_fetched_total |
Counter | provider, calendar_id, cache_hit | Total event fetch operations |
calendar_events_fetch_duration_ms |
Histogram | provider, cache_hit | Event fetch latency (API or cache) |
calendar_events_displayed |
Gauge | provider | Currently rendered events in timeline |
calendar_event_creation_total |
Counter | provider, status | Event creation attempts (success/failure) |
calendar_event_creation_duration_ms |
Histogram | provider | Event creation round-trip latency |
calendar_session_sync_total |
Counter | status | Session sync outcomes (success/failure/queued) |
calendar_session_sync_duration_ms |
Histogram | — | Session event to calendar event latency |
calendar_session_sync_retry_total |
Counter | — | Total retry attempts for failed session syncs |
calendar_session_sync_queue_depth |
Gauge | — | Pending session syncs in localStorage queue |
calendar_accounts_connected |
Gauge | provider | Connected calendar accounts |
calendar_cache_size_bytes |
Gauge | — | In-memory event cache size |
calendar_oauth_refresh_total |
Counter | status | OAuth2 token refresh attempts |
calendar_caldav_request_total |
Counter | method, status | CalDAV HTTP requests (PROPFIND/REPORT/PUT) |
Metrics are collected via performance.mark() / performance.measure() and exposed to the Command Center's gadget metrics aggregator via ctx.metrics.
Tracing
| Span | Parent | Attributes |
|---|---|---|
calendar.mount |
gadget.mount | provider_count, total_calendars |
calendar.fetch_events |
calendar.mount / calendar.navigate | provider, calendar_id, date, cache_hit |
calendar.render_timeline |
calendar.fetch_events | event_count, allday_count, render_ms |
calendar.create_event |
user_action | provider, calendar_id, title, duration_minutes |
calendar.update_event |
session_sync | provider, calendar_id, event_id, updated_fields |
calendar.oauth2_flow |
user_action | account_email, scopes |
calendar.caldav_connect |
user_action | server_url, calendars_found |
calendar.session_sync.open |
session.events | session_number, target_calendar |
calendar.session_sync.close |
session.events | session_number, duration_minutes |
calendar.session_sync.retry |
calendar.session_sync.open | session_number, attempt, backoff_ms |
calendar.cache_refresh |
calendar.navigate | provider, calendar_id, date, evicted_count |
Spans are propagated via the Command Center's ctx.tracing interface. Each span includes gadget_id: "orchestrata-calendar" as a resource attribute.
Health Check
The calendar gadget exposes health status via the Command Center's ctx.health gadget health protocol:
ctx.health.report('orchestrata-calendar', {
status: 'healthy',
providers: {
google: { status: 'connected', accounts: 1, calendars: 3 },
caldav: { status: 'connected', accounts: 1, calendars: 2 }
},
cache: {
entries: 5,
size_bytes: 24576,
oldest_entry_age_seconds: 180
},
session_sync: {
enabled: true,
pending_queue_depth: 0,
last_sync_at: '2026-03-28T13:00:00+03:00',
last_sync_status: 'success'
},
uptime_seconds: 3600.5
});
Health status levels:
- healthy: All connected providers responsive, session sync operational, cache fresh
- degraded: One or more providers unreachable but cached events displayed; or session sync has pending retries in queue
- unhealthy: No providers connected, or all provider authentications expired, or Web Crypto API unavailable (no credential storage)
Technical Design
Architecture
┌───��──────────────────────────────────────────────┐
│ Calendar Gadget (Browser) │
├────────��──────────────────────────���──────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Calendar │ │ Event │ │
│ │ Timeline │ │ Creator │ │
│ │ (day view) │ │ (form) │ ��
│ └──────────────┘ └���─────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌───��──────────┐ ��──────────────┐ │
│ │ Event │ │ Session │ │
│ │ Cache │ │ Sync │ │
│ │ (in-memory) │ │ Engine │ │
│ └─────���────────┘ └────��─────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ���──────────────────────────────────────────┐ │
│ │ Calendar Providers │ │
│ │ ��──────────────┐ ┌─────���────────┐ │ │
│ │ │ Google Cal │ │ CalDAV │ │ │
�� │ │ Provider │ │ Provider │ │ │
│ │ │ (OAuth2) │ │ (Basic Auth) │ │ │
│ │ └──────────────┘ └──────────��───┘ │ │
│ └───────��──────────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ ctx.bus │ │ localStorage │ │
��� │ (session │ │ (tokens, │ │
│ │ events) │ │ settings) │ │
│ └─────��────────┘ └──────────────┘ │
└──���───────────────────────────────────────────────┘
│ │
▼ ▼
┌─���────────────────┐ ┌──────────────────────┐
│ Google Calendar │ │ CalDAV Server │
│ API (REST) │ │ (Nextcloud, etc.) │
│ OAuth2 tokens │ │ Basic Auth │
└────────���─────────┘ └──────────────────────┘
Key Interfaces
/**
* CalendarGadget — Main gadget class for the calendar integration.
*/
class CalendarGadget {
/** Mount the gadget into the Command Center grid. */
async mount(ctx) {}
/** Unmount and clean up subscriptions and timers. */
async unmount() {}
/** Refresh event data for the currently displayed date. */
async refresh() {}
/** Navigate to a specific date. */
async navigateTo(date) {}
}
/**
* ICalendarProvider — Protocol for calendar backends.
*/
class ICalendarProvider {
/** Authenticate with the calendar service. */
async authenticate(credentials) {}
/** List available calendars. */
async listCalendars() {}
/** Fetch events for a date range. */
async fetchEvents(calendarId, startDate, endDate) {}
/** Create a new event. */
async createEvent(calendarId, event) {}
/** Update an existing event. */
async updateEvent(calendarId, eventId, updates) {}
/** Delete an event. */
async deleteEvent(calendarId, eventId) {}
/** Get connection status. */
getStatus() {}
}
/**
* SessionCalendarSync — Syncs session lifecycle to calendar events.
*/
class SessionCalendarSync {
/** Start listening for session events and syncing. */
async start(bridge, provider, targetCalendarId) {}
/** Stop session sync. */
async stop() {}
/** Retry any failed syncs. */
async retryPending() {}
}
Gadget Manifest
{
"id": "orchestrata-calendar",
"name": "Calendar",
"version": "1.0.0",
"description": "Calendar integration with Google Calendar and CalDAV support",
"entry": "gadgets/calendar/calendar.js",
"icon": "calendar",
"category": "productivity",
"dependencies": ["GadgetBridge"],
"min_api_version": "1.0.0",
"default_size": {"columns": 1, "rows": 2},
"supports_multi_instance": false,
"config_schema": {
"type": "object",
"properties": {
"session_sync_enabled": {"type": "boolean", "default": true},
"session_sync_calendar": {"type": "string", "default": "primary"},
"default_event_duration_minutes": {"type": "integer", "default": 30},
"cache_ttl_minutes": {"type": "integer", "default": 5},
"time_format": {"type": "string", "enum": ["12h", "24h"], "default": "24h"}
}
}
}
CalendarEvent Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "CalendarEvent",
"type": "object",
"required": ["id", "title", "start", "end", "calendar_id"],
"properties": {
"id": {"type": "string"},
"title": {"type": "string", "maxLength": 500},
"start": {"type": "string", "format": "date-time"},
"end": {"type": "string", "format": "date-time"},
"all_day": {"type": "boolean", "default": false},
"description": {"type": "string"},
"location": {"type": "string"},
"calendar_id": {"type": "string"},
"calendar_name": {"type": "string"},
"color": {"type": "string", "pattern": "^#[0-9a-fA-F]{6}$"},
"attendees": {
"type": "array",
"items": {"type": "string", "format": "email"}
},
"source": {
"type": "string",
"enum": ["google", "caldav"]
}
}
}
Configuration
# In configs/command-center.yaml under gadgets section
gadgets:
orchestrata-calendar:
session_sync_enabled: true
session_sync_calendar: "primary"
default_event_duration_minutes: 30
cache_ttl_minutes: 5
time_format: "24h"
google:
client_id: "${GOOGLE_CALENDAR_CLIENT_ID}"
scopes:
- "https://www.googleapis.com/auth/calendar.events"
- "https://www.googleapis.com/auth/calendar.readonly"
caldav:
servers: []
Integration Points
| Component | Integration |
|---|---|
| SPEC-016 VizInt Command Center | Gadget lifecycle, chrome, ctx.bus, GadgetBridge |
| Google Calendar API | OAuth2 implicit flow, events.list, events.insert, events.update |
| CalDAV Protocol | PROPFIND, REPORT, PUT for event CRUD |
| ctx.bus | Session lifecycle events from session-manager gadget |
| localStorage | Token storage, settings persistence, session-event ID mapping |
Success Criteria
- Calendar Display: Today's events from connected calendars displayed in timeline view within 2 seconds of gadget mount
- Multi-Provider: Both Google Calendar and CalDAV accounts connectable and displaying events simultaneously
- Event Creation: New events created from the Command Center appear in the external calendar within 5 seconds
- Session Sync: Session open/close times automatically recorded as calendar events with correct duration
- Offline Resilience: Cached events remain visible when calendar APIs are unreachable
- Performance: Timeline renders within 100ms with up to 20 events; no jank on scroll
Security
Authentication
- OAuth2 implicit grant flow for Google Calendar authentication using
https://accounts.google.com/o/oauth2/v2/authwith scopehttps://www.googleapis.com/auth/calendar - CalDAV authentication via HTTP Basic authentication with app password
- All authentication flows handled entirely in the browser — no credentials transmitted to Orchestrata gateway
Authorization
- Google Calendar API access scoped to minimum required permissions (
calendar.events.readonly,calendar.events) - CalDAV access limited to calendar read/write operations (no admin operations)
- Session sync requires explicit user opt-in via gadget settings
Input Validation
- All calendar event data validated client-side before API submission
- Event title: required, max 500 characters
- Event time: end must be after start, duration max 24 hours
- Calendar ID: validated against known calendars from connected accounts
- CalDAV server URL: validated for proper URI format
Audit Logging
All security-relevant events logged for audit trail:
| Event | Level | Fields |
|---|---|---|
| OAuth2 flow initiated | INFO | account_email, scopes_requested |
| OAuth2 token acquired | INFO | account_email, expires_at |
| OAuth2 token expired | WARNING | account_email |
| CalDAV authentication failed | ERROR | server_url, username, http_status |
| Credential encryption failed | ERROR | operation, error_type |
| Credential storage attempted without secure context | WARNING | reason |
Data Protection
- OAuth2 tokens stored in localStorage with Web Crypto API encryption
- CalDAV passwords encrypted at rest in localStorage using AES-GCM with user-derived key
- No credentials transmitted to external services beyond Google Calendar API or CalDAV servers
- All API calls use HTTPS exclusively
Governance
Architecture Decision Records
| ADR | Decision | Rationale |
|---|---|---|
| ADR-017-001 | Browser-side JavaScript gadget | No Python backend required; leverages browser APIs for OAuth2 and Web Crypto |
| ADR-017-002 | OAuth2 implicit flow over authorization code | Simplifies browser-only authentication; no server-side token exchange needed |
| ADR-017-003 | localStorage for credential storage | Meets requirements for persistence; Web Crypto API provides encryption |
| ADR-017-004 | ctx.bus for session lifecycle events | Integrates with existing GadgetBridge pattern for inter-gadget communication |
Compliance
- iCalendar format (RFC 5545) compliance for CalDAV event exchange
- CalDAV protocol (RFC 4791) compliance for calendar operations
- Google Calendar API v3 compliance for Google Calendar integration
Maintenance
- Calendar gadget uses semantic versioning aligned with SPEC-017 version
- API contract version tracks gadget version for backward compatibility
- Deprecation policy: 2 minor version notice before breaking changes
Support
- Issue tracking via project issue tracker with
calendarlabel - Supported CalDAV servers: Nextcloud, Radicale, Baikal, macOS Calendar Server
- Google Calendar: API v3 with OAuth2
Visual Architecture
Class Diagram
Diagram source shown as text: Mermaid could not parse it (Error: Parse error on line 36:).
classDiagram-direction LR
class CheetahClawsTUI {
+__init__()
#_load_commands(): dict[str, Any]
+async create_checkpoint(name: str): dict[str, Any]
+async rewind(steps: int = 1): dict[str, Any]
+async query_memory(query: str, depth: int = 1): dict[str, Any]
+async edit_file(file_path: str, old_string: str, new_string: str): dict[str, Any]
+async search_files(pattern: str, path: str = '.'): list[str]
+async execute_command(command: str, args: list[str] | None = None): dict[str, Any]
}
class OpenRouterProvider {
+__init__(api_key: str | None = None)
+async chat_completion(model: str, messages: list[dict[str, str]], **kwargs): dict[str, Any]
+get_available_models(): list[str]
+get_model_cost(model: str): float
}
class TodoistIntegration {
+__init__(api_key: str | None = None)
+is_configured(): bool
+async create_project(name: str): str
+async create_task(project_id: str, title: str, **kwargs): str
+async sync_tasks(project_id: str): list[dict[str, Any]]
+async close()
}
class VizIntWebUI {
+__init__(host: str = 'localhost', port: int = 8765)
+async start()
+async shutdown()
+async add_project(project_data: dict[str, Any])
+async broadcast_edit_complete(file_path: str, result: dict[str, Any])
+async update_task_status(task_id: str, status: str)
+async get_projects(): list[dict[str, Any]]
}
GoogleCalendarToken Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "GoogleCalendarToken",
"type": "object",
"required": ["access_token", "token_type", "expires_at", "email"],
"properties": {
"access_token": {"type": "string"},
"token_type": {"type": "string", "const": "Bearer"},
"expires_at": {"type": "string", "format": "date-time"},
"email": {"type": "string", "format": "email"},
"scopes": {
"type": "array",
"items": {"type": "string"}
}
}
}
CalDAVCredentials Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "CalDAVCredentials",
"type": "object",
"required": ["server_url", "username"],
"properties": {
"server_url": {
"type": "string",
"format": "uri",
"description": "CalDAV server URL (e.g., https://nextcloud.example.com/remote.php/dav)."
},
"username": {"type": "string"},
"encrypted_password": {
"type": "string",
"description": "AES-GCM encrypted app password, base64 encoded."
},
"display_name": {"type": "string"}
}
}
SessionSyncMapping Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "SessionSyncMapping",
"type": "object",
"required": ["session_id", "calendar_event_id", "calendar_id", "provider"],
"properties": {
"session_id": {"type": "string"},
"calendar_event_id": {"type": "string"},
"calendar_id": {"type": "string"},
"provider": {"type": "string", "enum": ["google", "caldav"]},
"created_at": {"type": "string", "format": "date-time"},
"synced": {"type": "boolean"}
}
}
Error Conditions
| ERR-ID | Condition | Handling |
|---|---|---|
| ERR-001 | Google OAuth2 token has expired and refresh fails | Display a "Re-authenticate" button and continue showing cached events |
| ERR-002 | CalDAV server returns 401 Unauthorized | Display "Authentication failed" for that account and prompt credential update |
| ERR-003 | CalDAV server does not support CORS headers | Display a configuration hint about setting up a CORS proxy |
| ERR-004 | Event creation fails due to API error | Display the error message and offer a retry button without clearing the form |
| ERR-005 | Session sync cannot create a calendar event (API unavailable) | Queue sync in localStorage and retry on next successful API call (up to 5 retries) |
| ERR-006 | Session close event arrives but no matching session-open calendar event exists | Create a new event with estimated start time from session metadata |
| ERR-007 | Multiple calendar accounts connected and target sync calendar is ambiguous | Use the first primary calendar found or prompt the user to select one |
| ERR-008 | Browser blocks pop-ups required for Google OAuth2 | Detect blocked popup and display instructions to allow pop-ups for the domain |
| ERR-009 | localStorage encryption key derivation fails (Web Crypto API unavailable) | Do not store CalDAV credentials and inform user that credential persistence requires a secure context (HTTPS) |
| ERR-010 | Event spans midnight (crosses date boundary) | Display visible portion for current date with a visual indicator that the event continues |
| ERR-011 | Google Calendar API rate limit is reached (10 QPS default) | Back off and display a rate-limit notice with estimated retry time |
| ERR-012 | CalDAV VEVENT contains unsupported iCalendar properties (RRULE for recurrence) | Display the next occurrence only and indicate it is recurring |
| ERR-013 | localStorage quota exceeded when caching events | Clear oldest cached events and notify user of cache eviction |
| ERR-014 | Invalid date range selected for event fetch | Validate date range and display error if end date is before start date |
Testing
Testability Considerations
- [TC-001] Calendar provider implementations SHALL be testable with real HTTP requests to a local CalDAV test server (e.g., Radicale) in integration tests.
- [TC-002] Google Calendar provider tests SHALL use a real Google Calendar API sandbox account with test calendars.
- [TC-003] Session sync tests SHALL use real
ctx.busevents and verify calendar event creation via the provider API. - [TC-004] Timeline rendering tests SHALL use real DOM via JSDOM in Vitest and verify correct time block positioning for known event sets.
- [TC-005] OAuth2 flow tests SHALL use Playwright E2E tests with a real browser to verify the full authentication cycle.
- [TC-006] Offline resilience tests SHALL disconnect the network (via Playwright) and verify cached events remain visible and creation is queued.
- [TC-007] Credential encryption tests SHALL verify round-trip encryption/decryption using Web Crypto API with real key derivation.
- [TC-008] iCalendar parsing tests SHALL use real RFC 5545 VEVENT samples from CalDAV servers and verify correct property extraction.
Definition of Done
- [DoD-001] All acceptance criteria across all user stories are met and verified with automated tests.
- [DoD-002] Vitest unit test coverage >= 90% for all calendar modules (providers, timeline, sync engine, cache).
- [DoD-003] Playwright E2E tests pass with a real browser, real Google Calendar API (sandbox), and real CalDAV server.
- [DoD-004] Performance benchmarks meet or exceed all NFR performance thresholds.
- [DoD-005] This specification document is updated to reflect the final implemented design.
- [DoD-006] Calendar gadget configuration and setup instructions are documented.
- [DoD-007] Security review completed covering: OAuth2 token storage, CalDAV credential encryption, CORS handling, and API scope minimization.
- [DoD-008] All changes are backward compatible with the stated API version, or migration guidance is provided.
Integration Points
| Component | Integration |
|---|---|
| SPEC-016 VizInt Command Center | Gadget lifecycle, chrome, ctx.bus, GadgetBridge |
| Google Calendar API | OAuth2 implicit flow, events.list, events.insert, events.update |
| CalDAV Protocol | PROPFIND, REPORT, PUT for event CRUD |
| ctx.bus | Session lifecycle events from session-manager gadget |
| localStorage | Token storage, settings persistence, session-event ID mapping |
Examples
Example: Google Calendar OAuth2 Flow
// User clicks "Connect Google Calendar"
const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');
authUrl.searchParams.set('client_id', config.google.client_id);
authUrl.searchParams.set('redirect_uri', window.location.origin + '/oauth/callback');
authUrl.searchParams.set('response_type', 'token');
authUrl.searchParams.set('scope', 'https://www.googleapis.com/auth/calendar.events https://www.googleapis.com/auth/calendar.readonly');
const popup = window.open(authUrl.toString(), 'google-auth', 'width=500,height=600');
// Callback handler extracts access_token from URL fragment
Example: Fetching Today's Events (Google)
const today = new Date();
const startOfDay = new Date(today.setHours(0, 0, 0, 0)).toISOString();
const endOfDay = new Date(today.setHours(23, 59, 59, 999)).toISOString();
const response = await fetch(
`https://www.googleapis.com/calendar/v3/calendars/primary/events?` +
`timeMin=${startOfDay}&timeMax=${endOfDay}&singleEvents=true&orderBy=startTime`,
{ headers: { 'Authorization': `Bearer ${token.access_token}` } }
);
const data = await response.json();
// data.items = [{summary, start, end, description, ...}, ...]
Example: Session Sync Calendar Event
When session 89 opens with purpose "Implement WebSocket Gateway":
{
"summary": "Orchestrata Session 89",
"description": "Purpose: Implement WebSocket Gateway\nDashboard: http://localhost:8080/#session-89",
"start": {
"dateTime": "2026-03-28T10:30:00+03:00"
},
"end": {
"dateTime": "2026-03-28T11:00:00+03:00"
},
"colorId": "9"
}
When session 89 closes 2.5 hours later, the event end time is updated:
{
"end": {
"dateTime": "2026-03-28T13:00:00+03:00"
}
}
Example: CalDAV VEVENT Creation
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//Orchestrata//Calendar Gadget//EN
BEGIN:VEVENT
DTSTART:20260328T103000
DTEND:20260328T130000
SUMMARY:Orchestrata Session 89
DESCRIPTION:Purpose: Implement WebSocket Gateway
UID:orchestrata-session-89@orchestrata.local
END:VEVENT
END:VCALENDAR
API Contracts
Contract: Calendar Gadget API (contracts/commandcenter/calendar-v1.0.0.yaml)
info:
title: Calendar Integration Gadget Contract
version: 1.0.0
spec_id: 017-calendar-integration
spec_version: 1.0.0
maturity: experimental
consumer: calendar-gadget
providers: [google-calendar-api, caldav-server]
schemas:
CalendarEvent:
type: object
required: [id, title, start, end, calendar_id]
properties:
id:
type: string
title:
type: string
maxLength: 500
start:
type: string
format: date-time
end:
type: string
format: date-time
all_day:
type: boolean
description:
type: string
location:
type: string
calendar_id:
type: string
calendar_name:
type: string
color:
type: string
attendees:
type: array
items:
type: string
source:
type: string
enum: [google, caldav]
CalendarAccount:
type: object
required: [provider, display_name, status]
properties:
provider:
type: string
enum: [google, caldav]
display_name:
type: string
email:
type: string
status:
type: string
enum: [connected, expired, error]
calendars:
type: array
items:
type: object
required: [id, name, writable]
properties:
id:
type: string
name:
type: string
color:
type: string
writable:
type: boolean
selected:
type: boolean
SessionSyncMapping:
type: object
required: [session_id, calendar_event_id, calendar_id, provider]
properties:
session_id:
type: string
calendar_event_id:
type: string
calendar_id:
type: string
provider:
type: string
enum: [google, caldav]
created_at:
type: string
format: date-time
synced:
type: boolean
bus_events:
calendar.event.created:
description: Emitted when a new event is created through the gadget
payload:
$ref: "#/schemas/CalendarEvent"
calendar.event.updated:
description: Emitted when an event is updated (e.g., session close updates end time)
payload:
$ref: "#/schemas/CalendarEvent"
calendar.account.connected:
description: Emitted when a new calendar account is connected
payload:
$ref: "#/schemas/CalendarAccount"
session_sync:
trigger_event: "session.events"
trigger_conditions:
open: "event_type == 'opened'"
close: "event_type == 'closed'"
event_title_format: "Orchestrata Session {session_number}"
target_calendar_key: "commandcenter.state.calendar.sync_target"
retry:
max_attempts: 5
backoff_initial_ms: 1000
backoff_max_ms: 30000
performance:
google_fetch_p95_ms: 1000
caldav_fetch_p95_ms: 2000
cache_hit_ms: 10
timeline_render_p95_ms: 100
event_creation_p95_ms: 2000
security:
oauth2_storage: localStorage_encrypted
caldav_credential_storage: localStorage_aes_gcm
api_transport: https_only
scope_minimization: true
spec_traceability:
spec_id: "017-calendar-integration"
spec_version: "1.0.0"
user_stories: ["US-001", "US-002", "US-003", "US-004", "US-005"]
Observability
Logging Requirements
- Component Logging
- Log all major operations with appropriate log levels
- Include correlation IDs for request tracing
- Log performance metrics for critical operations
- Log errors with full context for debugging
Metrics Requirements
- Performance Metrics
- Operation duration histograms
- Request/error rate counters
- Resource utilization gauges
- Business metrics relevant to domain
Tracing Requirements
- Distributed Tracing
- Trace propagation across component boundaries
- Span attributes for domain-specific context
- Trace sampling based on operational needs
Alerting Requirements
- Critical Alerts
- Error rate spikes
- Performance degradation
- Resource exhaustion
- Business metric anomalies
Dashboard Requirements
- Operational Dashboard
- Real-time system health
- Performance trends
- Error analysis
- Resource monitoring
Revision History
| Date | Version | Author | Changes |
|---|---|---|---|
| 2026-03-28 | 1.0.0 | Mohamed | Initial specification |
| 2026-03-29 | 1.0.1 | Mohamed | Added std_01 (Standardized Acceptance Criteria) and std_15 (Observability) sections |