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.
This commit is contained in:
Rūdolfs Ošiņš
2026-10-29 17:02:24 +01:00
parent 2c44c70ff2
commit 99b39ca287
2 changed files with 68 additions and 1 deletions
+67
View File
@@ -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.
+1 -1
View File
@@ -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