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.8 KiB

+++ 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 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.

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:
    [[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 so Trunk compiles the right entrypoint:

[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:

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.rssrc/bin/api/main.rs
MOVE api/src/tests.rssrc/bin/api/tests.rs
MOVE ui/src/main.rssrc/bin/ui/main.rs
MOVE ui/src/tests.rssrc/bin/ui/tests.rs
MOVE ui/index.htmlindex.html
MOVE ui/Trunk.tomlTrunk.toml (update --bin ui)
MERGE api/docs/ + ui/docs/ + tests/docs/docs/
MERGE api/README.md, ui/README.md, tests/README.mdREADME.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)