From ec2a4055ca166eb2b4be3b6126d51947a63a74bf Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Mon, 2 Mar 2026 13:15:05 -0800 Subject: [PATCH] docs(quotesdb): add XML tags to all tickets for improved LLM guidance All 80 nbd tickets updated with structured XML tags: - Task tickets: , , , , , - TRIAGE tickets: , , , , - Project tickets: , , , - ec118c (root): , , , , , - b38032 (done): , , , , , , Co-Authored-By: Claude Sonnet 4.6 --- quotesdb/.nbd/tickets/03bb91.md | 38 ++++ quotesdb/.nbd/tickets/04f865.md | 37 ++++ quotesdb/.nbd/tickets/05f8ae.md | 48 ++++ quotesdb/.nbd/tickets/07cafb.md | 24 ++ quotesdb/.nbd/tickets/07feaa.md | 24 ++ quotesdb/.nbd/tickets/08af7a.md | 35 +++ quotesdb/.nbd/tickets/0bc655.md | 24 ++ quotesdb/.nbd/tickets/0d84fa.md | 24 ++ quotesdb/.nbd/tickets/0d987f.md | 33 +++ quotesdb/.nbd/tickets/166996.md | 24 ++ quotesdb/.nbd/tickets/175382.md | 42 ++++ quotesdb/.nbd/tickets/182210.md | 24 ++ quotesdb/.nbd/tickets/1a274d.md | 32 +++ quotesdb/.nbd/tickets/1ba523.md | 32 +++ quotesdb/.nbd/tickets/1e6a09.md | 37 ++++ quotesdb/.nbd/tickets/1f5bb5.md | 36 +++ quotesdb/.nbd/tickets/25c413.md | 22 ++ quotesdb/.nbd/tickets/28e7d9.md | 38 ++++ quotesdb/.nbd/tickets/2ab7a8.md | 24 ++ quotesdb/.nbd/tickets/2c5a57.md | 31 +++ quotesdb/.nbd/tickets/2ce22e.md | 39 ++++ quotesdb/.nbd/tickets/2d1371.md | 31 +++ quotesdb/.nbd/tickets/2ec8b1.md | 24 ++ quotesdb/.nbd/tickets/33ed29.md | 24 ++ quotesdb/.nbd/tickets/372790.md | 21 ++ quotesdb/.nbd/tickets/4a4c26.md | 38 ++++ quotesdb/.nbd/tickets/580e66.md | 24 ++ quotesdb/.nbd/tickets/5c0c64.md | 24 ++ quotesdb/.nbd/tickets/5cdbd9.md | 34 +++ quotesdb/.nbd/tickets/5d9f5a.md | 45 ++++ quotesdb/.nbd/tickets/5dbb7d.md | 38 ++++ quotesdb/.nbd/tickets/5e3e37.md | 24 ++ quotesdb/.nbd/tickets/5f1112.md | 35 +++ quotesdb/.nbd/tickets/5f5ba0.md | 33 +++ quotesdb/.nbd/tickets/657836.md | 32 +++ quotesdb/.nbd/tickets/6e829e.md | 36 +++ quotesdb/.nbd/tickets/6ed325.md | 24 ++ quotesdb/.nbd/tickets/6f2e18.md | 24 ++ quotesdb/.nbd/tickets/71b1d4.md | 21 ++ quotesdb/.nbd/tickets/75489a.md | 23 ++ quotesdb/.nbd/tickets/75e3f0.md | 17 ++ quotesdb/.nbd/tickets/789d0f.md | 37 ++++ quotesdb/.nbd/tickets/886bfd.md | 44 ++++ quotesdb/.nbd/tickets/893eba.md | 36 +++ quotesdb/.nbd/tickets/8c87db.md | 37 ++++ quotesdb/.nbd/tickets/93515e.md | 32 +++ quotesdb/.nbd/tickets/93f1b6.md | 39 ++++ quotesdb/.nbd/tickets/9b581f.md | 40 ++++ quotesdb/.nbd/tickets/a23489.md | 35 +++ quotesdb/.nbd/tickets/a5049d.md | 43 ++++ quotesdb/.nbd/tickets/a6bce1.md | 38 ++++ quotesdb/.nbd/tickets/a91260.md | 24 ++ quotesdb/.nbd/tickets/a9534d.md | 24 ++ quotesdb/.nbd/tickets/aa0eab.md | 35 +++ quotesdb/.nbd/tickets/ae6a82.md | 33 +++ quotesdb/.nbd/tickets/ae886f.md | 34 +++ quotesdb/.nbd/tickets/af56a7.md | 26 +++ quotesdb/.nbd/tickets/b20b5a.md | 43 ++++ quotesdb/.nbd/tickets/b38032.md | 61 +++--- quotesdb/.nbd/tickets/b3ef98.md | 33 +++ quotesdb/.nbd/tickets/c3503b.md | 23 ++ quotesdb/.nbd/tickets/ce1e4f.md | 24 ++ quotesdb/.nbd/tickets/d0da0b.md | 33 +++ quotesdb/.nbd/tickets/d3d502.md | 26 +++ quotesdb/.nbd/tickets/d5839a.md | 18 ++ quotesdb/.nbd/tickets/d6ba23.md | 8 + quotesdb/.nbd/tickets/d792e2.md | 38 ++++ quotesdb/.nbd/tickets/dc3d2b.md | 30 +++ quotesdb/.nbd/tickets/e2bd9b.md | 24 ++ quotesdb/.nbd/tickets/e8a330.md | 24 ++ quotesdb/.nbd/tickets/e8f5cf.md | 37 ++++ quotesdb/.nbd/tickets/ec118c.md | 373 ++++++-------------------------- quotesdb/.nbd/tickets/efee79.md | 24 ++ quotesdb/.nbd/tickets/f3dc74.md | 24 ++ quotesdb/.nbd/tickets/f850c6.md | 32 +++ quotesdb/.nbd/tickets/f9f448.md | 36 +++ quotesdb/.nbd/tickets/fae330.md | 38 ++++ quotesdb/.nbd/tickets/fba598.md | 24 ++ quotesdb/.nbd/tickets/fc2f51.md | 26 +++ quotesdb/.nbd/tickets/fc9bfd.md | 24 ++ 80 files changed, 2480 insertions(+), 340 deletions(-) diff --git a/quotesdb/.nbd/tickets/03bb91.md b/quotesdb/.nbd/tickets/03bb91.md index 84639ba..8773597 100644 --- a/quotesdb/.nbd/tickets/03bb91.md +++ b/quotesdb/.nbd/tickets/03bb91.md @@ -5,3 +5,41 @@ status = "todo" ticket_type = "task" dependencies = ["f3dc74", "1f5bb5", "6ed325"] +++ + + +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). + + + +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`. + + + +- 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). + + + +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. + + + +Run in order from the `quotesdb/` directory: + +```sh +cargo fmt +cargo check +cargo clippy +cargo test +``` + + + +`feat(quotesdb): implement WASM-compatible 4-word passphrase auth_code generator` + diff --git a/quotesdb/.nbd/tickets/04f865.md b/quotesdb/.nbd/tickets/04f865.md index 68d7635..f1db407 100644 --- a/quotesdb/.nbd/tickets/04f865.md +++ b/quotesdb/.nbd/tickets/04f865.md @@ -5,3 +5,40 @@ status = "todo" ticket_type = "task" dependencies = ["c3503b", "93515e", "dc3d2b"] +++ + + +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 + + + +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` + + + +- 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! {

"Home"

}`) in this ticket; full implementation is in separate tickets. +
+ + +From the `quotesdb/` directory: + +```sh +trunk build +``` + + + +`feat(quotesdb): implement Yew app shell and BrowserRouter with all 5 routes` + diff --git a/quotesdb/.nbd/tickets/05f8ae.md b/quotesdb/.nbd/tickets/05f8ae.md index d7b10ee..c5b2f75 100644 --- a/quotesdb/.nbd/tickets/05f8ae.md +++ b/quotesdb/.nbd/tickets/05f8ae.md @@ -5,3 +5,51 @@ status = "todo" ticket_type = "task" dependencies = ["f3dc74", "a5049d", "d792e2", "03bb91", "175382", "6f2e18"] +++ + + +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" }` + + + +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 + + + +- 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). + + + +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. + + + +Run in order from the `quotesdb/` directory: + +```sh +cargo fmt +cargo check +cargo clippy +cargo test +``` + + + +`feat(quotesdb): implement PUT /api/quotes — create quote with NanoID and auth_code` + diff --git a/quotesdb/.nbd/tickets/07cafb.md b/quotesdb/.nbd/tickets/07cafb.md index 60f5047..f7e7c81 100644 --- a/quotesdb/.nbd/tickets/07cafb.md +++ b/quotesdb/.nbd/tickets/07cafb.md @@ -5,3 +5,27 @@ status = "todo" ticket_type = "task" dependencies = ["25c413"] +++ + + +This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed. + + + +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? + + + +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. + + + +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. + + + +`chore(quotesdb): resolve triage — d1-binding-chickenandegg-d1-id-not-known-until-after-apply-b` + diff --git a/quotesdb/.nbd/tickets/07feaa.md b/quotesdb/.nbd/tickets/07feaa.md index 8a16138..a007a16 100644 --- a/quotesdb/.nbd/tickets/07feaa.md +++ b/quotesdb/.nbd/tickets/07feaa.md @@ -5,3 +5,27 @@ status = "todo" ticket_type = "task" dependencies = ["25c413"] +++ + + +This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed. + + + +OpenTofu state backend: should the `.tfstate` file be stored locally (gitignored), in Terraform Cloud (free tier), or in Cloudflare R2 (S3-compatible backend)? + + + +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. + + + +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. + + + +`chore(quotesdb): resolve triage — opentofu-state-backend-local-file-gitignored-vs-terraform-cl` + diff --git a/quotesdb/.nbd/tickets/08af7a.md b/quotesdb/.nbd/tickets/08af7a.md index 49746c9..3b39c45 100644 --- a/quotesdb/.nbd/tickets/08af7a.md +++ b/quotesdb/.nbd/tickets/08af7a.md @@ -5,3 +5,38 @@ status = "todo" ticket_type = "task" dependencies = ["f3dc74", "a6bce1"] +++ + + +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`. + + + +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 + + + +- 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). + + + +Run in order from the `quotesdb/` directory: + +```sh +cargo fmt +cargo check +cargo clippy +cargo test +``` + + + +`docs(quotesdb): write api README, PLANNING.md, and ARCHITECTURE.md` + diff --git a/quotesdb/.nbd/tickets/0bc655.md b/quotesdb/.nbd/tickets/0bc655.md index c0a60c3..fb14d5c 100644 --- a/quotesdb/.nbd/tickets/0bc655.md +++ b/quotesdb/.nbd/tickets/0bc655.md @@ -5,3 +5,27 @@ status = "todo" ticket_type = "task" dependencies = ["c3503b"] +++ + + +This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed. + + + +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)? + + + +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. + + + +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. + + + +`chore(quotesdb): resolve triage — auth-code-storage-strategy-localstorage-persistence-vs-compo` + diff --git a/quotesdb/.nbd/tickets/0d84fa.md b/quotesdb/.nbd/tickets/0d84fa.md index e6d1b90..95eef9f 100644 --- a/quotesdb/.nbd/tickets/0d84fa.md +++ b/quotesdb/.nbd/tickets/0d84fa.md @@ -5,3 +5,27 @@ status = "todo" ticket_type = "task" dependencies = ["ce1e4f"] +++ + + +This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed. + + + +HTTP client for integration tests: should we use reqwest (async, tokio), hyper (low-level), or ureq (synchronous/blocking)? + + + +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. + + + +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. + + + +`chore(quotesdb): resolve triage — http-client-selection-for-integration-tests-reqwest-vs-hyper` + diff --git a/quotesdb/.nbd/tickets/0d987f.md b/quotesdb/.nbd/tickets/0d987f.md index 361b75e..06ebf19 100644 --- a/quotesdb/.nbd/tickets/0d987f.md +++ b/quotesdb/.nbd/tickets/0d987f.md @@ -5,3 +5,36 @@ status = "todo" ticket_type = "task" dependencies = ["c3503b", "93515e"] +++ + + +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. + + + +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. + + + +- 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. + + + +From the `quotesdb/` directory: + +```sh +trunk build +``` + + + +`feat(quotesdb): implement shared QuoteCard component` + diff --git a/quotesdb/.nbd/tickets/166996.md b/quotesdb/.nbd/tickets/166996.md index 6baf316..4e3714b 100644 --- a/quotesdb/.nbd/tickets/166996.md +++ b/quotesdb/.nbd/tickets/166996.md @@ -5,3 +5,27 @@ status = "todo" ticket_type = "task" dependencies = ["c3503b"] +++ + + +This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed. + + + +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? + + + +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. + + + +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. + + + +`chore(quotesdb): resolve triage — yew-version-selection-and-yewrouter-compatibility-021` + diff --git a/quotesdb/.nbd/tickets/175382.md b/quotesdb/.nbd/tickets/175382.md index 59a50ad..a5508f6 100644 --- a/quotesdb/.nbd/tickets/175382.md +++ b/quotesdb/.nbd/tickets/175382.md @@ -5,3 +5,45 @@ status = "todo" ticket_type = "task" dependencies = ["f3dc74", "a5049d"] +++ + + +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. + + + +Implement tag fetch and upsert logic used by the API handlers: +1. `fetch_tags_for_quote(pool, quote_id) -> Vec` — 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. + + + +- 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. + + + +Use `superpowers:test-driven-development` — write unit tests that verify tag insertion, replacement, and empty-tag cases. +Use `superpowers:verification-before-completion` before closing. + + + +Run in order from the `quotesdb/` directory: + +```sh +cargo fmt +cargo check +cargo clippy +cargo test +``` + + + +`feat(quotesdb): implement tag join logic — fetch and replace tags per quote` + diff --git a/quotesdb/.nbd/tickets/182210.md b/quotesdb/.nbd/tickets/182210.md index d041e61..907287d 100644 --- a/quotesdb/.nbd/tickets/182210.md +++ b/quotesdb/.nbd/tickets/182210.md @@ -5,3 +5,27 @@ status = "todo" ticket_type = "task" dependencies = ["f3dc74"] +++ + + +This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed. + + + +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? + + + +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). + + + +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. + + + +`chore(quotesdb): resolve triage — cloudflare-workers-wasm-size-limit-free-tier-1mb-limit-may-r` + diff --git a/quotesdb/.nbd/tickets/1a274d.md b/quotesdb/.nbd/tickets/1a274d.md index be930dd..1942b1f 100644 --- a/quotesdb/.nbd/tickets/1a274d.md +++ b/quotesdb/.nbd/tickets/1a274d.md @@ -5,3 +5,35 @@ status = "todo" ticket_type = "task" dependencies = ["c3503b", "04f865", "1e6a09", "0d987f", "fc2f51"] +++ + + +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`. + + + +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` + + + +- 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. + + + +From the `quotesdb/` directory: + +```sh +trunk build +``` + + + +`feat(quotesdb): implement Home page — random quote display` + diff --git a/quotesdb/.nbd/tickets/1ba523.md b/quotesdb/.nbd/tickets/1ba523.md index 5de02a1..be433a8 100644 --- a/quotesdb/.nbd/tickets/1ba523.md +++ b/quotesdb/.nbd/tickets/1ba523.md @@ -5,3 +5,35 @@ status = "todo" ticket_type = "task" dependencies = ["c3503b", "04f865", "1e6a09", "fc2f51"] +++ + + +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. + + + +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 + + + +- 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. + + + +From the `quotesdb/` directory: + +```sh +trunk build +``` + + + +`feat(quotesdb): implement Submit page — new quote form with auth code display` + diff --git a/quotesdb/.nbd/tickets/1e6a09.md b/quotesdb/.nbd/tickets/1e6a09.md index 2128c41..f8070a0 100644 --- a/quotesdb/.nbd/tickets/1e6a09.md +++ b/quotesdb/.nbd/tickets/1e6a09.md @@ -5,3 +5,40 @@ status = "todo" ticket_type = "task" dependencies = ["c3503b", "93515e"] +++ + + +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. + + + +Implement `src/bin/ui/api.rs` (or `src/bin/ui/client.rs`) with async functions for each endpoint: +- `list_quotes(page, author, tag) -> Result` +- `get_quote(id) -> Result` +- `get_random_quote() -> Result` +- `create_quote(body) -> Result` +- `update_quote(id, auth_code, body) -> Result` +- `delete_quote(id, auth_code) -> Result<()>` + +Each function sets the appropriate headers (including `X-Auth-Code` where needed) and deserialises the response. + + + +- 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`). + + + +From the `quotesdb/` directory: + +```sh +trunk build +``` + + + +`feat(quotesdb): implement typed API client module for all quotesdb-api endpoints` + diff --git a/quotesdb/.nbd/tickets/1f5bb5.md b/quotesdb/.nbd/tickets/1f5bb5.md index 16d2fe3..9775fcd 100644 --- a/quotesdb/.nbd/tickets/1f5bb5.md +++ b/quotesdb/.nbd/tickets/1f5bb5.md @@ -5,3 +5,39 @@ status = "todo" ticket_type = "task" dependencies = ["f3dc74", "a91260"] +++ + + +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`. + + + +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. + + + +- `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 + + + +Use `superpowers:verification-before-completion` after adding dependencies — run `cargo check` to confirm the dependency tree resolves. + + + +Run in order from the `quotesdb/` directory: + +```sh +cargo fmt +cargo check +cargo clippy +cargo test +``` + + + +`chore(quotesdb): set up Cargo.toml with api and ui dependencies` + diff --git a/quotesdb/.nbd/tickets/25c413.md b/quotesdb/.nbd/tickets/25c413.md index 96e51fb..239fc76 100644 --- a/quotesdb/.nbd/tickets/25c413.md +++ b/quotesdb/.nbd/tickets/25c413.md @@ -5,3 +5,25 @@ status = "todo" ticket_type = "project" dependencies = [] +++ + + +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. + + + +All `quotesdb/infra` tasks are planned, implemented, validated, and closed. `tofu plan` reports no unexpected changes and `tofu apply` provisions the full Cloudflare stack. + + + +Use `superpowers:dispatching-parallel-agents` when assigning multiple infra tasks to agents in parallel. +Use `superpowers:verification-before-completion` before marking this ticket done. + + + +Run from the `infra/` directory: + +```sh +tofu validate +tofu plan +``` + diff --git a/quotesdb/.nbd/tickets/28e7d9.md b/quotesdb/.nbd/tickets/28e7d9.md index e82dea8..1c52efb 100644 --- a/quotesdb/.nbd/tickets/28e7d9.md +++ b/quotesdb/.nbd/tickets/28e7d9.md @@ -5,3 +5,41 @@ status = "todo" ticket_type = "task" dependencies = ["f3dc74", "1f5bb5", "2ec8b1"] +++ + + +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. + + + +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. + + + +- 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. + + + +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. + + + +Run in order from the `quotesdb/` directory: + +```sh +cargo fmt +cargo check +cargo clippy +cargo test +``` + + + +`feat(quotesdb): implement GET /api/ to serve OpenAPI spec as JSON` + diff --git a/quotesdb/.nbd/tickets/2ab7a8.md b/quotesdb/.nbd/tickets/2ab7a8.md index 1e438b2..79aac35 100644 --- a/quotesdb/.nbd/tickets/2ab7a8.md +++ b/quotesdb/.nbd/tickets/2ab7a8.md @@ -5,3 +5,27 @@ status = "todo" ticket_type = "task" dependencies = ["ce1e4f"] +++ + + +This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed. + + + +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? + + + +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. + + + +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. + + + +`chore(quotesdb): resolve triage — test-harness-how-to-import-and-start-quotesdbapi-in-tests-wo` + diff --git a/quotesdb/.nbd/tickets/2c5a57.md b/quotesdb/.nbd/tickets/2c5a57.md index 697b2bd..d113892 100644 --- a/quotesdb/.nbd/tickets/2c5a57.md +++ b/quotesdb/.nbd/tickets/2c5a57.md @@ -5,3 +5,34 @@ status = "todo" ticket_type = "task" dependencies = ["c3503b", "93515e"] +++ + + +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. + + + +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` prop. + + + +- 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`. + + + +From the `quotesdb/` directory: + +```sh +trunk build +``` + + + +`feat(quotesdb): implement shared Pagination component` + diff --git a/quotesdb/.nbd/tickets/2ce22e.md b/quotesdb/.nbd/tickets/2ce22e.md index a9ebec7..8d1db33 100644 --- a/quotesdb/.nbd/tickets/2ce22e.md +++ b/quotesdb/.nbd/tickets/2ce22e.md @@ -5,3 +5,42 @@ status = "todo" ticket_type = "task" dependencies = ["f3dc74", "a5049d", "d792e2", "175382"] +++ + + +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"). + + + +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. + + + +- **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. + + + +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. + + + +Run in order from the `quotesdb/` directory: + +```sh +cargo fmt +cargo check +cargo clippy +cargo test +``` + + + +`feat(quotesdb): implement GET /api/quotes/random` + diff --git a/quotesdb/.nbd/tickets/2d1371.md b/quotesdb/.nbd/tickets/2d1371.md index 3bc0777..d796935 100644 --- a/quotesdb/.nbd/tickets/2d1371.md +++ b/quotesdb/.nbd/tickets/2d1371.md @@ -5,3 +5,34 @@ status = "todo" ticket_type = "task" dependencies = ["25c413", "07feaa"] +++ + + +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). + + + +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 + + + +- 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). + + + +Run from the `infra/` directory: + +```sh +tofu validate +tofu plan +``` + + + +`chore(quotesdb): bootstrap OpenTofu infra project with Cloudflare provider` + diff --git a/quotesdb/.nbd/tickets/2ec8b1.md b/quotesdb/.nbd/tickets/2ec8b1.md index fb3159e..954640c 100644 --- a/quotesdb/.nbd/tickets/2ec8b1.md +++ b/quotesdb/.nbd/tickets/2ec8b1.md @@ -5,3 +5,27 @@ status = "todo" ticket_type = "task" dependencies = ["f3dc74"] +++ + + +This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed. + + + +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? + + + +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. + + + +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. + + + +`chore(quotesdb): resolve triage — openapi-spec-serving-strategy-embed-yaml-at-compile-time-vs-` + diff --git a/quotesdb/.nbd/tickets/33ed29.md b/quotesdb/.nbd/tickets/33ed29.md index 6750e8e..c67fea0 100644 --- a/quotesdb/.nbd/tickets/33ed29.md +++ b/quotesdb/.nbd/tickets/33ed29.md @@ -5,3 +5,27 @@ status = "todo" ticket_type = "task" dependencies = ["f3dc74"] +++ + + +This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed. + + + +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? + + + +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. + + + +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. + + + +`chore(quotesdb): resolve triage — local-dev-config-turso-file-sqlite-vs-d1-binding-selection-s` + diff --git a/quotesdb/.nbd/tickets/372790.md b/quotesdb/.nbd/tickets/372790.md index 5c4bf59..6bbeaed 100644 --- a/quotesdb/.nbd/tickets/372790.md +++ b/quotesdb/.nbd/tickets/372790.md @@ -5,3 +5,24 @@ status = "todo" ticket_type = "task" dependencies = ["c3503b", "1a274d", "1ba523", "5f1112", "b3ef98", "5cdbd9"] +++ + + +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. + + + +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 + + + +- 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. + + + +`docs(quotesdb): write ui README, PLANNING.md, and ARCHITECTURE.md` + diff --git a/quotesdb/.nbd/tickets/4a4c26.md b/quotesdb/.nbd/tickets/4a4c26.md index b8c63f4..488f0ae 100644 --- a/quotesdb/.nbd/tickets/4a4c26.md +++ b/quotesdb/.nbd/tickets/4a4c26.md @@ -5,3 +5,41 @@ status = "todo" ticket_type = "task" dependencies = ["ce1e4f", "9b581f", "05f8ae"] +++ + + +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. + + + +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 + + + +- 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. + + + +Use `superpowers:verification-before-completion` before closing. + + + +Run in order from the `quotesdb/` directory: + +```sh +cargo fmt +cargo check +cargo clippy +cargo test +``` + + + +`test(quotesdb): add PUT /api/quotes test suite — create quote` + diff --git a/quotesdb/.nbd/tickets/580e66.md b/quotesdb/.nbd/tickets/580e66.md index b59db3c..736fa9b 100644 --- a/quotesdb/.nbd/tickets/580e66.md +++ b/quotesdb/.nbd/tickets/580e66.md @@ -5,3 +5,27 @@ status = "todo" ticket_type = "task" dependencies = ["f3dc74"] +++ + + +This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed. + + + +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. + + + +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). + + + +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. + + + +`chore(quotesdb): resolve triage — database-migration-strategy-for-cloudflare-workers-startup-v` + diff --git a/quotesdb/.nbd/tickets/5c0c64.md b/quotesdb/.nbd/tickets/5c0c64.md index cf39be9..58457cc 100644 --- a/quotesdb/.nbd/tickets/5c0c64.md +++ b/quotesdb/.nbd/tickets/5c0c64.md @@ -5,3 +5,27 @@ status = "todo" ticket_type = "task" dependencies = ["25c413"] +++ + + +This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed. + + + +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. + + + +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. + + + +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. + + + +`chore(quotesdb): resolve triage — d1-migrations-in-opentofu-nullresource-localexec-vs-separate` + diff --git a/quotesdb/.nbd/tickets/5cdbd9.md b/quotesdb/.nbd/tickets/5cdbd9.md index 1403054..3c2cbe9 100644 --- a/quotesdb/.nbd/tickets/5cdbd9.md +++ b/quotesdb/.nbd/tickets/5cdbd9.md @@ -5,3 +5,37 @@ status = "todo" ticket_type = "task" dependencies = ["c3503b", "04f865", "1e6a09", "0d987f", "2c5a57", "d3d502", "fc2f51"] +++ + + +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. + + + +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 + + + +- 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). + + + +From the `quotesdb/` directory: + +```sh +trunk build +``` + + + +`feat(quotesdb): implement Browse page — paginated list with filters` + diff --git a/quotesdb/.nbd/tickets/5d9f5a.md b/quotesdb/.nbd/tickets/5d9f5a.md index c387150..960e067 100644 --- a/quotesdb/.nbd/tickets/5d9f5a.md +++ b/quotesdb/.nbd/tickets/5d9f5a.md @@ -5,3 +5,48 @@ status = "todo" ticket_type = "task" dependencies = ["f3dc74", "a5049d", "d792e2", "175382"] +++ + + +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. + + + +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 + + + +- 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. + + + +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. + + + +Run in order from the `quotesdb/` directory: + +```sh +cargo fmt +cargo check +cargo clippy +cargo test +``` + + + +`feat(quotesdb): implement POST /api/quotes/:id — partial update with auth verification` + diff --git a/quotesdb/.nbd/tickets/5dbb7d.md b/quotesdb/.nbd/tickets/5dbb7d.md index f0c8905..1d41f95 100644 --- a/quotesdb/.nbd/tickets/5dbb7d.md +++ b/quotesdb/.nbd/tickets/5dbb7d.md @@ -5,3 +5,41 @@ status = "todo" ticket_type = "task" dependencies = ["f3dc74", "a5049d", "d792e2", "175382"] +++ + + +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. + + + +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. + + + +- 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. + + + +Use `superpowers:test-driven-development` — write tests for: 200 with quote object, 404 not found. +Use `superpowers:verification-before-completion` before closing. + + + +Run in order from the `quotesdb/` directory: + +```sh +cargo fmt +cargo check +cargo clippy +cargo test +``` + + + +`feat(quotesdb): implement GET /api/quotes/:id` + diff --git a/quotesdb/.nbd/tickets/5e3e37.md b/quotesdb/.nbd/tickets/5e3e37.md index 4978ed3..7f53397 100644 --- a/quotesdb/.nbd/tickets/5e3e37.md +++ b/quotesdb/.nbd/tickets/5e3e37.md @@ -5,3 +5,27 @@ status = "todo" ticket_type = "task" dependencies = ["c3503b"] +++ + + +This is a triage decision ticket. It must be resolved before dependent implementation tickets can proceed. + + + +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? + + + +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 `