In-app notifications (SQLite rows, no realtime): kinds, emit sites, inbox/API. Use when adding triggers, kinds, or notification UI/API work.
notifications); no WebSocket/SSE/worker—insert-on-write, read on load (shell + /app/notifications). Schema: migrations/20260325103000_notifications.sql; emailed_at exists for future email—do not assume email runs today.organization_id from the resource (project/node), not session alone; set explicit recipient_user_id on insert.src/app/db/notifications.rs (no raw SQL elsewhere).src/app/features/graph/notify.rs after successful writes; wired from src/app/features/graph/create_node.rs, src/app/features/graph/update_node.rs, and project due in src/app/features/projects/update_settings.rs.src/app/features/notifications.rs (merged in ). Routes: only—not .src/app/mod.rs/api/notifications/.../app/api/...src/app/shell.rs (load_app_shell), src/app/app_layout.html.kind values in existing kind TEXT usually need no migration; new columns → new migration only (see migrations skill).pub const KIND_* next to existing kinds in src/app/db/notifications.rs.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.
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.
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/notifications.rs; second user: setup_user_and_project + add_verified_user_to_org_team (tests/common/mod.rs). Run: cargo test --test notifications.NewNotification rows per event over a generic activity stream unless product requires it.