Notification Preferences + Ops KPI Dashboard — Design
Date: 2026-07-04 · Status: Approved (user "ok"; compact designs presented inline)
Modules: both CORE (no license gate). After both land: prep LDAP/AD, then SCIM (see memory obscura-ldap-scim-feasibility).
1. Per-user notification preferences
- Table
notification_prefs(next free migration number):user_idPK/FK,email_enabled BOOL NOT NULL DEFAULT TRUE,frequency TEXT NOT NULL DEFAULT 'instant' CHECK (frequency IN ('instant','daily','off')),muted_categories TEXT[] NOT NULL DEFAULT '{}',updated_at. Absent row = all defaults (everything on, instant) — migration seeds nothing; today's behavior unchanged until a user opts out. - Categories = the notify context's existing event families; enumerate at build time from
internal/notifyusage (expected: workflow, correspondence, retention/records, shares/requests, system/digest). Store canonical slugs; UI labels via i18n. - Enforcement at delivery time in
internal/notifydispatch + the digest sweeps: muted category → skip in-app+email for that user;email_enabled=false→ in-app only;frequency='daily'→ suppress instant email, item still appears in the daily digest;'off'→ no emails at all (in-app inbox untouched unless category muted). Subscriptions/data flow untouched — unmuting restores everything. - API:
GET/PUT /api/v1/me/notification-prefs(session-authed, self only). PUT validates frequency enum + known category slugs. OpenAPI + gen:api. - UI: "Notifications" panel on
/profile(next to the Account panel): per-category toggles, email on/off toggle, frequency select. en/id. - Failure posture: prefs read is best-effort in dispatch (error → treat as defaults, log) — notification delivery must never break on a prefs outage.
2. Ops KPI dashboard
- One new endpoint
GET /api/v1/admin/stats/ops?days=30(admin/report perm — match the existing stats page's gate), read-only SQL aggregation, NO new tables: - avg + p90 letter processing time (correspondence created→completed), same for workflow approvals;
- top 5 sender units + top 5 target units by letter volume (window);
- weekly volume trend (12 weeks): documents created, letters created;
- open overdue tasks + escalations count; e-sign envelope completion rate (window);
- returns
{generated_at, window_days, metrics:{...}}with explicit nulls when a module's tables are empty/unlicensed (UI hides those cards — core-only installs see only doc metrics). - UI: an "Operations" section on the existing stats/dashboard page (find current page; extend, don't fork): KPI tiles + a simple trend chart consistent with existing tiles' tech (reuse whatever chart approach exists; NO new npm deps — if no chart lib exists, CSS bar sparklines). en/id.
- Query budget: each aggregate is one indexed SQL statement; endpoint target <500ms on demo data; no caching v1.
Discipline (unchanged)
NEVER go test; verify build/vet + tsc/vite + curl e2e on deployed stack; deploy from repo root with --env-file; assert 5 modules post-deploy; commit per task on main; push only when asked; never git add -A; purge e2e artifacts.
Out of scope
Per-subscription granularity (category level only), push/WhatsApp channels, scheduled report emails, dashboard date-range picker beyond ?days=, exporting KPIs (XLSX export is a separate roadmap item).