Files
radicle-explorer/explorer/AGENTS.md
T
Daniel Norman 0dc1e557a5 Move repo facts outside the project payload
Repos need not carry a `xyz.radicle.project` payload; the default
branch can come from `xyz.radicle.crefs` alone. Such repos got no
counts and no default branch, and failed schema validation in
explorer, which broke the parse of a whole listing.

httpd:
- Add top-level `cobs` with issue, patch and bucketed release counts
- Add top-level `defaultBranch`, qualified like the crefs payload
- Omit a count the node cannot determine instead of reporting zero
- Drop `head`: the tip is `refs[defaultBranch]`
- Keep counts in the project payload for older clients until a
  major release
- Keep the project payload when a COB read fails
- Keep `meta.releases` a single count: delegate + other
- Log and skip canonical ref rules and refs that fail to read,
  so one bad ref doesn't drop the default branch tip

explorer:
- Model `cobs` and `defaultBranch`, adapting older nodes to one shape
- Resolve the default branch from the crefs HEAD on older nodes
- Collect the shims for older httpd in http-client/lib/compat.ts,
  each naming the httpd release that the client can drop it with
- Add `defaultBranchTip`, replacing `head`, with a fallback to the
  legacy `meta.head`
- Make the project payload and its legacy meta optional
- Fall back to the RID for the repo name, and show a placeholder
  for a missing name in repo cards and the header
- Hide a counter instead of reading a missing count as zero
- Fall back to a full-page check for "More" without counts
- Add a `repo.sourceUnavailable` route for repos without a
  default branch or tip
- Browse explicit revisions and offer the branch selector without
  a default branch
- List these repos in repo grids and on the landing page
- Say the node is older or built without releases
- Scope release pages in the client, so views never call the shims
- Read OG worker repos through the same compat shims
- Share the repo name and default branch helpers with the OG worker

CI:
- Add check:open-graph, a wrangler deploy dry run
- Run it in GitHub CI, Buildkite and check:ci
- Install explorer's dependencies first: the worker
  resolves compat.ts imports, such as zod, from there

Tests:
- Test that a build without releases omits the count
- Test that one failing rule keeps the others
- Test the repo shape across httpd versions and the release shims
- Test `hasMorePages` and `repoName`
- Test repos without a project payload end to end
2026-10-29 18:21:09 +01:00

3.7 KiB

radicle-explorer

The Svelte web app for browsing Radicle repositories, its TypeScript HTTP client, and the radicle-httpd and radicle-search backends it talks to.

The project lives in explorer/ of the radicle-gui monorepo. It is a package in the root npm workspace and its own Cargo workspace: install npm dependencies at the repository root with npm ci -w explorer, and run every other command here from explorer/. All paths in this file are relative to it. Conventions shared with desktop are in the root AGENTS.md, and UI code shared with it lives in shared/ui/.

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:

  1. crates/radicle-httpd/ — Rust handler (add route, types, logic)
  2. http-client/ — TypeScript client (add fetch function, types)
  3. 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. Put that fallback in http-client/lib/compat.ts, tagged with when it can go (Client: remove when explorer drops httpd 0.30.). Breaking changes go through expand and contract, see docs/adr/0001-expand-and-contract-api-changes.md, and get an entry in docs/api-changes.md.

Pre-push checklist

npm run check:ci runs the checks, unit, http-client, e2e and build smoke tests and the Open Graph worker bundle 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. If Rust code changed, also run the Rust commands and the :local variants above.

Svelte components

  • Optional props use export let foo: T = undefined, not ?:

{@html} and untrusted data (XSS)

  • Avoid {@html} with backend-derived data (branch/ref names, aliases, descriptions); when unavoidable, its argument must be DOMPurify-sanitized (sanitizeConfig in src/lib/markdown.ts) or built from escape()'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) → type Foo
  • API timestamps are in seconds; multiply by 1000 for JS Date
  • Relative imports inside http-client/ use .js extension (ESM)