diff --git a/quotesdb/.nbd/tickets/ec118c.md b/quotesdb/.nbd/tickets/ec118c.md new file mode 100644 index 0000000..f73d51e --- /dev/null +++ b/quotesdb/.nbd/tickets/ec118c.md @@ -0,0 +1,456 @@ ++++ +title = "quotesdb" +priority = 8 +status = "todo" +ticket_type = "project" +dependencies = [] ++++ +# QuotesDB Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Bootstrap the `quotesdb` service (Rust/Axum backend on Cloudflare Workers + Yew frontend on Cloudflare Pages), then orchestrate parallel agents to plan and implement the work. + +**Architecture:** Three-phase delivery: (0) design ✓, (1) 4 parallel planning agents ticket work in git worktrees, (2) 4 parallel implementation orchestrators dispatch sub-agents per ticket. Each subdomain (api, ui, tests, infra) lives in its own directory under `quotesdb/` as an independent Rust crate. + +**Tech Stack:** Rust, Axum, Tokio, workers-rs, SQLx, Turso (local) / Cloudflare D1 (prod), Yew, Trunk, OpenTofu, Cloudflare Workers + Pages + +--- + +## Context + +A new `quotesdb` service is being added to the vibed mono-repo. The design was finalized in Phase 0. This plan covers: + +0. **Task 0:** Create nbd project ticket `quotesdb` (stop after this — user will trigger the rest) +1. **Task 1:** Bootstrap directory structure and design doc +2. **Task 2:** Phase 1 — dispatch 4 parallel planning agents (each in a git worktree) to create nbd tickets +3. **Task 3:** Merge planning branches +4. **Task 4:** Phase 2 — dispatch 4 parallel implementation orchestrators + +--- + +## Finalized Design (Phase 0 Output) + +### Database Schema + +```sql +CREATE TABLE quotes ( + id TEXT PRIMARY KEY, -- NanoID (~21 chars) + text TEXT NOT NULL, + author TEXT NOT NULL, + source TEXT, -- optional: book, speech, etc. + date TEXT, -- optional: ISO date YYYY-MM-DD + auth_code TEXT NOT NULL, -- 4-word passphrase e.g. ocean-table-purple-storm + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE quote_tags ( + quote_id TEXT NOT NULL REFERENCES quotes(id) ON DELETE CASCADE, + tag TEXT NOT NULL, + PRIMARY KEY (quote_id, tag) +); +``` + +### API Endpoints + +| Method | Path | Description | Auth | +|--------|------|-------------|------| +| GET | `/api/` | OpenAPI spec (JSON) | None | +| GET | `/api/quotes` | List quotes, 10/page. Query: `?page=N&author=X&tag=Y` | None | +| GET | `/api/quotes/random` | Random quote | None | +| GET | `/api/quotes/:id` | Get quote by NanoID | None | +| PUT | `/api/quotes` | Create a quote | None (auth_code optional in body) | +| POST | `/api/quotes/:id` | Update a quote | `X-Auth-Code` header | +| DELETE | `/api/quotes/:id` | Delete a quote | `X-Auth-Code` header | + +**Note:** `GET /api/quotes/random` must be registered BEFORE `GET /api/quotes/:id` in the router. + +### Request/Response Shapes + +**PUT /api/quotes** — Create +```json +// Request body (auth_code optional — generated if omitted) +{ + "text": "...", + "author": "...", + "source": "Stanford Commencement 2005", + "tags": ["motivation"], + "date": "2005-06-12", + "auth_code": "ocean-table-purple-storm" +} + +// Response 201 +{ + "quote": { "id": "V1StG...", "text": "...", "author": "...", ... }, + "auth_code": "ocean-table-purple-storm" +} +``` + +**GET /api/quotes** — List +```json +{ + "quotes": [...], + "page": 1, + "total_pages": 4, + "total_count": 38 +} +``` + +**Error responses:** `{ "error": "message" }` with appropriate HTTP status. + +### Auth + +- No user accounts. Each quote has an `auth_code` (4-word passphrase). +- Auth codes are stored **plaintext**. +- Provided via `X-Auth-Code` header for update/delete. +- On mismatch: `403 Forbidden`. +- Auth code is always returned in the create response. + +### Frontend Pages (Yew) + +| Route | Page | +|-------|------| +| `/` | Home — random quote + "Browse all" link | +| `/browse` | Paginated list with author/tag filter controls | +| `/quotes/:id` | Single quote — view, edit (auth prompt), delete (auth prompt) | +| `/author/:name` | All quotes by an author | +| `/submit` | New quote form | + +--- + +## Task 1: Bootstrap Project Structure + +**Step 1: Create the quotesdb branch** +```bash +git checkout -b quotesdb +``` + +**Step 2: Create directory skeleton** + +``` +quotesdb/ +├── api/ +│ ├── Cargo.toml +│ ├── src/ +│ │ ├── main.rs +│ │ └── tests.rs +│ ├── tests/ +│ ├── docs/ +│ │ ├── PLANNING.md +│ │ └── ARCHITECTURE.md +│ ├── flake.nix +│ └── README.md +├── ui/ +│ ├── Cargo.toml +│ ├── src/ +│ │ ├── main.rs +│ │ └── tests.rs +│ ├── index.html +│ ├── Trunk.toml +│ ├── docs/ +│ │ ├── PLANNING.md +│ │ └── ARCHITECTURE.md +│ ├── flake.nix +│ └── README.md +├── tests/ +│ ├── Cargo.toml +│ ├── tests/ +│ ├── docs/ +│ │ └── PLANNING.md +│ └── README.md +├── infra/ +│ └── main.tf +└── docs/ + ├── PLANNING.md + ├── ARCHITECTURE.md + └── plans/ + └── 2026-02-27-quotesdb-design.md +``` + +**Step 3: Write design doc** + +Write the finalized design (from this plan's "Finalized Design" section) to `quotesdb/docs/plans/2026-02-27-quotesdb-design.md`. + +**Step 4: Stub Cargo.toml files** + +`quotesdb/api/Cargo.toml`: +```toml +[package] +name = "quotesdb-api" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +common = { path = "../../common" } + +[profile.release] +opt-level = "z" +lto = true +strip = true +codegen-units = 1 +``` + +`quotesdb/ui/Cargo.toml`: +```toml +[package] +name = "quotesdb-ui" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] +common = { path = "../../common" } + +[profile.release] +opt-level = "z" +lto = true +strip = true +codegen-units = 1 +``` + +`quotesdb/tests/Cargo.toml`: +```toml +[package] +name = "quotesdb-tests" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +``` + +**Step 5: Commit skeleton** +```bash +git add quotesdb/ +git commit -m "chore(quotesdb): bootstrap project skeleton and design doc" +``` + +--- + +## Task 2: Phase 1 — Parallel Planning Agents + +> **Required skill:** Use `superpowers:dispatching-parallel-agents` before dispatching. + +Dispatch 4 agents IN PARALLEL (single message, 4 Task tool calls). Each works in a dedicated git worktree on its own branch. Each agent's job is to: + +1. Explore the design doc at `quotesdb/docs/plans/2026-02-27-quotesdb-design.md` +2. Plan their domain thoroughly +3. Create nbd tickets for every piece of work in their domain +4. Commit their tickets on their branch + +The nbd tool is available in the devshell. Tickets live in `.nbd/tickets/`. Use `nbd` to create and manage tickets. + +### Agent 1: api-planner + +**Branch:** `quotesdb/api` +**Directory:** `quotesdb/api/` + +**Prompt:** +> You are a senior Rust backend engineer. Your job is to plan and ticket all backend API work for the `quotesdb` project. +> +> Read the design doc at `quotesdb/docs/plans/2026-02-27-quotesdb-design.md` for full context. +> +> The backend is: +> - Rust, Axum, Tokio +> - Target: Cloudflare Workers (workers-rs crate) +> - Database: SQLx with Turso (local file-based SQLite) for dev, Cloudflare D1 in production +> - Auto-generated OpenAPI spec required +> +> Create one nbd ticket per logical work item. Typical tickets include: +> - Set up Cargo.toml with all dependencies +> - Implement database migrations (schema creation) +> - Implement each API endpoint (list, get, random, create, update, delete) +> - Implement auth code generation (4-word passphrase) +> - Implement NanoID generation for quote IDs +> - Implement pagination logic +> - Implement tag filtering (join with quote_tags) +> - Implement OpenAPI spec generation +> - Write unit tests for each handler +> - Write README.md, PLANNING.md, ARCHITECTURE.md +> +> Work on branch `quotesdb/api` (use git worktrees). Each ticket should have enough detail for a developer to implement it independently. + +### Agent 2: ui-planner + +**Branch:** `quotesdb/ui` +**Directory:** `quotesdb/ui/` + +**Prompt:** +> You are a senior Rust frontend engineer with strong design sensibilities. Your job is to plan and ticket all frontend UI work for the `quotesdb` project. +> +> Read the design doc at `quotesdb/docs/plans/2026-02-27-quotesdb-design.md` for full context. +> +> The frontend is: +> - Rust + Yew compiled to Wasm (wasm32-unknown-unknown) +> - Build tool: Trunk (`trunk serve` for dev) +> - Hosted on Cloudflare Pages +> +> Pages to build: +> - `/` — Home: random quote of the day + "Browse all" link +> - `/browse` — Paginated quote list with author/tag filter controls +> - `/quotes/:id` — Single quote view + edit/delete forms (auth code prompt) +> - `/author/:name` — All quotes by an author +> - `/submit` — New quote submission form +> +> Create one nbd ticket per logical work item. Typical tickets include: +> - Set up Cargo.toml and Trunk.toml with all dependencies +> - Set up Yew app shell and routing +> - Implement each page component +> - Implement API client (fetch calls to backend) +> - Implement auth code prompt/modal +> - Implement pagination component +> - Write README.md, PLANNING.md, ARCHITECTURE.md +> +> Work on branch `quotesdb/ui` (use git worktrees). Each ticket should have enough detail for a developer to implement it independently. + +### Agent 3: qa-planner + +**Branch:** `quotesdb/qa` +**Directory:** `quotesdb/tests/` + +**Prompt:** +> You are a senior QA engineer specializing in Rust integration testing. Your job is to plan and ticket all integration test work for the `quotesdb` project. +> +> Read the design doc at `quotesdb/docs/plans/2026-02-27-quotesdb-design.md` for full context. +> +> Integration tests live in `quotesdb/tests/` as a separate Rust crate. Tests should: +> - Spin up the API server with an in-memory SQLite database +> - Make real HTTP requests to test each endpoint +> - Verify correct behavior for happy paths and error cases +> +> Create one nbd ticket per logical test suite. Typical tickets include: +> - Set up test harness (test server, in-memory DB) +> - Test quote creation (valid body, auth code generation, auth code provided by user) +> - Test quote retrieval (by ID, not found) +> - Test quote listing (pagination, author filter, tag filter) +> - Test random quote endpoint +> - Test quote update (valid auth, wrong auth, not found) +> - Test quote deletion (valid auth, wrong auth, not found) +> - Test OpenAPI spec endpoint +> - Write README.md +> +> Work on branch `quotesdb/qa` (use git worktrees). Each ticket should have enough detail for a developer to implement it independently. + +### Agent 4: infra-planner + +**Branch:** `quotesdb/infra` +**Directory:** `quotesdb/infra/` + +**Prompt:** +> You are a Terraform/OpenTofu expert. Your job is to plan and ticket all infrastructure work for the `quotesdb` project. +> +> Read the design doc at `quotesdb/docs/plans/2026-02-27-quotesdb-design.md` for full context. +> +> Infrastructure uses OpenTofu with the Cloudflare provider. Resources needed: +> - Cloudflare Worker for the backend API +> - Cloudflare D1 database (bound to the worker) +> - Cloudflare Pages project for the frontend +> - Custom domain: `quotes.elijah.run` for the frontend +> +> Create one nbd ticket per logical work item. Typical tickets include: +> - Set up OpenTofu project (providers, state backend) +> - Define Cloudflare Worker resource and build config +> - Define Cloudflare D1 database resource and binding +> - Define Cloudflare Pages project with custom domain +> - Write README.md for infra setup instructions +> +> Work on branch `quotesdb/infra` (use git worktrees). Each ticket should have enough detail for a developer to implement it independently. + +--- + +## Task 3: Merge Planning Branches + +After all 4 Phase 1 agents complete: + +**Step 1: Review tickets from each branch** +```bash +git log quotesdb/api --oneline +git log quotesdb/ui --oneline +git log quotesdb/qa --oneline +git log quotesdb/infra --oneline +``` + +**Step 2: Merge all planning branches into `quotesdb`** +```bash +git checkout quotesdb +git merge quotesdb/api +git merge quotesdb/ui +git merge quotesdb/qa +git merge quotesdb/infra +``` + +**Step 3: Resolve any conflicts, commit** + +--- + +## Task 4: Phase 2 — Parallel Implementation Orchestrators + +> **Required skill:** Use `superpowers:dispatching-parallel-agents` before dispatching. + +Dispatch 4 orchestrator agents IN PARALLEL. Each orchestrator: +1. Lists all tickets for their domain +2. Dispatches a fresh sub-agent per ticket (one at a time — complete one before starting the next) +3. Reviews the sub-agent's work after each ticket +4. Continues to the next ticket + +### Implementation agent instructions (for each domain): + +Each orchestrator should prompt its sub-agents with: +- Full design context from `quotesdb/docs/plans/2026-02-27-quotesdb-design.md` +- The specific ticket to implement +- Validation commands to run: `cargo fmt && cargo check && cargo clippy && cargo test` +- Conventional commit format: `feat(quotesdb-api): ...` + +### Domain orchestrators: + +**api-orchestrator** — works in `quotesdb/api/`, dispatches 1 sub-agent per api ticket +**ui-orchestrator** — works in `quotesdb/ui/`, dispatches 1 sub-agent per ui ticket +**qa-orchestrator** — works in `quotesdb/tests/`, dispatches 1 sub-agent per qa ticket +**infra-orchestrator** — works in `quotesdb/infra/`, dispatches 1 sub-agent per infra ticket + +> **Note:** api and tests must share an API contract. If the api-orchestrator makes endpoint changes, update the design doc and notify the qa-orchestrator. + +--- + +## Verification + +After Phase 2 completes: + +**Backend:** +```bash +cd quotesdb/api +cargo fmt && cargo check && cargo clippy && cargo test +cargo run # starts local server +``` + +**Frontend:** +```bash +cd quotesdb/ui +trunk serve # starts dev server at localhost:8080 +``` + +**Integration tests:** +```bash +cd quotesdb/tests +cargo test +``` + +**Infrastructure:** +```bash +cd quotesdb/infra +tofu plan # dry-run Cloudflare infra +``` + +**Manual smoke test:** +```bash +# Create a quote +curl -X PUT http://localhost:8787/api/quotes \ + -H "Content-Type: application/json" \ + -d '{"text":"Hello world","author":"Test"}' + +# List quotes +curl http://localhost:8787/api/quotes + +# Random quote +curl http://localhost:8787/api/quotes/random +``` \ No newline at end of file