think
16px
820px

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_id PK/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/notify usage (expected: workflow, correspondence, retention/records, shares/requests, system/digest). Store canonical slugs; UI labels via i18n.
  • Enforcement at delivery time in internal/notify dispatch + 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).