+++ title = "Refactor to single-crate with api and ui binaries" priority = 8 status = "done" 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) |