8.5 KiB
+++ title = "Refactor to single-crate with api and ui binaries" priority = 8 status = "done" ticket_type = "task" dependencies = ["ec118c"] +++
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.Status: done. This ticket is kept for historical reference.
``` 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 the old structure:
- Shared types/logic must go through
../../common— no quotesdb-specific shared code. - Running tests requires
cding into each sub-crate separately. - Three
Cargo.tomlfiles to maintain, threecargo fmt/check/clippyinvocations. - Trunk must be run from
ui/, not the project root.
Developer workflow after refactor (unchanged from user perspective):
cargo run— starts the API server (default binary isapi)trunk serve— compiles ui to Wasm and serves itcargo test— runs unit tests + integration tests
Single crate manifest with:
name = "quotesdb"default-run = "api"— ensurescargo runlaunches the apiedition = "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:[[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.rsapi/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.rsui/src/tests.rs→src/bin/ui/tests.rs- Delete
ui/Cargo.toml
5. Move Trunk files
ui/index.html→quotesdb/index.htmlui/Trunk.toml→quotesdb/Trunk.toml
Update Trunk.toml to explicitly name the ui binary:
[build]
target = "index.html"
[build.cargo]
args = ["--bin", "ui"]
6. Move integration tests
- Content from
tests/sub-crate moves intoquotesdb/tests/as.rsfiles (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 testautomatically — no separate crate needed.
7. Consolidate docs
Merge per-sub-crate docs into the project-level docs/ directory:
api/docs/PLANNING.mdandui/docs/PLANNING.md→ merge intodocs/PLANNING.mdapi/docs/ARCHITECTURE.mdandui/docs/ARCHITECTURE.md→ merge intodocs/ARCHITECTURE.mdapi/README.mdandui/README.mdandtests/README.md→ consolidate intoREADME.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/orsrc/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 (butquotesdb/tests/integration test files stay)
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.
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 testand remain a future concern. - Integration tests in
tests/exercise the api only and run on the host — these work fine withcargo 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.
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.
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