11 KiB
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:
cargo fmt && cargo check && cargo clippy && cargo test
Trunk validation (requires wasm32 toolchain):
trunk build
Task 1: Mark ticket in progress
Step 1: Update ticket status
nbd update b38032 --status in_progress --json
Expected: JSON with "status": "in_progress".
Task 2: Create directory structure
Step 1: Create binary directories
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
[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
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
//! 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
//! 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
//! 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
cargo check
Expected: No errors. If the common path dependency causes issues, verify ../common is correct relative to quotesdb/.
Step 2: Run full validation
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 fromui/index.html) - Create:
Trunk.toml(moved fromui/Trunk.toml, updated)
Step 1: Write index.html
Content is identical to ui/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>QuotesDB</title>
<link data-trunk rel="rust" data-bin="ui" />
</head>
<body></body>
</html>
Note: Added data-bin="ui" to the <link> tag so Trunk knows which binary to compile.
Step 2: Write Trunk.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:
# 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
# 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.mdexists; if not, create it)
Step 1: Check for existing README
ls README.md
Step 2: Write consolidated README.md
# 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
cargo fmt && cargo check && cargo clippy && cargo test
License
Licensed under either of Apache License, Version 2.0 or MIT License at your option.
Disclaimer
This software was written with 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
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
cargo fmt
Expected: exits 0.
Step 2: Check
cargo check
Expected: exits 0, no errors.
Step 3: Clippy
cargo clippy
Expected: exits 0, no warnings (or only acceptable ones).
Step 4: Test
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:
- Directory structure diagram — remove
api/,ui/,tests/as sub-crates - nbd note: tickets are scoped to the
quotesdb/directory — always runnbdfrom there, not from parent directories - Validation — commands run from
quotesdb/root (no sub-directorycdneeded) - Agent dispatch — agents work in
src/bin/api/orsrc/bin/ui/, not separate crates
See Task 14 for specific edits.
Task 14: Commit
Step 1: Stage all changes
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
git commit -m "refactor(quotesdb): collapse to single crate with api and ui binaries"
Task 15: Close ticket
Step 1: Mark ticket done
nbd update b38032 --status done --json
Expected: JSON with "status": "done".