diff --git a/docs/superpowers/specs/2026-03-27-scheduled-generation-design.md b/docs/superpowers/specs/2026-03-27-scheduled-generation-design.md new file mode 100644 index 0000000..d744d4c --- /dev/null +++ b/docs/superpowers/specs/2026-03-27-scheduled-generation-design.md @@ -0,0 +1,145 @@ +# Design: Scheduled Synthesis Generation with Email Delivery + +**Date**: 2026-03-27 +**Scope**: Per-theme scheduling (days + time), automatic generation + email to up to 3 addresses + +--- + +## Context + +Users want automated weekly syntheses delivered by email without manually triggering generation. Each theme can have its own schedule (e.g. "AI News every Mon/Wed/Fri at 8am, emailed to 3 people"). + +The app already runs as a Docker daemon (`restart: unless-stopped`), so no external cron or daemon script is needed — the scheduler is an internal background task. + +--- + +## 1. Data Model + +### New table: `theme_schedules` + +```sql +CREATE TABLE theme_schedules ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + theme_id UUID NOT NULL UNIQUE REFERENCES themes(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + enabled BOOLEAN NOT NULL DEFAULT true, + days JSONB NOT NULL DEFAULT '[]', + time_utc TEXT NOT NULL DEFAULT '08:00', + emails JSONB NOT NULL DEFAULT '[]', + last_run_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() +); +CREATE INDEX idx_theme_schedules_enabled ON theme_schedules(enabled) WHERE enabled = true; +``` + +- `theme_id` is UNIQUE — one schedule per theme (1:1) +- `days`: JSON array of day codes `["mon","tue","wed","thu","fri","sat","sun"]` +- `time_utc`: execution time in UTC, format `"HH:MM"` +- `emails`: JSON array of up to 3 email addresses +- `last_run_at`: prevents double runs on the same day + +--- + +## 2. API Endpoints + +- `GET /api/v1/themes/:id/schedule` — get schedule for a theme (null if none) +- `PUT /api/v1/themes/:id/schedule` — upsert schedule +- `DELETE /api/v1/themes/:id/schedule` — remove schedule + +### PUT body + +```json +{ + "enabled": true, + "days": ["mon", "wed", "fri"], + "time_utc": "08:00", + "emails": ["user@example.com", "colleague@example.com"] +} +``` + +### Validation +- `emails`: max 3, each must be valid email format +- `days`: each must be one of `mon, tue, wed, thu, fri, sat, sun` +- `time_utc`: must match `HH:MM` format (00:00-23:59) +- Theme must belong to the authenticated user + +--- + +## 3. Backend Scheduler + +### Background task in `main.rs` + +```rust +// Scheduled synthesis generation (check every 60 seconds) +tokio::spawn(async move { + let mut interval = tokio::time::interval(Duration::from_secs(60)); + loop { + interval.tick().await; + scheduler::run_scheduled_jobs(&state).await; + } +}); +``` + +### `services/scheduler.rs` + +**`run_scheduled_jobs(state)`:** + +1. Get current UTC time and day code (e.g. `"wed"`) +2. Query `theme_schedules` where: + - `enabled = true` + - `days` contains today's day code + - `time_utc <= current_time` + - `last_run_at` is NULL or `last_run_at::date < today` +3. For each due schedule: + - Check `job_store.has_active_job(user_id)` — skip if manual generation in progress + - Load theme settings + - Run `run_generation_inner` with a dummy watch channel (no SSE client) + - On success: send synthesis email to all addresses in `emails` + - Update `last_run_at = now()` + - Log success/failure + +**Error handling:** If generation or email fails, log the error and continue to the next schedule. Don't retry — the schedule will trigger again at the next interval if `last_run_at` wasn't updated. + +**Concurrency:** Jobs run sequentially (one at a time) to avoid overwhelming LLM rate limits. + +--- + +## 4. Frontend — Schedule Section in ThemeManager + +New "Planification" section in the theme page, below sources. + +### UI components: +- **Enable toggle**: checkbox to enable/disable +- **Day selector**: 7 buttons (L M M J V S D), toggle on click, selected = highlighted +- **Time picker**: `` +- **Email list**: up to 3 inputs with add/remove buttons +- **Auto-save**: changes sent immediately via `PUT /api/v1/themes/:id/schedule` + +### Section hidden when no theme is selected. + +--- + +## 5. Files + +### Backend +- **Create:** `backend/migrations/20260327000030_create_theme_schedules.sql` +- **Create:** `backend/src/models/schedule.rs` +- **Create:** `backend/src/db/schedules.rs` +- **Create:** `backend/src/handlers/schedules.rs` +- **Create:** `backend/src/services/scheduler.rs` +- **Modify:** `backend/src/models/mod.rs`, `db/mod.rs`, `handlers/mod.rs`, `services/mod.rs` +- **Modify:** `backend/src/router.rs` — schedule routes +- **Modify:** `backend/src/main.rs` — spawn scheduler task +- **Modify:** `CLAUDE.md` — migration count + +### Frontend +- **Create:** `frontend/src/api/schedules.ts` +- **Create:** `frontend/src/components/settings/SettingsSchedule.tsx` +- **Modify:** `frontend/src/pages/ThemeManager.tsx` +- **Modify:** `frontend/src/i18n/fr.ts` +- **Modify:** `frontend/src/types.ts` + +### Tests +- **Create:** `backend/tests/api_schedules_test.rs` +- **Modify:** `e2e/tests/themes.spec.ts`