You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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.toml files to maintain, three cargo fmt/check/clippy invocations.
  • Trunk must be run from ui/, not the project root.
``` 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
### 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:
    [[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.rssrc/bin/api/main.rs
  • api/src/tests.rssrc/bin/api/tests.rs (or inline unit tests within the binary module)
  • Delete api/Cargo.toml

4. Move ui source

  • ui/src/main.rssrc/bin/ui/main.rs
  • ui/src/tests.rssrc/bin/ui/tests.rs
  • Delete ui/Cargo.toml

5. Move Trunk files

  • ui/index.htmlquotesdb/index.html
  • ui/Trunk.tomlquotesdb/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 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 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)
### 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.

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.

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.

From `quotesdb/` root:
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
| 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) | `refactor(quotesdb): collapse to single crate with api and ui binaries`