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.
vibed/quotesdb/docs/plans/2026-02-27-single-crate-ref...

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 from ui/index.html)
  • Create: Trunk.toml (moved from ui/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.md exists; 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:

  1. Directory structure diagram — remove api/, ui/, tests/ as sub-crates
  2. nbd note: tickets are scoped to the quotesdb/ directory — always run nbd from there, not from parent directories
  3. Validation — commands run from quotesdb/ root (no sub-directory cd needed)
  4. Agent dispatch — agents work in src/bin/api/ or src/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".