Radish alpha
r
rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5
Radicle web interface
Radicle
Git
Document Rust backend conventions in CLAUDE.md
Open rudolfs opened 8 days ago
1 file changed +45 -11 64f91fa4 0266b809
modified CLAUDE.md
@@ -176,17 +176,51 @@ Read these when you need domain context for UI work:

### Rust backend (radicle-httpd)

-
- Each route module exposes `pub fn router(ctx: Context) -> Router`;
-
  handlers are private `async fn`s
-
- Handler signature:
-
  `async fn handler(State(ctx): State<Context>, Path(rid): Path<RepoId>) -> impl IntoResponse`
-
- Annotate the error type on Ok: `Ok::<_, Error>(Json(data))`
-
- All response structs: `#[serde(rename_all = "camelCase")]`
-
- Optional fields: `#[serde(skip_serializing_if = "Option::is_none")]`
-
- Use `json!()` macro for inline JSON; `immutable_response()` /
-
  `cached_response()` helpers for Cache-Control headers
-
- Module structure: `api/v1/` handlers, `api/json/` serialization helpers,
-
  `api/query.rs` query param types, `api/error.rs` error types
+
**Module structure.** `api/v1/` handlers, `api/json/` serialization
+
helpers, `api/query.rs` query param types, `api/error.rs` error types.
+
Each route module exposes `pub fn router(ctx: Context) -> Router`;
+
handlers are private `async fn`s with signature
+
`async fn handler(State(ctx): State<Context>, Path(rid): Path<RepoId>) -> impl IntoResponse`
+
and annotate the error type on `Ok`: `Ok::<_, Error>(Json(data))`.
+
Use `immutable_response()` / `cached_response()` helpers to set
+
Cache-Control; plain `Json(..)` for dynamic responses.
+

+
- **Types end-to-end.** Use domain types (`RefString`, `Qualified`,
+
  `Oid`, `Alias`, `NodeId`) throughout, including response structs —
+
  they already derive `Serialize`. Converting to `String` / `&str`
+
  internally signals a missing derive or a misplaced type boundary.
+
  Shortnames are `RefString`, fully-qualified refs are `Qualified`.
+
- **Named response structs, not `json!()`.** Define
+
  `#[derive(Serialize)]` structs at module level with
+
  `#[serde(rename_all = "camelCase")]`; optional fields use
+
  `#[serde(skip_serializing_if = "Option::is_none")]`. Reserve
+
  `json!()` for trivial one-off payloads (e.g. the root router
+
  listing, error bodies).
+
- **Don't reimplement upstream.** Before writing ref, quorum, or
+
  canonical logic, check the sibling crates (`../heartwood/`,
+
  `../radicle-git/` — paths in the "Radicle ecosystem" section).
+
  Prefer reading materialized state (e.g. `references_glob` on a
+
  pattern) over recomputing.
+
- **Separate IO from logic via single-purpose traits.** One trait per
+
  IO concern (`ReadCanonicalRefs`, `PeelToCommit`, `ResolveTag`, …).
+
  Business functions take `R: Trait1 + Trait2`, enabling mocks and
+
  future caching. Keep one impl per operation — if `peel_to_commit`
+
  is needed in multiple places, callers delegate to a single
+
  implementation.
+
- **Match typed components, don't string-prefix.** Use
+
  `qualified.non_empty_components()` / `refname.qualified()` over
+
  `starts_with("refs/heads/")`. Slicing typed refnames is an
+
  anti-pattern.
+
- **Extract shared helpers when two handlers share shape.** Field
+
  drift between equivalent JSON outputs is the smell.
+
- **Skip-with-warn, don't silently succeed.** For per-item IO
+
  failures: `tracing::warn!` and omit the item so clients don't
+
  receive data they'll fail to resolve downstream.
+
- **Observability.**
+
  `#[tracing::instrument(skip_all, fields(rid = %rid, …))]` on
+
  handler-facing fns, with relevant IDs as span fields.
+
- **Rustdoc on `pub` types and fields.** State intent, don't restate
+
  the name.

## Commit messages