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.dbholds 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 + outboxBuilding 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:8492Rules 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-apicomes with a JSON fixture undercrates/samu-api/tests/fixtures/. - Schema changes only as numbered migrations in
samu-store. - No
unsafeoutside FFI, nounwrap()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.