From bd0c2af0855631111d187f2e0e114e920fccb9d2 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Fri, 27 Feb 2026 22:15:19 -0800 Subject: [PATCH] refactor(quotesdb): collapse to single crate with api and ui binaries Replaces the three separate sub-crates (api/, ui/, tests/) with a single Cargo crate at the quotesdb/ root. Shared code lives in src/lib.rs; the api and ui are multi-binary targets; integration tests use the standard Cargo tests/ layout. Trunk files moved to project root with data-bin="ui". Closes ticket b38032. Co-Authored-By: Claude Sonnet 4.6 --- quotesdb/.nbd/tickets/b38032.md | 233 +++++++++ quotesdb/CLAUDE.md | 78 ++- quotesdb/Cargo.lock | 14 + quotesdb/Cargo.toml | 25 + quotesdb/README.md | 40 ++ quotesdb/{ui => }/Trunk.toml | 0 quotesdb/api/Cargo.toml | 14 - quotesdb/api/README.md | 31 -- quotesdb/api/docs/ARCHITECTURE.md | 9 - quotesdb/api/docs/PLANNING.md | 9 - quotesdb/api/src/main.rs | 1 - quotesdb/api/src/tests.rs | 2 - quotesdb/docs/ARCHITECTURE.md | 32 +- quotesdb/docs/PLANNING.md | 17 +- .../plans/2026-02-27-single-crate-refactor.md | 468 ++++++++++++++++++ quotesdb/{ui => }/index.html | 2 +- quotesdb/src/bin/api/main.rs | 9 + quotesdb/src/bin/ui/main.rs | 9 + quotesdb/src/lib.rs | 8 + quotesdb/tests/Cargo.toml | 5 - quotesdb/tests/README.md | 25 - quotesdb/tests/docs/PLANNING.md | 9 - quotesdb/ui/Cargo.toml | 14 - quotesdb/ui/README.md | 31 -- quotesdb/ui/docs/ARCHITECTURE.md | 9 - quotesdb/ui/docs/PLANNING.md | 9 - quotesdb/ui/src/main.rs | 1 - quotesdb/ui/src/tests.rs | 2 - 28 files changed, 904 insertions(+), 202 deletions(-) create mode 100644 quotesdb/.nbd/tickets/b38032.md create mode 100644 quotesdb/Cargo.lock create mode 100644 quotesdb/Cargo.toml create mode 100644 quotesdb/README.md rename quotesdb/{ui => }/Trunk.toml (100%) delete mode 100644 quotesdb/api/Cargo.toml delete mode 100644 quotesdb/api/README.md delete mode 100644 quotesdb/api/docs/ARCHITECTURE.md delete mode 100644 quotesdb/api/docs/PLANNING.md delete mode 100644 quotesdb/api/src/main.rs delete mode 100644 quotesdb/api/src/tests.rs create mode 100644 quotesdb/docs/plans/2026-02-27-single-crate-refactor.md rename quotesdb/{ui => }/index.html (73%) create mode 100644 quotesdb/src/bin/api/main.rs create mode 100644 quotesdb/src/bin/ui/main.rs create mode 100644 quotesdb/src/lib.rs delete mode 100644 quotesdb/tests/Cargo.toml delete mode 100644 quotesdb/tests/README.md delete mode 100644 quotesdb/tests/docs/PLANNING.md delete mode 100644 quotesdb/ui/Cargo.toml delete mode 100644 quotesdb/ui/README.md delete mode 100644 quotesdb/ui/docs/ARCHITECTURE.md delete mode 100644 quotesdb/ui/docs/PLANNING.md delete mode 100644 quotesdb/ui/src/main.rs delete mode 100644 quotesdb/ui/src/tests.rs diff --git a/quotesdb/.nbd/tickets/b38032.md b/quotesdb/.nbd/tickets/b38032.md new file mode 100644 index 0000000..2ef694a --- /dev/null +++ b/quotesdb/.nbd/tickets/b38032.md @@ -0,0 +1,233 @@ ++++ +title = "Refactor to single-crate with api and ui binaries" +priority = 8 +status = "in_progress" +ticket_type = "task" +dependencies = ["ec118c"] ++++ + +## 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 + +``` +quotesdb/ +├── api/ # independent crate "quotesdb-api" +│ ├── Cargo.toml +│ ├── src/main.rs +│ ├── src/tests.rs +│ ├── docs/ +│ └── README.md +├── ui/ # independent crate "quotesdb-ui" +│ ├── Cargo.toml +│ ├── src/main.rs +│ ├── src/tests.rs +│ ├── index.html +│ ├── Trunk.toml +│ ├── docs/ +│ └── README.md +├── tests/ # independent crate "quotesdb-tests" +│ ├── Cargo.toml +│ ├── docs/ +│ └── README.md +├── infra/ +└── docs/ +``` + +**Problems with current 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. + +--- + +## Target State + +``` +quotesdb/ +├── Cargo.toml # single crate "quotesdb", default-run = "api" +├── src/ +│ ├── lib.rs # shared code (types, models, auth logic, etc.) +│ └── bin/ +│ ├── api/ +│ │ └── main.rs # api binary entrypoint +│ └── ui/ +│ └── main.rs # ui binary entrypoint (for Trunk) +├── tests/ # integration tests — run by `cargo test` +│ └── (*.rs files) +├── index.html # Trunk HTML entry (moved from ui/) +├── Trunk.toml # updated to point to ui binary +├── infra/ +└── docs/ + ├── PLANNING.md + ├── ARCHITECTURE.md + └── plans/ + └── 2026-02-27-quotesdb-design.md +``` + +**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 + +--- + +## Changes Required + +### 1. Create `quotesdb/Cargo.toml` + +Single crate manifest with: +- `name = "quotesdb"` +- `default-run = "api"` — ensures `cargo run` launches the api +- `edition = "2021"`, `license = "MIT OR Apache-2.0"` +- `[profile.release]` block (opt-level z, lto, strip, codegen-units 1) +- All api dependencies (axum, tokio, workers-rs, sqlx, nanoid, etc.) +- All ui dependencies (yew, wasm-bindgen, web-sys, etc.) +- All test dependencies (reqwest, tokio, etc.) under `[dev-dependencies]` +- Two `[[bin]]` entries: + ```toml + [[bin]] + name = "api" + path = "src/bin/api/main.rs" + + [[bin]] + name = "ui" + path = "src/bin/ui/main.rs" + ``` + +### 2. Create `src/lib.rs` + +Shared module for code used by both binaries: +- Domain types: `Quote`, `QuoteTag`, pagination structs, request/response shapes +- Auth code generation (4-word passphrase) — shared so ui can display it and api generates it +- NanoID generation utility +- Any other shared logic + +### 3. Move api source + +- `api/src/main.rs` → `src/bin/api/main.rs` +- `api/src/tests.rs` → `src/bin/api/tests.rs` (or inline unit tests within the binary module) +- Delete `api/Cargo.toml` + +### 4. Move ui source + +- `ui/src/main.rs` → `src/bin/ui/main.rs` +- `ui/src/tests.rs` → `src/bin/ui/tests.rs` +- Delete `ui/Cargo.toml` + +### 5. Move Trunk files + +- `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: +```toml +[build] +target = "index.html" + +[build.cargo] +args = ["--bin", "ui"] +``` + +### 6. Move integration tests + +- Content from `tests/` sub-crate moves into `quotesdb/tests/` as `.rs` files (standard Cargo integration test layout). +- Delete `tests/Cargo.toml`. +- Integration tests import from the crate root (`use quotesdb::...`) and from dev-dependencies. +- They run with `cargo test` automatically — no separate crate needed. + +### 7. Consolidate docs + +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` +- Delete the now-empty `api/docs/`, `ui/docs/`, `tests/docs/` directories. + +### 8. Update `CLAUDE.md` + +Update `quotesdb/CLAUDE.md` to reflect: +- New directory structure (single crate, not three sub-crates) +- New validation commands run from `quotesdb/` root, not from sub-directories +- Updated branch naming and ticket hierarchy (the sub-project split is now logical, not a file-system split) +- Updated agent dispatch instructions (agents work in `src/bin/api/` or `src/bin/ui/`, not separate crates) + +### 9. Delete orphaned sub-crate roots + +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) + +--- + +## Considerations & Complications + +### 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. + +### `cargo test` and Wasm + +`cargo test` runs on the host target. The `ui` binary's tests cannot use DOM/browser APIs directly. Yew component tests that require a browser context must use `wasm-bindgen-test` and `wasm-pack test` — these cannot be run by `cargo test`. Therefore: +- Unit tests in `src/bin/ui/` must be limited to pure logic (routing, data transformations, API client request construction) guarded with `#[cfg(test)]`. +- Browser-only tests (component rendering) are out of scope for `cargo test` and remain a future concern. +- Integration tests in `tests/` exercise the **api** only and run on the host — these work fine with `cargo test`. + +### workers-rs and local dev + +The api uses `workers-rs` for Cloudflare Workers deployment. For local development `cargo run`, the api should either: +- 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 + +From `quotesdb/` root: +```sh +cargo fmt # must pass cleanly +cargo check # must pass for host target +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 +``` + +The conventional commit for this work: `refactor(quotesdb): collapse to single crate with api and ui binaries` + +--- + +## Files to Create / Move / Delete (Summary) + +| Action | Path | +|--------|------| +| CREATE | `quotesdb/Cargo.toml` | +| CREATE | `quotesdb/src/lib.rs` | +| MOVE | `api/src/main.rs` → `src/bin/api/main.rs` | +| MOVE | `api/src/tests.rs` → `src/bin/api/tests.rs` | +| MOVE | `ui/src/main.rs` → `src/bin/ui/main.rs` | +| MOVE | `ui/src/tests.rs` → `src/bin/ui/tests.rs` | +| MOVE | `ui/index.html` → `index.html` | +| MOVE | `ui/Trunk.toml` → `Trunk.toml` (update `--bin ui`) | +| MERGE | `api/docs/` + `ui/docs/` + `tests/docs/` → `docs/` | +| MERGE | `api/README.md`, `ui/README.md`, `tests/README.md` → `README.md` | +| DELETE | `api/Cargo.toml` | +| DELETE | `ui/Cargo.toml` | +| DELETE | `tests/Cargo.toml` | +| DELETE | `api/` (after moving contents) | +| DELETE | `ui/` (after moving contents) | +| UPDATE | `Trunk.toml` (add `[build.cargo] args = ["--bin", "ui"]`) | +| UPDATE | `quotesdb/CLAUDE.md` (structure, validation paths, agent instructions) | diff --git a/quotesdb/CLAUDE.md b/quotesdb/CLAUDE.md index c4503f5..0668cfe 100644 --- a/quotesdb/CLAUDE.md +++ b/quotesdb/CLAUDE.md @@ -10,13 +10,26 @@ Run all commands from the relevant sub-directory within `quotesdb/` (e.g., `api/ ## Project Overview -`quotesdb` is a quotes web application in the vibed mono-repo. It consists of: +`quotesdb` is a quotes web application in the vibed mono-repo. It is a **single Cargo crate** with two binaries and shared library code: -- **`api/`** — Rust/Axum backend on Cloudflare Workers -- **`ui/`** — Yew (Rust/Wasm) frontend on Cloudflare Pages -- **`tests/`** — Integration test crate -- **`infra/`** — OpenTofu infrastructure (Cloudflare D1, Workers, Pages) -- **`docs/`** — Project-level docs and design plans +``` +quotesdb/ +├── Cargo.toml # single crate, default-run = "api" +├── src/ +│ ├── lib.rs # shared types/utilities (compiles for host + wasm32) +│ └── bin/ +│ ├── api/ +│ │ └── main.rs # API server binary +│ └── ui/ +│ └── main.rs # Yew frontend binary (compiled by Trunk) +├── tests/ # Cargo integration tests (cargo test runs these) +├── index.html # Trunk HTML entry +├── Trunk.toml # Trunk config +├── infra/ # OpenTofu infrastructure +└── docs/ # Planning, architecture, and design docs + └── plans/ + └── 2026-02-27-quotesdb-design.md +``` Design reference: `docs/plans/2026-02-27-quotesdb-design.md` @@ -24,9 +37,21 @@ Design reference: `docs/plans/2026-02-27-quotesdb-design.md` -## Ticket Hierarchy +## nbd Ticket Tracking -Tickets are organised as a dependency tree rooted at the project ticket: +**Always run `nbd` commands from the `quotesdb/` directory.** Tickets are scoped to the directory where `nbd init` was run. Running `nbd` from a parent directory will not find these tickets. + +```sh +# Correct — from quotesdb/ +nbd list --json +nbd read b38032 --json +nbd update b38032 --status done --json + +# Wrong — from qdb/ or vibed/ +cd quotesdb && nbd list --json # must be in quotesdb/ +``` + +### Ticket Hierarchy ``` quotesdb (root project ticket, ec118c) @@ -77,9 +102,9 @@ All completed work branches merge into the **`quotesdb`** branch (not `main`). # Create worktree for a ticket git worktree add .claude/worktrees/quotesdb-api- -b quotesdb/api/ -# ... do the work ... +# ... do the work inside quotesdb/ root ... -# Validate before merging (from the relevant sub-directory) +# Validate before merging (always from quotesdb/ root) cargo fmt && cargo check && cargo clippy && cargo test # Merge into quotesdb branch @@ -98,19 +123,19 @@ git worktree remove .claude/worktrees/quotesdb-api- Dispatch all implementation work to a domain expert sub-agent. Match the domain to the agent: -| Domain | Agent role | -|--------|-----------| -| `api/` | Senior Rust backend engineer — expert in API design, Axum, workers-rs, SQLx, and unit testing | -| `ui/` | Senior Rust frontend engineer — expert in Yew, Wasm, Trunk, and web design | -| `tests/` | Senior QA engineer — expert in Rust integration testing and HTTP test harnesses | -| `infra/` | DevOps/infrastructure engineer — expert in OpenTofu, Terraform, and Cloudflare | -| Anything else | Software architect — expert in cloud services, Rust software patterns, and system design | +| Domain | Path | Agent role | +|--------|------|-----------| +| API | `src/bin/api/` | Senior Rust backend engineer — expert in API design, Axum, workers-rs, SQLx, and unit testing | +| UI | `src/bin/ui/` | Senior Rust frontend engineer — expert in Yew, Wasm, Trunk, and web design | +| Shared | `src/lib.rs` | Software architect — must keep code compatible with both host and wasm32 targets | +| Infra | `infra/` | DevOps/infrastructure engineer — expert in OpenTofu, Terraform, and Cloudflare | +| Anything else | — | Software architect — expert in cloud services, Rust software patterns, and system design | Each sub-agent should receive: 1. The relevant section of `docs/plans/2026-02-27-quotesdb-design.md` 2. The specific nbd ticket body 3. The validation commands to run before closing the ticket -4. The conventional commit scope to use (e.g., `feat(quotesdb-api): ...`) +4. The conventional commit scope to use (e.g., `feat(quotesdb): ...`) @@ -200,8 +225,23 @@ The API spec lives at `api/openapi.yaml` (OpenAPI 3.1.0, YAML format). redocly lint api/openapi.yaml ``` -For infra changes, run from the `infra/` directory: +## Validation + +Run in order from the **`quotesdb/` root** before closing any ticket: +```sh +cargo fmt # 1. formatting +cargo check # 2. syntax +cargo clippy # 3. best practices +cargo test # 4. correctness +``` + +For API spec changes: +```sh +redocly lint api/openapi.yaml +``` + +For infra changes, run from the `infra/` directory: ```sh tofu validate tofu plan diff --git a/quotesdb/Cargo.lock b/quotesdb/Cargo.lock new file mode 100644 index 0000000..507c8da --- /dev/null +++ b/quotesdb/Cargo.lock @@ -0,0 +1,14 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "common" +version = "0.1.0" + +[[package]] +name = "quotesdb" +version = "0.1.0" +dependencies = [ + "common", +] diff --git a/quotesdb/Cargo.toml b/quotesdb/Cargo.toml new file mode 100644 index 0000000..bf02279 --- /dev/null +++ b/quotesdb/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "quotesdb" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +default-run = "api" + +[[bin]] +name = "api" +path = "src/bin/api/main.rs" + +[[bin]] +name = "ui" +path = "src/bin/ui/main.rs" + +[dependencies] +common = { path = "../common" } + +[dev-dependencies] + +[profile.release] +opt-level = "z" +lto = true +strip = true +codegen-units = 1 diff --git a/quotesdb/README.md b/quotesdb/README.md new file mode 100644 index 0000000..fd212aa --- /dev/null +++ b/quotesdb/README.md @@ -0,0 +1,40 @@ +# quotesdb + +A quotes web application — browse, submit, and manage memorable quotes. + +## What + +quotesdb is a full-stack web application with: +- A JSON REST API (`api` binary) backed by Cloudflare Workers + D1 (SQLite) +- A Yew/Wasm frontend (`ui` binary) hosted on Cloudflare Pages +- NanoID-identified quotes protected by a 4-word passphrase auth code + +## How + +Single Cargo crate with two binaries sharing common types via `src/lib.rs`: +- `api`: Axum on Tokio, targeting Cloudflare Workers via workers-rs, SQLx + D1 +- `ui`: Yew compiled to `wasm32-unknown-unknown` via Trunk + +## Run + +```sh +# Start API server (local dev) +cargo run + +# Start UI dev server (requires wasm32 toolchain + trunk) +trunk serve +``` + +## Test + +```sh +cargo fmt && cargo check && cargo clippy && cargo test +``` + +## License + +Licensed under either of [Apache License, Version 2.0](../LICENSE-APACHE) or [MIT License](../LICENSE-MIT) at your option. + +## Disclaimer + +This software was written with [Claude Code](https://claude.ai/claude-code) (claude-sonnet-4-6). diff --git a/quotesdb/ui/Trunk.toml b/quotesdb/Trunk.toml similarity index 100% rename from quotesdb/ui/Trunk.toml rename to quotesdb/Trunk.toml diff --git a/quotesdb/api/Cargo.toml b/quotesdb/api/Cargo.toml deleted file mode 100644 index 0cc0ec8..0000000 --- a/quotesdb/api/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[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 diff --git a/quotesdb/api/README.md b/quotesdb/api/README.md deleted file mode 100644 index 007462d..0000000 --- a/quotesdb/api/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# quotesdb-api - -Rust/Axum backend for the quotesdb quotes service, deployed as a Cloudflare Worker. - -## What - -A JSON REST API for creating, browsing, and managing quotes. Each quote is identified by a NanoID and protected by a 4-word passphrase auth code. - -## How - -Built with Axum on Tokio, targeting Cloudflare Workers via `workers-rs`. Data is stored in Cloudflare D1 (SQLite-compatible) in production and Turso file-backed SQLite locally. Queries use SQLx. - -## Run - -```sh -cargo run -``` - -## Test - -```sh -cargo fmt && cargo check && cargo clippy && cargo test -``` - -## License - -Licensed under either of [Apache License, Version 2.0](../../LICENSE-APACHE) or [MIT License](../../LICENSE-MIT) at your option. - -## Disclaimer - -This software was written with [Claude Code](https://claude.ai/claude-code) (claude-sonnet-4-6). diff --git a/quotesdb/api/docs/ARCHITECTURE.md b/quotesdb/api/docs/ARCHITECTURE.md deleted file mode 100644 index 46dfa68..0000000 --- a/quotesdb/api/docs/ARCHITECTURE.md +++ /dev/null @@ -1,9 +0,0 @@ -# quotesdb-api — Architecture - -## Component Overview - -_To be filled in during Phase 1 planning._ - -## Component Interactions - -_To be filled in during Phase 1 planning._ diff --git a/quotesdb/api/docs/PLANNING.md b/quotesdb/api/docs/PLANNING.md deleted file mode 100644 index 0b0d73f..0000000 --- a/quotesdb/api/docs/PLANNING.md +++ /dev/null @@ -1,9 +0,0 @@ -# quotesdb-api — Planning - -## Development Phases - -_To be filled in during Phase 1 planning._ - -## Work Log - -_Updated as work progresses._ diff --git a/quotesdb/api/src/main.rs b/quotesdb/api/src/main.rs deleted file mode 100644 index f328e4d..0000000 --- a/quotesdb/api/src/main.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} diff --git a/quotesdb/api/src/tests.rs b/quotesdb/api/src/tests.rs deleted file mode 100644 index 5c6016f..0000000 --- a/quotesdb/api/src/tests.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(test)] -mod tests {} diff --git a/quotesdb/docs/ARCHITECTURE.md b/quotesdb/docs/ARCHITECTURE.md index 019222d..2e7c409 100644 --- a/quotesdb/docs/ARCHITECTURE.md +++ b/quotesdb/docs/ARCHITECTURE.md @@ -2,22 +2,40 @@ ## Component Overview -| Component | Directory | Description | -|-----------|-----------|-------------| -| API | `api/` | Rust/Axum backend on Cloudflare Workers. Handles all data operations via SQLx and Cloudflare D1. | -| UI | `ui/` | Yew (Rust/Wasm) frontend on Cloudflare Pages. Communicates with the API over HTTP. | -| Tests | `tests/` | Integration test crate. Spins up the API against in-memory SQLite and makes real HTTP requests. | +| Component | Path | Description | +|-----------|------|-------------| +| API binary | `src/bin/api/main.rs` | Rust/Axum backend on Cloudflare Workers. Handles all data operations via SQLx and Cloudflare D1. | +| UI binary | `src/bin/ui/main.rs` | Yew (Rust/Wasm) frontend on Cloudflare Pages. Communicates with the API over HTTP. | +| Shared library | `src/lib.rs` | Types and utilities shared between api and ui binaries. Must compile for both host and wasm32. | +| Integration tests | `tests/` | Standard Cargo integration tests. Spin up the API against in-memory SQLite and make real HTTP requests. | | Infra | `infra/` | OpenTofu configuration for Cloudflare Worker, D1 database, and Pages project. | ## Component Interactions ``` Browser - └──> Cloudflare Pages (quotesdb-ui) - └──> Cloudflare Workers (quotesdb-api) + └──> Cloudflare Pages (ui binary compiled to Wasm) + └──> Cloudflare Workers (api binary) └──> Cloudflare D1 (SQLite) ``` The UI is a static Wasm bundle served from Cloudflare Pages. It makes fetch requests to the Worker API, which reads and writes to a D1 database bound to the Worker. Integration tests bypass the UI and talk directly to the API over HTTP, using a local in-memory SQLite database. + +## Build Targets + +| Artifact | Command | Compile target | +|----------|---------|----------------| +| API server | `cargo run` or `cargo build` | host (native) | +| UI Wasm bundle | `trunk serve` or `trunk build` | `wasm32-unknown-unknown` | +| Tests | `cargo test` | host (native) | + +## Shared Code Constraints + +`src/lib.rs` must compile for **both** `wasm32-unknown-unknown` (ui) and the host target (api). Avoid: +- Threading primitives (`std::thread`, `std::sync::Mutex`) +- Filesystem access (`std::fs`) +- Any API that is not available in a Wasm environment + +Use `#[cfg(not(target_arch = "wasm32"))]` and `#[cfg(target_arch = "wasm32")]` guards where needed. diff --git a/quotesdb/docs/PLANNING.md b/quotesdb/docs/PLANNING.md index 31d185d..0a8094f 100644 --- a/quotesdb/docs/PLANNING.md +++ b/quotesdb/docs/PLANNING.md @@ -6,14 +6,23 @@ Finalized database schema, API endpoints, request/response shapes, auth model, and frontend routes. -### Phase 1: Ticket Planning (pending) +### Phase 1: Structure refactor (complete) -Four parallel planning agents create nbd tickets for each domain (api, ui, tests, infra). +Collapsed `api/`, `ui/`, and `tests/` sub-crates into a single `quotesdb` Cargo crate. +- Single `Cargo.toml` at project root with two binaries: `api` and `ui` +- Shared code lives in `src/lib.rs` +- Integration tests live in `tests/` as standard Cargo integration tests +- Trunk moved to project root -### Phase 2: Implementation (pending) +### Phase 2: Ticket Planning (pending) -Four parallel implementation orchestrators work through their domain tickets. +Planning agents create nbd tickets for api, ui, and infra implementation work. + +### Phase 3: Implementation (pending) + +Implementation agents work through domain tickets. ## Work Log - **2026-02-27** — Phase 0 complete. Project skeleton bootstrapped. Design doc written. +- **2026-02-27** — Phase 1 complete. Refactored to single-crate layout (ticket b38032). diff --git a/quotesdb/docs/plans/2026-02-27-single-crate-refactor.md b/quotesdb/docs/plans/2026-02-27-single-crate-refactor.md new file mode 100644 index 0000000..6733aa3 --- /dev/null +++ b/quotesdb/docs/plans/2026-02-27-single-crate-refactor.md @@ -0,0 +1,468 @@ +# Single-Crate Refactor Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Collapse `api/`, `ui/`, and `tests/` sub-crates into a single `quotesdb` Cargo crate with two binaries (`api`, `ui`) and standard integration tests. + +**Architecture:** One `Cargo.toml` at `quotesdb/` root; api code lives in `src/bin/api/main.rs`; ui code in `src/bin/ui/main.rs`; shared types in `src/lib.rs`; integration tests in `tests/` as plain `.rs` files. All source files are currently stubs so migration is purely structural. + +**Tech Stack:** Rust, Cargo multi-binary layout, Trunk (for Wasm/ui), nbd (ticket tracking) + +--- + +## Pre-flight + +All source files are stubs (`fn main() {}`). No logic to preserve. Validation commands run from `quotesdb/` root: + +```sh +cargo fmt && cargo check && cargo clippy && cargo test +``` + +Trunk validation (requires wasm32 toolchain): +```sh +trunk build +``` + +--- + +### Task 1: Mark ticket in progress + +**Step 1: Update ticket status** + +```sh +nbd update b38032 --status in_progress --json +``` + +Expected: JSON with `"status": "in_progress"`. + +--- + +### Task 2: Create directory structure + +**Step 1: Create binary directories** + +```sh +mkdir -p src/bin/api src/bin/ui +``` + +No output expected. + +--- + +### Task 3: Create root `Cargo.toml` + +**Files:** +- Create: `Cargo.toml` + +**Step 1: Write `Cargo.toml`** + +```toml +[package] +name = "quotesdb" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +default-run = "api" + +[[bin]] +name = "api" +path = "src/bin/api/main.rs" + +[[bin]] +name = "ui" +path = "src/bin/ui/main.rs" + +[dependencies] +common = { path = "../common" } + +[dev-dependencies] + +[profile.release] +opt-level = "z" +lto = true +strip = true +codegen-units = 1 +``` + +Note: Dependencies stay minimal — just what the stubs need (nothing). Additional deps are added in implementation tickets. + +**Step 2: Verify it's parseable** + +```sh +cargo metadata --no-deps --manifest-path Cargo.toml +``` + +Expected: JSON output without errors. + +--- + +### Task 4: Create `src/lib.rs` + +**Files:** +- Create: `src/lib.rs` + +**Step 1: Write `src/lib.rs`** + +```rust +//! Shared types and utilities for the `quotesdb` crate. +//! +//! This module is used by both the `api` and `ui` binaries. +//! Code placed here must compile for both the host target (api) +//! and `wasm32-unknown-unknown` (ui). +//! +//! Use `#[cfg(not(target_arch = "wasm32"))]` for host-only items +//! and `#[cfg(target_arch = "wasm32")]` for wasm-only items. +``` + +--- + +### Task 5: Create `src/bin/api/main.rs` + +**Files:** +- Create: `src/bin/api/main.rs` + +**Step 1: Write the api binary entrypoint** + +```rust +//! API server binary entrypoint. +//! +//! Runs the quotesdb REST API. In production this targets Cloudflare Workers +//! via workers-rs. For local development it runs a plain Axum/Tokio server. + +fn main() {} + +#[cfg(test)] +mod tests {} +``` + +Note: The old `api/src/tests.rs` was a separate file imported as a module. Here we inline the empty test module directly — simpler for a stub. + +--- + +### Task 6: Create `src/bin/ui/main.rs` + +**Files:** +- Create: `src/bin/ui/main.rs` + +**Step 1: Write the ui binary entrypoint** + +```rust +//! UI binary entrypoint. +//! +//! Compiled to WebAssembly via Trunk targeting `wasm32-unknown-unknown`. +//! Runs the Yew frontend application. + +fn main() {} + +#[cfg(test)] +mod tests {} +``` + +--- + +### Task 7: Verify `cargo check` passes + +**Step 1: Run check** + +```sh +cargo check +``` + +Expected: No errors. If the `common` path dependency causes issues, verify `../common` is correct relative to `quotesdb/`. + +**Step 2: Run full validation** + +```sh +cargo fmt && cargo check && cargo clippy && cargo test +``` + +Expected: All pass. `cargo test` will report 0 tests but exit 0. + +--- + +### Task 8: Move Trunk files + +**Files:** +- Create: `index.html` (moved from `ui/index.html`) +- Create: `Trunk.toml` (moved from `ui/Trunk.toml`, updated) + +**Step 1: Write `index.html`** + +Content is identical to `ui/index.html`: + +```html + + + + + QuotesDB + + + + +``` + +Note: Added `data-bin="ui"` to the `` tag so Trunk knows which binary to compile. + +**Step 2: Write `Trunk.toml`** + +```toml +[build] +target = "index.html" +``` + +No `[build.cargo]` block needed — the `data-bin="ui"` attribute in `index.html` is sufficient to tell Trunk which binary to build. + +--- + +### Task 9: Consolidate docs + +**Files:** +- Modify: `docs/PLANNING.md` +- Modify: `docs/ARCHITECTURE.md` + +**Step 1: Update `docs/PLANNING.md`** + +The sub-crate planning docs are all stubs with the same placeholder text. Replace the project-level `docs/PLANNING.md` with a consolidated version: + +```markdown +# quotesdb — Planning + +## Development Phases + +### Phase 0: Design (complete) + +Finalized database schema, API endpoints, request/response shapes, auth model, and frontend routes. + +### Phase 1: Structure refactor (complete) + +Collapsed `api/`, `ui/`, and `tests/` sub-crates into a single `quotesdb` Cargo crate. +- Single `Cargo.toml` at project root with two binaries: `api` and `ui` +- Shared code lives in `src/lib.rs` +- Integration tests live in `tests/` as standard Cargo integration tests +- Trunk moved to project root + +### Phase 2: Ticket Planning (pending) + +Planning agents create nbd tickets for api, ui, and infra implementation work. + +### Phase 3: Implementation (pending) + +Implementation agents work through domain tickets. + +## Work Log + +- **2026-02-27** — Phase 0 complete. Project skeleton bootstrapped. Design doc written. +- **2026-02-27** — Phase 1 complete. Refactored to single-crate layout (ticket b38032). +``` + +**Step 2: Update `docs/ARCHITECTURE.md`** + +```markdown +# quotesdb — Architecture + +## Component Overview + +| Component | Path | Description | +|-----------|------|-------------| +| API binary | `src/bin/api/main.rs` | Rust/Axum backend on Cloudflare Workers. Handles all data operations via SQLx and Cloudflare D1. | +| UI binary | `src/bin/ui/main.rs` | Yew (Rust/Wasm) frontend on Cloudflare Pages. Communicates with the API over HTTP. | +| Shared library | `src/lib.rs` | Types and utilities shared between api and ui binaries. Must compile for both host and wasm32. | +| Integration tests | `tests/` | Standard Cargo integration tests. Spin up the API against in-memory SQLite and make real HTTP requests. | +| Infra | `infra/` | OpenTofu configuration for Cloudflare Worker, D1 database, and Pages project. | + +## Component Interactions + +``` +Browser + └──> Cloudflare Pages (ui binary compiled to Wasm) + └──> Cloudflare Workers (api binary) + └──> Cloudflare D1 (SQLite) +``` + +The UI is a static Wasm bundle served from Cloudflare Pages. It makes fetch requests to the Worker API, which reads and writes to a D1 database bound to the Worker. + +Integration tests bypass the UI and talk directly to the API over HTTP, using a local in-memory SQLite database. + +## Build Targets + +| Artifact | Command | Compile target | +|----------|---------|----------------| +| API server | `cargo run` or `cargo build` | host (native) | +| UI Wasm bundle | `trunk serve` or `trunk build` | `wasm32-unknown-unknown` | +| Tests | `cargo test` | host (native) | + +## Shared Code Constraints + +`src/lib.rs` must compile for **both** `wasm32-unknown-unknown` (ui) and the host target (api). Avoid: +- Threading primitives (`std::thread`, `std::sync::Mutex`) +- Filesystem access (`std::fs`) +- Any API that is not available in a Wasm environment + +Use `#[cfg(not(target_arch = "wasm32"))]` and `#[cfg(target_arch = "wasm32")]` guards where needed. +``` + +--- + +### Task 10: Update project `README.md` + +**Files:** +- Modify: (check if `quotesdb/README.md` exists; if not, create it) + +**Step 1: Check for existing README** + +```sh +ls README.md +``` + +**Step 2: Write consolidated `README.md`** + +```markdown +# quotesdb + +A quotes web application — browse, submit, and manage memorable quotes. + +## What + +quotesdb is a full-stack web application with: +- A JSON REST API (`api` binary) backed by Cloudflare Workers + D1 (SQLite) +- A Yew/Wasm frontend (`ui` binary) hosted on Cloudflare Pages +- NanoID-identified quotes protected by a 4-word passphrase auth code + +## How + +Single Cargo crate with two binaries sharing common types via `src/lib.rs`: +- `api`: Axum on Tokio, targeting Cloudflare Workers via workers-rs, SQLx + D1 +- `ui`: Yew compiled to `wasm32-unknown-unknown` via Trunk + +## Run + +```sh +# Start API server (local dev) +cargo run + +# Start UI dev server (requires wasm32 toolchain + trunk) +trunk serve +``` + +## Test + +```sh +cargo fmt && cargo check && cargo clippy && cargo test +``` + +## License + +Licensed under either of [Apache License, Version 2.0](../LICENSE-APACHE) or [MIT License](../LICENSE-MIT) at your option. + +## Disclaimer + +This software was written with [Claude Code](https://claude.ai/claude-code) (claude-sonnet-4-6). +``` + +--- + +### Task 11: Delete old sub-crate directories + +**Step 1: Remove `api/`, `ui/`, `tests/` directories** + +```sh +rm -rf api/ ui/ tests/ +``` + +> **Warning:** Verify Task 5, 6, 7, and 8 are complete before running this. Double-check with `ls src/bin/api/main.rs src/bin/ui/main.rs index.html Trunk.toml`. + +**Step 2: Verify the correct files remain** + +```sh +find . -name "*.rs" | grep -v target | sort +``` + +Expected output: +``` +./src/bin/api/main.rs +./src/bin/ui/main.rs +./src/lib.rs +``` + +--- + +### Task 12: Run full validation + +**Step 1: Format** + +```sh +cargo fmt +``` + +Expected: exits 0. + +**Step 2: Check** + +```sh +cargo check +``` + +Expected: exits 0, no errors. + +**Step 3: Clippy** + +```sh +cargo clippy +``` + +Expected: exits 0, no warnings (or only acceptable ones). + +**Step 4: Test** + +```sh +cargo test +``` + +Expected: exits 0. Output: `running 0 tests` (stubs have no tests yet). + +--- + +### Task 13: Update `CLAUDE.md` + +**Files:** +- Modify: `CLAUDE.md` + +Update the `CLAUDE.md` to reflect the new single-crate structure. Key changes: +1. Directory structure diagram — remove `api/`, `ui/`, `tests/` as sub-crates +2. nbd note: tickets are scoped to the `quotesdb/` directory — always run `nbd` from there, not from parent directories +3. Validation — commands run from `quotesdb/` root (no sub-directory `cd` needed) +4. Agent dispatch — agents work in `src/bin/api/` or `src/bin/ui/`, not separate crates + +See Task 14 for specific edits. + +--- + +### Task 14: Commit + +**Step 1: Stage all changes** + +```sh +git add Cargo.toml src/ index.html Trunk.toml docs/PLANNING.md docs/ARCHITECTURE.md README.md CLAUDE.md +git status +``` + +Verify no leftover `api/`, `ui/`, or `tests/` files appear. + +**Step 2: Commit** + +```sh +git commit -m "refactor(quotesdb): collapse to single crate with api and ui binaries" +``` + +--- + +### Task 15: Close ticket + +**Step 1: Mark ticket done** + +```sh +nbd update b38032 --status done --json +``` + +Expected: JSON with `"status": "done"`. diff --git a/quotesdb/ui/index.html b/quotesdb/index.html similarity index 73% rename from quotesdb/ui/index.html rename to quotesdb/index.html index 77dd39a..f5b653c 100644 --- a/quotesdb/ui/index.html +++ b/quotesdb/index.html @@ -3,7 +3,7 @@ QuotesDB - + diff --git a/quotesdb/src/bin/api/main.rs b/quotesdb/src/bin/api/main.rs new file mode 100644 index 0000000..fef4bf3 --- /dev/null +++ b/quotesdb/src/bin/api/main.rs @@ -0,0 +1,9 @@ +//! API server binary entrypoint. +//! +//! Runs the quotesdb REST API. In production this targets Cloudflare Workers +//! via workers-rs. For local development it runs a plain Axum/Tokio server. + +fn main() {} + +#[cfg(test)] +mod tests {} diff --git a/quotesdb/src/bin/ui/main.rs b/quotesdb/src/bin/ui/main.rs new file mode 100644 index 0000000..a2d8c61 --- /dev/null +++ b/quotesdb/src/bin/ui/main.rs @@ -0,0 +1,9 @@ +//! UI binary entrypoint. +//! +//! Compiled to WebAssembly via Trunk targeting `wasm32-unknown-unknown`. +//! Runs the Yew frontend application. + +fn main() {} + +#[cfg(test)] +mod tests {} diff --git a/quotesdb/src/lib.rs b/quotesdb/src/lib.rs new file mode 100644 index 0000000..778809b --- /dev/null +++ b/quotesdb/src/lib.rs @@ -0,0 +1,8 @@ +//! Shared types and utilities for the `quotesdb` crate. +//! +//! This module is used by both the `api` and `ui` binaries. +//! Code placed here must compile for both the host target (api) +//! and `wasm32-unknown-unknown` (ui). +//! +//! Use `#[cfg(not(target_arch = "wasm32"))]` for host-only items +//! and `#[cfg(target_arch = "wasm32")]` for wasm-only items. diff --git a/quotesdb/tests/Cargo.toml b/quotesdb/tests/Cargo.toml deleted file mode 100644 index 460bee2..0000000 --- a/quotesdb/tests/Cargo.toml +++ /dev/null @@ -1,5 +0,0 @@ -[package] -name = "quotesdb-tests" -version = "0.1.0" -edition = "2021" -license = "MIT OR Apache-2.0" diff --git a/quotesdb/tests/README.md b/quotesdb/tests/README.md deleted file mode 100644 index 13560f4..0000000 --- a/quotesdb/tests/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# quotesdb-tests - -Integration test suite for the quotesdb service. - -## What - -HTTP-level integration tests that spin up the API server against an in-memory SQLite database and verify all endpoints. - -## How - -Separate Rust crate with tests in `tests/`. Each test suite covers a distinct endpoint or behaviour. - -## Run - -```sh -cargo test -``` - -## License - -Licensed under either of [Apache License, Version 2.0](../../LICENSE-APACHE) or [MIT License](../../LICENSE-MIT) at your option. - -## Disclaimer - -This software was written with [Claude Code](https://claude.ai/claude-code) (claude-sonnet-4-6). diff --git a/quotesdb/tests/docs/PLANNING.md b/quotesdb/tests/docs/PLANNING.md deleted file mode 100644 index cdc9565..0000000 --- a/quotesdb/tests/docs/PLANNING.md +++ /dev/null @@ -1,9 +0,0 @@ -# quotesdb-tests — Planning - -## Development Phases - -_To be filled in during Phase 1 planning._ - -## Work Log - -_Updated as work progresses._ diff --git a/quotesdb/ui/Cargo.toml b/quotesdb/ui/Cargo.toml deleted file mode 100644 index 08d3c40..0000000 --- a/quotesdb/ui/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[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 diff --git a/quotesdb/ui/README.md b/quotesdb/ui/README.md deleted file mode 100644 index 2ebb94e..0000000 --- a/quotesdb/ui/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# quotesdb-ui - -Yew (Rust/Wasm) frontend for the quotesdb quotes service, hosted on Cloudflare Pages. - -## What - -A web UI for browsing, submitting, and managing quotes. Compiled to WebAssembly from Rust using the Yew framework. - -## How - -Built with Yew, compiled to `wasm32-unknown-unknown` via Trunk. Communicates with the quotesdb-api backend. - -## Run - -```sh -trunk serve -``` - -## Test - -```sh -cargo fmt && cargo check && cargo clippy && cargo test -``` - -## License - -Licensed under either of [Apache License, Version 2.0](../../LICENSE-APACHE) or [MIT License](../../LICENSE-MIT) at your option. - -## Disclaimer - -This software was written with [Claude Code](https://claude.ai/claude-code) (claude-sonnet-4-6). diff --git a/quotesdb/ui/docs/ARCHITECTURE.md b/quotesdb/ui/docs/ARCHITECTURE.md deleted file mode 100644 index 0b247e7..0000000 --- a/quotesdb/ui/docs/ARCHITECTURE.md +++ /dev/null @@ -1,9 +0,0 @@ -# quotesdb-ui — Architecture - -## Component Overview - -_To be filled in during Phase 1 planning._ - -## Component Interactions - -_To be filled in during Phase 1 planning._ diff --git a/quotesdb/ui/docs/PLANNING.md b/quotesdb/ui/docs/PLANNING.md deleted file mode 100644 index 54c10a9..0000000 --- a/quotesdb/ui/docs/PLANNING.md +++ /dev/null @@ -1,9 +0,0 @@ -# quotesdb-ui — Planning - -## Development Phases - -_To be filled in during Phase 1 planning._ - -## Work Log - -_Updated as work progresses._ diff --git a/quotesdb/ui/src/main.rs b/quotesdb/ui/src/main.rs deleted file mode 100644 index f328e4d..0000000 --- a/quotesdb/ui/src/main.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} diff --git a/quotesdb/ui/src/tests.rs b/quotesdb/ui/src/tests.rs deleted file mode 100644 index 5c6016f..0000000 --- a/quotesdb/ui/src/tests.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(test)] -mod tests {}