name: notifications description: In-app notifications (SQLite rows, no realtime): kinds, emit sites, inbox/API. Use when adding triggers, kinds, or notification UI/API work.
Notifications (DB-backed)
Layout
- Model: Durable rows (
notifications); no WebSocket/SSE/worker—insert-on-write, read on load (shell +/app/notifications). Schema:migrations/20260325103000_notifications.sql;emailed_atexists for future email—do not assume email runs today. - Tenant safety:
organization_idfrom the resource (project/node), not session alone; set explicitrecipient_user_idon insert. - All DB access in
src/app/db/notifications.rs(no raw SQL elsewhere). - Graph emits:
src/app/features/graph/notify.rsafter successful writes; wired fromsrc/app/features/graph/create_node.rs,src/app/features/graph/update_node.rs, and project due insrc/app/features/projects/update_settings.rs. - Inbox + JSON API:
src/app/features/notifications.rs(merged insrc/app/mod.rs). Routes:/api/notifications/...only—not/app/api/.... - Header:
src/app/shell.rs(load_app_shell),src/app/app_layout.html. - New
kindvalues in existingkind TEXTusually need no migration; new columns → new migration only (see migrations skill).
Add a kind
- Add
pub const KIND_*next to existing kinds insrc/app/db/notifications.rs. - New column shapes (e.g. JSON payload) require a new migration file; never edit shipped migrations.
Emit
After the main DB write succeeds, build db::notifications::NewNotification: organization_id (from loaded resource), recipient_user_id, actor_user_id (usually Some(session.user_id)), kind (KIND_*), project_id, node_id (Some for tasks, None for project-only), title / body (short; body optional). Call db::notifications::insert via the db layer or a helper that logs on failure and does not fail the HTTP response (insert_or_log in graph/notify.rs). Skip when recipient_user_id == actor_user_id. Prefer extending graph/notify.rs for graph/task events; other features can use a small notify helper or insert after the write.
Hooks (examples)
after_node_created (assignee), after_node_updated (assignee/due/status diffs), after_project_due_updated (team, except actor). New triggers: load before state if diffing; reuse the mutation’s access checks; then insert.
Read path and cleanup
Listing/mark-read live in db::notifications (list_for_user, mark_read_by_ids, etc.). Read rows older than 90 days removed at startup (delete_read_older_than_days in src/main.rs); change retention in the db module + that startup call.
Tests and pitfalls
- Extend
tests/notifications.rs; second user:setup_user_and_project+add_verified_user_to_org_team(tests/common/mod.rs). Run:cargo test --test notifications. - Prefer explicit
NewNotificationrows per event over a generic activity stream unless product requires it. - Do not add realtime in the same PR as new kinds unless asked.