From 223c94f52ebaa878f6951ebf7d08754e413bdca7 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Thu, 5 Feb 2026 10:37:09 -1000 Subject: Document multi-agent workflow, ADRs, and Agent API - Add Development Workflow section to DESIGN.md documenting three-role system (Architect, Implementor, Reviewer) with handoff documents - Update CLAUDE.md with Key Documents section pointing to DESIGN.md, role definitions, and ADRs - Add ADR-first documentation policy across all role definitions - Update REVIEWER_ROLE.md with comprehensive test quality checklist - Document Agent API and completed tasks log in DESIGN.md - Update database schema table with 5 missing tables - Update endpoint reference with 10 missing routes - Create ADRs 002-005 capturing key architectural decisions: - 002: Timeline aggregation architecture - 003: HTMX write operations pattern - 004: Concurrent data fetching with graceful degradation - 005: Agent API notification-based authentication - Add migrations/README.md documenting schema history and 007 gap Co-Authored-By: Claude Opus 4.5 --- docs/adr/002-timeline-aggregation.md | 76 ++++++++++++++++++++++ docs/adr/003-htmx-write-operations.md | 85 +++++++++++++++++++++++++ docs/adr/004-concurrent-data-fetching.md | 104 +++++++++++++++++++++++++++++++ docs/adr/005-agent-api-authentication.md | 95 ++++++++++++++++++++++++++++ 4 files changed, 360 insertions(+) create mode 100644 docs/adr/002-timeline-aggregation.md create mode 100644 docs/adr/003-htmx-write-operations.md create mode 100644 docs/adr/004-concurrent-data-fetching.md create mode 100644 docs/adr/005-agent-api-authentication.md (limited to 'docs/adr') diff --git a/docs/adr/002-timeline-aggregation.md b/docs/adr/002-timeline-aggregation.md new file mode 100644 index 0000000..2700776 --- /dev/null +++ b/docs/adr/002-timeline-aggregation.md @@ -0,0 +1,76 @@ +# ADR 002: Timeline Aggregation Architecture + +## Status +Accepted + +## Context +The dashboard aggregates data from multiple sources (Todoist, Trello, PlanToEat, Google Calendar, Google Tasks). Users need a unified chronological view of upcoming items across all sources without opening multiple apps. + +Key challenges: +- Different data sources have different models (Task, Card, Meal, Event) +- Some sources have time-of-day, others only have dates +- Meals have no explicit time but should appear at logical meal times +- Need to support grouping by day section (Today, Tomorrow, Later) + +## Decision +Implement a **polymorphic TimelineItem model** that normalizes all data sources into a single sortable structure. + +### Technical Details: + +**Unified Model (`internal/models/timeline.go`):** +```go +type TimelineItem struct { + ID string + Type TimelineItemType // task, meal, card, event, gtask + Title string + Description string + Time time.Time + AllDay bool + URL string + DaySection string // overdue, today, tomorrow, later + // ... additional fields +} +``` + +**Meal Time Defaults:** +- Breakfast → 08:00 +- Lunch → 12:00 +- Dinner → 19:00 +- Other → 12:00 + +**Implementation:** +- `internal/handlers/timeline_logic.go` - `BuildTimeline()` aggregation function +- `internal/handlers/timeline.go` - HTTP handler +- `internal/models/timeline.go` - Data models +- Store methods: `GetTasksByDateRange()`, `GetMealsByDateRange()`, `GetCardsByDateRange()` + +**Grouping Strategy:** +Items are grouped into collapsible day sections: +- **Overdue** - Past due items (expanded) +- **Today** - Due today (expanded) +- **Tomorrow** - Due tomorrow (expanded) +- **Later** - 2+ days out (collapsed by default) + +## Consequences + +**Pros:** +- Single unified view reduces context switching +- Consistent sorting regardless of data source +- Extensible - new sources just need a converter to TimelineItem +- Collapsible sections reduce cognitive load + +**Cons:** +- Meal times are assumed, not actual (user might eat breakfast at 10am) +- All-day items appear at midnight, requiring special handling +- Multiple API calls per request (mitigated by caching) + +## Alternatives Considered + +**Option A: Source-specific views only** +- Rejected: Defeats the purpose of a unified dashboard + +**Option B: Store normalized items in database** +- Rejected: Adds complexity, staleness issues, harder to keep in sync + +**Option C: Client-side aggregation (JavaScript)** +- Rejected: More complex, slower on mobile, harder to test diff --git a/docs/adr/003-htmx-write-operations.md b/docs/adr/003-htmx-write-operations.md new file mode 100644 index 0000000..2471a90 --- /dev/null +++ b/docs/adr/003-htmx-write-operations.md @@ -0,0 +1,85 @@ +# ADR 003: HTMX-Based Write Operations + +## Status +Accepted + +## Context +Phase 1-2 of the dashboard were read-only views. Phase 3 required transforming it into an active command center where users can: +- Complete tasks/cards +- Create new tasks +- Add shopping items +- Toggle item states + +Traditional approaches would require either: +- Full page reloads (poor UX) +- Complex JavaScript SPA framework (React, Vue) +- Custom AJAX handlers + +## Decision +Use **HTMX attributes** for all write operations, returning HTML partials that swap into the DOM. + +### Technical Details: + +**Pattern: Form submission with partial swap** +```html +
+ +
+``` + +**Handler returns HTML partial:** +```go +func (h *Handler) HandleCompleteAtom(w http.ResponseWriter, r *http.Request) { + // ... complete the task via API + // Return completed state HTML or empty response + HTMLResponse(w, h.renderer, "completed-atom", data) +} +``` + +**Quick Add Pattern:** +```html + +``` + +**Optimistic UI:** +- Use CSS transitions for immediate visual feedback +- On error, swap in error banner partial +- HTMX `hx-on` events for state management + +**Unified Quick Add:** +- Single input parses text for routing hints +- `#groceries` tag → routes to shopping +- `#work` tag → routes to Trello +- Default → routes to Todoist + +## Consequences + +**Pros:** +- No JavaScript framework needed +- Server renders all HTML (single source of truth) +- Progressive enhancement (works without JS, enhanced with) +- Trivial to test (just HTTP handlers) +- Fast development cycle + +**Cons:** +- More HTTP requests than SPA approach +- Partial HTML responses require careful template organization +- Limited offline capability + +## Alternatives Considered + +**Option A: React/Vue SPA** +- Rejected: Overkill for this use case, adds build complexity, harder to maintain + +**Option B: Full page reloads** +- Rejected: Poor UX, especially on mobile + +**Option C: Custom JavaScript + JSON APIs** +- Rejected: Reinventing what HTMX provides, more code to maintain diff --git a/docs/adr/004-concurrent-data-fetching.md b/docs/adr/004-concurrent-data-fetching.md new file mode 100644 index 0000000..c7238f8 --- /dev/null +++ b/docs/adr/004-concurrent-data-fetching.md @@ -0,0 +1,104 @@ +# ADR 004: Concurrent Data Fetching with Graceful Degradation + +## Status +Accepted + +## Context +The dashboard fetches data from 5+ external APIs on each page load: +- Todoist (tasks) +- Trello (boards, cards) +- PlanToEat (meals, shopping) +- Google Calendar (events) +- Google Tasks (tasks) + +Sequential fetching would be unacceptably slow (5+ seconds). However, concurrent fetching introduces: +- Rate limiting concerns (especially Trello) +- Partial failure scenarios +- Data race risks + +## Decision +Implement **parallel fetching with semaphore-limited concurrency** and **cache-first fallback** for graceful degradation. + +### Technical Details: + +**Parallel Aggregation (`internal/handlers/handlers.go`):** +```go +func (h *Handler) aggregateData(ctx context.Context) (*DashboardData, error) { + var wg sync.WaitGroup + var mu sync.Mutex + data := &DashboardData{} + + // Launch goroutines for each data source + wg.Add(4) + go func() { defer wg.Done(); fetchTasks(ctx, &mu, data) }() + go func() { defer wg.Done(); fetchBoards(ctx, &mu, data) }() + go func() { defer wg.Done(); fetchMeals(ctx, &mu, data) }() + go func() { defer wg.Done(); fetchCalendar(ctx, &mu, data) }() + wg.Wait() + + return data, nil +} +``` + +**Semaphore for Trello (`internal/api/trello.go`):** +```go +const maxConcurrentRequests = 5 + +func (c *TrelloClient) GetBoardsWithCards(ctx context.Context) ([]Board, error) { + sem := make(chan struct{}, maxConcurrentRequests) + // ... limit concurrent board fetches +} +``` + +**Cache-First Fallback:** +```go +func (h *Handler) fetchWithFallback(ctx context.Context, fetch func() error, getCache func() (T, error)) T { + if err := fetch(); err != nil { + log.Printf("Warning: API fetch failed, using cache: %v", err) + cached, _ := getCache() + return cached + } + return freshData +} +``` + +**Error Collection:** +- Errors are collected in `data.Errors` slice +- UI displays warning banner if any source failed +- Page still renders with available data + +## Consequences + +**Pros:** +- Fast page loads (parallel fetching) +- Resilient to individual API failures +- Respects rate limits (semaphore pattern) +- Users see cached data rather than error page + +**Cons:** +- Complexity in error handling +- Cache fallback may show stale data without clear indication +- Debugging requires checking multiple error sources +- Mutex needed for shared data structure + +## Alternatives Considered + +**Option A: Sequential fetching** +- Rejected: 5+ second page loads unacceptable + +**Option B: Fail-fast on any error** +- Rejected: One flaky API shouldn't break entire dashboard + +**Option C: Background sync with eventual consistency** +- Rejected: Adds complexity, users expect fresh data on load + +## Implementation Notes + +**Race Condition Prevention:** +- All goroutines write to different fields OR use mutex +- Trello client uses mutex for `boards[i]` slice element writes +- Run `go test -race ./...` to verify + +**Monitoring:** +- Log all API failures with `Warning:` prefix +- Consider adding metrics for cache hit/miss rates diff --git a/docs/adr/005-agent-api-authentication.md b/docs/adr/005-agent-api-authentication.md new file mode 100644 index 0000000..e64a5a8 --- /dev/null +++ b/docs/adr/005-agent-api-authentication.md @@ -0,0 +1,95 @@ +# ADR 005: Agent API Authentication Architecture + +## Status +Accepted + +## Context +External agents (CLI tools, automation scripts, AI assistants) need secure access to dashboard context without exposing user credentials. Traditional approaches have weaknesses: + +- **API Keys**: Long-lived, often stored insecurely, difficult to revoke +- **OAuth2**: Complex setup, overkill for personal dashboard +- **SSH Keys**: Key management burden on user + +Requirements: +- User awareness of agent access (no hidden API keys) +- Easy revocation (deny re-auth requests) +- Time-limited sessions to reduce exposure +- Support for both CLI and browser-based agents + +## Decision +Implement **notification-based approval flow** with ephemeral sessions. + +### Technical Details: + +**Identity Model:** +- Agents identified by `name` + `agent_id` (UUID) pair +- Binding persisted on first approval +- Impersonation detected if name/agent_id mismatch + +**Trust Levels:** +- `TRUSTED`: Previously approved, auto-approve on re-auth +- `UNKNOWN`: New agent, requires user approval +- `BLOCKED`: Denied agent, reject immediately + +**Session Model:** +- 1-hour TTL tokens +- One active session per agent_id +- Session token in Authorization header + +**Approval Flow:** +``` +Agent Dashboard Browser + | | | + |-- POST /auth/request --> | | + | |-- WebSocket push ----> | + | | | (user sees modal) + | | | + |<-- request_token --------| | + | | | + |-- GET /auth/poll ------> | | + | (polling) | | + | | <-- POST /approve ---- | + | | | + |<-- session_token --------| | + | | | + |-- GET /context --------> | | + | (with session_token) | | +``` + +**Implementation:** +- `internal/handlers/agent.go` - Auth flow handlers (615 LOC) +- `internal/handlers/websocket.go` - Real-time notifications (216 LOC) +- `migrations/010_agent_tables.sql` - Storage schema +- `web/static/js/app.js` - Browser UI for approval modal + +**Rate Limiting:** +- 10 requests/minute per IP on auth endpoints +- Prevents brute-force token guessing + +## Consequences + +**Pros:** +- User-visible agent access (approval modal shows agent name) +- Easy revocation by denying re-auth or blocking agent +- Trust level caching reduces approval fatigue for known agents +- Time-limited sessions reduce credential exposure +- Works for both CLI agents (polling) and browser agents (web endpoints) + +**Cons:** +- Requires browser window open for first-time approval +- Poll loop adds latency to auth flow (~1-5 seconds) +- Session expiry interrupts long-running agent tasks (must re-auth) + +## Alternatives Considered + +**Option A: Traditional API Keys** +- Rejected: Long-lived credentials, no user awareness of access + +**Option B: OAuth2 Device Flow** +- Rejected: Complex implementation, requires external auth server setup + +**Option C: SSH-style Key Pairs** +- Rejected: Key management burden, harder to revoke + +**Option D: Magic Links (email-based)** +- Rejected: Adds email dependency, slower approval flow -- cgit v1.2.3