mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-02 18:20:29 +08:00
Trim CLAUDE.md and bring it in line with the code
This commit is contained in:
@@ -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] <command> <ms>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/<file>.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<T>("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<T>` that wraps it, keeping domain logic
|
||||
separate from the Radicle SDK. Currently implemented:
|
||||
|
||||
- `domain/inbox/` — `InboxStorage` port + `Service<I: InboxStorage>`
|
||||
- `domain/patch/` — `PatchStorage` port + `Service<I: PatchStorage>`
|
||||
|
||||
**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<AppState>`, 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): `<script lang="ts" module>`,
|
||||
`<script lang="ts">`, `<style>`, markup
|
||||
- Props use `$props()`: `const { foo, bar = undefined }: Props = $props()`
|
||||
- CSS: scoped styles with design tokens (`var(--color-text-primary)`,
|
||||
`var(--txt-body-m-regular)`, `var(--border-radius-sm)`); use `:global()`
|
||||
@@ -290,13 +173,6 @@ Read these when you need domain context for UI work:
|
||||
|
||||
### Rust backend
|
||||
|
||||
- Business logic belongs in trait default implementations (`crates/radicle-types/src/traits/`),
|
||||
not in command handlers
|
||||
- Tauri command signature: `pub fn cmd(ctx: tauri::State<AppState>, ...) -> Result<T, Error>`
|
||||
- Commands are registered in `crates/radicle-tauri/src/lib.rs` via `tauri::generate_handler![...]`
|
||||
- Mirror every new command in `crates/test-http-api/src/` so E2E tests continue to work.
|
||||
A command that can't work over HTTP goes in `TAURI_ONLY` in
|
||||
`crates/test-http-api/tests/parity.rs`, with the reason
|
||||
- All serialized types live in `crates/radicle-types/src/`; annotate new types
|
||||
with `#[derive(Serialize, TS)]`, `#[serde(rename_all = "camelCase")]`,
|
||||
`#[ts(export)]`, and `#[ts(export_to = "<dir>/")]`
|
||||
@@ -316,4 +192,3 @@ Read these when you need domain context for UI work:
|
||||
- Do not use `npm test` — no default test script exists
|
||||
- Do not use `yarn` or `pnpm` — use npm
|
||||
- Do not use `npx vitest` or `npx playwright test` — use the `npm run` scripts
|
||||
- Do not mix legacy Svelte syntax (`export let`, `$:`) with runes in the same component
|
||||
|
||||
Reference in New Issue
Block a user