diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..fa979de75 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,279 @@ +# radicle-desktop + +Tauri desktop app for Radicle. Svelte 5 frontend communicates with a Rust +backend via Tauri IPC commands. TypeScript types are generated from Rust +structs using ts-rs (`crates/radicle-types/`). + +## Plan mode + +- Propose the minimal MVP first — no tests, no docs, no speculative extras +- Tests are added only when explicitly requested after MVP review +- Keep plans concise: short bullet points listing what changes and where +- Skip obvious details and boilerplate explanations + +## Tech stack + +- Svelte 5 with runes syntax (`$state`, `$derived`, `$props`, `$effect`) +- TypeScript, Vite, Vitest, Playwright +- Rust backend: Tauri commands in `crates/radicle-tauri/` +- Types generated by ts-rs: `crates/radicle-types/` → `crates/radicle-types/bindings/` +- Path aliases: `@app` (src), `@bindings` (crates/radicle-types/bindings), `@tests` + +## Commands + +### Development + +```sh +npm run tauri dev # full Tauri dev build with hot reload +npm run start:http # Vite dev server backed by test-http-api (no Tauri runtime) +``` + +### Checks and linting (tsc, svelte-check, eslint, prettier) + +```sh +npm run check +npm run format # auto-fix prettier issues +``` + +### Unit tests + +```sh +npm run test:unit +``` + +### E2E tests + +```sh +npm run test:e2e -- --project webkit +npm run test:e2e -- --project webkit tests/e2e/.spec.ts +``` + +`SKIP_SETUP=true` skips fixture creation for faster iteration. +Only use it when you are solely editing `.spec.ts` files and fixtures +already exist from a previous full run. Any change to app code, the Rust +backend, or test fixtures requires a full run. + +```sh +SKIP_SETUP=true npm run test:e2e -- --project webkit +``` + +### Rust backend (`crates/`) + +```sh +scripts/check-rs # cargo fmt --check, clippy --workspace, check, test +``` + +### Regenerate TypeScript bindings + +Run after changing any type annotated with `#[derive(TS)]` in `crates/radicle-types/`: + +```sh +npm run generate-types +``` + +### Changes across layers + +Adding or changing a Tauri command touches these layers in order: + +1. `crates/radicle-types/src/` — add/update Rust types with `#[derive(TS)]` and + `#[ts(export)]`, run `npm run generate-types` to update `@bindings` +2. `crates/radicle-types/src/traits/` — add the method to the relevant port trait; + default implementation goes here, not in the command handler +3. `crates/radicle-tauri/src/commands/` — add the `#[tauri::command]` handler + (thin wrapper: `ctx.method()`), register in `crates/radicle-tauri/src/lib.rs` +4. `crates/test-http-api/src/` — mirror the same route so E2E tests keep working +5. `src/` — call via `invoke("command_name", args)`, import types from `@bindings` + +### Pre-push checklist + +These mirror what CI runs on every PR. Run them all before shipping a feature. + +```sh +npm run check # tsc, svelte-check, eslint, prettier, + # cargo fmt, cargo clippy, cargo test +npm run test:unit +npm run test:e2e -- --project webkit +``` + +If backend types changed, regenerate and commit the bindings first: + +```sh +npm run generate-types +``` + +**E2E prerequisite** — run once after cloning or when Radicle binaries are updated: + +```sh +./scripts/install-binaries +npm run build:http +``` + +## Backend architecture + +The Rust backend follows a hexagonal (ports & adapters) architecture split across +three crates: + +``` +crates/ +├── radicle-types/ # domain types, ports (traits), adapters, ts-rs bindings +├── radicle-tauri/ # Tauri driver — thin IPC wrappers around port methods +└── test-http-api/ # Axum HTTP driver — same ports, used by E2E tests +``` + +Both driver crates depend on `radicle-types` and share no logic between them. + +### radicle-types — ports and adapters + +**Ports** are Rust traits in `src/traits/`. Each trait has a full default +implementation that calls `self.profile()` to access the Radicle SDK directly. +`AppState` (the single concrete type shared by drivers) holds a +`radicle::Profile` and derives all behaviour from these default implementations: + +```rust +pub struct AppState { pub profile: radicle::Profile } +impl Repo for AppState {} // all methods come from the trait defaults +impl Issues for AppState {} +impl Patches for AppState {} +// ... +``` + +Current ports: `Profile`, `Repo`, `Cobs`, `Thread`, `Issues`, `IssuesMut`, +`Patches`, `PatchesMut`, `Jobs`. + +**Domain modules** in `src/domain/` go one step further: they define their own +storage port + a generic `Service` that wraps it, keeping domain logic +separate from the Radicle SDK. Currently implemented: + +- `domain/inbox/` — `InboxStorage` port + `Service` +- `domain/patch/` — `PatchStorage` port + `Service` + +**Outbound adapter** in `src/outbound/sqlite.rs`: a single `Sqlite` struct that +implements both `InboxStorage` and `PatchStorage` by querying the Radicle SQLite +COB cache and notifications DB (read-only, thread-safe). + +### radicle-tauri — Tauri driver + +Commands in `src/commands/` are thin wrappers: they receive +`tauri::State`, call the matching port method (e.g. `ctx.list_repos()`), +and return the result. No business logic lives here. + +Note: the domain `Service` types (`InboxService`, `PatchService`) are instantiated +in `startup.rs` but not yet wired into `AppState` — the Tauri commands currently +reach the same data through the trait default implementations. Completing this +wiring is future work. + +### test-http-api — HTTP driver + +Mirrors `radicle-tauri` as an Axum HTTP server. Used by Playwright E2E tests +(and `npm run start:http`) so the frontend can run without the Tauri runtime. +`Context` implements all the same port traits as `AppState`. + +## Radicle ecosystem (sibling repos) + +`crates/radicle-tauri` depends on crates from sibling repositories. Read +source from these paths when working on Rust code. + +### heartwood (`../heartwood`) + +Core Radicle protocol implementation. Key crates used by radicle-tauri: +- `radicle` — standard library (storage, identity, COBs, git, node) +- `radicle-cob` — collaborative objects (issues, patches as CRDTs) +- `radicle-crypto` — Ed25519 signing, SSH key handling +- `radicle-core` — fundamental types (`RepoId`, etc.) + +Key files: +- `../heartwood/HACKING.md` — development guide, environment variables +- `../heartwood/ARCHITECTURE.md` — high-level architecture +- `../heartwood/crates/radicle/src/lib.rs` — main library entry point + +### radicle-git (`../radicle-git`) + +Git library wrappers. Key crate: +- `radicle-surf` — code browsing (files, diffs, commits, branches, + tags). This is what radicle-tauri uses to serve repository content. + +Key file: `../radicle-git/radicle-surf/src/lib.rs` + +### radicle-job (`../radicle-job`) + +Decentralized job execution (CI/CD) on the Radicle network. +Key file: `../radicle-job/README.md` + +### RIPs — protocol specs (`../rips`) + +- `../rips/0001-heartwood.md` — protocol overview +- `../rips/0002-identity.md` — identity system (DIDs, Ed25519) +- `../rips/0003-storage-layout.md` — git storage layout + +### Radicle documentation (`../radicle.xyz`) + +Read these when you need domain context for UI work: +- `../radicle.xyz/_guides/user.md` — end-to-end user workflows + (init, clone, seed, issues, patches, code review, private repos) +- `../radicle.xyz/_guides/protocol.md` — protocol internals + (gossip, replication, identity documents, COB data model, trust) +- `../radicle.xyz/_guides/seeder.md` — seed node operation + (seeding policies, httpd setup, DNS-SD) +- `../radicle.xyz/_posts/2025-07-23-using-radicle-ci-for-development.md` — CI integration +- `../radicle.xyz/_posts/2025-08-12-canonical-references.md` — canonical refs design + +## Domain glossary + +- **RID** — Repository ID (`rad:z3gqc...`) +- **DID** — Decentralized Identifier, user identity (`did:key:z6Mk...`) +- **NID** — Node ID, public key suffix of a DID +- **COB** — Collaborative Object (issue, patch, or identity as a Git DAG) +- **Delegate** — authorized repo maintainer; signatures determine canonical state +- **Patch** — pull-request equivalent with immutable revisions and reviews +- **Seed** — hosting/replicating a repo; seed nodes are always-on servers +- **Canonical refs** — branches/tags resolved by delegate quorum + +## Code conventions + +- Prefer `undefined` over `null` +- Do not add comments unless explicitly asked. When writing comments, + use proper English sentences +- Ask before adding new dependencies + +### Svelte components + +- Script order (enforced by prettier): `