diff --git a/explorer/AGENTS.md b/explorer/AGENTS.md index 87dc4f91b..d3b71f916 100644 --- a/explorer/AGENTS.md +++ b/explorer/AGENTS.md @@ -3,10 +3,10 @@ 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 its own -npm package and Cargo workspace: run every command here from `explorer/`, -and all paths in this file are relative to it. Conventions shared with -desktop are in the root `AGENTS.md`. +The project lives in `explorer/` of the radicle-gui monorepo. It is its own npm +package and Cargo workspace: run every command here from `explorer/`, and all +paths in this file are relative to it. Conventions shared with desktop are in +the root `AGENTS.md`. ## Checks and linting (tsc, svelte-check, eslint, prettier) @@ -29,10 +29,10 @@ npm run test:e2e -- --project chromium npm run test:e2e -- tests/e2e/.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` 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. ```sh SKIP_SETUP=true npm run test:e2e -- --project chromium @@ -50,9 +50,9 @@ 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: +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: ```sh npm run test:e2e:local -- --project chromium @@ -62,22 +62,22 @@ 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. +**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 the checks, unit, http-client, e2e and build smoke -tests 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. +`npm run check:ci` runs the checks, unit, http-client, e2e and build smoke tests +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 @@ -87,13 +87,13 @@ above. - 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. + (`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`), never - hand-write interfaces that duplicate a schema +- Derive types from Zod schemas (`z.infer`), 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) diff --git a/explorer/CHANGELOG.md b/explorer/CHANGELOG.md index 6ce19c783..834a60729 100644 --- a/explorer/CHANGELOG.md +++ b/explorer/CHANGELOG.md @@ -2,54 +2,97 @@ ## radicle-httpd + radicle-search 0.30.0 -- **`tar.zst` archive downloads**: `/raw/{rid}/archive/{ref}.tar.zst` joins the tar, tar.gz and zip formats; it needs the `zstd` binary in `PATH` -- **Archive ref errors**: Unknown refs now return 404 and malformed ones 400, instead of 500 -- **Runtime log format**: `RADICLE_HTTPD_LOG_FORMAT` selects `text` (default) or `json` logs at startup, with the access log emitted as structured fields in JSON; this replaces the `logfmt` build feature -- **Batched search reindexing**: radicle-search ignores seed events and reindexes each touched repo once per batch (`RADICLE_SEARCH_BATCH_SECS`, default 30s), so a flapping peer no longer pins meilisearch at 100% CPU -- Updated `rustls-webpki` and `webpki-roots` to clear advisories reported by `cargo audit` +- **`tar.zst` archive downloads**: `/raw/{rid}/archive/{ref}.tar.zst` joins the + tar, tar.gz and zip formats; it needs the `zstd` binary in `PATH` +- **Archive ref errors**: Unknown refs now return 404 and malformed ones 400, + instead of 500 +- **Runtime log format**: `RADICLE_HTTPD_LOG_FORMAT` selects `text` (default) or + `json` logs at startup, with the access log emitted as structured fields in + JSON; this replaces the `logfmt` build feature +- **Batched search reindexing**: radicle-search ignores seed events and + reindexes each touched repo once per batch (`RADICLE_SEARCH_BATCH_SECS`, + default 30s), so a flapping peer no longer pins meilisearch at 100% CPU +- Updated `rustls-webpki` and `webpki-roots` to clear advisories reported by + `cargo audit` ## radicle-httpd + radicle-search 0.29.0 -- **Complete commit lists for merge-commit diffs**: The diff endpoint no longer stops the walk early when the head merges the target branch in, which reduced such patches to a single commit +- **Complete commit lists for merge-commit diffs**: The diff endpoint no longer + stops the walk early when the head merges the target branch in, which reduced + such patches to a single commit ## radicle-httpd + radicle-search 0.28.0 -- **Releases API**: New `/repos/{rid}/releases` endpoints serve radicle-artifact release COBs, with Releases and Release views in the UI that degrade gracefully on older nodes -- **`Git-Protocol` header passed through**: Git HTTP routes forward the header to `git-http-backend`, fixing clients that require Git wire protocol version 2 -- Updated `chacha20`, `h2` and `lru` to clear advisories reported by `cargo audit` +- **Releases API**: New `/repos/{rid}/releases` endpoints serve radicle-artifact + release COBs, with Releases and Release views in the UI that degrade + gracefully on older nodes +- **`Git-Protocol` header passed through**: Git HTTP routes forward the header + to `git-http-backend`, fixing clients that require Git wire protocol version 2 +- Updated `chacha20`, `h2` and `lru` to clear advisories reported by + `cargo audit` ## radicle-httpd + radicle-search 0.27.0 -- **Commit-count endpoint**: New `/repos/{rid}/stats/commits/{sha}` counts reachable commits from the commit-graph, avoiding the full history walk of `/stats/tree` -- **Diff-stats endpoint**: New `/repos/{rid}/diff/{base}/{oid}/stats` returns line counts without building hunks, 6–15x faster than `/diff`; it skips rename detection, so counts can differ slightly on renamed or copied files -- **Faster file browsing**: Blob requests no longer walk the whole history to find a file's last commit, which took seconds to minutes on large repositories -- **Repository reads off the async workers**: Synchronous libgit2 and COB reads run on the blocking pool, so one slow read no longer stalls queued requests -- **Repo aliases in the JSON and raw APIs**: `--alias` path segments resolve there too, not just on the git routes, and the repo endpoint exposes an optional `alias` field -- **Higher open-file limit**: Raised at startup, fixing spurious "missing object" errors in storage with thousands of packfiles +- **Commit-count endpoint**: New `/repos/{rid}/stats/commits/{sha}` counts + reachable commits from the commit-graph, avoiding the full history walk of + `/stats/tree` +- **Diff-stats endpoint**: New `/repos/{rid}/diff/{base}/{oid}/stats` returns + line counts without building hunks, 6–15x faster than `/diff`; it skips rename + detection, so counts can differ slightly on renamed or copied files +- **Faster file browsing**: Blob requests no longer walk the whole history to + find a file's last commit, which took seconds to minutes on large repositories +- **Repository reads off the async workers**: Synchronous libgit2 and COB reads + run on the blocking pool, so one slow read no longer stalls queued requests +- **Repo aliases in the JSON and raw APIs**: `--alias` path segments resolve + there too, not just on the git routes, and the repo endpoint exposes an + optional `alias` field +- **Higher open-file limit**: Raised at startup, fixing spurious "missing + object" errors in storage with thousands of packfiles - **Empty job COBs excluded**: Job listings omit jobs with no runs -- **`RUST_LOG` in the default logger**: Honoured without the `logfmt` feature, defaulting to `info` +- **`RUST_LOG` in the default logger**: Honoured without the `logfmt` feature, + defaulting to `info` - Updated radicle crates, cargo dependencies and the Rust toolchain (1.97.1) ## radicle-httpd + radicle-search 0.26.0 -- **Repository search**: New optional `radicle-search` indexing daemon backs a `/repos/search?q=…` endpoint for typo-tolerant search over repo names and descriptions, plus faster `/repos?sort=activity|seeding` listings; httpd falls back to its built-in storage walk when the daemon is absent, so existing behavior is unchanged -- **Git archive downloads**: Additional formats ZIP and TAR are now supported. Also, a prefix is added to the archive by default, which can be disabled by using the query `?prefix=false`. This query string is immediately deprecated. It only exists as a workaround for users to adapt their download scripts etc. to accommodate for the prefix. It will be removed in a future version. -- **Streaming archive downloads**: Archives are streamed to the client instead of being buffered in memory, avoiding excessive memory use on large repositories -- **Faster archive HEAD requests**: `HEAD` requests no longer build an archive that is then discarded -- **Streaming git operations**: Clone, fetch, and push stream request and response bodies and accept gzip-compressed requests, reducing memory use on large operations +- **Repository search**: New optional `radicle-search` indexing daemon backs a + `/repos/search?q=…` endpoint for typo-tolerant search over repo names and + descriptions, plus faster `/repos?sort=activity|seeding` listings; httpd falls + back to its built-in storage walk when the daemon is absent, so existing + behavior is unchanged +- **Git archive downloads**: Additional formats ZIP and TAR are now supported. + Also, a prefix is added to the archive by default, which can be disabled by + using the query `?prefix=false`. This query string is immediately deprecated. + It only exists as a workaround for users to adapt their download scripts etc. + to accommodate for the prefix. It will be removed in a future version. +- **Streaming archive downloads**: Archives are streamed to the client instead + of being buffered in memory, avoiding excessive memory use on large + repositories +- **Faster archive HEAD requests**: `HEAD` requests no longer build an archive + that is then discarded +- **Streaming git operations**: Clone, fetch, and push stream request and + response bodies and accept gzip-compressed requests, reducing memory use on + large operations ## radicle-httpd 0.25.0 -- **Canonical and peer refs in repo API**: The repo endpoint now exposes canonical refs and per-peer refs, letting clients discover branches and tags resolved by delegate quorum as well as those from individual nodes -- **Job COBs endpoint**: New `/repos/{rid}/jobs/{sha}` endpoint serves job collaborative objects, enabling clients to query CI/CD job state +- **Canonical and peer refs in repo API**: The repo endpoint now exposes + canonical refs and per-peer refs, letting clients discover branches and tags + resolved by delegate quorum as well as those from individual nodes +- **Job COBs endpoint**: New `/repos/{rid}/jobs/{sha}` endpoint serves job + collaborative objects, enabling clients to query CI/CD job state ## radicle-httpd 0.24.0 -- **Windows support**: The HTTP daemon now runs on Windows with platform-specific adaptations +- **Windows support**: The HTTP daemon now runs on Windows with + platform-specific adaptations - **Faster API responses**: Improved `/node` endpoint performance -- **Git namespace paths**: Direct access to node ref namespaces via `/{rid}.git/{nid}/` remote URLs, simplifying clones and fetches from specific nodes +- **Git namespace paths**: Direct access to node ref namespaces via + `/{rid}.git/{nid}/` remote URLs, simplifying clones and fetches from specific + nodes - Updated dependencies and internal improvements ## radicle-httpd 0.23.0 -- **Live config reload**: Update configuration without restarting by sending a SIGHUP signal +- **Live config reload**: Update configuration without restarting by sending a + SIGHUP signal diff --git a/explorer/README.md b/explorer/README.md index 226a98d7f..f4a0ba64b 100644 --- a/explorer/README.md +++ b/explorer/README.md @@ -9,20 +9,20 @@ It lives in the `explorer/` folder of the radicle-gui repository, next to the **Deployment Options:** -* **Public Server:** The Radicle team maintains a public instance at [radicle.network][app] -* **Local-first:** Run the UI locally -* **Self-hosting:** Deploy the UI on your own server - +- **Public Server:** The Radicle team maintains a public instance at + [radicle.network][app] +- **Local-first:** Run the UI locally +- **Self-hosting:** Deploy the UI on your own server ## Local-first **Prerequisites:** -* Recent versions of [Node.js][nod] (24 or higher) and [npm][npm] installed +- Recent versions of [Node.js][nod] (24 or higher) and [npm][npm] installed Run the following commands to access the web UI locally: -``` shell +```shell git clone https://seed.radicle.dev/z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git radicle-gui cd radicle-gui/explorer npm install @@ -31,7 +31,6 @@ npm start Then open http://localhost:3000 in your favourite browser. - ## Self-Hosting the UI There are several ways to deploy the UI publicly. Here are two common options: @@ -68,8 +67,8 @@ There are two ways to configure the UI: **at build time** and **at run time**. ### Build-Time Configuration -This method is recommended when deploying to static hosting platforms such -as Vercel. +This method is recommended when deploying to static hosting platforms such as +Vercel. #### Option 1: Create a `local.json` file @@ -86,7 +85,6 @@ as Vercel. > For advanced configuration options, refer to the [`node-config`][nco] > documentation. - ### Run-Time Configuration This method is useful when the app is distributed as a precompiled static @@ -105,42 +103,40 @@ VITE_RUNTIME_CONFIG=true npm run build This will inject a blocking script into the `index.html` that attempts to load the configuration from a pre-defined location (`/config.json`) on the server. -The config file must be served as static content and must be publicly accessible. -The structure of the runtime `config.json` must match the shape of the -application's base configuration defined in `config/default.json`. - +The config file must be served as static content and must be publicly +accessible. The structure of the runtime `config.json` must match the shape of +the application's base configuration defined in `config/default.json`. ### Homepage The `nodes.homepage` key controls what is rendered at the root URL (`/`): -* `"node"` (default) — show the node view of the user's primary seed - directly, as if the user had navigated to `/nodes/`. The - primary seed is resolved from `preferredSeeds` (see [`default.json`][def]). - This is the default so that a self-hosted deployment acts as a dedicated - front-end for its own seed. -* `"landing"` — serve the full Radicle marketing site: the landing page and - its `/learn`, `/install` and documentation (`/glossary`, `/guides/*`) - sub-routes. This is opt-in and intended for the canonical - [radicle.network][app] deployment; the marketing sub-routes are only - reachable in this mode. +- `"node"` (default) — show the node view of the user's primary seed directly, + as if the user had navigated to `/nodes/`. The primary seed is + resolved from `preferredSeeds` (see [`default.json`][def]). This is the + default so that a self-hosted deployment acts as a dedicated front-end for its + own seed. +- `"landing"` — serve the full Radicle marketing site: the landing page and its + `/learn`, `/install` and documentation (`/glossary`, `/guides/*`) sub-routes. + This is opt-in and intended for the canonical [radicle.network][app] + deployment; the marketing sub-routes are only reachable in this mode. -The cross-seed Explore page is always available at `/explore`, regardless of -the `homepage` setting. +The cross-seed Explore page is always available at `/explore`, regardless of the +`homepage` setting. #### radicle.network deployment -`default.json` keeps `homepage` set to `"node"`, so self-hosted deployments -are unaffected. The canonical radicle.network deployment opts into the -marketing site at **build time** without changing that default. To deploy -radicle.network, run `npm run deploy`. +`default.json` keeps `homepage` set to `"node"`, so self-hosted deployments are +unaffected. The canonical radicle.network deployment opts into the marketing +site at **build time** without changing that default. To deploy radicle.network, +run `npm run deploy`. #### Node-homepage deployment -If you operate your own seed node and want the explorer to act as a -dedicated front-end for it (the default behaviour), put your own node first -(and ideally as the only entry) in `preferredSeeds` so it is always picked -as the primary seed. +If you operate your own seed node and want the explorer to act as a dedicated +front-end for it (the default behaviour), put your own node first (and ideally +as the only entry) in `preferredSeeds` so it is always picked as the primary +seed. Example `local.json`: @@ -156,26 +152,25 @@ Example `local.json`: } ``` -With this configuration, opening `/` lands the visitor on the node view -for `seed.example.com`. Keeping `preferredSeeds` to a single entry -ensures the primary seed is deterministic; if you list multiple, users -are bucketed across them and may land on a different node. - +With this configuration, opening `/` lands the visitor on the node view for +`seed.example.com`. Keeping `preferredSeeds` to a single entry ensures the +primary seed is deterministic; if you list multiple, users are bucketed across +them and may land on a different node. ## Repository aliases -`radicle-httpd` can map a short, human-readable alias to a repository ID -(RID), so a repository can be referred to by name instead of its full -`rad:` identifier. Aliases are configured on the node with the -`--alias ` flag, which may be repeated: +`radicle-httpd` can map a short, human-readable alias to a repository ID (RID), +so a repository can be referred to by name instead of its full `rad:` +identifier. Aliases are configured on the node with the `--alias ` +flag, which may be repeated: ```sh radicle-httpd --alias heartwood rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 ``` -An alias works anywhere the RID is accepted: `git clone`, the HTTP API, the -raw endpoints, and Explorer URLs — the router resolves it transparently. -With the alias above, these two Explorer URLs point at the same repository: +An alias works anywhere the RID is accepted: `git clone`, the HTTP API, the raw +endpoints, and Explorer URLs — the router resolves it transparently. With the +alias above, these two Explorer URLs point at the same repository: ``` https://radicle.network/nodes/seed.example.com/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5/issues @@ -188,7 +183,6 @@ and the repository can be cloned over Git by its alias: git clone https://seed.example.com/heartwood.git ``` - ## Development Run `npm run check` before proposing a patch, along with the test suites: @@ -204,44 +198,40 @@ SKIP_SETUP=true npm run test:e2e -- tests/e2e/repo/commits.spec.ts:90 --project ``` When changing `radicle-httpd`, `npm run test:e2e:local` compiles it from -`crates/radicle-httpd/` and runs the suite against that build instead of -the released binary. - -If Playwright's browsers don't support your OS, `scripts/test-e2e-docker` -runs the suite in a container (Docker with the Compose plugin). It forwards -its arguments and `SKIP_SETUP` to `npm run test:e2e`, and `--local` tests -against httpd built from source. The first run downloads Chromium and is -slow. +`crates/radicle-httpd/` and runs the suite against that build instead of the +released binary. +If Playwright's browsers don't support your OS, `scripts/test-e2e-docker` runs +the suite in a container (Docker with the Compose plugin). It forwards its +arguments and `SKIP_SETUP` to `npm run test:e2e`, and `--local` tests against +httpd built from source. The first run downloads Chromium and is slow. ## Contributing -See [CONTRIBUTING.md][con] for how to propose changes and write commit -messages. Open an [issue][iss] or a [patch][pat] using Radicle. - +See [CONTRIBUTING.md][con] for how to propose changes and write commit messages. +Open an [issue][iss] or a [patch][pat] using Radicle. ## Getting in touch -To get in touch with the maintainers, sign up to our -[official chat on Zulip][zul]. - +To get in touch with the maintainers, sign up to our [official chat on +Zulip][zul]. ## License The UI is distributed under the terms of GPLv3. See [LICENSE][lic] for details. - - [app]: https://radicle.network [con]: ../CONTRIBUTING.md [def]: ./config/default.json [env]: ./config/custom-environment-variables.json -[iss]: https://radicle.network/nodes/iris.radicle.network/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5/issues +[iss]: + https://radicle.network/nodes/iris.radicle.network/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5/issues [lic]: ./LICENSE [nco]: https://github.com/node-config/node-config/wiki/Configuration-Files [nod]: https://nodejs.org [npm]: https://www.npmjs.com -[pat]: https://radicle.network/nodes/iris.radicle.network/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5/patches +[pat]: + https://radicle.network/nodes/iris.radicle.network/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5/patches [rad]: https://radicle.dev [try]: https://caddyserver.com/docs/caddyfile/patterns#single-page-apps-spas [zul]: https://radicle.zulipchat.com/#narrow/stream/369278-web diff --git a/explorer/RELEASE.md b/explorer/RELEASE.md index f3a0fc908..cba6d64c6 100644 --- a/explorer/RELEASE.md +++ b/explorer/RELEASE.md @@ -3,10 +3,10 @@ `radicle-httpd` and `radicle-search` are released **together** from this workspace: a single tag, a single changelog, and one build run that produces artifacts for both binaries. They share the same version number, bumped in -lockstep. On [files.radicle.dev][files] each binary gets its own release -folder — `releases/radicle-httpd//` and, as a sibling, -`releases/radicle-search//` — so existing `radicle-httpd` download -URLs are unaffected and `radicle-search` is downloaded independently. +lockstep. On [files.radicle.dev][files] each binary gets its own release folder +— `releases/radicle-httpd//` and, as a sibling, +`releases/radicle-search//` — so existing `radicle-httpd` download URLs +are unaffected and `radicle-search` is downloaded independently. The release tooling lives in [`crates/radicle-httpd/build/`][build]. @@ -14,8 +14,8 @@ The release tooling lives in [`crates/radicle-httpd/build/`][build]. Releases cover **only** the `radicle-httpd` and `radicle-search` binaries. The web front-end (`radicle-explorer`) is not released this way: it is deployed -continuously (rolling release) and must always remain compatible with the -latest `radicle-httpd`/`radicle-search` release. +continuously (rolling release) and must always remain compatible with the latest +`radicle-httpd`/`radicle-search` release. ## Prerequisites @@ -30,6 +30,7 @@ latest `radicle-httpd`/`radicle-search` release. ## Prepare Release 1. Create release branch: + ```sh git checkout -b release/httpd/ ``` @@ -40,12 +41,14 @@ latest `radicle-httpd`/`radicle-search` release. - `crates/radicle-search/Cargo.toml` 3. Update lockfiles: + ```sh cargo build ``` 4. Generate changelog (the version is read from `crates/radicle-httpd/Cargo.toml`): + ```sh crates/radicle-httpd/build/changelog >> CHANGELOG.md ``` @@ -54,6 +57,7 @@ latest `radicle-httpd`/`radicle-search` release. change belongs to `radicle-httpd` or `radicle-search`. 6. Commit release: + ```sh git commit -m "Release " ``` @@ -71,13 +75,15 @@ latest `radicle-httpd`/`radicle-search` release. ## Tag Release Create and sign the release tag (one tag covers both binaries): + ```sh crates/radicle-httpd/build/tag ``` -Publish the tag to Radicle. Push only this tag: every tag under `releases/` -that a delegate pushes becomes canonical, so `--tags` would also publish any -stray local ones. +Publish the tag to Radicle. Push only this tag: every tag under `releases/` that +a delegate pushes becomes canonical, so `--tags` would also publish any stray +local ones. + ```sh git push rad refs/tags/releases/httpd/ rad sync @@ -87,11 +93,13 @@ rad sync Run the reproducible build (Linux x86_64 or macOS). A single run compiles the workspace and packages **both** binaries — one archive per binary per target: + ```sh crates/radicle-httpd/build/build ``` This writes to `crates/radicle-httpd/build/artifacts/`: + - `radicle-httpd--.tar.xz` (binary + man page) - `radicle-search--.tar.xz` (binary) @@ -104,6 +112,7 @@ each with a `.sig` and `.sha256` alongside. The upload publishes each binary into its own sibling folder on [files.radicle.dev][files] and creates the version-less and `latest` symlinks for both: + ```sh SSH_LOGIN= crates/radicle-httpd/build/upload ``` @@ -119,6 +128,7 @@ SSH_LOGIN= crates/radicle-httpd/build/upload **Topic:** `radicle-httpd + radicle-search ` **Message:** + ````markdown # 👾 radicle-httpd + radicle-search @@ -127,6 +137,7 @@ SSH_LOGIN= crates/radicle-httpd/build/upload ## Installation Download binaries from: + - https://files.radicle.dev/releases/radicle-httpd/ - https://files.radicle.dev/releases/radicle-search/ - https://radicle.dev/download @@ -141,18 +152,21 @@ SSH_LOGIN= crates/radicle-httpd/build/upload ``` - :info: This release was built on . Build reproducibility across platforms is unsupported; checksums may differ if rebuilt elsewhere. + :info: This release was built on . Build reproducibility across + platforms is unsupported; checksums may differ if rebuilt elsewhere. ```` ## Notes > **Important:** +> > - macOS binaries are unsigned and not notarized; download via CLI only > - Build reproducibility across platforms is not guaranteed ## Podman Setup -We use `podman` for reproducible builds across machines. It's preferred over Docker because it runs without a daemon and doesn't require root access. +We use `podman` for reproducible builds across machines. It's preferred over +Docker because it runs without a daemon and doesn't require root access. On first run, you may need to configure UID/GID ranges: diff --git a/explorer/crates/radicle-search/README.md b/explorer/crates/radicle-search/README.md index d15696b09..78f80e300 100644 --- a/explorer/crates/radicle-search/README.md +++ b/explorer/crates/radicle-search/README.md @@ -1,13 +1,13 @@ # radicle-search Optional indexing daemon for Radicle. Runs alongside `radicle-node` and -maintains a Meilisearch index of repo metadata that `radicle-httpd` can -use to serve fast, typo-tolerant repo listings and full-text search. +maintains a Meilisearch index of repo metadata that `radicle-httpd` can use to +serve fast, typo-tolerant repo listings and full-text search. When configured, httpd routes `/repos?sort=activity|seeding` and `/repos/search?q=…` through the index. When absent or unreachable, httpd -transparently falls back to its built-in storage walk — no configuration -on the client side, no API contract changes. +transparently falls back to its built-in storage walk — no configuration on the +client side, no API contract changes. ## What each repository document contains @@ -29,23 +29,24 @@ Only public repos this node seeds are indexed. ## How it stays up to date -1. **Bootstrap.** On startup and after every event-stream reconnect, the - daemon walks the storage tree end-to-end and re-upserts everything. +1. **Bootstrap.** On startup and after every event-stream reconnect, the daemon + walks the storage tree end-to-end and re-upserts everything. 2. **Real time.** Subscribes to the node's control socket (same stream as `rad node events`) and reacts to: - - `RefsFetched`, `LocalRefsAnnounced`, `CanonicalRefUpdated`, `RefsSynced` - → re-index the affected repo. - - `RefsAnnounced` → re-index only if the rid is one we locally seed - (filtered against an in-memory cache, so gossip about repos we don't - host is dropped at near-zero cost). + - `RefsFetched`, `LocalRefsAnnounced`, `CanonicalRefUpdated`, `RefsSynced` → + re-index the affected repo. + - `RefsAnnounced` → re-index only if the rid is one we locally seed (filtered + against an in-memory cache, so gossip about repos we don't host is dropped + at near-zero cost). - `SeedDiscovered` and `SeedDropped` are ignored; the seeding count they affect is refreshed by the periodic rescan. Affected repos are collected and re-indexed together every - `RADICLE_SEARCH_BATCH_SECS` (default 30s), so a burst of events for the - same repo costs a single re-index. -3. **Periodic rescan.** Every `RADICLE_SEARCH_RESCAN_SECS` (default 1h) - as a safety net for missed events. + `RADICLE_SEARCH_BATCH_SECS` (default 30s), so a burst of events for the same + repo costs a single re-index. + +3. **Periodic rescan.** Every `RADICLE_SEARCH_RESCAN_SECS` (default 1h) as a + safety net for missed events. ## Setup @@ -77,19 +78,18 @@ For production set a master key with `--master-key ` and run with ### 2. Build and run radicle-search -`radicle-search` is a member of the Cargo workspace rooted at the -repository root (it lives under `crates/`), so build it from there. Cargo -places the binary in the workspace-wide `target/`, not under this crate's -directory: +`radicle-search` is a member of the Cargo workspace rooted at the repository +root (it lives under `crates/`), so build it from there. Cargo places the binary +in the workspace-wide `target/`, not under this crate's directory: ```sh cargo build --release -p radicle-search ./target/release/radicle-search ``` -The daemon picks up the Radicle profile from `RAD_HOME` and the node -control socket from `RAD_SOCKET` (or their defaults). On first launch it -bootstraps the index in a few seconds, then listens for node events. +The daemon picks up the Radicle profile from `RAD_HOME` and the node control +socket from `RAD_SOCKET` (or their defaults). On first launch it bootstraps the +index in a few seconds, then listens for node events. ### 3. Point httpd at the index @@ -99,35 +99,35 @@ Set `RADICLE_SEARCH_URL` (plus optional friends) before launching httpd: RADICLE_SEARCH_URL=http://127.0.0.1:7700 radicle-httpd ``` -On boot httpd logs `search backend enabled: url=… index=repos`. With the -env var absent, httpd works exactly as before. +On boot httpd logs `search backend enabled: url=… index=repos`. With the env var +absent, httpd works exactly as before. ## Configuration All via environment variables: -| Variable | Default | Purpose | -|---|---|---| -| `RADICLE_SEARCH_MEILI_URL` | `http://localhost:7700` | Meilisearch instance to connect to. | -| `RADICLE_SEARCH_MEILI_KEY` | _(none)_ | Meilisearch master key (production mode). | -| `RADICLE_SEARCH_INDEX_NAME` | `repos` | Index name this daemon maintains. | -| `RADICLE_SEARCH_RESCAN_SECS` | `3600` | Interval between safety-net full rescans. | -| `RADICLE_SEARCH_RECONNECT_BACKOFF_SECS` | `5` | Delay before reconnecting after an event-stream disconnect. | -| `RADICLE_SEARCH_BATCH_SECS` | `30` | Interval at which repos touched by node events are re-indexed. | -| `RAD_HOME` | `~/.radicle` | Standard Radicle profile path. | -| `RAD_SOCKET` | `$RAD_HOME/node/control.sock` | Standard node control socket. | +| Variable | Default | Purpose | +| --------------------------------------- | ----------------------------- | -------------------------------------------------------------- | +| `RADICLE_SEARCH_MEILI_URL` | `http://localhost:7700` | Meilisearch instance to connect to. | +| `RADICLE_SEARCH_MEILI_KEY` | _(none)_ | Meilisearch master key (production mode). | +| `RADICLE_SEARCH_INDEX_NAME` | `repos` | Index name this daemon maintains. | +| `RADICLE_SEARCH_RESCAN_SECS` | `3600` | Interval between safety-net full rescans. | +| `RADICLE_SEARCH_RECONNECT_BACKOFF_SECS` | `5` | Delay before reconnecting after an event-stream disconnect. | +| `RADICLE_SEARCH_BATCH_SECS` | `30` | Interval at which repos touched by node events are re-indexed. | +| `RAD_HOME` | `~/.radicle` | Standard Radicle profile path. | +| `RAD_SOCKET` | `$RAD_HOME/node/control.sock` | Standard node control socket. | httpd reads a parallel set to decide, at runtime, whether to use the index: -| Variable | Default | Purpose | -|---|---|---| -| `RADICLE_SEARCH_URL` | _(none)_ | Meilisearch instance httpd queries. **Unset disables search** — httpd serves listings and search from its storage walk. | -| `RADICLE_SEARCH_KEY` | _(none)_ | Meilisearch API key. | -| `RADICLE_SEARCH_INDEX_NAME` | `repos` | Index name to query. | -| `RADICLE_SEARCH_TIMEOUT_MS` | `500` | Per-query timeout in milliseconds (must be a non-zero integer). | +| Variable | Default | Purpose | +| --------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | +| `RADICLE_SEARCH_URL` | _(none)_ | Meilisearch instance httpd queries. **Unset disables search** — httpd serves listings and search from its storage walk. | +| `RADICLE_SEARCH_KEY` | _(none)_ | Meilisearch API key. | +| `RADICLE_SEARCH_INDEX_NAME` | `repos` | Index name to query. | +| `RADICLE_SEARCH_TIMEOUT_MS` | `500` | Per-query timeout in milliseconds (must be a non-zero integer). | -Use the same URL/key/index values in both processes. A single -`radicle-httpd` binary handles both modes — no build-time feature flag is -involved. When `RADICLE_SEARCH_URL` is unset, or when a query to the -backend fails or times out, httpd transparently falls back to the storage -walk, so the API behaves identically either way. +Use the same URL/key/index values in both processes. A single `radicle-httpd` +binary handles both modes — no build-time feature flag is involved. When +`RADICLE_SEARCH_URL` is unset, or when a query to the backend fails or times +out, httpd transparently falls back to the storage walk, so the API behaves +identically either way. diff --git a/explorer/src/marketing/docs/download.md b/explorer/src/marketing/docs/download.md index 82e600aec..f82a765ea 100644 --- a/explorer/src/marketing/docs/download.md +++ b/explorer/src/marketing/docs/download.md @@ -39,15 +39,18 @@ sudo apt install radicle ### Pacman (Arch Linux) -Install [`radicle`](https://archlinux.org/packages/extra/x86_64/radicle/) via pacman. +Install [`radicle`](https://archlinux.org/packages/extra/x86_64/radicle/) via +pacman. ### Nix -Install `radicle-node` from [Nixpkgs](https://search.nixos.org/packages?query=radicle-node). +Install `radicle-node` from +[Nixpkgs](https://search.nixos.org/packages?query=radicle-node). ## Reproducible binaries -Radicle publishes reproducible builds for Linux and macOS. Each release includes binaries, SHA256 checksums, and SSH signatures. +Radicle publishes reproducible builds for Linux and macOS. Each release includes +binaries, SHA256 checksums, and SSH signatures. ### Radicle @@ -59,12 +62,12 @@ curl -O -L https://files.radicle.dev/releases/latest/radicle-$TARGET.tar.xz Replace `$TARGET` with one of: -| Platform | Target | -|----------|--------| -| Linux x86_64 | `x86_64-unknown-linux-musl` | -| Linux ARM64 | `aarch64-unknown-linux-musl` | -| macOS x86_64 | `x86_64-apple-darwin` | -| macOS ARM64 | `aarch64-apple-darwin` | +| Platform | Target | +| ------------ | ---------------------------- | +| Linux x86_64 | `x86_64-unknown-linux-musl` | +| Linux ARM64 | `aarch64-unknown-linux-musl` | +| macOS x86_64 | `x86_64-apple-darwin` | +| macOS ARM64 | `aarch64-apple-darwin` | ### Radicle HTTP daemon @@ -82,11 +85,13 @@ Extract the archive to `~/.radicle/`: tar -xf radicle-$TARGET.tar.xz -C ~/.radicle/ ``` -This places binaries in `~/.radicle/bin` and man pages in `~/.radicle/man`. Make sure `~/.radicle/bin` is in your `PATH`. +This places binaries in `~/.radicle/bin` and man pages in `~/.radicle/man`. Make +sure `~/.radicle/bin` is in your `PATH`. ## Verification -Each archive is accompanied by a `.sig` (SSH signature) and `.sha256` (checksum) file. +Each archive is accompanied by a `.sig` (SSH signature) and `.sha256` (checksum) +file. ### Signature verification @@ -98,13 +103,13 @@ ssh-keygen -Y check-novalidate -n file -s ARCHIVE.tar.xz.sig < ARCHIVE.tar.xz The following team members sign releases: -| Name | Public key | -|------|-----------| +| Name | Public key | +| --------------------- | ---------------------------------------------------------------------------------- | | cloudhead@radicle.xyz | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL460KIEccS4881p7PPpiiQBsxF+H5tgC6De6crw9rbU` | -| erik@radicle.xyz | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBrJyJTwj/xG7F7qY0HDFXbb8A+xNNH8eILQ8hlvKW7/` | -| fintan@radicle.xyz | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEFsaRqAJ1r6bBFwlcWzJKN7DdjItQDumCNc0wqw6Dvk` | -| lorenz@radicle.xyz | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFhK7CqgIIbSthoNn8ea32krOnMzC807Z+PpBkR2YOVj` | -| rudolfs@osins.org | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPueml1FxzjvwbD7vRZfwoaoyuxLy0L+WLBwSNiVoJe5` | +| erik@radicle.xyz | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBrJyJTwj/xG7F7qY0HDFXbb8A+xNNH8eILQ8hlvKW7/` | +| fintan@radicle.xyz | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEFsaRqAJ1r6bBFwlcWzJKN7DdjItQDumCNc0wqw6Dvk` | +| lorenz@radicle.xyz | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFhK7CqgIIbSthoNn8ea32krOnMzC807Z+PpBkR2YOVj` | +| rudolfs@osins.org | `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPueml1FxzjvwbD7vRZfwoaoyuxLy0L+WLBwSNiVoJe5` | ### Checksum verification diff --git a/explorer/src/marketing/docs/glossary.md b/explorer/src/marketing/docs/glossary.md index 9cefdfc9f..83ac7e065 100644 --- a/explorer/src/marketing/docs/glossary.md +++ b/explorer/src/marketing/docs/glossary.md @@ -15,52 +15,83 @@ title: Glossary

Glossary. Definitions of Radicle-specific terms.

-**Bootstrap node** — A well-known seed node that new nodes connect to on first startup to discover the rest of the network. Radicle ships pre-configured with `seed.radicle.garden` and `seed.radicle.xyz`. +**Bootstrap node** — A well-known seed node that new nodes connect to on first +startup to discover the rest of the network. Radicle ships pre-configured with +`seed.radicle.garden` and `seed.radicle.xyz`. -**Canonical JSON** — A standardized JSON encoding with sorted keys and no unnecessary whitespace. Used for identity documents so that hashing produces deterministic results. +**Canonical JSON** — A standardized JSON encoding with sorted keys and no +unnecessary whitespace. Used for identity documents so that hashing produces +deterministic results. -**Canonical reference** — A Git reference whose authoritative state is determined by delegate signatures meeting the threshold requirement. +**Canonical reference** — A Git reference whose authoritative state is +determined by delegate signatures meeting the threshold requirement. -**COB (Collaborative Object)** — A CRDT-based data structure stored as Git objects. Used to implement issues, patches, and other social features. +**COB (Collaborative Object)** — A CRDT-based data structure stored as Git +objects. Used to implement issues, patches, and other social features. -**`config.json`** — The node configuration file, typically at `~/.radicle/config.json`. Controls seeding policy, external addresses, bootstrap peers, and scope. +**`config.json`** — The node configuration file, typically at +`~/.radicle/config.json`. Controls seeding policy, external addresses, bootstrap +peers, and scope. -**CRDT (Conflict-Free Replicated Data Type)** — A data structure designed so that concurrent modifications by different participants always merge deterministically without conflicts. The foundation of COBs. +**CRDT (Conflict-Free Replicated Data Type)** — A data structure designed so +that concurrent modifications by different participants always merge +deterministically without conflicts. The foundation of COBs. -**Delegate** — A person (identified by their DID) authorized to make changes to a repository’s identity document and canonical branches. +**Delegate** — A person (identified by their DID) authorized to make changes to +a repository’s identity document and canonical branches. -**DID (Decentralized Identifier)** — A W3C standard identifier encoding a public key. In Radicle, DIDs use the `did:key` method with Ed25519 keys. Format: `did:key:z6Mk...`. +**DID (Decentralized Identifier)** — A W3C standard identifier encoding a public +key. In Radicle, DIDs use the `did:key` method with Ed25519 keys. Format: +`did:key:z6Mk...`. -**Gossip protocol** — The communication mechanism by which Radicle nodes discover peers and learn about repository updates. +**Gossip protocol** — The communication mechanism by which Radicle nodes +discover peers and learn about repository updates. -**Identity document** — A JSON document at `refs/rad/id` that defines a repository’s metadata, delegates, and threshold. +**Identity document** — A JSON document at `refs/rad/id` that defines a +repository’s metadata, delegates, and threshold. -**Magic ref** — A special Git reference (like `refs/patches`) that Radicle intercepts during push to trigger specific actions. +**Magic ref** — A special Git reference (like `refs/patches`) that Radicle +intercepts during push to trigger specific actions. -**Namespace** — Git’s mechanism for partitioning references. Radicle uses namespaces to store each peer’s data under their Node ID. +**Namespace** — Git’s mechanism for partitioning references. Radicle uses +namespaces to store each peer’s data under their Node ID. -**Node** — A participant in the Radicle network, running `radicle-node`. Identified by a public key (Node ID). +**Node** — A participant in the Radicle network, running `radicle-node`. +Identified by a public key (Node ID). -**Node ID (NID)** — The public key that identifies a node on the network. Retrieve yours with `rad self --nid`. +**Node ID (NID)** — The public key that identifies a node on the network. +Retrieve yours with `rad self --nid`. -**Patch** — A proposed code change, stored as a COB. Radicle’s equivalent of a pull request. +**Patch** — A proposed code change, stored as a COB. Radicle’s equivalent of a +pull request. -**`rad` remote** — The Git remote configured in Radicle working copies. Handled by `git-remote-rad`. +**`rad` remote** — The Git remote configured in Radicle working copies. Handled +by `git-remote-rad`. -**Radicle Explorer** — The web interface for browsing Radicle repositories, powered by `radicle-httpd`. Currently read-only. +**Radicle Explorer** — The web interface for browsing Radicle repositories, +powered by `radicle-httpd`. Currently read-only. -**Reference announcement** — A gossip message that informs peers a repository has been updated. Triggers Git fetches by interested nodes. +**Reference announcement** — A gossip message that informs peers a repository +has been updated. Triggers Git fetches by interested nodes. -**RID (Repository Identity)** — A globally unique, self-certifying identifier for a Radicle repository. Prefixed with `rad:`. +**RID (Repository Identity)** — A globally unique, self-certifying identifier +for a Radicle repository. Prefixed with `rad:`. -**Scope** — Controls which peers’ data your node synchronizes. Options are `followed` (only explicit follows) or `all`. +**Scope** — Controls which peers’ data your node synchronizes. Options are +`followed` (only explicit follows) or `all`. -**Seed / Seed node** — An always-on server node that provides high availability for repositories. Listens on TCP port 8776 by default. +**Seed / Seed node** — An always-on server node that provides high availability +for repositories. Listens on TCP port 8776 by default. -**Seeding** — Serving a repository to other nodes on the network. Distinct from tracking (storing locally for your own use). +**Seeding** — Serving a repository to other nodes on the network. Distinct from +tracking (storing locally for your own use). -**Seeding policy** — Configuration that determines which repositories a node will replicate. `allow` means replicate everything; `block` means only replicate explicitly allowed repos. +**Seeding policy** — Configuration that determines which repositories a node +will replicate. `allow` means replicate everything; `block` means only replicate +explicitly allowed repos. -**Threshold** — The number of delegates required to agree on a change for it to be considered canonical. Defined in the identity document. +**Threshold** — The number of delegates required to agree on a change for it to +be considered canonical. Defined in the identity document. -**Tracking** — Telling your node to store a local copy of a repository and fetch updates. Distinct from seeding (also serving it to others). +**Tracking** — Telling your node to store a local copy of a repository and fetch +updates. Distinct from seeding (also serving it to others). diff --git a/explorer/tests/visual/desktop/markdown.spec.ts b/explorer/tests/visual/desktop/markdown.spec.ts index 81548cc73..d06b45c23 100644 --- a/explorer/tests/visual/desktop/markdown.spec.ts +++ b/explorer/tests/visual/desktop/markdown.spec.ts @@ -122,7 +122,9 @@ test("math", async ({ page }) => { waitUntil: "networkidle", }); await expect(page.getByText("The Cauchy-Schwarz Inequality")).toBeVisible(); - await expect(page.locator(".katex-display .katex-html").first()).toBeVisible(); + await expect( + page.locator(".katex-display .katex-html").first(), + ).toBeVisible(); await expect(page).toHaveScreenshot({ fullPage: true }); }); diff --git a/explorer/workers/README.md b/explorer/workers/README.md index e12198563..548f9ad3d 100644 --- a/explorer/workers/README.md +++ b/explorer/workers/README.md @@ -1,14 +1,14 @@ # Open Graph -Worker on `open-graph.radicle.network` that serves OG meta tags and -preview card images for social media embeds. +Worker on `open-graph.radicle.network` that serves OG meta tags and preview card +images for social media embeds. ## How it works -`radicle.network` is a plain static SPA. A Cloudflare Redirect Rule -302s social media crawlers to `open-graph.radicle.network`, which -returns HTML with OG tags. The `og:image` points to `/cards/...` on -the same domain, where the worker renders PNG cards via Satori + resvg-wasm. +`radicle.network` is a plain static SPA. A Cloudflare Redirect Rule 302s social +media crawlers to `open-graph.radicle.network`, which returns HTML with OG tags. +The `og:image` points to `/cards/...` on the same domain, where the worker +renders PNG cards via Satori + resvg-wasm. Browsers and search crawlers hit the SPA directly — no worker cost. @@ -18,8 +18,8 @@ Browsers and search crawlers hit the SPA directly — no worker cost. - `/cards/*` — PNG card images (cached 2 hours) Card routes mirror app routes: `/cards/nodes/:host/:rid`, -`/cards/nodes/:host/:rid/issues/:id`, etc. When API fetches fail, -cards degrade: specific item -> repo -> node -> home. +`/cards/nodes/:host/:rid/issues/:id`, etc. When API fetches fail, cards degrade: +specific item -> repo -> node -> home. ## Cloudflare Redirect Rule @@ -42,14 +42,15 @@ cards degrade: specific item -> repo -> node -> home. ) ``` -Dynamic 302 to `concat("https://open-graph.radicle.network", http.request.uri.path)` +Dynamic 302 to +`concat("https://open-graph.radicle.network", http.request.uri.path)` ## Rate Limiting **Security > WAF > Rate limiting rules** on the `radicle.network` zone: -Verified bots are rate limited to 50 requests per 10 seconds across -all of `radicle.network`, with a 1 minute block. +Verified bots are rate limited to 50 requests per 10 seconds across all of +`radicle.network`, with a 1 minute block. ## Deploy