From bd57ef824f8b771f8a01b92aacdcf03454487901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C5=ABdolfs=20O=C5=A1i=C5=86=C5=A1?= Date: Thu, 1 Oct 2026 19:55:22 +0200 Subject: [PATCH] Trim CLAUDE.md and bring it in line with the code --- CLAUDE.md | 185 +++++++++--------------------------------------------- 1 file changed, 30 insertions(+), 155 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index cc4efdcc9..ca7463347 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,19 +4,10 @@ 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 @@ -25,16 +16,17 @@ structs using ts-rs (`crates/radicle-types/`). ```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) +npm run start:http # test-http-api backend on :8081 (no Tauri runtime) +npm start # Vite dev server; in a browser it talks to test-http-api ``` Set `VITE_LOG_INVOKE_TIMINGS=true` to log each command's duration (`[invoke] ms`) to the browser console. It is off by default. -### Checks and linting (svelte-check, eslint, prettier) +### Checks and linting ```sh -npm run check +npm run check # svelte-check, eslint, prettier, then scripts/check-rs npm run format # auto-fix prettier issues ``` @@ -56,6 +48,9 @@ npm run test:e2e npm run test:e2e -- tests/e2e/.spec.ts ``` +Run `./scripts/install-binaries` once after cloning and whenever the Radicle +binaries are updated. + Peer keys have no passphrase, so the app authenticates without an ssh-agent. Tests that need a running node use the `peer` fixture; tests that need an ssh-agent use the `sshAuthSock` fixture and create a peer with a @@ -91,7 +86,8 @@ scripts/check-rs # cargo fmt --check, clippy on all targets, test ### Regenerate TypeScript bindings -Run after changing any type annotated with `#[derive(TS)]` in `crates/radicle-types/`: +Run after changing any type annotated with `#[derive(TS)]` in +`crates/radicle-types/`, and commit the regenerated bindings: ```sh npm run generate-types @@ -108,7 +104,9 @@ Adding or changing a Tauri command touches these layers in order: 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; - `cargo test -p test-http-api --test parity` fails until you do + `cargo test -p test-http-api --test parity` fails until you do. A command + that can't work over HTTP goes in `TAURI_ONLY` in + `crates/test-http-api/tests/parity.rs`, with the reason 5. `src/` — call via `invoke("command_name", args)`, import types from `@bindings` ### Tests for a feature @@ -119,147 +117,32 @@ in `docs/adr/0001-testing-strategy.md`. ### Pre-push checklist -`npm run check:ci` runs everything CI runs on every PR, in order, and stops -at the first failure. It writes the full output, with each step's duration -and exit code, to `tests/tmp/check-ci.log`: - -```sh -npm run check # svelte-check, eslint, prettier, - # cargo fmt, cargo clippy, cargo test -npm run test:unit -scripts/install-binaries # skipped when already installed -npm run test:e2e -``` - -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 check:ci` runs everything CI runs on every PR (checks, unit tests, +`install-binaries`, E2E tests) and stops at the first failure. It writes the +full output, with each step's duration and exit code, to +`tests/tmp/check-ci.log`. ## Backend architecture -The Rust backend follows a hexagonal (ports & adapters) architecture split across -three crates: +Three crates in a hexagonal architecture; the reasoning is in +`docs/adr/0002-backend-architecture.md`. -``` -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 -``` +- `radicle-types` — domain types, ports (`src/traits/`), domain services + (`src/domain/`), the SQLite adapter (`src/outbound/`), ts-rs bindings +- `radicle-tauri` — Tauri driver; `AppState` implements the ports +- `test-http-api` — HTTP driver for E2E tests and `npm run start:http`; + `Context` implements the same ports -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.dev`) - -Read these when you need domain context for UI work: -- `../radicle.dev/_guides/user.md` — end-to-end user workflows - (init, clone, seed, issues, patches, code review, private repos) -- `../radicle.dev/_guides/protocol.md` — protocol internals - (gossip, replication, identity documents, COB data model, trust) -- `../radicle.dev/_guides/seeder.md` — seed node operation - (seeding policies, httpd setup, DNS-SD) -- `../radicle.dev/_posts/2025-07-23-using-radicle-ci-for-development.md` — CI integration -- `../radicle.dev/_posts/2025-08-12-canonical-references.md` — canonical refs design +Ports have full default implementations, so a driver only implements +`Profile`. Paginated list views (issues, patches, notifications) go through +a domain `Service` backed by the SQLite adapter instead of the ports; new +lists over large data sets should do the same. ## Domain glossary -- **RID** — Repository ID (`rad:z3gqc...`) -- **DID** — Decentralized Identifier, user identity (`did:key:z6Mk...`) -- **NID** — Node ID, public key suffix of a DID +- **NID** — Node ID, the public key in a DID (`did:key:z6Mk...`) - **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 @@ -269,11 +152,11 @@ Read these when you need domain context for UI work: - Do not add comments unless explicitly asked. When writing comments, use proper English sentences - Ask before adding new dependencies +- When proposing a bigger architectural change, record it as a new ADR in + `docs/adr/`, numbered after the last one and in the same format ### Svelte components -- Script order (enforced by prettier): `