summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.env.example5
-rw-r--r--AI_AGENT_ACCESS.md340
-rw-r--r--AI_AGENT_SETUP.md276
-rw-r--r--PROJECT_SUMMARY.md43
-rw-r--r--README.md5
-rw-r--r--SECURITY_CHECKLIST.md59
-rw-r--r--SESSION_STATE.md41
-rw-r--r--internal/handlers/ai_handlers.go273
8 files changed, 39 insertions, 1003 deletions
diff --git a/.env.example b/.env.example
index a86520c..dddb927 100644
--- a/.env.example
+++ b/.env.example
@@ -33,8 +33,3 @@ CACHE_TTL_MINUTES=5
# Development Settings
# Set to "true" to enable debug logging
DEBUG=false
-
-# AI Agent Access (Optional)
-# Generate with: openssl rand -hex 32
-# Used by Claude.ai to access dashboard via /api/claude/snapshot
-# AI_AGENT_API_KEY=
diff --git a/AI_AGENT_ACCESS.md b/AI_AGENT_ACCESS.md
deleted file mode 100644
index 40fe6a1..0000000
--- a/AI_AGENT_ACCESS.md
+++ /dev/null
@@ -1,340 +0,0 @@
-# AI Agent Access Design
-
-## Overview
-Add a dedicated API endpoint optimized for AI agents (like Claude) to access dashboard data in a structured, machine-readable format with simple authentication.
-
-## Authentication Strategy
-
-### API Key-Based Auth
-Simple bearer token authentication that's easy to use from Claude.ai:
-- Generate a long, random API key for AI access
-- Store in environment variable: `AI_AGENT_API_KEY`
-- Use standard `Authorization: Bearer {key}` header
-- No complex OAuth flows or JWT rotation
-
-### Why This Approach?
-1. **Simple for Claude**: Just include auth header in requests
-2. **Stateless**: No session management needed
-3. **Revocable**: Change key in .env to revoke access
-4. **Standard**: Uses common HTTP authorization pattern
-
-## API Endpoints
-
-### GET /api/ai/dashboard
-Returns complete dashboard data in AI-optimized JSON format.
-
-**Request:**
-```http
-GET /api/ai/dashboard HTTP/1.1
-Host: localhost:8080
-Authorization: Bearer {AI_AGENT_API_KEY}
-```
-
-**Response (Success - 200 OK):**
-```json
-{
- "status": "success",
- "timestamp": "2026-01-09T14:30:00Z",
- "data": {
- "trello_boards": [
- {
- "id": "board123",
- "name": "Work Projects",
- "cards": [
- {
- "id": "card1",
- "name": "Complete project proposal",
- "list": "In Progress",
- "due_date": "2026-01-15",
- "url": "https://trello.com/c/card1"
- }
- ]
- }
- ],
- "todoist_tasks": [
- {
- "id": "task1",
- "content": "Buy groceries",
- "project": "Personal",
- "priority": 1,
- "due_date": "2026-01-10",
- "completed": false,
- "labels": ["shopping"],
- "url": "https://todoist.com/task/1"
- }
- ],
- "obsidian_notes": [
- {
- "filename": "meeting-notes.md",
- "title": "Team Meeting Notes",
- "content_preview": "Discussed Q1 goals...",
- "modified": "2026-01-09T10:00:00Z",
- "tags": ["meetings", "work"]
- }
- ],
- "plantoeat_meals": [
- {
- "id": "meal1",
- "recipe": "Spaghetti Carbonara",
- "date": "2026-01-09",
- "meal_type": "dinner",
- "url": "https://plantoeat.com/recipe/123"
- }
- ]
- },
- "metadata": {
- "cache_age_seconds": 45,
- "sources_refreshed": ["trello", "todoist"],
- "errors": []
- }
-}
-```
-
-**Response (Unauthorized - 401):**
-```json
-{
- "status": "error",
- "error": "Invalid or missing API key",
- "hint": "Include 'Authorization: Bearer {key}' header"
-}
-```
-
-### GET /api/ai/boards/{board_id}/cards
-Get cards from a specific Trello board.
-
-**Request:**
-```http
-GET /api/ai/boards/board123/cards HTTP/1.1
-Authorization: Bearer {AI_AGENT_API_KEY}
-```
-
-### GET /api/ai/tasks?filter={filter}
-Get filtered tasks (today, week, overdue, completed).
-
-**Request:**
-```http
-GET /api/ai/tasks?filter=today HTTP/1.1
-Authorization: Bearer {AI_AGENT_API_KEY}
-```
-
-### POST /api/ai/task
-Create a new Todoist task (Phase 2).
-
-**Request:**
-```http
-POST /api/ai/task HTTP/1.1
-Authorization: Bearer {AI_AGENT_API_KEY}
-Content-Type: application/json
-
-{
- "content": "Buy milk",
- "project": "Personal",
- "due_date": "2026-01-10",
- "priority": 1,
- "labels": ["shopping"]
-}
-```
-
-## AI-Optimized Response Format
-
-### Design Principles
-1. **Flat structure**: Avoid deep nesting where possible
-2. **Explicit field names**: Use `due_date` not `dueDate` for clarity
-3. **Include metadata**: Provide context about data freshness
-4. **Clear errors**: Return actionable error messages
-5. **Consistent types**: Always use ISO 8601 for dates
-
-### Example Claude.ai Usage
-```markdown
-User: "What tasks do I have due today?"
-
-Claude uses MCP tool or API call:
-GET /api/ai/tasks?filter=today
-Authorization: Bearer sk-ai-1234567890abcdef
-
-Claude response: "You have 3 tasks due today:
-1. Buy groceries (Personal, Priority 1)
-2. Complete project proposal (Work, Priority 2)
-3. Call dentist (Health, Priority 3)"
-```
-
-## Implementation
-
-### Middleware: Auth Check
-```go
-// internal/middleware/ai_auth.go
-func AIAuthMiddleware(apiKey string) func(http.Handler) http.Handler {
- return func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- authHeader := r.Header.Get("Authorization")
-
- if !strings.HasPrefix(authHeader, "Bearer ") {
- respondJSON(w, 401, map[string]string{
- "status": "error",
- "error": "Missing or invalid Authorization header",
- "hint": "Include 'Authorization: Bearer {key}' header",
- })
- return
- }
-
- token := strings.TrimPrefix(authHeader, "Bearer ")
- if token != apiKey {
- respondJSON(w, 401, map[string]string{
- "status": "error",
- "error": "Invalid API key",
- })
- return
- }
-
- next.ServeHTTP(w, r)
- })
- }
-}
-```
-
-### Handler: AI Dashboard
-```go
-// internal/handlers/ai_handlers.go
-func (h *Handler) HandleAIDashboard(w http.ResponseWriter, r *http.Request) {
- ctx := r.Context()
-
- // Fetch all data
- data, err := h.fetchDashboardData(ctx, false)
- if err != nil {
- respondJSON(w, 500, map[string]interface{}{
- "status": "error",
- "error": err.Error(),
- })
- return
- }
-
- // Format for AI consumption
- response := map[string]interface{}{
- "status": "success",
- "timestamp": time.Now().Format(time.RFC3339),
- "data": map[string]interface{}{
- "trello_boards": formatBoardsForAI(data.Boards),
- "todoist_tasks": formatTasksForAI(data.Tasks),
- "obsidian_notes": formatNotesForAI(data.Notes),
- "plantoeat_meals": formatMealsForAI(data.Meals),
- },
- "metadata": map[string]interface{}{
- "errors": data.Errors,
- },
- }
-
- respondJSON(w, 200, response)
-}
-```
-
-### Router Setup
-```go
-// cmd/dashboard/main.go
-func main() {
- // ... existing setup ...
-
- // AI Agent routes (protected by auth middleware)
- if cfg.AIAgentAPIKey != "" {
- aiRouter := chi.NewRouter()
- aiRouter.Use(middleware.AIAuthMiddleware(cfg.AIAgentAPIKey))
-
- r.Route("/api/ai", func(r chi.Router) {
- r.Use(middleware.AIAuthMiddleware(cfg.AIAgentAPIKey))
- r.Get("/dashboard", h.HandleAIDashboard)
- r.Get("/boards/{boardID}/cards", h.HandleAIBoardCards)
- r.Get("/tasks", h.HandleAITasks)
- // Phase 2: Write operations
- // r.Post("/task", h.HandleAICreateTask)
- })
- }
-}
-```
-
-## Security Considerations
-
-1. **Key Storage**: Never commit API key to git
-2. **Key Rotation**: Easy to change in .env file
-3. **Rate Limiting**: Consider adding rate limits per key
-4. **HTTPS Only**: Use HTTPS in production
-5. **Logging**: Log API key usage (last 4 chars only)
-
-## Configuration
-
-### .env.example
-```bash
-# AI Agent Access
-AI_AGENT_API_KEY=sk-ai-1234567890abcdef...
-# Generate with: openssl rand -hex 32
-```
-
-### config.go
-```go
-type Config struct {
- // ... existing fields ...
- AIAgentAPIKey string
-}
-
-func Load() (*Config, error) {
- cfg := &Config{
- // ... existing fields ...
- AIAgentAPIKey: os.Getenv("AI_AGENT_API_KEY"),
- }
- return cfg, nil
-}
-```
-
-## Usage from Claude.ai
-
-### With MCP (Model Context Protocol)
-```json
-{
- "mcpServers": {
- "dashboard": {
- "command": "curl",
- "args": [
- "-H", "Authorization: Bearer ${AI_AGENT_API_KEY}",
- "http://localhost:8080/api/ai/dashboard"
- ]
- }
- }
-}
-```
-
-### Direct API Call
-Claude can make HTTP requests directly:
-```
-GET http://localhost:8080/api/ai/dashboard
-Authorization: Bearer sk-ai-1234567890abcdef
-```
-
-## Future Enhancements
-
-1. **Webhook Support**: POST updates to Claude.ai when data changes
-2. **Streaming**: Server-sent events for real-time updates
-3. **Scoped Keys**: Different keys for read vs write access
-4. **GraphQL**: More flexible querying for AI agents
-5. **Natural Language Queries**: `/api/ai/query?q=What's due today?`
-
-## Testing
-
-### Manual Test
-```bash
-# Generate API key
-export AI_KEY=$(openssl rand -hex 32)
-
-# Add to .env
-echo "AI_AGENT_API_KEY=sk-ai-$AI_KEY" >> .env
-
-# Test endpoint
-curl -H "Authorization: Bearer sk-ai-$AI_KEY" \
- http://localhost:8080/api/ai/dashboard | jq .
-```
-
-### Unit Test
-```go
-func TestAIAuthMiddleware(t *testing.T) {
- // Test valid key
- // Test invalid key
- // Test missing header
-}
-```
diff --git a/AI_AGENT_SETUP.md b/AI_AGENT_SETUP.md
deleted file mode 100644
index acf2da4..0000000
--- a/AI_AGENT_SETUP.md
+++ /dev/null
@@ -1,276 +0,0 @@
-# AI Agent Access Setup Guide
-
-## Quick Start
-
-### 1. Generate API Key
-```bash
-# Generate a secure 64-character API key
-openssl rand -hex 32
-```
-
-Example output: `a1b2c3d4e5f6...`
-
-### 2. Add to Environment
-Add to your `.env` file:
-```bash
-AI_AGENT_API_KEY=a1b2c3d4e5f6...
-```
-
-### 3. Start the Dashboard
-```bash
-go run cmd/dashboard/main.go
-```
-
-You should see:
-```
-AI agent access enabled at /api/claude/snapshot
-Starting server on http://localhost:8080
-```
-
-### 4. Test the Endpoint
-```bash
-curl -H "Authorization: Bearer a1b2c3d4e5f6..." \
- http://localhost:8080/api/claude/snapshot | jq .
-```
-
-## Usage from Claude.ai
-
-### Method 1: Direct WebFetch
-When conversing with Claude, share:
-
-**URL:** `http://localhost:8080/api/claude/snapshot`
-**Token:** `a1b2c3d4e5f6...` (share separately, not in chat history)
-
-Claude can then fetch your dashboard:
-```
-User: "What tasks do I have today?"
-
-Claude: [Calls WebFetch with Authorization header]
-Claude: "You have 3 tasks due today:
-1. Review PRs (Work, Priority 4)
-2. Buy groceries (Personal, Priority 1)
-3. Call dentist (Health, Priority 2)"
-```
-
-### Method 2: MCP Server (Advanced)
-If you have Claude Desktop with MCP support:
-
-Add to your MCP config:
-```json
-{
- "mcpServers": {
- "personal-dashboard": {
- "command": "curl",
- "args": [
- "-H",
- "Authorization: Bearer YOUR_TOKEN_HERE",
- "-s",
- "http://localhost:8080/api/claude/snapshot"
- ]
- }
- }
-}
-```
-
-## Response Format
-
-### Successful Response (200 OK)
-```json
-{
- "generated_at": "2026-01-09T15:30:00Z",
- "tasks": {
- "today": [
- {
- "id": "task_123",
- "content": "Review PRs",
- "priority": 4,
- "due": "2026-01-09T17:00:00Z",
- "project": "Work",
- "completed": false
- }
- ],
- "overdue": [],
- "next_7_days": []
- },
- "meals": {
- "today": {
- "date": "2026-01-09",
- "breakfast": "Oatmeal with protein powder",
- "lunch": "Chicken salad",
- "dinner": "Salmon with veggies"
- },
- "next_7_days": []
- },
- "notes": {
- "recent": [
- {
- "title": "Sprint planning notes",
- "modified": "2026-01-09T10:15:00Z",
- "preview": "Discussed Q1 goals and team capacity...",
- "path": "work/sprint-planning.md"
- }
- ]
- },
- "trello_boards": [
- {
- "id": "board_123",
- "name": "Work Projects",
- "cards": [
- {
- "id": "card_456",
- "name": "Complete project proposal",
- "list": "In Progress",
- "due": "2026-01-15T00:00:00Z",
- "url": "https://trello.com/c/card456"
- }
- ]
- }
- ]
-}
-```
-
-### Error Response (401 Unauthorized)
-```json
-{
- "error": "unauthorized",
- "message": "Invalid or missing token"
-}
-```
-
-## Security Best Practices
-
-### ✅ Do
-- Generate a long, random key (32+ bytes)
-- Store key in `.env` file (gitignored)
-- Use HTTPS in production
-- Rotate key periodically (change in `.env`)
-- Share token with Claude separately from URL
-
-### ❌ Don't
-- Commit API key to git
-- Share key in chat history
-- Use simple/guessable keys
-- Expose over HTTP in production
-- Include key in URL query parameters
-
-## Data Limits
-
-The endpoint is optimized for AI consumption:
-
-- **Tasks**: Today + overdue + next 7 days only
-- **Notes**: 10 most recent, 150 char preview each
-- **Meals**: Today + next 7 days
-- **Trello**: All boards and cards
-- **Total Response**: < 100KB (typically 10-50KB)
-
-## Caching
-
-- Responses are cached for 5 minutes
-- Same cache as web dashboard
-- Reduces load on external APIs (Todoist, Trello, etc.)
-
-## Troubleshooting
-
-### "Invalid or missing token"
-- Check `Authorization: Bearer` prefix is included
-- Verify token matches `.env` file exactly
-- No extra spaces or newlines in token
-
-### "AI agent access not configured"
-- Ensure `AI_AGENT_API_KEY` is set in `.env`
-- Restart the server after changing `.env`
-
-### Empty response sections
-- Trello/Todoist/PlanToEat APIs may be down
-- Check dashboard web UI to see if data loads there
-- Review server logs for API errors
-
-## Production Deployment
-
-### With Caddy (Automatic HTTPS)
-```caddyfile
-dashboard.yourdomain.com {
- reverse_proxy localhost:8080
-}
-```
-
-### With nginx
-```nginx
-server {
- listen 443 ssl http2;
- server_name dashboard.yourdomain.com;
-
- ssl_certificate /path/to/cert.pem;
- ssl_certificate_key /path/to/key.pem;
-
- location / {
- proxy_pass http://localhost:8080;
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- }
-}
-```
-
-Then share with Claude:
-- **URL**: `https://dashboard.yourdomain.com/api/claude/snapshot`
-- **Token**: (your API key)
-
-## Example Claude Conversations
-
-### Check Today's Tasks
-```
-User: "What do I need to do today?"
-
-Claude: [Fetches /api/claude/snapshot]
-
-Claude: "Here's what you have for today:
-
-Tasks:
-- Review PRs (Work, Priority 4, due 5pm)
-- Buy groceries (Personal, Priority 1)
-
-Meals:
-- Breakfast: Oatmeal
-- Lunch: Chicken salad
-- Dinner: Salmon with veggies"
-```
-
-### Check Overdue Items
-```
-User: "Do I have anything overdue?"
-
-Claude: [Fetches snapshot]
-
-Claude: "You have 2 overdue tasks:
-1. Submit expense report (Work, was due Jan 5)
-2. Schedule dentist appointment (Health, was due Jan 7)"
-```
-
-### Review Recent Notes
-```
-User: "What notes have I created recently?"
-
-Claude: [Fetches snapshot]
-
-Claude: "Your 3 most recent notes:
-1. Sprint planning notes (modified today at 10:15am)
-2. Customer feedback summary (modified Jan 8)
-3. Architecture decision record (modified Jan 6)"
-```
-
-## Rate Limiting (Future)
-
-Currently no rate limiting. For production use, consider adding:
-- 100 requests/hour per token
-- Implement using middleware
-- Return 429 status when exceeded
-
-## Next Steps
-
-1. ✅ Generate API key
-2. ✅ Add to .env
-3. ✅ Test with curl
-4. ✅ Share URL + token with Claude
-5. ✅ Start conversing!
-
-Enjoy your AI-powered personal dashboard! 🚀
diff --git a/PROJECT_SUMMARY.md b/PROJECT_SUMMARY.md
index de7cd00..daa36eb 100644
--- a/PROJECT_SUMMARY.md
+++ b/PROJECT_SUMMARY.md
@@ -8,8 +8,6 @@ A **unified personal dashboard** that aggregates your productivity data into one
- 📝 **Obsidian notes** (optional)
- 🍽️ **PlanToEat meals** (optional)
-Plus a dedicated **AI agent API** for Claude.ai to access your data.
-
## Current Status: ✅ Phase 1 Complete
### What Works
@@ -20,7 +18,6 @@ Plus a dedicated **AI agent API** for Claude.ai to access your data.
- ✅ Mobile-responsive web UI
- ✅ SQLite caching (5-min TTL)
- ✅ Auto-refresh (5 min)
-- ✅ AI agent endpoint (`/api/claude/snapshot`)
- ✅ Full test coverage (9/9 tests passing)
### Statistics
@@ -70,8 +67,6 @@ http://localhost:8080
- **[QUICKSTART.md](QUICKSTART.md)** - Get running in 5 minutes
- **[SETUP_GUIDE.md](SETUP_GUIDE.md)** - Detailed setup instructions
-- **[AI_AGENT_SETUP.md](AI_AGENT_SETUP.md)** - Claude.ai integration guide
-- **[CLAUDE.md](CLAUDE.md)** - For future Claude Code instances
- **[README.md](README.md)** - Full project documentation
## Architecture
@@ -101,9 +96,6 @@ internal/
│ └── plantoeat.go Meal planning
├── handlers/
│ ├── handlers.go Web handlers
- │ └── ai_handlers.go AI endpoint
- ├── middleware/
- │ └── ai_auth.go Bearer token auth
├── config/config.go Environment config
└── store/sqlite.go Database ops
web/
@@ -121,17 +113,11 @@ web/
- `GET /api/meals` - Meals JSON
- `GET /api/boards` - Trello boards JSON
-### AI Agent
-- `GET /api/claude/snapshot` - AI-optimized JSON
- - Auth: `Authorization: Bearer <token>`
- - Returns: tasks (today/overdue/week), meals (7 days), notes (10 recent), all Trello boards
-
## What's Optional
### Optional Features
- **PlanToEat:** API not publicly available - leave blank
- **Obsidian:** Only if you use Obsidian notes
-- **AI Access:** Only if you want Claude.ai integration
### Required Features
- **Todoist:** Yes - tasks integration
@@ -149,7 +135,6 @@ TRELLO_TOKEN=...
# Optional
OBSIDIAN_VAULT_PATH=/path/to/vault
-AI_AGENT_API_KEY=...
# Server (with defaults)
PORT=8080
@@ -175,23 +160,6 @@ go install github.com/cosmtrek/air@latest
air
```
-## For Claude.ai
-
-### Setup
-1. Generate API key: `openssl rand -hex 32`
-2. Add to `.env`: `AI_AGENT_API_KEY=...`
-3. Share with Claude:
- - URL: `http://localhost:8080/api/claude/snapshot`
- - Token: (your key)
-
-### Usage Examples
-- "What tasks do I have today?"
-- "What's for dinner?"
-- "Show me my overdue items"
-- "What have I been working on?"
-
-Claude fetches your dashboard and answers naturally.
-
## Troubleshooting
### "TODOIST_API_KEY is required"
@@ -213,17 +181,16 @@ TRELLO_TOKEN=something
## What's Next
-### Phase 2 (Future)
-- Create tasks via AI
-- Mark tasks complete
-- Quick note capture
-- Create Trello cards
+### Phase 2 (In Progress)
+- [x] Mark tasks complete
+- [x] Create Trello cards
+- [x] Create Todoist tasks
+- [ ] Quick note capture
### Phase 3 (Future)
- Unified search
- Daily digest
- PWA support
-- Rate limiting for AI
## License
diff --git a/README.md b/README.md
index ae05263..01ddb3a 100644
--- a/README.md
+++ b/README.md
@@ -98,7 +98,6 @@ All configuration is done through environment variables. See `.env.example` for
- `OBSIDIAN_VAULT_PATH`: Path to your Obsidian vault
- `PLANTOEAT_API_KEY`: PlanToEat API key (not publicly available - leave empty)
-- `AI_AGENT_API_KEY`: For Claude.ai access (generate with `openssl rand -hex 32`)
- `PORT`: Server port (default: 8080)
- `CACHE_TTL_MINUTES`: Cache duration (default: 5)
- `DEBUG`: Enable debug logging (default: false)
@@ -166,8 +165,8 @@ go build -o dashboard cmd/dashboard/main.go
- [ ] Responsive UI
### Phase 2: Write Operations
-- [ ] Create Todoist tasks
-- [ ] Mark tasks complete
+- [x] Create Todoist tasks
+- [x] Mark tasks complete
- [ ] Create Obsidian notes
- [ ] Add meals to planner
diff --git a/SECURITY_CHECKLIST.md b/SECURITY_CHECKLIST.md
index 4e63174..46b8cea 100644
--- a/SECURITY_CHECKLIST.md
+++ b/SECURITY_CHECKLIST.md
@@ -2,14 +2,8 @@
## Critical Security Issues (Must Fix Before Production)
-### Authentication & Authorization
-- [ ] **Timing Attack in AI Auth** (15 min)
- - File: `internal/middleware/ai_auth.go:31`
- - Change: Use `crypto/subtle.ConstantTimeCompare()` instead of `!=`
- - Impact: Prevents token brute-forcing
-
### Database Security
-- [ ] **SQL Injection in GetNotes()** (15 min)
+- [x] **SQL Injection in GetNotes()** (15 min)
- File: `internal/store/sqlite.go:208`
- Change: Use parameterized query for LIMIT clause
- Impact: Prevents SQL injection attacks
@@ -30,11 +24,6 @@
- Change: Validate paths stay within vault, skip symlinks
- Impact: Prevents arbitrary file read attacks
-- [ ] **JSON Injection in Error Responses** (15 min)
- - File: `internal/middleware/ai_auth.go:42-45`
- - Change: Use `json.Encoder` instead of string concatenation
- - Impact: Prevents JSON structure manipulation
-
### Network Security
- [ ] **HTTPS Support** (1 hour)
- File: `cmd/dashboard/main.go:86-94`
@@ -119,16 +108,6 @@
- Impact: Protection against malicious API servers
### Testing
-- [ ] **Add AI Handler Tests** (2 hours)
- - File: `internal/handlers/ai_handlers_test.go` (new)
- - Tests: Task categorization, meal grouping, response size
- - Impact: Better test coverage
-
-- [ ] **Add Middleware Tests** (1 hour)
- - File: `internal/middleware/ai_auth_test.go` (new)
- - Tests: Valid/invalid tokens, missing headers
- - Impact: Better test coverage
-
- [ ] **Add Edge Case Tests** (2 hours)
- Files: Various test files
- Tests: Empty responses, malformed JSON, network errors
@@ -202,22 +181,22 @@
| Priority | Count | Estimated Time |
|----------|-------|----------------|
-| Critical | 6 items | ~4 hours |
+| Critical | 4 items | ~2.5 hours |
| High | 7 items | ~6.5 hours |
-| Medium | 11 items | ~13.5 hours |
+| Medium | 8 items | ~9.5 hours |
| Low | 8 items | ~14 hours |
-| **Total** | **32 items** | **~38 hours** |
+| **Total** | **27 items** | **~32.5 hours** |
### Recommended Sprint 1 (Critical + High Priority)
- **Duration**: 1-2 weeks part-time
-- **Items**: 13 items
-- **Time**: ~10.5 hours
+- **Items**: 11 items
+- **Time**: ~9 hours
- **Focus**: Security hardening and performance
### Recommended Sprint 2 (Medium Priority)
- **Duration**: 1-2 weeks part-time
-- **Items**: 11 items
-- **Time**: ~13.5 hours
+- **Items**: 8 items
+- **Time**: ~9.5 hours
- **Focus**: Code quality and testing
---
@@ -226,18 +205,16 @@
These can be done in small chunks:
-1. ✓ Timing attack fix (15 min)
-2. ✓ SQL injection fix (15 min)
-3. ✓ JSON injection fix (15 min)
-4. ✓ Database permissions (15 min)
-5. ✓ Health check endpoint (15 min)
-6. ✓ Security headers middleware (30 min)
-7. ✓ Database ping check (15 min)
-8. ✓ Check JSON unmarshal errors (30 min)
-9. ✓ Extract constants (30 min)
-10. ✓ Config validation (30 min)
-
-**Total Quick Wins**: ~4 hours, addresses 10 issues
+1. ✓ SQL injection fix (15 min)
+2. ✓ Database permissions (15 min)
+3. ✓ Health check endpoint (15 min)
+4. ✓ Security headers middleware (30 min)
+5. ✓ Database ping check (15 min)
+6. ✓ Check JSON unmarshal errors (30 min)
+7. ✓ Extract constants (30 min)
+8. ✓ Config validation (30 min)
+
+**Total Quick Wins**: ~3.5 hours, addresses 8 issues
---
diff --git a/SESSION_STATE.md b/SESSION_STATE.md
index 3755940..cff3103 100644
--- a/SESSION_STATE.md
+++ b/SESSION_STATE.md
@@ -1,32 +1,19 @@
# Session State
-## Current Phase: Phase 3 - Interactivity & Write Operations
+**Current Phase:** Phase 3: Write Operations & UI Integration
+**Current Step:** Step 6: Trello Tasks Heuristic & Tasks Tab
-**Goal:** Transform the dashboard from a read-only viewer into an interactive tool.
+**Recent Completed Steps:**
+* Phase 3 Step 1: Trello Write Ops (Backend)
+* Phase 3 Step 2: Trello Lists Support
+* Phase 3 Step 3: Trello UI (Boards & Add Card)
+* Phase 3 Step 4: Todoist Write Ops
+* Phase 3 Step 5: Fix Tasks Tab (Identified as needing Heuristic)
-### Progress
-- [x] **Phase 1: Core Infrastructure** (Completed)
-- [x] **Phase 2: Read-Only Dashboard** (Completed)
-- [x] **Phase 2.5: Visual Overhaul** (Completed)
- - [x] Glassmorphism UI
-- [ ] **Phase 3: Interactivity** (In Progress)
- - [x] Step 1: Trello Write Ops (Backend)
- - [x] Step 2: Trello Lists Support
- - [x] Step 3: Trello UI Integration
- - [ ] **Step 4: Todoist Write Ops** (Active)
- - [ ] Step 5: Unified Quick Add
+**Current Focus:**
+Implementing heuristics to extract actionable tasks from Trello boards and displaying them in a unified list on the Tasks tab.
-## Active Task: Todoist Write Operations
-We are implementing `CreateTask` and `CompleteTask` for Todoist, along with the UI integration.
-
-### Immediate Next Steps
-1. Refactor Todoist client for testability.
-2. Implement write operations.
-3. Update handlers and UI.
-
-## Known Issues
-- None currently.
-
-## Context
-- **Project Root:** `//wsl.localhost/Ubuntu-Preview/home/terst/workspace/task-dashboard`
-- **Design System:** Glassmorphism (see `design_system.md`)
+**Next Steps:**
+1. Implement Heuristics in Handlers.
+2. Create Trello Tasks Partial.
+3. Update Tasks Tab Template.
diff --git a/internal/handlers/ai_handlers.go b/internal/handlers/ai_handlers.go
deleted file mode 100644
index 26c945e..0000000
--- a/internal/handlers/ai_handlers.go
+++ /dev/null
@@ -1,273 +0,0 @@
-package handlers
-
-import (
- "encoding/json"
- "log"
- "net/http"
- "time"
-
- "task-dashboard/internal/models"
-)
-
-// AISnapshotResponse matches the exact format requested by the user
-type AISnapshotResponse struct {
- GeneratedAt string `json:"generated_at"`
- Tasks AITasksSection `json:"tasks"`
- Meals AIMealsSection `json:"meals"`
- Notes AINotesSection `json:"notes"`
- TrelloBoards []AITrelloBoard `json:"trello_boards,omitempty"`
-}
-
-type AITasksSection struct {
- Today []AITask `json:"today"`
- Overdue []AITask `json:"overdue"`
- Next7Days []AITask `json:"next_7_days"`
-}
-
-type AITask struct {
- ID string `json:"id"`
- Content string `json:"content"`
- Priority int `json:"priority"`
- Due *string `json:"due,omitempty"`
- Project string `json:"project"`
- Completed bool `json:"completed"`
-}
-
-type AIMealsSection struct {
- Today AIDayMeals `json:"today"`
- Next7Days []AIDayMeals `json:"next_7_days"`
-}
-
-type AIDayMeals struct {
- Date string `json:"date"`
- Breakfast string `json:"breakfast,omitempty"`
- Lunch string `json:"lunch,omitempty"`
- Dinner string `json:"dinner,omitempty"`
- Snack string `json:"snack,omitempty"`
-}
-
-type AINotesSection struct {
- Recent []AINote `json:"recent"`
-}
-
-type AINote struct {
- Title string `json:"title"`
- Modified string `json:"modified"`
- Preview string `json:"preview"`
- Path string `json:"path"`
-}
-
-type AITrelloBoard struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Cards []AITrelloCard `json:"cards"`
-}
-
-type AITrelloCard struct {
- ID string `json:"id"`
- Name string `json:"name"`
- List string `json:"list"`
- Due *string `json:"due,omitempty"`
- URL string `json:"url"`
-}
-
-// HandleAISnapshot returns a complete dashboard snapshot optimized for AI consumption
-func (h *Handler) HandleAISnapshot(w http.ResponseWriter, r *http.Request) {
- ctx := r.Context()
-
- // Fetch all data (with caching)
- data, err := h.aggregateData(ctx, false)
- if err != nil {
- respondJSON(w, http.StatusInternalServerError, map[string]string{
- "error": "server_error",
- "message": "Failed to fetch dashboard data",
- })
- log.Printf("AI snapshot error: %v", err)
- return
- }
-
- // Build AI-optimized response
- response := AISnapshotResponse{
- GeneratedAt: time.Now().UTC().Format(time.RFC3339),
- Tasks: buildAITasksSection(data.Tasks),
- Meals: buildAIMealsSection(data.Meals),
- Notes: buildAINotesSection(data.Notes),
- TrelloBoards: buildAITrelloBoardsSection(data.Boards),
- }
-
- respondJSON(w, http.StatusOK, response)
-}
-
-// buildAITasksSection organizes tasks by time window
-func buildAITasksSection(tasks []models.Task) AITasksSection {
- now := time.Now()
- today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
- next7Days := today.AddDate(0, 0, 7)
-
- section := AITasksSection{
- Today: []AITask{},
- Overdue: []AITask{},
- Next7Days: []AITask{},
- }
-
- for _, task := range tasks {
- if task.Completed {
- continue // Skip completed tasks
- }
-
- aiTask := AITask{
- ID: task.ID,
- Content: task.Content,
- Priority: task.Priority,
- Project: task.ProjectName,
- Completed: task.Completed,
- }
-
- if task.DueDate != nil {
- dueStr := task.DueDate.UTC().Format(time.RFC3339)
- aiTask.Due = &dueStr
-
- taskDay := time.Date(task.DueDate.Year(), task.DueDate.Month(), task.DueDate.Day(), 0, 0, 0, 0, task.DueDate.Location())
-
- if taskDay.Before(today) {
- section.Overdue = append(section.Overdue, aiTask)
- } else if taskDay.Equal(today) {
- section.Today = append(section.Today, aiTask)
- } else if taskDay.Before(next7Days) {
- section.Next7Days = append(section.Next7Days, aiTask)
- }
- }
- }
-
- return section
-}
-
-// buildAIMealsSection organizes meals by day
-func buildAIMealsSection(meals []models.Meal) AIMealsSection {
- now := time.Now()
- today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
- next7Days := today.AddDate(0, 0, 7)
-
- section := AIMealsSection{
- Today: AIDayMeals{Date: today.Format("2006-01-02")},
- Next7Days: []AIDayMeals{},
- }
-
- // Group meals by date
- mealsByDate := make(map[string]*AIDayMeals)
-
- for _, meal := range meals {
- mealDay := time.Date(meal.Date.Year(), meal.Date.Month(), meal.Date.Day(), 0, 0, 0, 0, meal.Date.Location())
-
- if mealDay.Before(today) || mealDay.After(next7Days) {
- continue // Skip meals outside our window
- }
-
- dateStr := mealDay.Format("2006-01-02")
-
- if _, exists := mealsByDate[dateStr]; !exists {
- mealsByDate[dateStr] = &AIDayMeals{Date: dateStr}
- }
-
- dayMeals := mealsByDate[dateStr]
-
- switch meal.MealType {
- case "breakfast":
- dayMeals.Breakfast = meal.RecipeName
- case "lunch":
- dayMeals.Lunch = meal.RecipeName
- case "dinner":
- dayMeals.Dinner = meal.RecipeName
- case "snack":
- dayMeals.Snack = meal.RecipeName
- }
- }
-
- // Assign today's meals
- if todayMeals, exists := mealsByDate[today.Format("2006-01-02")]; exists {
- section.Today = *todayMeals
- }
-
- // Collect next 7 days (excluding today)
- for i := 1; i <= 7; i++ {
- day := today.AddDate(0, 0, i)
- dateStr := day.Format("2006-01-02")
- if dayMeals, exists := mealsByDate[dateStr]; exists {
- section.Next7Days = append(section.Next7Days, *dayMeals)
- }
- }
-
- return section
-}
-
-// buildAINotesSection returns the 10 most recent notes with previews
-func buildAINotesSection(notes []models.Note) AINotesSection {
- section := AINotesSection{
- Recent: []AINote{},
- }
-
- // Limit to 10 most recent
- limit := 10
- if len(notes) < limit {
- limit = len(notes)
- }
-
- for i := 0; i < limit; i++ {
- note := notes[i]
-
- // Limit preview to 150 chars
- preview := note.Content
- if len(preview) > 150 {
- preview = preview[:150] + "..."
- }
-
- section.Recent = append(section.Recent, AINote{
- Title: note.Title,
- Modified: note.Modified.UTC().Format(time.RFC3339),
- Preview: preview,
- Path: note.Path,
- })
- }
-
- return section
-}
-
-// buildAITrelloBoardsSection formats Trello boards for AI
-func buildAITrelloBoardsSection(boards []models.Board) []AITrelloBoard {
- aiBoards := []AITrelloBoard{}
-
- for _, board := range boards {
- aiBoard := AITrelloBoard{
- ID: board.ID,
- Name: board.Name,
- Cards: []AITrelloCard{},
- }
-
- for _, card := range board.Cards {
- aiCard := AITrelloCard{
- ID: card.ID,
- Name: card.Name,
- List: card.ListName,
- URL: card.URL,
- }
-
- if card.DueDate != nil {
- dueStr := card.DueDate.UTC().Format(time.RFC3339)
- aiCard.Due = &dueStr
- }
-
- aiBoard.Cards = append(aiBoard.Cards, aiCard)
- }
-
- aiBoards = append(aiBoards, aiBoard)
- }
-
- return aiBoards
-}
-
-// respondJSON sends a JSON response
-func respondJSON(w http.ResponseWriter, status int, data interface{}) {
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(status)
- json.NewEncoder(w).Encode(data)
-}