Skip to content

DB Schema & Notify-Affected

This subsystem covers the relational source of truth (src/db/schema.ts) and the server-side fan-out that decides which users a write should notify (src/api/lib/notify.ts).

The Drizzle tables: users, lists, groups, tasks, steps, myDayEntries, listMembers, idempotencyKeys. See the Domain Model for columns and rules. Notable cross-cutting columns: rank (fractional ordering), assigneeId (task assignment, spec 012), and per-viewer order/group on listMembers (per-member List placement, spec 011).

After a successful write, the Worker must signal the right users (ADR-011). src/api/lib/notify.ts:

  • affectedUsers(db, listId) — returns the owner ∪ active (non-tombstoned) members of a list: the existing accessible() set. This is the authorization-aligned recipient set.
  • notifyList(c, listId, surfaces) — fan a signal to everyone with access to a list, for the given surface tags.
  • notifyUsers(c, userIds, surfaces) — signal a specific set of users (e.g. the acting user for owner-scoped surfaces like My Day).
  • notifyMembership(c, …) — handle membership add/remove, which must also notify the user whose access just changed.

Fan-out runs off the request path and is bounded by member count; a notify failure never fails the already-committed write (constitution F-17/F-18). The internal dispatch(...) helper sends via offResponsePath(c, …), which calls c.executionCtx.waitUntil(...) in the Worker runtime and falls back to a detached run under a test harness that has no ExecutionContext — so a notify never breaks the handler. The recipient set deliberately mirrors the read-authorization rule, so a user is only ever signalled about data they can actually read.

Related: Worker API & Clerk Auth (the write routes), SyncInbox Durable Object (the delivery target).