refactor(quotesdb): collapse to single crate with api and ui binaries

Replaces the three separate sub-crates (api/, ui/, tests/) with a single
Cargo crate at the quotesdb/ root. Shared code lives in src/lib.rs; the
api and ui are multi-binary targets; integration tests use the standard
Cargo tests/ layout. Trunk files moved to project root with data-bin="ui".

Closes ticket b38032.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
quotesdb
Elijah Voigt 3 months ago
parent de99f1c047
commit 91ecd91176

@ -0,0 +1,233 @@
+++
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 `cd`ing 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:
```toml
[[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.rs`
- `api/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.rs`
- `ui/src/tests.rs``src/bin/ui/tests.rs`
- Delete `ui/Cargo.toml`
### 5. Move Trunk files
- `ui/index.html``quotesdb/index.html`
- `ui/Trunk.toml``quotesdb/Trunk.toml`
Update `Trunk.toml` to explicitly name the ui binary so Trunk compiles the right entrypoint:
```toml
[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:
```sh
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.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) |

@ -10,13 +10,26 @@ Run all commands from the relevant sub-directory within `quotesdb/` (e.g., `api/
## Project Overview ## Project Overview
`quotesdb` is a quotes web application in the vibed mono-repo. It consists of: `quotesdb` is a quotes web application in the vibed mono-repo. It is a **single Cargo crate** with two binaries and shared library code:
- **`api/`** — Rust/Axum backend on Cloudflare Workers ```
- **`ui/`** — Yew (Rust/Wasm) frontend on Cloudflare Pages quotesdb/
- **`tests/`** — Integration test crate ├── Cargo.toml # single crate, default-run = "api"
- **`infra/`** — OpenTofu infrastructure (Cloudflare D1, Workers, Pages) ├── src/
- **`docs/`** — Project-level docs and design plans │ ├── lib.rs # shared types/utilities (compiles for host + wasm32)
│ └── bin/
│ ├── api/
│ │ └── main.rs # API server binary
│ └── ui/
│ └── main.rs # Yew frontend binary (compiled by Trunk)
├── tests/ # Cargo integration tests (cargo test runs these)
├── index.html # Trunk HTML entry
├── Trunk.toml # Trunk config
├── infra/ # OpenTofu infrastructure
└── docs/ # Planning, architecture, and design docs
└── plans/
└── 2026-02-27-quotesdb-design.md
```
Design reference: `docs/plans/2026-02-27-quotesdb-design.md` Design reference: `docs/plans/2026-02-27-quotesdb-design.md`
@ -24,9 +37,21 @@ Design reference: `docs/plans/2026-02-27-quotesdb-design.md`
<ticket-hierarchy> <ticket-hierarchy>
## Ticket Hierarchy ## nbd Ticket Tracking
Tickets are organised as a dependency tree rooted at the project ticket: **Always run `nbd` commands from the `quotesdb/` directory.** Tickets are scoped to the directory where `nbd init` was run. Running `nbd` from a parent directory will not find these tickets.
```sh
# Correct — from quotesdb/
nbd list --json
nbd read b38032 --json
nbd update b38032 --status done --json
# Wrong — from qdb/ or vibed/
cd quotesdb && nbd list --json # must be in quotesdb/
```
### Ticket Hierarchy
``` ```
quotesdb (root project ticket, ec118c) quotesdb (root project ticket, ec118c)
@ -77,9 +102,9 @@ All completed work branches merge into the **`quotesdb`** branch (not `main`).
# Create worktree for a ticket # Create worktree for a ticket
git worktree add .claude/worktrees/quotesdb-api-<id> -b quotesdb/api/<id> git worktree add .claude/worktrees/quotesdb-api-<id> -b quotesdb/api/<id>
# ... do the work ... # ... do the work inside quotesdb/ root ...
# Validate before merging (from the relevant sub-directory) # Validate before merging (always from quotesdb/ root)
cargo fmt && cargo check && cargo clippy && cargo test cargo fmt && cargo check && cargo clippy && cargo test
# Merge into quotesdb branch # Merge into quotesdb branch
@ -98,19 +123,19 @@ git worktree remove .claude/worktrees/quotesdb-api-<id>
Dispatch all implementation work to a domain expert sub-agent. Match the domain to the agent: Dispatch all implementation work to a domain expert sub-agent. Match the domain to the agent:
| Domain | Agent role | | Domain | Path | Agent role |
|--------|-----------| |--------|------|-----------|
| `api/` | Senior Rust backend engineer — expert in API design, Axum, workers-rs, SQLx, and unit testing | | API | `src/bin/api/` | Senior Rust backend engineer — expert in API design, Axum, workers-rs, SQLx, and unit testing |
| `ui/` | Senior Rust frontend engineer — expert in Yew, Wasm, Trunk, and web design | | UI | `src/bin/ui/` | Senior Rust frontend engineer — expert in Yew, Wasm, Trunk, and web design |
| `tests/` | Senior QA engineer — expert in Rust integration testing and HTTP test harnesses | | Shared | `src/lib.rs` | Software architect — must keep code compatible with both host and wasm32 targets |
| `infra/` | DevOps/infrastructure engineer — expert in OpenTofu, Terraform, and Cloudflare | | Infra | `infra/` | DevOps/infrastructure engineer — expert in OpenTofu, Terraform, and Cloudflare |
| Anything else | Software architect — expert in cloud services, Rust software patterns, and system design | | Anything else | — | Software architect — expert in cloud services, Rust software patterns, and system design |
Each sub-agent should receive: Each sub-agent should receive:
1. The relevant section of `docs/plans/2026-02-27-quotesdb-design.md` 1. The relevant section of `docs/plans/2026-02-27-quotesdb-design.md`
2. The specific nbd ticket body 2. The specific nbd ticket body
3. The validation commands to run before closing the ticket 3. The validation commands to run before closing the ticket
4. The conventional commit scope to use (e.g., `feat(quotesdb-api): ...`) 4. The conventional commit scope to use (e.g., `feat(quotesdb): ...`)
</domain-expert-agents> </domain-expert-agents>
@ -200,8 +225,23 @@ The API spec lives at `api/openapi.yaml` (OpenAPI 3.1.0, YAML format).
redocly lint api/openapi.yaml redocly lint api/openapi.yaml
``` ```
For infra changes, run from the `infra/` directory: ## Validation
Run in order from the **`quotesdb/` root** before closing any ticket:
```sh
cargo fmt # 1. formatting
cargo check # 2. syntax
cargo clippy # 3. best practices
cargo test # 4. correctness
```
For API spec changes:
```sh
redocly lint api/openapi.yaml
```
For infra changes, run from the `infra/` directory:
```sh ```sh
tofu validate tofu validate
tofu plan tofu plan

14
quotesdb/Cargo.lock generated

@ -0,0 +1,14 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "common"
version = "0.1.0"
[[package]]
name = "quotesdb"
version = "0.1.0"
dependencies = [
"common",
]

@ -0,0 +1,25 @@
[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

@ -0,0 +1,40 @@
# 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
```sh
cargo fmt && cargo check && cargo clippy && cargo test
```
## License
Licensed under either of [Apache License, Version 2.0](../LICENSE-APACHE) or [MIT License](../LICENSE-MIT) at your option.
## Disclaimer
This software was written with [Claude Code](https://claude.ai/claude-code) (claude-sonnet-4-6).

@ -1,14 +0,0 @@
[package]
name = "quotesdb-api"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
common = { path = "../../common" }
[profile.release]
opt-level = "z"
lto = true
strip = true
codegen-units = 1

@ -1,31 +0,0 @@
# quotesdb-api
Rust/Axum backend for the quotesdb quotes service, deployed as a Cloudflare Worker.
## What
A JSON REST API for creating, browsing, and managing quotes. Each quote is identified by a NanoID and protected by a 4-word passphrase auth code.
## How
Built with Axum on Tokio, targeting Cloudflare Workers via `workers-rs`. Data is stored in Cloudflare D1 (SQLite-compatible) in production and Turso file-backed SQLite locally. Queries use SQLx.
## Run
```sh
cargo run
```
## Test
```sh
cargo fmt && cargo check && cargo clippy && cargo test
```
## License
Licensed under either of [Apache License, Version 2.0](../../LICENSE-APACHE) or [MIT License](../../LICENSE-MIT) at your option.
## Disclaimer
This software was written with [Claude Code](https://claude.ai/claude-code) (claude-sonnet-4-6).

@ -1,9 +0,0 @@
# quotesdb-api — Architecture
## Component Overview
_To be filled in during Phase 1 planning._
## Component Interactions
_To be filled in during Phase 1 planning._

@ -1,9 +0,0 @@
# quotesdb-api — Planning
## Development Phases
_To be filled in during Phase 1 planning._
## Work Log
_Updated as work progresses._

@ -1 +0,0 @@
fn main() {}

@ -1,2 +0,0 @@
#[cfg(test)]
mod tests {}

@ -2,22 +2,40 @@
## Component Overview ## Component Overview
| Component | Directory | Description | | Component | Path | Description |
|-----------|-----------|-------------| |-----------|------|-------------|
| API | `api/` | Rust/Axum backend on Cloudflare Workers. Handles all data operations via SQLx and Cloudflare D1. | | API binary | `src/bin/api/main.rs` | Rust/Axum backend on Cloudflare Workers. Handles all data operations via SQLx and Cloudflare D1. |
| UI | `ui/` | Yew (Rust/Wasm) frontend on Cloudflare Pages. Communicates with the API over HTTP. | | UI binary | `src/bin/ui/main.rs` | Yew (Rust/Wasm) frontend on Cloudflare Pages. Communicates with the API over HTTP. |
| Tests | `tests/` | Integration test crate. Spins up the API against in-memory SQLite and makes real HTTP requests. | | 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. | | Infra | `infra/` | OpenTofu configuration for Cloudflare Worker, D1 database, and Pages project. |
## Component Interactions ## Component Interactions
``` ```
Browser Browser
└──> Cloudflare Pages (quotesdb-ui) └──> Cloudflare Pages (ui binary compiled to Wasm)
└──> Cloudflare Workers (quotesdb-api) └──> Cloudflare Workers (api binary)
└──> Cloudflare D1 (SQLite) └──> 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. 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. 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.

@ -6,14 +6,23 @@
Finalized database schema, API endpoints, request/response shapes, auth model, and frontend routes. Finalized database schema, API endpoints, request/response shapes, auth model, and frontend routes.
### Phase 1: Ticket Planning (pending) ### Phase 1: Structure refactor (complete)
Four parallel planning agents create nbd tickets for each domain (api, ui, tests, infra). 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: Implementation (pending) ### Phase 2: Ticket Planning (pending)
Four parallel implementation orchestrators work through their domain tickets. Planning agents create nbd tickets for api, ui, and infra implementation work.
### Phase 3: Implementation (pending)
Implementation agents work through domain tickets.
## Work Log ## Work Log
- **2026-02-27** — Phase 0 complete. Project skeleton bootstrapped. Design doc written. - **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).

@ -0,0 +1,468 @@
# 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:
```sh
cargo fmt && cargo check && cargo clippy && cargo test
```
Trunk validation (requires wasm32 toolchain):
```sh
trunk build
```
---
### Task 1: Mark ticket in progress
**Step 1: Update ticket status**
```sh
nbd update b38032 --status in_progress --json
```
Expected: JSON with `"status": "in_progress"`.
---
### Task 2: Create directory structure
**Step 1: Create binary directories**
```sh
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`**
```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**
```sh
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`**
```rust
//! 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**
```rust
//! 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**
```rust
//! 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**
```sh
cargo check
```
Expected: No errors. If the `common` path dependency causes issues, verify `../common` is correct relative to `quotesdb/`.
**Step 2: Run full validation**
```sh
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`:
```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`**
```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:
```markdown
# 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`**
```markdown
# 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**
```sh
ls README.md
```
**Step 2: Write consolidated `README.md`**
```markdown
# 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
```sh
cargo fmt && cargo check && cargo clippy && cargo test
```
## License
Licensed under either of [Apache License, Version 2.0](../LICENSE-APACHE) or [MIT License](../LICENSE-MIT) at your option.
## Disclaimer
This software was written with [Claude Code](https://claude.ai/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**
```sh
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**
```sh
cargo fmt
```
Expected: exits 0.
**Step 2: Check**
```sh
cargo check
```
Expected: exits 0, no errors.
**Step 3: Clippy**
```sh
cargo clippy
```
Expected: exits 0, no warnings (or only acceptable ones).
**Step 4: Test**
```sh
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**
```sh
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**
```sh
git commit -m "refactor(quotesdb): collapse to single crate with api and ui binaries"
```
---
### Task 15: Close ticket
**Step 1: Mark ticket done**
```sh
nbd update b38032 --status done --json
```
Expected: JSON with `"status": "done"`.

@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>QuotesDB</title> <title>QuotesDB</title>
<link data-trunk rel="rust" /> <link data-trunk rel="rust" data-bin="ui" />
</head> </head>
<body></body> <body></body>
</html> </html>

@ -0,0 +1,9 @@
//! 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 {}

@ -0,0 +1,9 @@
//! UI binary entrypoint.
//!
//! Compiled to WebAssembly via Trunk targeting `wasm32-unknown-unknown`.
//! Runs the Yew frontend application.
fn main() {}
#[cfg(test)]
mod tests {}

@ -0,0 +1,8 @@
//! 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.

@ -1,5 +0,0 @@
[package]
name = "quotesdb-tests"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"

@ -1,25 +0,0 @@
# quotesdb-tests
Integration test suite for the quotesdb service.
## What
HTTP-level integration tests that spin up the API server against an in-memory SQLite database and verify all endpoints.
## How
Separate Rust crate with tests in `tests/`. Each test suite covers a distinct endpoint or behaviour.
## Run
```sh
cargo test
```
## License
Licensed under either of [Apache License, Version 2.0](../../LICENSE-APACHE) or [MIT License](../../LICENSE-MIT) at your option.
## Disclaimer
This software was written with [Claude Code](https://claude.ai/claude-code) (claude-sonnet-4-6).

@ -1,9 +0,0 @@
# quotesdb-tests — Planning
## Development Phases
_To be filled in during Phase 1 planning._
## Work Log
_Updated as work progresses._

@ -1,14 +0,0 @@
[package]
name = "quotesdb-ui"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
common = { path = "../../common" }
[profile.release]
opt-level = "z"
lto = true
strip = true
codegen-units = 1

@ -1,31 +0,0 @@
# quotesdb-ui
Yew (Rust/Wasm) frontend for the quotesdb quotes service, hosted on Cloudflare Pages.
## What
A web UI for browsing, submitting, and managing quotes. Compiled to WebAssembly from Rust using the Yew framework.
## How
Built with Yew, compiled to `wasm32-unknown-unknown` via Trunk. Communicates with the quotesdb-api backend.
## Run
```sh
trunk serve
```
## Test
```sh
cargo fmt && cargo check && cargo clippy && cargo test
```
## License
Licensed under either of [Apache License, Version 2.0](../../LICENSE-APACHE) or [MIT License](../../LICENSE-MIT) at your option.
## Disclaimer
This software was written with [Claude Code](https://claude.ai/claude-code) (claude-sonnet-4-6).

@ -1,9 +0,0 @@
# quotesdb-ui — Architecture
## Component Overview
_To be filled in during Phase 1 planning._
## Component Interactions
_To be filled in during Phase 1 planning._

@ -1,9 +0,0 @@
# quotesdb-ui — Planning
## Development Phases
_To be filled in during Phase 1 planning._
## Work Log
_Updated as work progresses._

@ -1 +0,0 @@
fn main() {}

@ -1,2 +0,0 @@
#[cfg(test)]
mod tests {}
Loading…
Cancel
Save