Design Decisions (ADRs)
Architecture Decision Records live in adrs/ and record why each significant decision was made (constitution §5.3). All nineteen are Accepted. Summaries below; follow the link for full context and alternatives.
| ADR | Decision | Constitution refs |
|---|---|---|
| 001 | API versioning via URL prefix /v1 | §7.3, §15 |
| 002 | Error envelope = RFC 9457 problem+json | §7.3, §7.5 |
| 003 | Contract tooling = @hono/zod-openapi (contract-first) | §7.3, §7.9 |
| 004 | CLI auth = OAuth PKCE | §7.12, §7.15 |
| 005 | CLI = TypeScript, distributed via npm | §7.15, §8.1 |
| 006 | Real-time transport = polling for MVP; DO WebSocket the planned upgrade | §7.14.6, §8.4 |
| 007 | Supply-chain tooling = OSS stack (SBOM, osv-scanner, gitleaks) | §9.6, §11.9 |
| 008 | i18n = Paraglide/Inlang + native Intl | §7.16, §8.1 |
| 009 | PWA tooling = post-build workbox-build + prerendered app shell | §7.14.2, §8.1 |
| 010 | Auth UI = Clerk prebuilt components | §7.16.1, §7.12 |
| 011 | Event push = signal-only WebSocket over a per-user inbox Durable Object | §7.14.6, §8.1, §8.4 |
| 012 | Docs toolchain = Astro Starlight (build-time tooling; no §8.1 amendment) | §8.1, §8.2, §9.6, §14 |
| 013 | Account deletion = hard delete; D1-first → Clerk, idempotent convergence (app-orchestrated, no webhook) | §9.7, §7.12, §7.10 |
| 014 | CLI token = JWT access tokens + short TTL + refresh-revoke; broaden acceptsToken (supplements 004) | §7.12, §7.15, §9.5 |
| 015 | Scheduled evaluation = Cloudflare Cron Triggers (~1-min reminder scan); §8.1 amendment v0.19.0 | §8.1, §7.8, §8.4 |
| 016 | Web Push transport = VAPID + aes128gcm on WebCrypto (no Node web-push); §8.1 amendment v0.19.0 | §8.1, §9.2, §9.3, §9.5 |
| 017 | Outbound email = Cloudflare Email Routing send_email to a verified destination (free tier; no secret/paid plan); §8.1 amendment v0.20.0 | §8.1, §7.1, §9.1, §9.3 |
| 018 | App monitoring = Sentry (Issues/Traces/Logs/Metrics) behind a swappable facade; first third-party sub-processor (PII-scrubbed); §8.1 amendment v0.21.0 | §8.1, §9.1, §9.3, §10.2 |
Notable threads
Section titled “Notable threads”- Real-time transport (006 → 011). Polling was the correct MVP transport; ADR-011 realises ADR-006’s named upgrade as signal-only WebSocket push over the
SyncInboxDurable Object, demoting polling to a fallback. ADR-006 remains Accepted, not superseded. - Contract-first (003). The OpenAPI document is generated from Zod schemas and is the single source of truth shared by all clients — including the API Reference on this site.
- Stack governance & the §8.1 amendment ladder (011, 015–019). Each ADR that adds a production platform capability also amends §8.1: Durable Objects (011), Cron Triggers + Web Push (015/016, v0.19.0), outbound email (017, v0.20.0), Sentry monitoring (018, v0.21.0), and R2 object storage (019, v0.22.0). Build-time-only tooling like the docs site (012) reuses the approved stack and does not amend §8.1.
- Reuse needs no ADR (spec 038). The 30-day auto-deletion of completed Tasks ships with no ADR and no §8.1 amendment (constitution stays v0.22.0): it reuses the existing Cron Trigger (015), D1 soft-delete/tombstones, the Clerk auth boundary, and the
SyncInboxfan-out (011) — no new dependency, secret, external service, or authz actor. A feature is only an ADR/amendment when it introduces a new production capability; composing approved ones is ordinary spec work. - Object storage (019). Task file attachments store bytes in a private Cloudflare R2 bucket reached only through the Worker download endpoint (no public bucket, no presigned URLs); metadata lives in D1. Free-tier scoped (the 5 MB cap + no thumbnails are budget levers); no npm dependency (EXIF stripping is hand-rolled), no new secret. Uploads are validated server-side and image EXIF is stripped before storage so shared images can’t leak the uploader’s location/device between task members.
- Observability (018). Sentry is the project’s first third-party data sub-processor. The decision is wired behind a vendor-neutral facade (
src/lib/obs/report.ts) so the provider is swappable from two adapter files, and the privacy posture is load-bearing: only scrubbed error/trace context and route-shape metadata leave the app — never task/list content, cookies, auth headers, oruser. See Observability.