Skip to content
The English translation is still being built: some pages may lag behind the German ones.
Architecture

Architecture

For contributors: how the pieces fit. Every decision has an ADR in specs/adr/ (German), indexed in its README.md; the plan is specs/plan.md.

Principles

  • The server holds the truth. Clients keep a read cache and an outbox of field changes stamped with a hybrid logical clock (HLC) and their device. The server decides last-writer-wins per field. There is no second operation log.
  • One account, one database. Each account is its own SQLite file; server.db holds accounts and sessions.
  • Availability is computed, never stored, in exactly one place: samu-core. The server and the phone call the same code.
  • Time: a whole day is a local date without zone; a time is an instant plus zone. Repetition is an RRULE plus one of three modes.
  • The API is the core. Every interface is a client; every route has a command in the CLI.

Crates

crates/
  samu-api/      wire types (serde only), versioned and published on its own
  samu-core/     model rules, availability, repetition, quick capture,
                 search, forecast, reminders, report, habits; no I/O, no async
  samu-store/    SQLite (rusqlite), numbered migrations
  samu-server/   axum: API, server-sent events, hooks, calendars, the web UI
                 embedded; binary `samu-server` (serve, replica, profile, front)
  samu-client/   HTTP client (rustls) on samu-api; shared by CLI and app
  samu-cli/      binary `samu`
  samu-replica/  mirror, outbox and sync of a client, without uniffi;
                 shared by the app and the local replica
  samu-mobile/   Rust side of the Android app (uniffi bindings)

Dependencies point one way: samu-api ← samu-core ← samu-store ← samu-server; samu-api ← samu-client ← samu-cli, samu-mobile; samu-core, samu-client ← samu-replica ← samu-mobile, samu-server. Nothing depends on samu-server; samu-api depends on nothing in the workspace.

Other parts

Directory
web/the web UI: ES modules, no framework, no build step; embedded in the server binary (crates/samu-server/src/web.rs lists the files)
android/Kotlin and Compose over samu-mobile
emacs/samu.el, over the CLI
dms/the desktop widget, over the CLI
tui/, pult/fzf menus and their helper, over the CLI
deploy/Dockerfile, compose file, systemd units
specs/plan, ADRs, research (German)
testdata/synthetic data only

Data flow

 web UI ─┐
 CLI ────┼── HTTPS ──► samu-server ──► accounts/<name>.db
 Emacs ──┤  (via CLI)      │
 widget ─┘                 └─ GET /events (SSE) ──► every client
 Android app: local mirror + outbox ──► POST /changes, GET /changes
 replica on a computer: the same API on 127.0.0.1, mirror + outbox

Building and checking

Stable Rust (edition 2024), see rust-toolchain.toml.

make check        # fmt, clippy -D warnings, tests, samu-api packaging, samu.el
make install      # samu into ~/.local/bin
cargo build -p samu-server
target/debug/samu-server --data /tmp/samu serve --listen 127.0.0.1:8492

Rules the project keeps:

  • Every new feature gets tests, every bug fix a regression test. Tests use synthetic data only and call no outside services.
  • Every API route gets a CLI command in the same commit. CLI output: data on stdout, messages on stderr, --format plain|json|jsonl|tsv, defined exit codes.
  • Every change to samu-api comes with a JSON fixture under crates/samu-api/tests/fixtures/.
  • Schema changes only as numbered migrations in samu-store.
  • No unsafe outside FFI, no unwrap() outside tests.

Sister project

samudo has a sister project, catastr, a document and knowledge store. Tasks link to its objects by URI (catastr:<id>), and catastr reads samudo as a source through the same API and the samu-api crate. Neither touches the other’s database.