docs(quotesdb): add XML tags to all tickets for improved LLM guidance

All 80 nbd tickets updated with structured XML tags:
- Task tickets: <context>, <goal>, <constraints>, <skills>, <validation>, <commit>
- TRIAGE tickets: <context>, <question>, <options>, <resolution>, <commit>
- Project tickets: <context>, <goal>, <skills>, <validation>
- ec118c (root): <skills>, <goal>, <context>, <design>, <phases>, <verification>
- b38032 (done): <goal>, <current-state>, <target-state>, <changes>, <constraints>, <validation>, <summary>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
quotesdb
Elijah Voigt 3 months ago
parent 65b0fdf1e2
commit ec2a4055ca

@ -5,3 +5,41 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "1f5bb5", "6ed325"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
Auth codes are 4-word passphrases (e.g. `ocean-table-purple-storm`) assigned to quotes on creation. They are stored plaintext and used to authorise updates and deletes. The generator must compile and run in both the native host environment and the `wasm32-unknown-unknown` target (workers-rs).
</context>
<goal>
Implement a `generate_auth_code() -> String` function in `src/lib.rs` that produces a random 4-word passphrase. Place it in shared lib code so both the API (generation) and UI (display) can reference the type. The chosen word list crate must support `no_std` or at minimum compile for `wasm32-unknown-unknown`.
</goal>
<constraints>
- Resolve TRIAGE ticket 6ed325 (passphrase crate selection) before choosing the dependency.
- Must compile for both host (`cargo check`) and `wasm32` (`trunk build`).
- Do not use `std::fs` or thread-based RNG in shared code — use a WASM-compatible RNG (e.g. `getrandom` with the `js` feature).
</constraints>
<skills>
Use `superpowers:test-driven-development` — write a unit test that generates 100 codes and verifies each matches `word-word-word-word` format.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`feat(quotesdb): implement WASM-compatible 4-word passphrase auth_code generator`
</commit>

@ -5,3 +5,40 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "93515e", "dc3d2b"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
The five frontend routes are:
- `/` — Home (random quote)
- `/browse` — Paginated quote list
- `/quotes/:id` — Single quote view/edit/delete
- `/author/:name` — All quotes by an author
- `/submit` — New quote form
</context>
<goal>
Implement `src/bin/ui/main.rs` — the Yew app shell and router:
1. Set up `BrowserRouter` (from yew-router)
2. Define a `Route` enum for all five routes
3. Render each route to its respective page component (stubs are fine initially)
4. Mount the app to the `#app` div in `index.html`
</goal>
<constraints>
- Resolve TRIAGE ticket 166996 (Yew/yew-router version) before starting.
- The `Route` enum must be exhaustive — all five routes listed above.
- Page components can be stubs (`html! { <p>"Home"</p> }`) in this ticket; full implementation is in separate tickets.
</constraints>
<validation>
From the `quotesdb/` directory:
```sh
trunk build
```
</validation>
<commit>
`feat(quotesdb): implement Yew app shell and BrowserRouter with all 5 routes`
</commit>

@ -5,3 +5,51 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "a5049d", "d792e2", "03bb91", "175382", "6f2e18"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
`PUT /api/quotes` creates a new quote. The request body is JSON; `auth_code` is optional — if omitted, one is generated. The response is 201 with the full quote object and the `auth_code` (always returned so the user can save it).
Request body: `{ text, author, source?, tags?, date?, auth_code? }`
Response 201: `{ quote: {...}, auth_code: "word-word-word-word" }`
</context>
<goal>
Implement the `PUT /api/quotes` handler:
1. Deserialise and validate the request body (text and author are required)
2. Generate a NanoID for the quote ID
3. Generate an auth_code if not provided in the request
4. INSERT the quote into the `quotes` table
5. INSERT any tags into `quote_tags`
6. Return 201 with the created quote and auth_code
</goal>
<constraints>
- Return 422 if `text` or `author` is missing or empty.
- NanoID generation must be WASM-compatible (see TRIAGE ticket 6f2e18).
- Use the shared `generate_auth_code()` function from `src/lib.rs`.
- Tag insertion must use the shared `replace_tags_for_quote()` logic (ticket 175382).
</constraints>
<skills>
Use `superpowers:test-driven-development` — write tests for: auto-generated auth_code, custom auth_code, missing required fields 422.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`feat(quotesdb): implement PUT /api/quotes — create quote with NanoID and auth_code`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
D1 binding chicken-and-egg: the D1 database ID is not known until after `tofu apply`, but the Worker resource needs the D1 ID at plan time. How do we break this circular dependency?
</question>
<options>
1. **Two-phase apply** — apply D1 resource first, capture the output ID, then apply the Worker with the ID. Requires splitting `tofu apply` into two steps.
2. **`data` source lookup** — use a `cloudflare_d1_database` data source to look up an already-existing D1 database by name. Requires D1 to be created manually first or in a prior apply.
3. **OpenTofu `depends_on`** — express the dependency explicitly and let OpenTofu plan the two resources in the correct order. May work if the Cloudflare provider handles the reference correctly.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update the `infra/worker.tf` and `infra/d1.tf` resources with the chosen approach. Update ticket a23489 and d0da0b with any constraints.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — d1-binding-chickenandegg-d1-id-not-known-until-after-apply-b`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
OpenTofu state backend: should the `.tfstate` file be stored locally (gitignored), in Terraform Cloud (free tier), or in Cloudflare R2 (S3-compatible backend)?
</question>
<options>
1. **Local file** — simplest, but state is lost if the machine changes and cannot be shared. Suitable for solo development.
2. **Terraform Cloud** — free tier available, remote state with locking. Requires a Terraform Cloud account.
3. **Cloudflare R2** — S3-compatible, keeps state within Cloudflare ecosystem. Requires an R2 bucket and API credentials.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Set the chosen backend in `infra/terraform.tf`. Update `infra/.gitignore` if using local state. Document the decision in `infra/README.md`.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — opentofu-state-backend-local-file-gitignored-vs-terraform-cl`
</commit>

@ -5,3 +5,38 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "a6bce1"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
</context>
<goal>
Write the three documentation files for the API domain:
1. `README.md` — what the API does, how to run it (`cargo run`), how to test it, license, Claude Code disclaimer
2. `docs/PLANNING.md` — development phases and work log for the API sub-domain
3. `docs/ARCHITECTURE.md` — API component overview: router, handlers, database layer, auth, OpenAPI spec
</goal>
<constraints>
- README must include the dual Apache-2.0 + MIT license notice.
- README must include a disclaimer that the software was written with Claude Code (model: claude-sonnet-4-6).
- ARCHITECTURE.md must describe how the API binary wires together (router → handlers → db layer).
- PLANNING.md must reflect the actual work done (link to ticket IDs where appropriate).
</constraints>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`docs(quotesdb): write api README, PLANNING.md, and ARCHITECTURE.md`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
Auth code storage strategy for the UI: should the auth code be stored in localStorage (persisted across sessions) or kept only in component state (lost on page reload)?
</question>
<options>
1. **Component state only** — auth code is lost on page reload. User must re-enter it each time. Simpler and more secure.
2. **localStorage per quote ID** — store `auth_code_{id}` in localStorage so the user doesn't need to re-enter it for quotes they created. Risk: plaintext in localStorage.
3. **Session storage** — same as localStorage but cleared when the tab closes. Middle ground.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update the `AuthModal` component (ticket f850c6) with the chosen strategy. If localStorage is chosen, implement a clear-on-delete path.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — auth-code-storage-strategy-localstorage-persistence-vs-compo`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
HTTP client for integration tests: should we use reqwest (async, tokio), hyper (low-level), or ureq (synchronous/blocking)?
</question>
<options>
1. **reqwest** — most ergonomic, async, works well with tokio::test. Adds a heavier dependency but is widely used.
2. **hyper** — low-level, minimal dependencies. More verbose.
3. **ureq** — synchronous, no async runtime needed. Simple but requires spawning a background thread to run the server.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Add the chosen crate to `[dev-dependencies]` in `Cargo.toml`. Update ticket 5f5ba0.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — http-client-selection-for-integration-tests-reqwest-vs-hyper`
</commit>

@ -5,3 +5,36 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "93515e"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
</context>
<goal>
Implement a shared `QuoteCard` Yew component (`src/bin/ui/components/quote_card.rs`) that displays:
- Quote text (styled as a blockquote)
- Author name (linked to `/author/:name`)
- Optional source and date
- Tags as clickable chips (linking to `/browse?tag=X`)
This component is reused on the Home, Browse, Author, and Quote Detail pages.
</goal>
<constraints>
- Resolve TRIAGE ticket 5e3e37 (CSS/styling approach) before adding class names.
- Accept a `Quote` struct as a prop (from shared types in `src/lib.rs`).
- Author link must navigate to `/author/:name` using yew-router's `Link` component.
- Tags are optional — render nothing if the quote has no tags.
</constraints>
<validation>
From the `quotesdb/` directory:
```sh
trunk build
```
</validation>
<commit>
`feat(quotesdb): implement shared QuoteCard component`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
Yew version selection: which version of Yew and yew-router should be used, and are they compatible with each other and the Nix dev shell?
</question>
<options>
1. **Yew 0.21 + yew-router 0.18** — latest stable as of early 2026. Check crates.io for current versions.
2. **Yew 0.20** — previous stable, more documentation available.
3. **Check nixpkgs** — the Nix dev shell may pin a specific version via rust-overlay.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Pin the chosen versions in `Cargo.toml`. Update ticket 93515e. Document the version in `docs/ARCHITECTURE.md`.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — yew-version-selection-and-yewrouter-compatibility-021`
</commit>

@ -5,3 +5,45 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "a5049d"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
Each quote can have multiple tags stored in the `quote_tags` join table. Tags are not normalised — they are stored as plain strings per quote. On create/update, all tags for the quote are replaced atomically.
</context>
<goal>
Implement tag fetch and upsert logic used by the API handlers:
1. `fetch_tags_for_quote(pool, quote_id) -> Vec<String>` — SELECT from quote_tags
2. `replace_tags_for_quote(pool, quote_id, tags: &[String])` — DELETE existing, INSERT new tags in a transaction
This logic should live in a `db` or `tags` module and be called from the create and update handlers.
</goal>
<constraints>
- Tag replacement must be atomic (use a transaction).
- Empty `tags` array means "remove all tags" — this is valid.
- Cascade delete on `quote_tags` handles tag cleanup when a quote is deleted — no separate delete-tags step needed.
</constraints>
<skills>
Use `superpowers:test-driven-development` — write unit tests that verify tag insertion, replacement, and empty-tag cases.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`feat(quotesdb): implement tag join logic — fetch and replace tags per quote`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
Cloudflare Workers WASM size limit: the free tier has a 1MB Worker script size limit. A Rust binary compiled for workers-rs may exceed this. Does this project require a paid Workers plan?
</question>
<options>
1. **Paid Workers plan** — removes the 1MB limit ($5/month). Simplest solution.
2. **Optimise binary size** — use `opt-level = "z"`, `lto = true`, `strip = true`, `wasm-opt`, and minimise dependencies. May bring the binary under 1MB.
3. **Split the Worker** — serve static assets from Pages and keep the Worker API-only (fewer dependencies).
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Check the compiled `api` binary size with `trunk build --release` and `ls -lh`. Update the infra plan accordingly.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — cloudflare-workers-wasm-size-limit-free-tier-1mb-limit-may-r`
</commit>

@ -5,3 +5,35 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "04f865", "1e6a09", "0d987f", "fc2f51"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
The Home page is the landing page of the app. It displays a random quote fetched from `GET /api/quotes/random` and a "Browse all" link to `/browse`.
</context>
<goal>
Implement the Home page component (`src/bin/ui/pages/home.rs`):
1. On mount, fetch a random quote from the API via the API client module (ticket 1e6a09)
2. While loading, show a loading indicator
3. On success, render the `QuoteCard` component (ticket 0d987f)
4. On error, render the `ErrorDisplay` component (ticket fc2f51)
5. Render a "Browse all quotes →" link to `/browse`
</goal>
<constraints>
- Use `use_effect_with` (Yew 0.21+) or the equivalent hook to trigger the fetch on mount.
- The random quote endpoint returns 404 if the database is empty — display a friendly "no quotes yet" message in this case.
</constraints>
<validation>
From the `quotesdb/` directory:
```sh
trunk build
```
</validation>
<commit>
`feat(quotesdb): implement Home page — random quote display`
</commit>

@ -5,3 +5,35 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "04f865", "1e6a09", "fc2f51"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
The Submit page (`/submit`) provides a form for creating a new quote. On success, it displays the returned auth code prominently so the user can save it.
</context>
<goal>
Implement the Submit page component (`src/bin/ui/pages/submit.rs`):
1. Render a form with fields: text (textarea), author, source (optional), date (optional), tags (comma-separated input), auth code (optional)
2. On submit, call `PUT /api/quotes` via the API client
3. On 201 success: show a success message and display the returned auth code in a copyable box
4. On error: render `ErrorDisplay` with the error message
</goal>
<constraints>
- The auth code returned must be displayed clearly — it cannot be recovered after the user leaves this page.
- Validate client-side: text and author are required (non-empty) before submitting.
- Parse the tags input by splitting on commas and trimming whitespace.
</constraints>
<validation>
From the `quotesdb/` directory:
```sh
trunk build
```
</validation>
<commit>
`feat(quotesdb): implement Submit page — new quote form with auth code display`
</commit>

@ -5,3 +5,40 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "93515e"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
The API client module provides typed fetch wrappers around all quotesdb-api endpoints. The UI calls these functions from page components rather than making raw fetch calls directly.
</context>
<goal>
Implement `src/bin/ui/api.rs` (or `src/bin/ui/client.rs`) with async functions for each endpoint:
- `list_quotes(page, author, tag) -> Result<ListResponse>`
- `get_quote(id) -> Result<Quote>`
- `get_random_quote() -> Result<Quote>`
- `create_quote(body) -> Result<CreateResponse>`
- `update_quote(id, auth_code, body) -> Result<Quote>`
- `delete_quote(id, auth_code) -> Result<()>`
Each function sets the appropriate headers (including `X-Auth-Code` where needed) and deserialises the response.
</goal>
<constraints>
- Use `gloo::net::http` or `web_sys::fetch` for HTTP requests (not reqwest — not available in WASM).
- Resolve TRIAGE ticket a9534d (CORS and Trunk proxy config) — during `trunk serve`, the API URL may differ.
- All functions must be `async` and return `Result` with a meaningful error type.
- The base URL should be configurable (env var at compile time or from `window.location.origin`).
</constraints>
<validation>
From the `quotesdb/` directory:
```sh
trunk build
```
</validation>
<commit>
`feat(quotesdb): implement typed API client module for all quotesdb-api endpoints`
</commit>

@ -5,3 +5,39 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "a91260"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
</context>
<goal>
Create `Cargo.toml` for the `quotesdb` crate with all API-side dependencies. Include `[[bin]]` entries for both `api` and `ui` binaries, platform-specific dependency sections (`cfg(target_arch = "wasm32")`), dev-dependencies for tests, and the release profile with size optimizations.
</goal>
<constraints>
- `workers-rs` and Axum are API-only — gate them under `[target.'cfg(not(target_arch = "wasm32"))'.dependencies]`
- Yew, wasm-bindgen, and web-sys are UI-only — gate under `[target.'cfg(target_arch = "wasm32")'.dependencies]`
- The `[profile.release]` block must set `opt-level = "z"`, `lto = true`, `strip = true`, `codegen-units = 1`
- Resolve TRIAGE tickets 6ed325 (passphrase crate) and 6f2e18 (NanoID crate) before finalising those dependency choices
</constraints>
<skills>
Use `superpowers:verification-before-completion` after adding dependencies — run `cargo check` to confirm the dependency tree resolves.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`chore(quotesdb): set up Cargo.toml with api and ui dependencies`
</commit>

@ -5,3 +5,25 @@ status = "todo"
ticket_type = "project"
dependencies = []
+++
<context>
This is the sub-project tracking ticket for `quotesdb/infra`. All infrastructure tasks depend on this ticket. The infra domain covers: OpenTofu project setup, Cloudflare Worker, D1 database, Pages project, custom domain, and documentation.
</context>
<goal>
All `quotesdb/infra` tasks are planned, implemented, validated, and closed. `tofu plan` reports no unexpected changes and `tofu apply` provisions the full Cloudflare stack.
</goal>
<skills>
Use `superpowers:dispatching-parallel-agents` when assigning multiple infra tasks to agents in parallel.
Use `superpowers:verification-before-completion` before marking this ticket done.
</skills>
<validation>
Run from the `infra/` directory:
```sh
tofu validate
tofu plan
```
</validation>

@ -5,3 +5,41 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "1f5bb5", "2ec8b1"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
The `GET /api/` endpoint serves the OpenAPI 3.1.0 specification as JSON. This endpoint requires no authentication and is the entry point for API documentation and client generation.
</context>
<goal>
Implement the `GET /api/` handler that returns the OpenAPI spec as `application/json`. The spec can be embedded at compile time using `include_str!("../../../api/openapi.yaml")` (or equivalent path) and parsed/re-serialised as JSON, or generated programmatically.
</goal>
<constraints>
- Resolve TRIAGE ticket 2ec8b1 (OpenAPI spec serving strategy) before choosing compile-time embed vs runtime load.
- The response `Content-Type` must be `application/json`.
- The spec at `api/openapi.yaml` is the source of truth — validate it with `redocly lint api/openapi.yaml` after any changes.
</constraints>
<skills>
Use `superpowers:test-driven-development` — write a test that hits `GET /api/` and asserts the response is valid JSON with an `openapi` key.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`feat(quotesdb): implement GET /api/ to serve OpenAPI spec as JSON`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
Test harness: how do we import and start the quotesdb-api binary in integration tests when it uses workers-rs, which targets the Cloudflare Workers runtime rather than a native Rust binary?
</question>
<options>
1. **Native feature flag** — add a `#[cfg(not(target_env = "worker"))]` branch in `main.rs` that exposes a plain Axum server. Integration tests use this branch (compiled for host target).
2. **Separate test binary** — create a `src/bin/api_test.rs` that is a native Axum server without workers-rs, used only in tests.
3. **Wrangler dev** — run `wrangler dev` in the background and point tests at it. Complex setup, slower CI.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update ticket 9b581f (test harness) and ticket 6e829e (api main.rs) with the chosen approach.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — test-harness-how-to-import-and-start-quotesdbapi-in-tests-wo`
</commit>

@ -5,3 +5,34 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "93515e"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
</context>
<goal>
Implement a shared `Pagination` Yew component (`src/bin/ui/components/pagination.rs`) that renders:
- A "Previous" button (disabled on page 1)
- Current page indicator (e.g. "Page 2 of 5")
- A "Next" button (disabled on the last page)
The component accepts `page`, `total_pages`, and an `on_page_change: Callback<u32>` prop.
</goal>
<constraints>
- Resolve TRIAGE ticket 5e3e37 (CSS/styling approach) before adding class names.
- Do not navigate programmatically — call `on_page_change` and let the parent update the URL or state.
- Render nothing (or a disabled shell) if `total_pages <= 1`.
</constraints>
<validation>
From the `quotesdb/` directory:
```sh
trunk build
```
</validation>
<commit>
`feat(quotesdb): implement shared Pagination component`
</commit>

@ -5,3 +5,42 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "a5049d", "d792e2", "175382"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
`GET /api/quotes/random` returns a single random quote from the database. This endpoint **must be registered before** `GET /api/quotes/:id` in the Axum router, or it will never be reached (Axum matches in registration order and ":id" would match the literal string "random").
</context>
<goal>
Implement the `GET /api/quotes/random` handler that selects a random row from the `quotes` table and returns it with its tags. Return 404 if the database is empty.
</goal>
<constraints>
- **Router ordering is critical** — document the ordering requirement in a comment in `main.rs`.
- Use `ORDER BY RANDOM() LIMIT 1` for SQLite random selection.
- Include the quote's tags in the response.
- Return `404 Not Found` with `{"error": "no quotes available"}` if the table is empty.
</constraints>
<skills>
Use `superpowers:test-driven-development` — write tests for: random quote returned (non-empty DB), 404 when DB is empty.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`feat(quotesdb): implement GET /api/quotes/random`
</commit>

@ -5,3 +5,34 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413", "07feaa"]
+++
<context>
Infrastructure is managed with OpenTofu using the Cloudflare provider. Configuration lives in `infra/`. Resources include a Cloudflare Worker (API), Cloudflare D1 database (bound to the worker), and a Cloudflare Pages project (UI frontend).
</context>
<goal>
Bootstrap the OpenTofu project in `infra/`:
1. Create `infra/providers.tf` — declare the Cloudflare provider with the required version
2. Create `infra/terraform.tf` — configure the OpenTofu backend (resolve TRIAGE ticket 07feaa for state backend choice)
3. Create `infra/.gitignore` — ignore `*.tfstate`, `*.tfstate.backup`, `.terraform/`
4. Run `tofu init` to initialise the provider
</goal>
<constraints>
- Resolve TRIAGE ticket 07feaa (state backend: local file vs Terraform Cloud vs R2) before creating `terraform.tf`.
- The Cloudflare provider requires an API token — document the expected environment variable (`CLOUDFLARE_API_TOKEN`) in a comment in `providers.tf`, do not hardcode it.
- Every `resource` and `data` block must have a comment explaining its purpose (per CLAUDE.md).
</constraints>
<validation>
Run from the `infra/` directory:
```sh
tofu validate
tofu plan
```
</validation>
<commit>
`chore(quotesdb): bootstrap OpenTofu infra project with Cloudflare provider`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
OpenAPI spec serving strategy: should the spec be embedded at compile time (include_str! macro) or loaded at runtime from a file or generated programmatically?
</question>
<options>
1. **Compile-time embed**`include_str!("../../api/openapi.yaml")` bakes the YAML into the binary. Simple, no runtime file I/O needed for Workers.
2. **Runtime load** — read the file at startup. Does not work in Cloudflare Workers (no filesystem).
3. **Programmatic generation** — use a crate like `utoipa` to generate the spec from handler annotations. Most maintainable but adds complexity.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update ticket 28e7d9 (GET /api/ handler) with the chosen approach. If using utoipa, update `Cargo.toml`.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — openapi-spec-serving-strategy-embed-yaml-at-compile-time-vs-`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
Local dev config: should the API use Turso (file-backed SQLite via libsql) or a D1 binding (via wrangler dev) for local development? How is the selection made at runtime?
</question>
<options>
1. **Turso/libsql** — lightweight local SQLite file, no Cloudflare account needed. Connection string via env var. SQLx-compatible.
2. **Wrangler D1 local**`wrangler dev` spins up a local D1 emulator. Closer to production but requires wrangler and a Cloudflare account even locally.
3. **Plain SQLite via sqlx** — use sqlx's SQLite driver with a local file. No Turso dependency needed for dev.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update ticket a5049d (database connection module) and ticket af56a7 (local dev docs) with the chosen strategy.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — local-dev-config-turso-file-sqlite-vs-d1-binding-selection-s`
</commit>

@ -5,3 +5,24 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "1a274d", "1ba523", "5f1112", "b3ef98", "5cdbd9"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
</context>
<goal>
Write the three documentation files for the UI domain:
1. `README.md` — what the UI is, how to run it (`trunk serve`), how to build (`trunk build`), license, Claude Code disclaimer
2. `docs/PLANNING.md` — development phases and work log for the UI sub-domain
3. `docs/ARCHITECTURE.md` — UI component tree overview, routing, API client, WASM compilation notes
</goal>
<constraints>
- README must include the dual Apache-2.0 + MIT license notice.
- README must include a disclaimer that the software was written with Claude Code (model: claude-sonnet-4-6).
- ARCHITECTURE.md must describe the component hierarchy and how the Yew router maps to page components.
</constraints>
<commit>
`docs(quotesdb): write ui README, PLANNING.md, and ARCHITECTURE.md`
</commit>

@ -5,3 +5,41 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f", "9b581f", "05f8ae"]
+++
<context>
Integration tests live in `tests/` and exercise the API binary against a temporary SQLite database. They run with `cargo test` and must not require a running Cloudflare environment. The test harness spawns the API server on a random port and returns the base URL.
</context>
<goal>
Write the `PUT /api/quotes` test suite in `tests/test_create_quote.rs` (or similar). Test cases:
1. Create with auto-generated auth_code — verify 201, quote object returned, auth_code present in response
2. Create with custom auth_code in body — verify the provided code is stored and returned
3. Missing `text` field — verify 422 Unprocessable Entity
4. Missing `author` field — verify 422 Unprocessable Entity
5. Create with tags — verify tags appear in the returned quote
</goal>
<constraints>
- Use the shared test harness from ticket 9b581f.
- Auth code in the response must match the pattern `word-word-word-word`.
- Verify the created quote is retrievable via `GET /api/quotes/:id` after creation.
</constraints>
<skills>
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`test(quotesdb): add PUT /api/quotes test suite — create quote`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
Database migration strategy for Cloudflare Workers: how should the `quotes` and `quote_tags` tables be created? Workers do not have a persistent startup phase like a long-running server.
</question>
<options>
1. **Startup migration** — run `CREATE TABLE IF NOT EXISTS` in the Worker fetch handler before processing the first request. Simple but adds latency to the first request.
2. **`wrangler d1 execute`** — apply the schema separately using the wrangler CLI. No runtime overhead but requires a separate CI step.
3. **SQLx migrate! macro** — embed migrations in the binary and run them at startup. Depends on SQLx compatibility with workers-rs (see TRIAGE e8a330).
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update ticket a5049d (database connection + migrations) with the chosen strategy.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — database-migration-strategy-for-cloudflare-workers-startup-v`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
D1 migrations in OpenTofu: how do we apply the SQL schema to a newly created D1 database? Options are a null_resource local-exec in OpenTofu, a separate wrangler d1 execute step, or a manual migration step.
</question>
<options>
1. **null_resource local-exec** — run `wrangler d1 execute` as a provisioner in OpenTofu. Ties infra and schema together in one `tofu apply`.
2. **Separate wrangler step** — document as a manual step after `tofu apply`. Simpler OpenTofu config, slightly more manual.
3. **API startup migration** — the API runs `CREATE TABLE IF NOT EXISTS` on startup. Works but risks schema drift in production.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update ticket d0da0b (D1 resource), ticket a5049d (migrations module), and ticket 75489a (migration workflow docs) with the chosen strategy.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — d1-migrations-in-opentofu-nullresource-localexec-vs-separate`
</commit>

@ -5,3 +5,37 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "04f865", "1e6a09", "0d987f", "2c5a57", "d3d502", "fc2f51"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
The Browse page displays a paginated list of all quotes with optional author and tag filters.
</context>
<goal>
Implement the Browse page component (`src/bin/ui/pages/browse.rs`):
1. Read `?page`, `?author`, `?tag` from the URL query string
2. Fetch quotes from `GET /api/quotes` with the query parameters
3. Render each quote with the `QuoteCard` component
4. Render the `Pagination` component with prev/next navigation (update URL query params on page change)
5. Render the `TagFilter` component and an author text input for filtering
6. Render `ErrorDisplay` on error
</goal>
<constraints>
- URL query parameters are the source of truth for current page and filters — use yew-router location hooks to read/write them.
- Changing a filter should reset to page 1.
- The author filter is a free-text input (case-insensitive match on the API side).
</constraints>
<validation>
From the `quotesdb/` directory:
```sh
trunk build
```
</validation>
<commit>
`feat(quotesdb): implement Browse page — paginated list with filters`
</commit>

@ -5,3 +5,48 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "a5049d", "d792e2", "175382"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
`POST /api/quotes/:id` performs a partial update of a quote. The caller must provide the correct auth code via the `X-Auth-Code` request header. Only fields present in the request body are updated; absent fields are left unchanged. Optional fields (`source`, `date`) can be explicitly set to `null` to clear them.
</context>
<goal>
Implement the `POST /api/quotes/:id` handler:
1. Extract `:id` from the path
2. Verify the `X-Auth-Code` header matches the stored `auth_code` — return 403 on mismatch
3. Apply a partial UPDATE to the `quotes` row (only update supplied fields)
4. Update `updated_at` timestamp
5. If `tags` is present in the body, replace all tags for the quote
6. Return 200 with the updated quote
</goal>
<constraints>
- Return 404 if the quote ID does not exist.
- Return 403 (not 401) on auth code mismatch; do not reveal whether the ID exists to unauthenticated callers.
- Setting a field to `null` in the request body should clear it (for `source` and `date`).
- `updated_at` must be set to `CURRENT_TIMESTAMP` on every update.
</constraints>
<skills>
Use `superpowers:test-driven-development` — write tests for: valid auth 200, wrong auth 403, not found 404, partial update, null-to-clear.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`feat(quotesdb): implement POST /api/quotes/:id — partial update with auth verification`
</commit>

@ -5,3 +5,41 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "a5049d", "d792e2", "175382"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
`GET /api/quotes/:id` returns a single quote by its NanoID. Returns 404 if no quote with that ID exists.
</context>
<goal>
Implement the `GET /api/quotes/:id` handler that looks up a quote by NanoID, fetches its tags, and returns the full quote JSON. Return 404 if the ID is not found.
</goal>
<constraints>
- Extract the `:id` path parameter using Axum's `Path` extractor.
- Include the quote's tags in the response.
- Return `404 Not Found` with `{"error": "not found"}` if the ID doesn't match any row.
</constraints>
<skills>
Use `superpowers:test-driven-development` — write tests for: 200 with quote object, 404 not found.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`feat(quotesdb): implement GET /api/quotes/:id`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
CSS/styling approach for the Yew Wasm UI: plain CSS (separate .css file), CDN Tailwind (loaded in index.html), or a Wasm-compatible Rust styling crate?
</question>
<options>
1. **Plain CSS** — write a `style.css` file, include it in `index.html`. No build complexity. Simple and portable.
2. **CDN Tailwind** — add Tailwind CDN `<script>` to `index.html`. No build step needed. Larger page load; fine for small apps.
3. **Stylist or yew-style** — Rust crates for CSS-in-Wasm. More idiomatic but less documentation.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update ticket dc3d2b (Trunk.toml + index.html) and all UI component tickets with the chosen CSS class strategy.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — cssstyling-approach-for-wasm-plain-css-cdn-tailwind-or-wasmc`
</commit>

@ -5,3 +5,38 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "04f865", "1e6a09", "0d987f", "f850c6", "fc2f51"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
The Quote Detail page (`/quotes/:id`) shows a single quote. It also provides edit and delete actions, each guarded by the `AuthModal` component that prompts for the auth code.
</context>
<goal>
Implement the Quote Detail page component (`src/bin/ui/pages/quote_detail.rs`):
1. Extract `:id` from the route
2. Fetch the quote from `GET /api/quotes/:id`
3. Render the quote with `QuoteCard`
4. Render Edit and Delete buttons
5. Edit: show `AuthModal`, then show an edit form pre-filled with current values; on submit call `POST /api/quotes/:id`
6. Delete: show `AuthModal`, then call `DELETE /api/quotes/:id`; on success navigate to `/`
7. Show 403 error message on wrong auth code
</goal>
<constraints>
- 404 from the API should display a user-friendly "quote not found" message.
- After successful edit, re-fetch the quote to show updated data.
- After successful delete, navigate to `/browse`.
</constraints>
<validation>
From the `quotesdb/` directory:
```sh
trunk build
```
</validation>
<commit>
`feat(quotesdb): implement Quote Detail page — view, edit, delete with auth`
</commit>

@ -5,3 +5,36 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f", "0d84fa"]
+++
<context>
Integration tests live in `tests/` and exercise the API binary against a temporary SQLite database. They run with `cargo test` and must not require a running Cloudflare environment. The test harness spawns the API server on a random port and returns the base URL.
</context>
<goal>
Add integration test dependencies to `Cargo.toml` under `[dev-dependencies]`. Resolve TRIAGE ticket 0d84fa (HTTP client selection) first, then add the chosen HTTP client, plus `tokio` (test runtime), `serde_json`, and any other test utilities.
</goal>
<constraints>
- Resolve TRIAGE ticket 0d84fa (HTTP client: reqwest vs hyper vs ureq) before adding the dependency.
- Integration tests in `tests/` run on the host target only — dev-dependencies do not need WASM compatibility.
- Use `#[tokio::test]` for async test functions.
</constraints>
<skills>
Use `superpowers:verification-before-completion` — run `cargo check` after adding deps to confirm they resolve.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`chore(quotesdb): set up integration test dependencies in Cargo.toml`
</commit>

@ -5,3 +5,35 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413", "ae886f"]
+++
<context>
Infrastructure is managed with OpenTofu using the Cloudflare provider. Configuration lives in `infra/`. Resources include a Cloudflare Worker (API), Cloudflare D1 database (bound to the worker), and a Cloudflare Pages project (UI frontend).
The frontend is served at the custom domain `quotes.elijah.run`. This requires a DNS record pointing to Cloudflare Pages and a custom domain binding on the Pages project.
</context>
<goal>
Configure the custom domain in `infra/pages.tf` (or `infra/dns.tf`):
1. `cloudflare_pages_domain` resource — binds `quotes.elijah.run` to the Pages project
2. `cloudflare_record` resource — DNS CNAME record pointing `quotes` → the Pages `*.pages.dev` domain
Every block must have a comment.
</goal>
<constraints>
- The Cloudflare zone ID for `elijah.run` must be provided as a variable or looked up via a `data` source.
- SSL is handled automatically by Cloudflare — no certificate resources needed.
</constraints>
<validation>
Run from the `infra/` directory:
```sh
tofu validate
tofu plan
```
</validation>
<commit>
`feat(quotesdb): configure custom domain quotes.elijah.run for Cloudflare Pages`
</commit>

@ -5,3 +5,39 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "1f5bb5"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
</context>
<goal>
Implement `src/bin/api/main.rs` — the Cloudflare Workers entry point and Axum router wiring. Set up the router with all seven API routes in the correct order (`GET /api/quotes/random` before `GET /api/quotes/:id`), connect the SQLx database pool, and wire in the workers-rs event handler.
</goal>
<constraints>
- Route registration order is critical: `GET /api/quotes/random` must be registered **before** `GET /api/quotes/:id` or the random endpoint will never match.
- Provide a `#[cfg(not(target_env = "worker"))]` conditional for running the API as a plain Axum server during local `cargo run`, alongside the workers-rs event macro for Cloudflare deployment.
- Database pool initialisation must handle both Turso (local) and D1 (worker) connection strings.
</constraints>
<skills>
Use `superpowers:test-driven-development` — write integration test stubs in `tests/` before wiring up handlers.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`feat(quotesdb): wire Axum router and workers-rs entry point in api main.rs`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
4-word passphrase crate selection: which crate generates 4-word passphrases and compiles for wasm32-unknown-unknown without std thread-local RNG or filesystem access?
</question>
<options>
1. **passphrase-wordlist** — small crate, check WASM compatibility.
2. **bip39** — BIP-39 mnemonic words, widely available. Returns 12-word phrases by default; can take first 4 words.
3. **Custom word list** — embed a static word list in `src/lib.rs` and select 4 random words using `getrandom` with the `js` feature for WASM-compatible randomness.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update ticket 03bb91 (auth_code generator) and `Cargo.toml` (ticket 1f5bb5) with the chosen crate.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — 4word-passphrase-crate-selection-for-wasm-target-nostdwasm32`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
NanoID crate WASM compatibility: does the chosen nanoid crate compile for wasm32-unknown-unknown with the workers-rs target? Some crates use thread-local RNG which is not available in WASM.
</question>
<options>
1. **nanoid crate** — check if it supports `getrandom` with `js` feature for WASM.
2. **uuid v4** — widely compatible, UUIDs are slightly longer than NanoIDs but universally supported.
3. **Custom NanoID** — implement NanoID generation using `getrandom` + custom alphabet. ~20 lines of code, no extra dependency.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update ticket 05f8ae (PUT /api/quotes) and `Cargo.toml` (ticket 1f5bb5) with the chosen approach.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — nanoid-crate-wasm-compatibility-with-workersrs-target`
</commit>

@ -5,3 +5,24 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413", "2d1371"]
+++
<context>
Infrastructure is managed with OpenTofu using the Cloudflare provider. Configuration lives in `infra/`. Resources include a Cloudflare Worker (API), Cloudflare D1 database (bound to the worker), and a Cloudflare Pages project (UI frontend).
</context>
<goal>
Write documentation in `infra/README.md` or `docs/SECRETS.md` covering:
1. What secrets/credentials are required (Cloudflare API token, account ID)
2. How to provide them for local OpenTofu runs (environment variables or `.env` file — never commit)
3. How to provide them in CI/CD (GitHub Actions secrets or equivalent)
4. What permissions the Cloudflare API token needs (Workers, D1, Pages, DNS)
</goal>
<constraints>
- Do not commit any actual secrets or tokens — document the variable names only.
- Cross-reference the `.gitignore` for infra secrets files.
</constraints>
<commit>
`docs(quotesdb): document secrets management for Cloudflare credentials`
</commit>

@ -5,3 +5,26 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413", "d0da0b"]
+++
<context>
Infrastructure is managed with OpenTofu using the Cloudflare provider. Configuration lives in `infra/`. Resources include a Cloudflare Worker (API), Cloudflare D1 database (bound to the worker), and a Cloudflare Pages project (UI frontend).
Cloudflare D1 uses SQL migrations. Because the Worker runs in the Cloudflare runtime (not a standard server), migrations must be applied via a separate mechanism (e.g. `wrangler d1 execute` or a startup script). This workflow must be documented.
</context>
<goal>
Document the D1 schema migration workflow in `infra/README.md` or `docs/MIGRATIONS.md`:
1. How to apply the initial schema SQL to D1 (`wrangler d1 execute --file schema.sql`)
2. How to apply incremental migrations
3. How to apply migrations in CI/CD
4. Where the schema SQL file lives (e.g. `infra/schema.sql` or `src/migrations/`)
5. Cross-reference the TRIAGE decision from ticket 5c0c64 (D1 migrations strategy)
</goal>
<constraints>
- Resolve TRIAGE ticket 5c0c64 before writing this doc — the strategy determines the workflow.
</constraints>
<commit>
`docs(quotesdb): document D1 schema migration workflow`
</commit>

@ -5,3 +5,20 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f", "9b581f"]
+++
<context>
Integration tests live in `tests/` and exercise the API binary against a temporary SQLite database. They run with `cargo test` and must not require a running Cloudflare environment. The test harness spawns the API server on a random port and returns the base URL.
</context>
<goal>
Write `tests/README.md` explaining:
1. What the integration test suite covers
2. How to run the tests (`cargo test` from the `quotesdb/` directory)
3. How the test harness works (temporary SQLite DB, port binding, cleanup)
4. Any prerequisites for running tests locally
5. License notice
</goal>
<commit>
`docs(quotesdb): write integration tests README`
</commit>

@ -5,3 +5,40 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f", "9b581f", "28e7d9"]
+++
<context>
Integration tests live in `tests/` and exercise the API binary against a temporary SQLite database. They run with `cargo test` and must not require a running Cloudflare environment. The test harness spawns the API server on a random port and returns the base URL.
</context>
<goal>
Write the `GET /api/` test suite in `tests/test_openapi.rs` (or similar). Assert that the endpoint:
1. Returns HTTP 200
2. Returns `Content-Type: application/json`
3. Returns a body that is valid JSON
4. The JSON object contains an `openapi` key with value `"3.1.0"` (or `"3.0.x"`)
5. The JSON object contains `paths` and `info` keys
</goal>
<constraints>
- Use the shared test harness from ticket 9b581f.
- No authentication required for this endpoint.
</constraints>
<skills>
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`test(quotesdb): add GET /api/ test suite — OpenAPI spec endpoint`
</commit>

@ -5,3 +5,47 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "a5049d", "d792e2", "175382"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
`GET /api/quotes` returns a paginated list of quotes. Query parameters:
- `page` (default 1): page number (1-indexed)
- `author`: case-insensitive author filter (partial match acceptable)
- `tag`: filter to quotes that have this tag
Response shape: `{"quotes": [...], "page": N, "total_pages": N, "total_count": N}`. Page size is 10.
</context>
<goal>
Implement the `GET /api/quotes` handler with pagination, optional author filter, and optional tag filter. Each quote in the response must include its tags (fetched from `quote_tags`). Return the pagination metadata in the response envelope.
</goal>
<constraints>
- Author filter should be case-insensitive (`LIKE lower(?)` or `COLLATE NOCASE`).
- Tag filter requires a JOIN with `quote_tags` — ensure the query doesn't return duplicate quotes when a quote has multiple tags.
- Out-of-range page numbers should return an empty `quotes` array, not a 404.
- Tags must be fetched for each returned quote — either via a JOIN or N+1 queries (N+1 is acceptable for now given small dataset size).
</constraints>
<skills>
Use `superpowers:test-driven-development` — write tests for: page=1 default, page=2 with 15 quotes, author filter, tag filter, combined filters.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`feat(quotesdb): implement GET /api/quotes — paginated list with author and tag filters`
</commit>

@ -5,3 +5,39 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f", "9b581f", "175382"]
+++
<context>
Integration tests live in `tests/` and exercise the API binary against a temporary SQLite database. They run with `cargo test` and must not require a running Cloudflare environment. The test harness spawns the API server on a random port and returns the base URL.
</context>
<goal>
Write the tag operations test suite in `tests/test_tags.rs` (or similar). Test cases:
1. Create quote with tags — verify tags appear in the response
2. List quotes filtered by tag — `?tag=motivation` returns only tagged quotes
3. Update quote replaces all tags — old tags gone, new tags present
4. Delete quote cascades — no orphaned rows in `quote_tags`
</goal>
<constraints>
- Use the shared test harness from ticket 9b581f.
- Seed quotes with distinct tag sets to avoid test interference.
</constraints>
<skills>
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`test(quotesdb): add tag operations test suite`
</commit>

@ -5,3 +5,40 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f", "9b581f", "b20b5a"]
+++
<context>
Integration tests live in `tests/` and exercise the API binary against a temporary SQLite database. They run with `cargo test` and must not require a running Cloudflare environment. The test harness spawns the API server on a random port and returns the base URL.
</context>
<goal>
Write the `DELETE /api/quotes/:id` test suite in `tests/test_delete_quote.rs` (or similar). Test cases:
1. Valid auth — 204 No Content, no response body
2. Wrong auth code — 403 Forbidden
3. Not found ID — 404 Not Found
4. Cascade deletes tags — verify `GET /api/quotes/:id` returns 404 after deletion and tags are gone
</goal>
<constraints>
- Use the shared test harness from ticket 9b581f.
- Create a quote with tags before each test, use its auth_code for valid-auth tests.
- After successful delete, verify the quote is no longer retrievable.
</constraints>
<skills>
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`test(quotesdb): add DELETE /api/quotes/:id test suite`
</commit>

@ -5,3 +5,35 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "166996"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
</context>
<goal>
Add UI-side Yew/Wasm dependencies to `Cargo.toml` under `[target.'cfg(target_arch = "wasm32")'.dependencies]`. Resolve TRIAGE ticket 166996 (Yew version selection) first, then add: `yew`, `yew-router`, `gloo` (timers, fetch), `wasm-bindgen`, `web-sys`, `serde`, `serde_json`, and `wasm-bindgen-futures`.
</goal>
<constraints>
- Resolve TRIAGE ticket 166996 (Yew + yew-router version compatibility) before pinning versions.
- All UI dependencies must be scoped to the wasm32 target — they must not appear in host builds.
- `wasm-bindgen` version must be compatible with the `wasm-bindgen-cli` version in the Nix dev shell.
</constraints>
<skills>
Use `superpowers:verification-before-completion` — run `trunk build` to confirm WASM compilation succeeds.
</skills>
<validation>
From the `quotesdb/` directory:
```sh
cargo fmt
cargo check
trunk build
```
</validation>
<commit>
`chore(quotesdb): set up ui Cargo dependencies for Yew/Wasm`
</commit>

@ -5,3 +5,42 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f", "9b581f", "886bfd"]
+++
<context>
Integration tests live in `tests/` and exercise the API binary against a temporary SQLite database. They run with `cargo test` and must not require a running Cloudflare environment. The test harness spawns the API server on a random port and returns the base URL.
</context>
<goal>
Write the `GET /api/quotes` test suite in `tests/test_list_quotes.rs` (or similar). Test cases:
1. Pagination — page=1 with 15 quotes returns 10, page=2 returns 5; verify total_count, total_pages
2. Out-of-range page — returns empty `quotes` array, not 404
3. Author filter — `?author=X` returns only quotes by that author (case-insensitive)
4. Tag filter — `?tag=X` returns only quotes with that tag
5. Combined filters — author + tag
6. No results — filters that match nothing return empty array with total_count=0
</goal>
<constraints>
- Use the shared test harness from ticket 9b581f.
- Seed multiple quotes before running filter tests.
- Page size is 10 — tests that rely on pagination must seed at least 11 quotes.
</constraints>
<skills>
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`test(quotesdb): add GET /api/quotes test suite — pagination and filters`
</commit>

@ -5,3 +5,43 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f", "5f5ba0", "2ab7a8", "fba598"]
+++
<context>
Integration tests live in `tests/` and exercise the API binary against a temporary SQLite database. They run with `cargo test` and must not require a running Cloudflare environment. The test harness spawns the API server on a random port and returns the base URL.
</context>
<goal>
Implement a test server harness in `tests/helpers.rs` (or similar) that:
1. Creates a temporary SQLite database file (or in-memory DB)
2. Runs migrations to initialise the schema
3. Spawns the `quotesdb-api` binary on a random available port
4. Returns the base URL (e.g. `http://127.0.0.1:PORT`) for use in test functions
5. Cleans up (drops DB, stops server) when the test ends
</goal>
<constraints>
- Resolve TRIAGE ticket 2ab7a8 (workers-rs test binary compatibility) and 33ed29 (local dev config) before implementing.
- Resolve TRIAGE ticket fba598 (test isolation strategy: per-test DB vs transaction rollback) before deciding on isolation approach.
- The harness must be reusable across all test modules — import it as a shared helper.
- Each test must get a clean, isolated database state (no cross-test pollution).
</constraints>
<skills>
Use `superpowers:test-driven-development` — the harness is itself tested by running `cargo test`.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`test(quotesdb): implement test server harness with temp SQLite DB`
</commit>

@ -5,3 +5,38 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413", "2d1371", "d0da0b", "07cafb", "efee79"]
+++
<context>
Infrastructure is managed with OpenTofu using the Cloudflare provider. Configuration lives in `infra/`. Resources include a Cloudflare Worker (API), Cloudflare D1 database (bound to the worker), and a Cloudflare Pages project (UI frontend).
The Cloudflare Worker hosts the `quotesdb-api` binary compiled for the Workers runtime. It is bound to the D1 database and deployed via OpenTofu.
</context>
<goal>
Define the Cloudflare Workers script resource in `infra/worker.tf`:
1. `cloudflare_workers_script` resource (resolve TRIAGE ticket efee79 for correct resource name in current provider version)
2. Set the WASM artifact path (the compiled `api` binary)
3. Bind the D1 database (name must match what workers-rs expects — resolve TRIAGE ticket 07cafb)
4. Set required environment variables
Every block must have a comment.
</goal>
<constraints>
- Resolve TRIAGE ticket efee79 (correct Workers resource name) before writing the resource.
- Resolve TRIAGE ticket 07cafb (D1 chicken-and-egg) before wiring the D1 binding.
- The D1 binding name in the Worker must match the binding name in the workers-rs code.
</constraints>
<validation>
Run from the `infra/` directory:
```sh
tofu validate
tofu plan
```
</validation>
<commit>
`feat(quotesdb): define Cloudflare Workers script resource in OpenTofu`
</commit>

@ -5,3 +5,46 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "1f5bb5", "e8a330", "580e66", "33ed29"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
The database schema consists of two tables:
- `quotes` — stores id (NanoID), text, author, source, date, auth_code, created_at, updated_at
- `quote_tags` — join table for quote-to-tag relationships with cascade delete
</context>
<goal>
Implement `src/bin/api/db.rs` (or equivalent module) providing:
1. A database connection pool constructor (Turso/SQLite locally, D1 in production)
2. SQLx migrations that create the `quotes` and `quote_tags` tables if they don't exist
3. Re-export the pool type for use by handlers
</goal>
<constraints>
- Migration strategy depends on TRIAGE ticket 580e66 (DB migration strategy for Workers) — resolve that first.
- Schema must exactly match the design: NanoID primary key, `auth_code` stored plaintext, optional `source` and `date` fields, cascade delete on `quote_tags`.
- SQLx compatibility with workers-rs is tracked in TRIAGE ticket e8a330 — check that first.
</constraints>
<skills>
Use `superpowers:test-driven-development` — write a test that verifies migration runs and tables exist.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`feat(quotesdb): implement database connection module and SQLx migrations`
</commit>

@ -5,3 +5,41 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "2ce22e", "5dbb7d", "886bfd", "05f8ae", "5d9f5a", "b20b5a", "28e7d9"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
</context>
<goal>
Write unit tests in `src/bin/api/tests.rs` (or a `#[cfg(test)]` module) covering all API handlers, the auth logic, and pagination calculations. Unit tests should test handler logic in isolation using mock or in-memory databases where possible.
</goal>
<constraints>
- Unit tests must run with `cargo test` on the host target — no WASM or browser context required.
- Test auth code matching logic (correct code → pass, wrong code → 403).
- Test pagination edge cases: page 1, last page, out-of-range page (empty array).
- Test tag insertion and replacement (correct rows added/removed).
- Aim for 80%+ code coverage of the API handler module.
</constraints>
<skills>
Use `superpowers:test-driven-development` throughout implementation — unit tests should already exist from prior tickets.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`test(quotesdb): add unit tests for api handlers, auth logic, and pagination`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
workers-rs compatibility with native Rust test binaries: the workers-rs crate targets the Cloudflare Workers runtime, not native Linux/macOS. Can the API code be compiled as a native binary for `cargo test`?
</question>
<options>
1. **Conditional compilation** — use `#[cfg(target_env = "worker")]` to switch between workers-rs entry point and a plain Axum server. The native build is used for testing.
2. **Feature flags** — add a `native` feature that enables the Axum server path. `cargo test` uses `--features native`.
3. **Separate test binary** — integration tests spawn a separately compiled native test server binary.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update ticket 6e829e (api main.rs) and ticket 9b581f (test harness) with the chosen approach.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — workersrs-compatibility-with-native-rust-test-binaries-may-n`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
Local dev CORS and Trunk proxy config: during `trunk serve`, the UI runs on one port and the API on another. How do we handle cross-origin API calls in development?
</question>
<options>
1. **Trunk proxy** — configure Trunk to proxy `/api/*` requests to the API server. No CORS needed. Add to `Trunk.toml`.
2. **CORS middleware on API** — add `tower-http` CORS middleware to the Axum router, allowing localhost origins in development.
3. **Same-origin in production** — in production, both are served from the same Cloudflare account; in dev, use the Trunk proxy.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update ticket dc3d2b (Trunk.toml) and ticket 1e6a09 (API client module) with the chosen approach.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — local-dev-cors-and-trunk-api-proxy-config-trunk-serve-proxyi`
</commit>

@ -5,3 +5,38 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f", "9b581f", "2ce22e"]
+++
<context>
Integration tests live in `tests/` and exercise the API binary against a temporary SQLite database. They run with `cargo test` and must not require a running Cloudflare environment. The test harness spawns the API server on a random port and returns the base URL.
</context>
<goal>
Write the `GET /api/quotes/random` test suite in `tests/test_random_quote.rs` (or similar). Test cases:
1. 200 with a valid quote object when the database has quotes
2. 404 with `{"error": "..."}` when the database is empty
</goal>
<constraints>
- Use the shared test harness from ticket 9b581f.
- Test the 404 case against a fresh empty database (no seeded quotes).
- For the 200 case, seed at least one quote first.
</constraints>
<skills>
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`test(quotesdb): add GET /api/quotes/random test suite`
</commit>

@ -5,3 +5,36 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413", "a23489"]
+++
<context>
Infrastructure is managed with OpenTofu using the Cloudflare provider. Configuration lives in `infra/`. Resources include a Cloudflare Worker (API), Cloudflare D1 database (bound to the worker), and a Cloudflare Pages project (UI frontend).
The API Worker needs a publicly accessible route. This can be the default `*.workers.dev` subdomain or a custom route under `elijah.run`.
</context>
<goal>
Define the Cloudflare Worker route or subdomain in OpenTofu. Options:
1. Use the default `quotesdb.your-account.workers.dev` URL (no DNS record needed)
2. Define a `cloudflare_worker_route` resource for a custom subdomain (e.g. `api.quotes.elijah.run`)
Choose the simpler option first. Document the final API base URL in the project README.
</goal>
<constraints>
- The UI API client must know the API base URL — if a custom route is used, update the UI to point to it.
- If using a custom route, a `cloudflare_record` DNS entry may be needed.
- Every block must have a comment.
</constraints>
<validation>
Run from the `infra/` directory:
```sh
tofu validate
tofu plan
```
</validation>
<commit>
`feat(quotesdb): define Cloudflare Worker route/domain for API`
</commit>

@ -5,3 +5,37 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413", "2d1371", "fc9bfd", "e2bd9b"]
+++
<context>
Infrastructure is managed with OpenTofu using the Cloudflare provider. Configuration lives in `infra/`. Resources include a Cloudflare Worker (API), Cloudflare D1 database (bound to the worker), and a Cloudflare Pages project (UI frontend).
Cloudflare Pages hosts the Yew/Wasm frontend. Resolve TRIAGE ticket fc9bfd (Pages build strategy: CI build vs pre-built artifact upload) before implementing this resource.
</context>
<goal>
Define the Cloudflare Pages project resource in `infra/pages.tf`:
1. `cloudflare_pages_project` resource for the `quotesdb-ui` project
2. Configure the build settings (build command: `trunk build`, output directory: `dist/`)
3. Connect to the git repository or configure for direct artifact upload (per TRIAGE fc9bfd)
Every block must have a comment.
</goal>
<constraints>
- Resolve TRIAGE ticket fc9bfd (Pages build strategy) before choosing git-connected vs artifact upload.
- Resolve TRIAGE ticket e2bd9b (SPA routing — 404 fallback) before finalising Pages config.
- The output directory must match Trunk's `dist/` output.
</constraints>
<validation>
Run from the `infra/` directory:
```sh
tofu validate
tofu plan
```
</validation>
<commit>
`feat(quotesdb): define Cloudflare Pages project resource in OpenTofu`
</commit>

@ -5,3 +5,29 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "33ed29"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
Local development uses Turso (file-backed SQLite) instead of Cloudflare D1. The API reads the database connection string from an environment variable. There may also be `wrangler.toml` configuration needed for `wrangler dev`.
</context>
<goal>
Write documentation (in `docs/PLANNING.md` or a dedicated `docs/LOCAL_DEV.md`) explaining how to set up and run the API locally:
1. How to install/run Turso for a local SQLite file
2. What environment variables to set (database URL, etc.)
3. How to run `cargo run` to start the API server
4. Any `wrangler.toml` configuration needed for `wrangler dev` (if applicable)
5. How the D1 vs Turso selection is made at runtime
</goal>
<constraints>
- Do not commit any `.env` files — document the variables, not the values.
- Cross-reference the TRIAGE ticket 33ed29 decision on Turso vs D1 local selection strategy.
</constraints>
<commit>
`docs(quotesdb): document local dev environment setup for api`
</commit>

@ -5,3 +5,46 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "a5049d", "d792e2"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
`DELETE /api/quotes/:id` permanently deletes a quote. The caller must provide the correct auth code via the `X-Auth-Code` header. On success, returns 204 No Content. The `quote_tags` rows cascade-delete automatically via the foreign key constraint.
</context>
<goal>
Implement the `DELETE /api/quotes/:id` handler:
1. Extract `:id` from the path
2. Verify the `X-Auth-Code` header matches the stored `auth_code` — return 403 on mismatch
3. DELETE the quote row (cascade handles tag deletion)
4. Return 204 No Content on success
</goal>
<constraints>
- Return 404 if the quote ID does not exist.
- Return 403 on auth code mismatch.
- No response body on 204.
- The `quote_tags` cascade delete is handled by the schema — do not manually delete tags.
</constraints>
<skills>
Use `superpowers:test-driven-development` — write tests for: valid auth 204, wrong auth 403, not found 404, verify cascade deletes tags.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`feat(quotesdb): implement DELETE /api/quotes/:id with auth verification`
</commit>

@ -6,14 +6,13 @@ ticket_type = "task"
dependencies = ["ec118c"]
+++
## Goal
<goal>
Collapse the three separate sub-crates (`api/`, `ui/`, `tests/`) into a single Cargo crate rooted at `quotesdb/`. This simplifies the project structure, enables direct code sharing between the api and ui via `src/lib.rs`, and makes `cargo test` run all tests (unit + integration) in a single invocation.
---
## Current State
**Status: done.** This ticket is kept for historical reference.
</goal>
<current-state>
```
quotesdb/
├── api/ # independent crate "quotesdb-api"
@ -38,16 +37,14 @@ quotesdb/
└── docs/
```
**Problems with current structure:**
Problems with the old structure:
- Shared types/logic must go through `../../common` — no quotesdb-specific shared code.
- Running tests requires `cd`ing into each sub-crate separately.
- Three `Cargo.toml` files to maintain, three `cargo fmt/check/clippy` invocations.
- Trunk must be run from `ui/`, not the project root.
</current-state>
---
## Target State
<target-state>
```
quotesdb/
├── Cargo.toml # single crate "quotesdb", default-run = "api"
@ -70,15 +67,13 @@ quotesdb/
└── 2026-02-27-quotesdb-design.md
```
**Developer workflow after refactor (unchanged from user perspective):**
Developer workflow after refactor (unchanged from user perspective):
- `cargo run` — starts the API server (default binary is `api`)
- `trunk serve` — compiles ui to Wasm and serves it
- `cargo test` — runs unit tests + integration tests
</target-state>
---
## Changes Required
<changes>
### 1. Create `quotesdb/Cargo.toml`
Single crate manifest with:
@ -125,7 +120,7 @@ Shared module for code used by both binaries:
- `ui/index.html``quotesdb/index.html`
- `ui/Trunk.toml``quotesdb/Trunk.toml`
Update `Trunk.toml` to explicitly name the ui binary so Trunk compiles the right entrypoint:
Update `Trunk.toml` to explicitly name the ui binary:
```toml
[build]
target = "index.html"
@ -146,7 +141,7 @@ args = ["--bin", "ui"]
Merge per-sub-crate docs into the project-level `docs/` directory:
- `api/docs/PLANNING.md` and `ui/docs/PLANNING.md` → merge into `docs/PLANNING.md`
- `api/docs/ARCHITECTURE.md` and `ui/docs/ARCHITECTURE.md` → merge into `docs/ARCHITECTURE.md`
- `api/README.md` and `ui/README.md` and `tests/README.md` → consolidate into the project-level `quotesdb/README.md`
- `api/README.md` and `ui/README.md` and `tests/README.md` → consolidate into `README.md`
- Delete the now-empty `api/docs/`, `ui/docs/`, `tests/docs/` directories.
### 8. Update `CLAUDE.md`
@ -163,16 +158,14 @@ After moving all contents:
- Delete `api/` directory entirely
- Delete `ui/` directory entirely
- Delete `tests/` old sub-crate directory (but `quotesdb/tests/` integration test files stay)
</changes>
---
## Considerations & Complications
<constraints>
### Compilation targets
The `api` binary compiles for the **host** target during local dev (`cargo run`). The `ui` binary compiles for `wasm32-unknown-unknown` via Trunk. These are separate compilation invocations — they don't conflict in a single Cargo crate.
However, **shared code in `src/lib.rs` must compile for both targets.** Avoid host-only APIs (threading, filesystem) in `lib.rs`. Use `#[cfg(target_arch = "wasm32")]` and `#[cfg(not(target_arch = "wasm32"))]` guards where needed.
**Shared code in `src/lib.rs` must compile for both targets.** Avoid host-only APIs (threading, filesystem) in `lib.rs`. Use `#[cfg(target_arch = "wasm32")]` and `#[cfg(not(target_arch = "wasm32"))]` guards where needed.
### `cargo test` and Wasm
@ -187,17 +180,14 @@ The api uses `workers-rs` for Cloudflare Workers deployment. For local developme
- Use a plain Axum server (conditional compilation: `#[cfg(not(target_env = "worker"))]`), OR
- Use the workers-rs local dev entrypoint.
The existing `api/src/main.rs` stub is empty — the implementation tickets will determine the approach. This ticket should preserve the stub structure and make no assumptions about the final api implementation.
### Dependency conflicts
Some dependencies may not compile for all targets. Use `[target.'cfg(not(target_arch = "wasm32"))'.dependencies]` for api-only deps and `[target.'cfg(target_arch = "wasm32")'.dependencies]` for ui-only deps in `Cargo.toml` where needed. This keeps compile times reasonable and avoids linker conflicts.
---
## Validation
Some dependencies may not compile for all targets. Use `[target.'cfg(not(target_arch = "wasm32"))'.dependencies]` for api-only deps and `[target.'cfg(target_arch = "wasm32")'.dependencies]` for ui-only deps in `Cargo.toml` where needed.
</constraints>
<validation>
From `quotesdb/` root:
```sh
cargo fmt # must pass cleanly
cargo check # must pass for host target
@ -205,13 +195,9 @@ cargo clippy # must pass with no warnings
cargo test # must run and pass all tests (unit + integration)
trunk build # must successfully compile the ui binary to wasm
```
</validation>
The conventional commit for this work: `refactor(quotesdb): collapse to single crate with api and ui binaries`
---
## Files to Create / Move / Delete (Summary)
<summary>
| Action | Path |
|--------|------|
| CREATE | `quotesdb/Cargo.toml` |
@ -231,3 +217,8 @@ The conventional commit for this work: `refactor(quotesdb): collapse to single c
| DELETE | `ui/` (after moving contents) |
| UPDATE | `Trunk.toml` (add `[build.cargo] args = ["--bin", "ui"]`) |
| UPDATE | `quotesdb/CLAUDE.md` (structure, validation paths, agent instructions) |
</summary>
<commit>
`refactor(quotesdb): collapse to single crate with api and ui binaries`
</commit>

@ -5,3 +5,36 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "04f865", "1e6a09", "0d987f", "2c5a57", "d3d502", "fc2f51"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
The Author page (`/author/:name`) shows all quotes by a specific author, paginated.
</context>
<goal>
Implement the Author page component (`src/bin/ui/pages/author.rs`):
1. Extract `:name` from the route
2. Fetch quotes from `GET /api/quotes?author=:name&page=N`
3. Render the author name as a heading
4. Render each quote with `QuoteCard`
5. Render `Pagination` for prev/next navigation
6. Render `ErrorDisplay` on error
</goal>
<constraints>
- Author name in the URL may be URL-encoded — decode it before using in the API call and heading.
- Page is tracked in the URL query string (`?page=N`).
</constraints>
<validation>
From the `quotesdb/` directory:
```sh
trunk build
```
</validation>
<commit>
`feat(quotesdb): implement Author page — paginated quotes by author`
</commit>

@ -5,3 +5,26 @@ status = "todo"
ticket_type = "project"
dependencies = []
+++
<context>
This is the sub-project tracking ticket for `quotesdb/ui`. All UI implementation tasks depend on this ticket. The UI domain covers: Cargo/Trunk setup, Yew app shell, page components, API client, auth modal, pagination, and documentation.
</context>
<goal>
All `quotesdb/ui` tasks are planned, implemented, validated, and closed. The UI compiles to Wasm, `trunk serve` works, and all five pages function correctly.
</goal>
<skills>
Use `superpowers:dispatching-parallel-agents` when assigning multiple UI tasks to agents in parallel.
Use `superpowers:verification-before-completion` before marking this ticket done.
</skills>
<validation>
From the `quotesdb/` directory:
```sh
cargo fmt
cargo check
trunk build
```
</validation>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "project"
dependencies = []
+++
<context>
This is the sub-project tracking ticket for `quotesdb/qa`. All integration test tasks depend on this ticket. The QA domain covers: test Cargo setup, test server harness, and a test suite per API endpoint.
</context>
<goal>
All `quotesdb/qa` tasks are planned, implemented, validated, and closed. `cargo test` runs all integration tests and they pass against a temporary local SQLite database.
</goal>
<skills>
Use `superpowers:dispatching-parallel-agents` when assigning multiple test suites to agents in parallel.
Use `superpowers:verification-before-completion` before marking this ticket done.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>

@ -5,3 +5,36 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413", "2d1371", "5c0c64"]
+++
<context>
Infrastructure is managed with OpenTofu using the Cloudflare provider. Configuration lives in `infra/`. Resources include a Cloudflare Worker (API), Cloudflare D1 database (bound to the worker), and a Cloudflare Pages project (UI frontend).
Cloudflare D1 is the production SQLite-compatible database. It must be provisioned before the Worker can bind to it.
</context>
<goal>
Define the Cloudflare D1 database resource in `infra/d1.tf`:
1. `cloudflare_d1_database` resource for the `quotesdb` database
2. Output the D1 database ID so it can be referenced in the Worker binding
3. Document the binding name (e.g. `DB`) that the Worker expects — this must match the workers-rs binding name in the API code
Every block must have a comment.
</goal>
<constraints>
- Resolve TRIAGE ticket 5c0c64 (D1 migrations in OpenTofu) for how to apply the schema after provisioning.
- Document the D1 database ID output — needed for the Worker binding (ticket 07cafb).
</constraints>
<validation>
Run from the `infra/` directory:
```sh
tofu validate
tofu plan
```
</validation>
<commit>
`feat(quotesdb): define Cloudflare D1 database resource in OpenTofu`
</commit>

@ -5,3 +5,29 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "93515e"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
</context>
<goal>
Implement a shared `TagFilter` Yew component (`src/bin/ui/components/tag_filter.rs`) that renders a text input for filtering by tag. Used on the Browse and Author pages. The component accepts an `on_tag_change: Callback<String>` prop and calls it when the user types or clears the input.
</goal>
<constraints>
- Resolve TRIAGE ticket 5e3e37 (CSS/styling approach) before adding class names.
- Debounce or on-submit? Decide based on API latency — for now, fire on input change.
- Empty string means "no filter" — the parent clears the tag filter when the input is empty.
</constraints>
<validation>
From the `quotesdb/` directory:
```sh
trunk build
```
</validation>
<commit>
`feat(quotesdb): implement shared TagFilter component`
</commit>

@ -5,3 +5,21 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413", "2d1371", "d0da0b", "a23489", "ae886f", "ae6a82"]
+++
<context>
Infrastructure is managed with OpenTofu using the Cloudflare provider. Configuration lives in `infra/`. Resources include a Cloudflare Worker (API), Cloudflare D1 database (bound to the worker), and a Cloudflare Pages project (UI frontend).
</context>
<goal>
Write `infra/README.md` covering:
1. Overview of the infra resources (Worker, D1, Pages, custom domain)
2. Prerequisites (Nix dev shell, Cloudflare account, API token)
3. How to apply the infrastructure (`tofu init && tofu plan && tofu apply`)
4. How to destroy the infrastructure (`tofu destroy`)
5. Required credentials and environment variables
6. Known issues / TRIAGE decisions and their resolutions
</goal>
<commit>
`docs(quotesdb): write infra README with setup, apply, and destroy instructions`
</commit>

@ -5,3 +5,11 @@ status = "done"
ticket_type = "task"
dependencies = ["ec118c"]
+++
<context>
This was the initial bootstrap task for the quotesdb project. It has been completed.
</context>
<goal>
Bootstrap the quotesdb project directory structure and write the initial design doc. **Status: done.**
</goal>

@ -5,3 +5,41 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74", "1f5bb5", "6e829e"]
+++
<context>
The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`.
Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`.
All error responses must use a consistent JSON envelope: `{"error": "message"}`. The API returns errors with appropriate HTTP status codes: 400 Bad Request, 403 Forbidden (wrong auth), 404 Not Found, 422 Unprocessable Entity (validation), 500 Internal Server Error.
</context>
<goal>
Implement an error type and Axum `IntoResponse` impl that serialises errors as `{"error": "..."}` with the correct HTTP status. Use this type consistently across all handlers — no handler should return raw strings or ad-hoc JSON error bodies.
</goal>
<constraints>
- All handler functions must return `Result<impl IntoResponse, ApiError>` (or equivalent).
- The error type should implement `From` conversions for `sqlx::Error`, `serde_json::Error`, and other common error types used in handlers.
- 500 errors must not leak internal details to the client — log the full error server-side, return a generic message to the client.
</constraints>
<skills>
Use `superpowers:test-driven-development` — write unit tests that verify each error variant serialises correctly.
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`feat(quotesdb): implement consistent error envelope type for all API responses`
</commit>

@ -5,3 +5,33 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "a9534d"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
</context>
<goal>
Create `Trunk.toml` and `index.html` in the `quotesdb/` root (not `src/bin/ui/`):
1. `Trunk.toml` must specify `--bin ui` so Trunk compiles the `ui` binary, not the default `api` binary
2. `index.html` is the Trunk HTML entry point — it should `<link>` to compiled CSS and include the Wasm loader script tag
Verify `trunk serve` starts successfully.
</goal>
<constraints>
- Resolve TRIAGE ticket 5e3e37 (CSS approach) before adding stylesheet links.
- The `Trunk.toml` `[build.cargo]` section must set `args = ["--bin", "ui"]`.
- `index.html` must include a `<div id="app">` mount point for the Yew app.
</constraints>
<validation>
From the `quotesdb/` directory:
```sh
trunk build
```
</validation>
<commit>
`chore(quotesdb): set up Trunk.toml and index.html for UI build`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
Cloudflare Pages SPA routing: Yew uses client-side routing. A direct URL to `/browse` or `/quotes/:id` will 404 on Cloudflare Pages unless a fallback is configured.
</question>
<options>
1. **\_redirects file** — add a `_redirects` file to the `dist/` directory: `/* /index.html 200`. Cloudflare Pages supports this.
2. **\_headers file** — configure headers; does not fix routing by itself.
3. **Cloudflare Pages custom 404 page** — set `404.html` to be the same as `index.html`. Less clean.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Add a `_redirects` file (or equivalent) to the Trunk build output. Update ticket ae886f (Pages resource) and ticket dc3d2b (Trunk.toml) if the file needs to be included in the build.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — cloudflare-pages-spa-routing-404-fallback-config-for-clients`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["f3dc74"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
SQLx + workers-rs + Cloudflare D1 compatibility: does SQLx work with the Cloudflare D1 driver in a workers-rs environment? D1 uses a non-standard connection protocol.
</question>
<options>
1. **SQLx with libsql (Turso)** — Turso provides a SQLx-compatible driver. Use for local dev; switch to D1 in production via a different connection string.
2. **workers-rs D1 bindings** — workers-rs provides native D1 bindings that bypass SQLx. Requires rewriting DB access without SQLx macros.
3. **SQLite over HTTP (Turso)** — use Turso in both dev and production (Turso cloud instead of D1). Avoids D1 entirely.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update ticket a5049d (database connection module) and ticket 1f5bb5 (Cargo.toml) with the chosen database access strategy.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — sqlx-workersrs-cloudflare-d1-compatibility-known-issues`
</commit>

@ -5,3 +5,40 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f", "9b581f", "6e829e"]
+++
<context>
Integration tests live in `tests/` and exercise the API binary against a temporary SQLite database. They run with `cargo test` and must not require a running Cloudflare environment. The test harness spawns the API server on a random port and returns the base URL.
The Axum router must register `GET /api/quotes/random` **before** `GET /api/quotes/:id`, otherwise the string `"random"` is matched as a path parameter value and the random endpoint is never reached.
</context>
<goal>
Write a test in `tests/test_router_order.rs` (or similar) that explicitly verifies `GET /api/quotes/random` is not matched as a `:id` parameter:
1. Seed at least one quote
2. Hit `GET /api/quotes/random` — assert 200 (or 200/404 if no quotes), not 404 for ID "random"
3. Confirm the response body is a quote object (if seeded), not an ID-not-found error
</goal>
<constraints>
- Use the shared test harness from ticket 9b581f.
- This test is a regression guard — if the router order is wrong, this test fails loudly.
</constraints>
<skills>
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`test(quotesdb): add router ordering regression test for /api/quotes/random`
</commit>

@ -5,39 +5,34 @@ status = "todo"
ticket_type = "project"
dependencies = ["ce1e4f", "f3dc74", "c3503b", "25c413"]
+++
# QuotesDB Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
> **For Claude:** SUGGESTED SUB-SKILL: Use agent-development for agent dispatch
> **For Claude:** SUGGESTED SUB-SKILL: Use dispatching-parallel-agents for agent dispatch
> **For Claude:** SUGGESTED SUB-SKILL: Use subagent-driven-development for agent dispatch
> **For Claude:** SUGGESTED SUB-SKILL: Use verification-before-completion for verification
> **For Claude:** SUGGESTED SUB-SKILL: Use finishing-a-development-branch for verification
> **For Claude:** SUGGESTED SUB-SKILL: Use using-git-worktrees for parallel work
> **For Claude:** SUGGESTED SUB-SKILL: Use test-driven-development for development workflow
<skills>
REQUIRED: Use `superpowers:executing-plans` to implement this plan task-by-task.
SUGGESTED: Use `superpowers:dispatching-parallel-agents` for parallel agent dispatch.
SUGGESTED: Use `superpowers:subagent-driven-development` for agent dispatch within a session.
SUGGESTED: Use `superpowers:verification-before-completion` before marking any phase complete.
SUGGESTED: Use `superpowers:finishing-a-development-branch` when ready to merge.
SUGGESTED: Use `superpowers:using-git-worktrees` for parallel work in isolated branches.
SUGGESTED: Use `superpowers:test-driven-development` during development.
</skills>
**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.
<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.
All four sub-project tickets (`f3dc74` api, `c3503b` ui, `ce1e4f` qa, `25c413` infra) must be completed and closed before this ticket can close.
</goal>
**Tech Stack:** Rust, Axum, Tokio, workers-rs, SQLx, Turso (local) / Cloudflare D1 (prod), Yew, Trunk, OpenTofu, Cloudflare Workers + Pages
---
## Context
<context>
A new `quotesdb` service is being added to the vibed mono-repo. The design was finalized in Phase 0. This plan covers three phases:
A new `quotesdb` service is being added to the vibed mono-repo. The design was finalized in Phase 0. This plan covers:
1. **Phase 0 (done):** Design finalized — see `docs/plans/2026-02-27-quotesdb-design.md`
2. **Phase 1:** Dispatch 4 parallel planning agents (each in a git worktree) to create nbd tickets per domain
3. **Phase 2:** Dispatch 4 parallel implementation orchestrators per domain
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)
**Tech Stack:** Rust, Axum, Tokio, workers-rs, SQLx, Turso (local) / Cloudflare D1 (prod), Yew, Trunk, OpenTofu, Cloudflare Workers + Pages
</context>
<design>
### Database Schema
```sql
@ -123,333 +118,100 @@ CREATE TABLE quote_tags (
| `/quotes/:id` | Single quote — view, edit (auth prompt), delete (auth prompt) |
| `/author/:name` | All quotes by an author |
| `/submit` | New quote form |
</design>
---
## 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
<phases>
## 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:
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:
1. Explore the design doc at `quotesdb/docs/plans/2026-02-27-quotesdb-design.md`
1. Read the design doc at `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
3. Create nbd tickets for every piece of work (using `nbd create --ftype md --json`)
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/`
**Branch:** `quotesdb/api` | **Directory:** `src/bin/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.
> You are a senior Rust backend engineer. Plan and ticket all backend API work for `quotesdb`.
>
> 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
> The backend is Rust + Axum + Tokio targeting Cloudflare Workers (workers-rs), with SQLx/Turso for dev and Cloudflare D1 in production. An auto-generated OpenAPI spec is 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.
> Create one ticket per: Cargo.toml setup, database migrations, each endpoint handler, auth code generator, NanoID generator, pagination logic, tag filtering, OpenAPI spec generation, unit tests, and documentation files.
### Agent 2: ui-planner
**Branch:** `quotesdb/ui`
**Directory:** `quotesdb/ui/`
**Branch:** `quotesdb/ui` | **Directory:** `src/bin/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
> You are a senior Rust frontend engineer. Plan and ticket all frontend UI work for `quotesdb`.
>
> 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
> The frontend is Rust + Yew compiled to wasm32-unknown-unknown, built by Trunk, hosted on Cloudflare Pages.
>
> Work on branch `quotesdb/ui` (use git worktrees). Each ticket should have enough detail for a developer to implement it independently.
> Create one ticket per: Cargo.toml + Trunk.toml setup, Yew app shell + routing, each page component (Home, Browse, Quote Detail, Author, Submit), API client module, auth code modal, pagination component, and documentation files.
### Agent 3: qa-planner
**Branch:** `quotesdb/qa`
**Directory:** `quotesdb/tests/`
**Branch:** `quotesdb/qa` | **Directory:** `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.
> You are a senior QA engineer. Plan and ticket all integration test work for `quotesdb`.
>
> Read the design doc at `quotesdb/docs/plans/2026-02-27-quotesdb-design.md` for full context.
> Tests live in `tests/` as Cargo integration tests, using a real HTTP client against a locally-spawned API server with a temporary SQLite database.
>
> 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.
> Create one ticket per: dev-dependencies setup, test server harness, and one test suite per endpoint (list, get, random, create, update, delete, tags, router ordering).
### Agent 4: infra-planner
**Branch:** `quotesdb/infra`
**Directory:** `quotesdb/infra/`
**Branch:** `quotesdb/infra` | **Directory:** `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
> You are a Terraform/OpenTofu expert. Plan and ticket all infrastructure work for `quotesdb`.
>
> 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
> Resources needed: Cloudflare Worker (API), Cloudflare D1 database, Cloudflare Pages project, custom domain `quotes.elijah.run`. Use OpenTofu with the Cloudflare provider.
>
> 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**
> Create one ticket per: OpenTofu project init, Workers script resource, D1 database resource, Pages project, custom domain, secrets docs, migration workflow docs, and infra README.
---
## Task 4: Phase 2 — Parallel Implementation Orchestrators
## 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
After all planning branches are merged, dispatch 4 orchestrator agents IN PARALLEL. Each orchestrator:
1. Lists all tickets for their domain (`nbd ready --json`)
2. Dispatches a sub-agent per ticket (one at a time — complete one before starting the next)
3. Reviews the sub-agent's work after each ticket (validate: `cargo fmt && cargo check && cargo clippy && cargo test`)
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:
**Domain orchestrators:**
- **api-orchestrator** — works in `src/bin/api/`, dispatches 1 sub-agent per api ticket
- **ui-orchestrator** — works in `src/bin/ui/`, dispatches 1 sub-agent per ui ticket
- **qa-orchestrator** — works in `tests/`, dispatches 1 sub-agent per qa ticket
- **infra-orchestrator** — works in `infra/`, dispatches 1 sub-agent per infra ticket
**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
Each sub-agent receives: design doc context, the specific ticket body, validation commands, and the conventional commit scope (`feat(quotesdb): ...`).
</phases>
> **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, verify the full stack:
---
## Verification
After Phase 2 completes:
**Backend:**
```bash
cd quotesdb/api
```sh
# From quotesdb/
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
```
# Start API server locally
cargo run
**Integration tests:**
```bash
cd quotesdb/tests
cargo test
```
# Build UI to Wasm
trunk build
**Infrastructure:**
```bash
cd quotesdb/infra
tofu plan # dry-run Cloudflare infra
# Dry-run infra (from infra/)
tofu plan
```
**Manual smoke test:**
```bash
Manual smoke test:
```sh
# Create a quote
curl -X PUT http://localhost:8787/api/quotes \
-H "Content-Type: application/json" \
@ -461,3 +223,4 @@ curl http://localhost:8787/api/quotes
# Random quote
curl http://localhost:8787/api/quotes/random
```
</verification>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
Correct Cloudflare Workers script resource name: the Cloudflare OpenTofu provider has changed resource names across versions. What is the correct resource name in the currently pinned provider version?
</question>
<options>
1. **cloudflare_workers_script** — older resource name, may be deprecated.
2. **cloudflare_worker_script** — alternative naming (singular "worker").
3. **Check provider changelog** — run `tofu providers schema` after `tofu init` to see available resources.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Run `tofu providers schema | jq '.provider_schemas[].resource_schemas | keys | map(select(contains("worker")))'` to find the correct resource name. Update ticket a23489.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — correct-cloudflareworkersscript-resource-name-in-current-clo`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "project"
dependencies = []
+++
<context>
This is the sub-project tracking ticket for `quotesdb/api`. All API implementation tasks depend on this ticket. The API domain covers: Cargo setup, database layer, HTTP handlers, auth, OpenAPI spec, unit tests, and documentation.
</context>
<goal>
All `quotesdb/api` tasks are planned, implemented, validated, and closed. The API binary builds, tests pass, and the OpenAPI spec is accurate.
</goal>
<skills>
Use `superpowers:dispatching-parallel-agents` when assigning multiple API tasks to agents in parallel.
Use `superpowers:verification-before-completion` before marking this ticket done.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>

@ -5,3 +5,35 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "93515e", "0bc655"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
The auth code modal prompts the user to enter their `X-Auth-Code` (4-word passphrase) before allowing edit or delete operations on a quote. The auth code is never stored in the backend session — the UI must send it with each authenticated request.
</context>
<goal>
Implement an `AuthModal` Yew component (`src/bin/ui/components/auth_modal.rs`) that:
1. Shows a dialog overlay prompting for the auth code
2. Accepts `on_submit: Callback<String>` and `on_cancel: Callback<()>` props
3. Renders an `<input type="text">` for the auth code and Submit/Cancel buttons
4. Calls `on_submit` with the entered code when submitted
</goal>
<constraints>
- Resolve TRIAGE ticket 0bc655 (auth code storage strategy) before deciding whether to persist the code in `localStorage` or keep it in component-only state.
- The modal must be accessible (label the input, support keyboard dismiss).
- Do not persist the auth code across sessions unless explicitly decided in ticket 0bc655.
</constraints>
<validation>
From the `quotesdb/` directory:
```sh
trunk build
```
</validation>
<commit>
`feat(quotesdb): implement AuthModal component for auth code prompt`
</commit>

@ -5,3 +5,39 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f", "9b581f", "5dbb7d"]
+++
<context>
Integration tests live in `tests/` and exercise the API binary against a temporary SQLite database. They run with `cargo test` and must not require a running Cloudflare environment. The test harness spawns the API server on a random port and returns the base URL.
</context>
<goal>
Write the `GET /api/quotes/:id` test suite in `tests/test_get_quote.rs` (or similar). Test cases:
1. 200 with full quote object — verify all fields present, tags included
2. 404 Not Found — verify JSON error body `{"error": "..."}`
3. Schema validation — verify the returned quote matches the expected JSON shape
</goal>
<constraints>
- Use the shared test harness from ticket 9b581f.
- Create a quote via `PUT /api/quotes` in the test setup before fetching it.
- Validate the response body against the expected quote schema (all required fields present).
</constraints>
<skills>
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`test(quotesdb): add GET /api/quotes/:id test suite`
</commit>

@ -5,3 +5,41 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f", "9b581f", "5d9f5a"]
+++
<context>
Integration tests live in `tests/` and exercise the API binary against a temporary SQLite database. They run with `cargo test` and must not require a running Cloudflare environment. The test harness spawns the API server on a random port and returns the base URL.
</context>
<goal>
Write the `POST /api/quotes/:id` test suite in `tests/test_update_quote.rs` (or similar). Test cases:
1. Valid auth — 200 with updated quote
2. Wrong auth code — 403 Forbidden
3. Not found ID — 404 Not Found
4. Partial update — only the fields in the body are changed, others unchanged
5. Null to clear optional fields — `{"source": null}` clears the source field
</goal>
<constraints>
- Use the shared test harness from ticket 9b581f.
- Create a quote before each test, use its auth_code for valid-auth tests.
- Verify the update persists by fetching the quote after update.
</constraints>
<skills>
Use `superpowers:verification-before-completion` before closing.
</skills>
<validation>
Run in order from the `quotesdb/` directory:
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
</validation>
<commit>
`test(quotesdb): add POST /api/quotes/:id test suite — update with auth`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["ce1e4f"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
Integration test isolation strategy: should each test get its own temporary database (per-test DB creation/deletion) or should tests share a database and use transaction rollback for cleanup?
</question>
<options>
1. **Per-test temp DB** — each test creates a fresh SQLite file (or in-memory DB) and drops it on cleanup. Maximum isolation, slower due to migration overhead per test.
2. **Shared DB with transaction rollback** — all tests share one DB, each test wraps its operations in a transaction that is rolled back at the end. Faster, but requires the test harness to manage transactions.
3. **Per-test in-memory SQLite** — SQLite `:memory:` database per test. Fast (no file I/O) and fully isolated. May require `--test-threads=1` if the server shares state.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update ticket 9b581f (test harness) with the chosen isolation strategy.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — integration-test-isolation-strategy-pertest-temp-db-vs-share`
</commit>

@ -5,3 +5,29 @@ status = "todo"
ticket_type = "task"
dependencies = ["c3503b", "93515e"]
+++
<context>
The `quotesdb` UI is a Yew (Rust → Wasm) single-page app compiled by Trunk and hosted on Cloudflare Pages. It communicates with the backend API via `fetch` calls. Source lives in `src/bin/ui/`. Run with `trunk serve` for local development.
</context>
<goal>
Implement a shared `ErrorDisplay` Yew component (`src/bin/ui/components/error_display.rs`) that renders a styled error message when an API call fails. Used on all pages. Accepts an `Option<String>` error message prop — renders nothing when `None`.
</goal>
<constraints>
- Resolve TRIAGE ticket 5e3e37 (CSS/styling approach) before adding class names.
- Do not use `panic!` or `unwrap` in component code — all errors should surface via this component.
- Render nothing (not an empty box) when the error is `None`.
</constraints>
<validation>
From the `quotesdb/` directory:
```sh
trunk build
```
</validation>
<commit>
`feat(quotesdb): implement shared ErrorDisplay component`
</commit>

@ -5,3 +5,27 @@ status = "todo"
ticket_type = "task"
dependencies = ["25c413"]
+++
<context>
This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed.
</context>
<question>
Cloudflare Pages build strategy: should the Trunk build run in Cloudflare Pages CI (triggered by git push) or should we upload a pre-built `dist/` artifact via OpenTofu?
</question>
<options>
1. **Cloudflare Pages CI build** — connect the git repo to Cloudflare Pages; Pages runs `trunk build` on each push. Requires Nix/Rust in the Pages build environment (may need custom build image).
2. **Pre-built artifact upload** — build `dist/` locally or in GitHub Actions, then upload via the Cloudflare Pages API or `wrangler pages deploy`. More control, avoids Pages build env limitations.
3. **Wrangler Pages deploy** — use `wrangler pages deploy dist/` in CI after `trunk build`.
</options>
<resolution>
1. Research the options above and choose the best approach for this project.
2. Update ticket ae886f (Pages project resource) with the chosen strategy. Document the CI/CD flow in `infra/README.md`.
3. Mark this ticket done with a note on the chosen approach in the body or a comment.
</resolution>
<commit>
`chore(quotesdb): resolve triage — cloudflare-pages-build-strategy-pages-ci-build-vs-prebuilt-t`
</commit>

Loading…
Cancel
Save