System Architecture
Tasuku is an offline-first Progressive Web App on the Cloudflare edge. The server-side D1 database is the single source of truth; the client is a cache plus an outbox, never a second source of truth (Product Brief §1.1, constitution §7.14).
Components
Section titled “Components”- Astro app + React islands — static Astro pages hydrate small interactive React islands (
src/islands,src/pages,src/layouts). Interactivity is opt-in per island to keep the JS budget small (constitution §8.1–§8.2). - Service worker (Workbox) — built post-build over
dist/clientand paired with a prerendered app-shell route; precaches the shell and assets for offline launch (ADR-009, constitution §7.14.2). - Client store (Dexie/IndexedDB) — the sole client store: a read cache of the user’s data plus the mutation outbox. Reads, search, and filtering work offline; writes are optimistic and queued (Product Brief §1.1).
- Worker API (Hono) — a Cloudflare Worker serving the HTTP API under
/api, built bycreateApp()insrc/api/index.ts. Contract-first via@hono/zod-openapi, versioned under/v1(ADR-001, ADR-003). - D1 + Drizzle — the relational source of truth; typed access through repository functions in
src/db/repositories(constitution §7.10). SyncInboxDurable Object — the project’s first and only Durable Object; a per-user inbox holding hibernatable WebSockets that pushes tiny change signals (ADR-011,src/durable-objects/sync-inbox.ts).- Clerk — the single authentication boundary; the Worker derives the user identity from the verified session (constitution §7.12).
Request and sync flow
Section titled “Request and sync flow”A user action follows the offline-first path (Product Brief §5.2, “Sharing and sync rules”):
- Optimistic local write — the island applies the change to the Dexie cache and enqueues it in the outbox; the UI updates immediately.
- Replay to the server — when online, the outbox replays the mutation to the Worker API; an idempotency key lets the server deduplicate replays (
idempotencyKeystable,src/db/schema.ts). - Authoritative write — the Worker validates (Zod), authorizes (Clerk session + the ownership/membership rule), and writes to D1 through a repository function.
- Fan-out signal — after a successful write, the Worker computes the affected users (owner ∪ active members) and, off the request path (
waitUntil), RPC-calls each user’sSyncInboxto broadcast aSyncSignalnaming the changed surface (ADR-011;affectedUsers()/notifyList()insrc/api/lib/notify.ts). - Client reacts — a connected client runs its existing
?sincedelta read for that surface and reconciles into Dexie. The WebSocket replaces the polling timer, not the data path; while disconnected, a slow polling fallback + a catch-up read on reconnect preserve convergence (ADR-006 → ADR-011).
Transport evolution
Section titled “Transport evolution”Real-time propagation began as periodic polling for the MVP (ADR-006) and was upgraded to signal-only WebSocket push over the SyncInbox Durable Object (ADR-011, constitution §7.14.6). Polling remains the documented fallback. Offline-first is unchanged throughout: the server stays the source of truth and the client degrades to cached reads + outbox replay.
Concrete code paths are covered in the module guides: Worker API & Clerk Auth, Sync-Core Reconcilers, SyncInbox Durable Object, LiveConnection WS Client, and Offline Outbox & Checklists.