From 99b39ca287ba8beceb098d5003beec898544c709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C5=ABdolfs=20O=C5=A1i=C5=86=C5=A1?= Date: Wed, 28 Oct 2026 10:59:11 +0100 Subject: [PATCH] Add ADR for a shared core package Both apps carry diverging copies of the same domain logic. Record where shared logic lives, which types it uses and how it moves over. --- docs/adr/0004-shared-core.md | 67 ++++++++++++++++++++++++++++++++++++ shared/ui/AGENTS.md | 2 +- 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 docs/adr/0004-shared-core.md diff --git a/docs/adr/0004-shared-core.md b/docs/adr/0004-shared-core.md new file mode 100644 index 000000000..62cc1f914 --- /dev/null +++ b/docs/adr/0004-shared-core.md @@ -0,0 +1,67 @@ +# 4. Shared core package + +Date: 2026-10-28 + +## Status + +Accepted + +## Context + +ADR 3 starts sharing with visual components and leaves open how to share the +logic behind them. Both apps carry their own copies of much of it, some +near-identical, such as frontmatter parsing, and some diverged, such as +markdown rendering. Desktop has most of the domain logic, such as patch and +issue timelines, and nearly all of it depends only on its generated types. + +The apps reach their data differently. Explorer reads coarse JSON from +radicle-httpd, described by hand in Zod: issues embed their discussion, +patches their revisions. Desktop calls its Rust backend through Tauri, with +types generated by ts-rs, and fetches finer pieces. Both backends build on +heartwood but share no serialization types, and names differ, such as an +author's `id` in explorer and `did` in desktop. + +We also considered keeping the logic in `shared/ui/lib`, which mixes code that +needs no UI with code that does, and sharing it in Rust, served by both +backends. httpd doesn't serve the inputs of most derivations, and explorer +needs the logic in the browser now. + +## Decision + +- A second package, `@radicle-gui/core` in `shared/core`, holds the logic both + apps need that doesn't touch the UI, from formatting helpers to patch and + issue derivations, as pure functions and types. It has no Svelte, + DOM, Tauri or network dependencies, enforced by lint, and its own checks and + CI like `shared/ui`. +- `@radicle-gui/ui` depends on core, never the other way round. +- Core defines the domain types that shared code works with. They are narrow + projections of heartwood's types as serialized to JSON: same field names and + meanings, only the fields shared code needs. Their shapes can differ where + the derivations need it, such as a list instead of a map keyed by ID. The + shared prop types planned for `shared/ui/lib/types.ts` live here instead. +- Each app maps its data to these types in one place: explorer in its HTTP + client, desktop where its bindings differ from + heartwood. +- Markdown rendering and syntax highlighting become one implementation in + `@radicle-gui/ui`, not core, because they need the DOM. Each app configures + what differs, such as how links and mentions resolve. +- Views stay in the apps and get thinner as their logic moves to core. + Sharing whole views stays out of scope. + +## Consequences + +- Fixes and features in domain logic happen once and reach both apps, as far + as each backend serves the data they need. +- Desktop's derivations are rewritten against core's narrow types, and + desktop maps its bindings to them. +- Content renders the same in both apps. +- Explorer maintains both its Zod schemas and a mapping to core's types until + the apps share generated types. Once they do, core's types become subsets of + the generated ones, and the mappings go away except where a shape differs. +- Changes to core run the checks of core, `shared/ui` and both apps. + +## Open questions + +- Generating the domain types for both apps from heartwood's Rust types, as + ADR 3 names as the long-term path, waits for a new radicle-httpd API + version; see ADR 5's open question. diff --git a/shared/ui/AGENTS.md b/shared/ui/AGENTS.md index b1475b902..8188da121 100644 --- a/shared/ui/AGENTS.md +++ b/shared/ui/AGENTS.md @@ -60,7 +60,7 @@ the components: heartwood - Define them in `lib/types.ts`, named after domain concepts such as `Author` or `Reaction`. The first extraction that needs a domain type creates the - file + file. ADR 4 moves them to `@radicle-gui/core` once that package exists - Apps pass their own objects when the shapes match, and map them in one place where they don't. Never import an app's types here