Runs the checks from the GitHub workflows in one go, like desktop's `scripts/check-ci`, and logs them to `tests/tmp/check-ci.log`.
8.3 KiB
radicle-gui
Monorepo for Radicle's graphical interfaces. It holds two independent projects, each with its own npm package, Cargo workspace, toolchain files, checks and conventions:
- Explorer (repository root): the Svelte web app for browsing Radicle
repositories, its TypeScript HTTP client, and the
radicle-httpdandradicle-searchbackends it talks to. - Desktop (
desktop/): the Tauri desktop app. Its owndesktop/CLAUDE.mdis the source of truth for anything underdesktop/.
Layout
| Path | Project | What it is |
|---|---|---|
src/, public/, tests/ |
explorer | Svelte web app and its unit, e2e and visual tests |
http-client/ |
explorer | TypeScript client for the httpd API |
crates/radicle-httpd/ |
explorer | HTTP daemon serving the API (Rust), with its own CLAUDE.md for code conventions |
crates/radicle-search/ |
explorer | Search service, released together with httpd (Rust) |
workers/ |
explorer | Cloudflare workers (open-graph images) |
scripts/ |
explorer | Check, CI and e2e helper scripts |
desktop/ |
desktop | Tauri app: Svelte frontend in desktop/src/, Rust in desktop/crates/ |
.github/workflows/ |
both | GitHub CI: check*.yml for explorer, desktop-*.yml for desktop |
.buildkite/ |
explorer | Radicle CI pipelines |
.ryph/ |
both | Dev machine image and setup for agents, see .ryph/environment.md |
Working in this repo
- Work out which project a change belongs to first, and run every command
from that project's root: the repository root for explorer,
desktop/for desktop. Rootnpmandcargocommands never coverdesktop/, and the root lint and format checks skip it on purpose. - The two projects don't share code. A change that touches both is two changes: check each project with its own commands.
- The rules under "Explorer" below don't apply in
desktop/. In particular, explorer uses legacy Svelte syntax while desktop uses Svelte 5 runes.
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
Shared conventions
- Prefer
undefinedovernull - Do not add comments unless explicitly asked. When writing comments, use proper English sentences
- Ask before adding new dependencies
- Use npm, never
yarnorpnpm - Do not use
npm test— neither project has a default test script - Do not use
npx vitestornpx playwright test— use thenpm runscripts
Commit messages
- Imperative mood: "Add feature" not "Added feature"
- Capitalize subject, no trailing period, max 50 chars
- Prefix backend-only commits with
httpd:
Releases and tags
Each project is released on its own. Release tags are
releases/<component>/<version>; the refs/tags/releases/* rule in the
repository's identity decides when they become canonical.
| Component | How it's released | Tag |
|---|---|---|
| Explorer web app | Deployed continuously, no releases or tags | — |
radicle-httpd + radicle-search |
Together, one version, see RELEASE.md |
releases/httpd/<version> |
| Desktop app | desktop/scripts/release, see desktop/README.md |
releases/desktop/<version> |
The older releases/<version> tags (httpd 0.22.0–0.29.0, desktop
0.9.0–0.16.0) are left over from before the two repositories were merged.
Never create, match or describe against them; scripts must always match a
component's own prefix.
Radicle ecosystem (sibling repos)
See the radicle-ecosystem skill for the map of sibling repositories
(heartwood, radicle-git, radicle-job, rips, radicle.dev) and the key
crates and files in each.
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
Explorer (repository root)
Everything in this section applies to the repository root only. Run all commands from there.
Tech stack
- Svelte 5 with legacy syntax (
$:, stores,export let). Do not use Svelte 5 runes ($state,$derived,$props) here - TypeScript, Vite, Vitest, Playwright, Zod
- Rust for
crates/radicle-httpdandcrates/radicle-search
Checks and linting (tsc, svelte-check, eslint, prettier)
npm run check
npm run format # auto-fix prettier issues
Unit tests
npm run test:unit
npm run test:http-client:unit
E2E tests
npm run test:e2e -- --project chromium
npm run test:e2e -- tests/e2e/<file>.spec.ts --project chromium
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, httpd,
or test fixtures requires a full run.
SKIP_SETUP=true npm run test:e2e -- --project chromium
Rust backend (crates/)
The same commands CI runs:
RUSTFLAGS="-D warnings" cargo clippy --workspace --all-features --tests
RUSTFLAGS="-D warnings" cargo clippy -p radicle-httpd --no-default-features --tests
cargo fmt --all -- --check
cargo test --workspace --all-features
cargo test -p radicle-httpd
E2E and http-client tests run against a stable pre-built httpd by
default. When changing crates/radicle-httpd/, also test against the
local build and run both to verify backwards compatibility:
npm run test:e2e:local -- --project chromium
npm run test:http-client:unit:local
API changes across layers
Adding or changing an API endpoint typically touches three layers:
crates/radicle-httpd/— Rust handler (add route, types, logic)http-client/— TypeScript client (add fetch function, types)src/— Svelte UI (consume the new data)
Backwards compatibility: Only add to the backend API — never rename or remove fields/endpoints unless explicitly deprecating. The frontend must handle older nodes gracefully: treat new API fields as optional and fall back when they're absent.
Pre-push checklist
npm run check:ci runs everything below except the :local variants, plus
the build smoke test, from the GitHub workflows, and stops at the first
failure. It logs each step's output, duration and exit code to
tests/tmp/check-ci.log.
npm run checknpm run test:unitnpm run test:http-client:unitnpm run test:e2e -- --project chromium- If Rust code changed: clippy, fmt, cargo test (see above),
plus
:localvariants of e2e and http-client tests
Svelte components
- Script order (enforced by prettier):
<script context="module">,<script lang="ts">,<style>, markup - Optional props use
export let foo: T = undefined, not?: - CSS: scoped styles with design tokens (
var(--color-text-primary),var(--txt-body-m-regular),var(--border-radius-sm)); use:global()for styling slotted or{@html}content - Loading states:
{#await promise}blocks for inline async; localloadingboolean withtry/catchfor imperative fetches
{@html} and untrusted data (XSS)
- Avoid
{@html}with backend-derived data (branch/ref names, aliases, descriptions); when unavoidable, its argument must be DOMPurify-sanitized (sanitizeConfiginsrc/lib/markdown.ts) or built fromescape()'d parts, in the component that renders it.
TypeScript / HTTP client
- Derive types from Zod schemas (
z.infer<typeof fooSchema>), never hand-write interfaces that duplicate a schema - Schema naming:
fooSchema(camelCase + Schema suffix) → typeFoo - Use ES private fields (
#field), not the TypeScriptprivatekeyword - API timestamps are in seconds; multiply by 1000 for JS
Date - Relative imports inside
http-client/use.jsextension (ESM)
Desktop (desktop/)
See desktop/CLAUDE.md. In short: Tauri with a Svelte 5 frontend that uses
runes, a hexagonal Rust backend in desktop/crates/, and its own checks
(npm run check:ci from desktop/).