Architecture Decision Records
Every significant technical decision is recorded here, so that anyone exploring or contributing to the project understands why it is built the way it is.
How we decide
Section titled “How we decide”Every decision is evaluated from first principles, always with the same structure:
- What it physically has to do in this app.
- Criteria that actually matter here, and how much each one weighs.
- Options compared against those criteria (with evidence: maintenance status, dependencies, measurements).
- Verdict, the cost we accept, and what would make us revisit it.
Accepted ADRs are not rewritten. A decision that changes gets a new ADR that supersedes the old one.
| # | Decision | Status |
|---|---|---|
| 0001 | Distribution: one self-hosted server, Docker image + native service, no cloud | Accepted |
| 0002 | Backend language: Go | Accepted |
| 0003 | Architecture: one process, channel-agnostic action layer | Accepted |
| 0004 | Data: SQLite + sqlc + goose | Accepted |
| 0005 | API contract: huma v2, code-first OpenAPI 3.1 | Accepted |
| 0006 | Users and authentication: household model, composed libraries, OIDC in v1 | Accepted |
| 0007 | Channels: go-telegram/bot, slack-go, go-mail | Accepted |
| 0008 | Time and money: own billing engine, bojanz/currency, ECB rates | Accepted |
| 0009 | Runtime and system: kong, suture, slog, kardianos/service | Accepted |
| 0010 | Testing strategy | Accepted |
| 0011 | Observability module | Accepted |
| 0012 | Frontend framework: React 19 + Vite SPA embedded in the binary | Accepted |
| 0013 | Web data layer: orval, TanStack Router/Query/Form, zod | Accepted |
| 0014 | Visual layer: shadcn/ui, Tailwind, Recharts, own calendar, i18n | Accepted |
| 0015 | Frontend tooling: mise, pnpm, TypeScript 7, Biome, Vitest, Playwright | Accepted |
| 0016 | Repository, CI and releases | Accepted |
| 0017 | License: AGPL-3.0-or-later + DCO | Accepted |
| 0018 | Agent-ready app: AI is a first-class feature of v1 | Accepted |
| 0019 | The message is the app: one screen format, a living home message in chat | Accepted |
| 0020 | One binary for homes and teams | Accepted |
| 0021 | The dates that matter: trials, notice periods and expiries | Accepted |
| 0022 | Data in and out: import with a review list, export, calendar feed | Accepted |
| 0023 | Translating chat messages | Accepted |
| 0025 | Two-factor sign-in: TOTP and backup codes, standard library, QR in the browser | Accepted |
| 0027 | Own logos and documents: stored in the database, served as inert files | Accepted |
| 0024 | Remote MCP: own minimal OAuth 2.1 authorization server | Accepted |
| 0026 | Web Push from the installed app, on the standard library | Accepted |
| 0028 | Keep this computer awake, from the web, without cgo | Accepted |
| 0029 | Desktop installers (dmg, setup.exe, deb/rpm) and a download page | Accepted |
| 0030 | Exhale’s own window, with Wails | Accepted |
| 0031 | The window draws its own title bar | Accepted |
| 0032 | A menu bar icon, and keeping a closed laptop awake | Accepted |
| 0033 | Updating Exhale with one click, from GitHub Releases | Accepted |
Open questions are tracked in ../open-questions.md.
Template
Section titled “Template”# NNNN. Title
- Status: Proposed | Accepted | Superseded by NNNN- Date: YYYY-MM-DD
## Context — what it has to do## Criteria## Options## Decision## Consequences — cost we accept## Revisit when