Skip to content
Jonathan D.A. Jewell edited this page Sep 9, 2026 · 2 revisions

Architecture

Thirteen crates, layered so that the parts which must be provably correct have no I/O to be wrong about.

                 berrywiki-cli ──── berrywiki-import
                       │            (pure; foreign notebooks in)
        ┌──────────────┴───────────────┐
   berrywiki-serve                berrywiki-sync
   (+ -render, -draft)                  │
        │  └── berrywiki-a11y      berrywiki-git
        │      (audits the output)      │
        └──────────┬───────────────────┘
             berrywiki-store ── berrywiki-appstate
                   │                 (out-of-clone state)
             berrywiki-core
             (no I/O; the rules)

Where the truth lives

Layer Rebuildable?
Markdown files in .wiki.git No — this is the data.
_Sidebar.md Yes, deterministically.
Page graph, backlinks, diagnostics Yes, from the files alone.
Drafts, journal, search index Yes; losing it costs at most unsaved drafts.

The crates

berrywiki-core holds every rule and touches no filesystem: metadata parsing and serialisation, heading and link extraction, hierarchy, backlinks, diagnostics, sidebar generation. Because it has no I/O, all of that is testable in-process.

berrywiki-store is the only layer that writes wiki content. Writes are atomic — a temp file renamed into place, so no reader ever sees half a page. Paths are validated component-wise against traversal and against names that break Windows checkouts. A move writes and verifies the new file before removing the old, so a crash leaves both files — a visible duplicate-id diagnostic — never neither.

berrywiki-appstate and berrywiki-draft keep BerryWiki's own state under the XDG state home, outside the clone.

berrywiki-git wraps a deliberately closed set of git operations. No force-push, no reset --hard, no blanket working-tree discard, and fetch before push. berrywiki-sync turns each completed store mutation into one atomic logical commit. berrywiki-github mirrors a .wiki.git working copy, read-only.

berrywiki-git-compat is not a feature — it is evidence. It reproduces remote-change, non-fast-forward and merge-conflict situations and asserts that no data is lost, so the safety claims above are tested rather than asserted.

berrywiki-render converts Markdown to HTML with comrak, escaping raw HTML and neutralising dangerous URL schemes. berrywiki-serve is the three-pane explorer and editor, with no third-party dependencies at all: a hand-rolled blocking std::net server, no async runtime, no web framework.

berrywiki-a11y is a structural accessibility audit over a rendered page, with no dependencies. It answers the only part of accessibility a program can answer by itself: whether the structure a screen reader and a keyboard depend on is present — a missing lang, an unlabelled control, a link with no text, a heading level that jumps, a skip link pointing at nothing. It cannot tell you whether the reading order makes sense, and nothing here should be read as evidence that BerryWiki has been tested with a screen reader; it has not. Colour contrast is deliberately out of scope and is recorded by hand in ADR-0012 instead. It is a crate rather than a test helper because berrywiki-serve sweeps its routes from an integration test as well as a unit test, and an integration test cannot see a #[cfg(test)] item in src/.

berrywiki-import reads a foreign notebook into BerryWiki, and it is pure: it opens no file, writes no file and holds no store. It takes the bytes of a notebook and returns a model, which the CLI either prints as a dry run or applies — so a dry run and a real import compute exactly the same value. CherryTree .ctd XML is read; .ctb, .ctz and .ctx are refused by name rather than half-handled, because reading them means a SQLite or 7-zip reader and the workspace deliberately has one third-party crate. ADR-0015 rules how that line is held: hand-roll plain-text formats, and shell out to a binary the user already has only behind an explicit flag, never silently.

Deliberate absences

  • No JavaScript or TypeScript, hand-written or generated. Whether generated client script may ever ship was ruled in ADR-0007 (2026-09-03): it may, but only with a checked-in manifest recording each artefact's sources, pinned toolchain, reproduced hash and permitted routes. Nothing shipped changed on that day. The no-<script> test stays in place byte-for-byte until a first generated artefact lands, and that manifest gate must exist before it may be relaxed.
  • No async runtime and no web framework in the server. It is a single-user localhost tool; the HTTP surface is the seam to swap later if that changes.
  • No database. The file tree is the database.

Reading further

Design reasoning lives in docs/decisions/ as ADRs, indexed on Decisions. Package-by-package state is in docs/execution/work-packages.adoc, and known debt — including the absence of the proofs the project intends to have — is in docs/execution/debt-register.adoc.

Clone this wiki locally