Skip to content

Notifications Center — one page to read, route, and configure every notification (in-app + email + watchdogs)

Why

The notification machinery is real but scattered: the in-app inbox lives at /notifications yet is linked from NOWHERE (reachable only by typing the URL); who receives system EMAILS is configured in a Tools-page row (SystemEmailRoutingToolaote-system/system/emailPolicy); watchdog health lives in systemHealth/* docs with no UI; and there is no per-user notification preference of any kind — users cannot choose which events reach them or how. Owner directive (2026-07-07, verbatim): "I'd like a formal Notifications (Center) page … not only handles in-app notifications, but a centralized place to handle and configure notifications."

Current state (mapped 2026-07-07)

  • Inbox page app/notifications/ — full table, 8 topic groups over the 25-value NotificationType union, filters, mark-read/delete, live Firestore listener (useSharedNotifications, 100-doc cap).
  • Email routing components/tools/SystemEmailRoutingTool.tsx + /api/system/email-policy + lib/notifications/emailPolicy.ts — per-CATEGORY (4 auto-email categories) enable + recipient override + greeting; admin-global, NOT per-user. Emails fire only at severity ≥ warning (notify.ts:202).
  • Producers — 16+ notifyXxx helpers in lib/notifications/notify.ts; the AUTO paths are unwired (that's the T-037T-041 cluster, still todo: auto-match, vendor-invoice upload, bank-sync, suggestions; plus dormant Resend webhook + overdue cron).
  • Watchdogs — reconciliation (NAS-pinged), scrape per-pipeline (Vercel-cron-pinged, T-147), markers in systemHealth/*; alerts land as data_integrity notifications; no status UI.
  • Gaps — no per-user prefs (type/topic × in-app/email/off), no digest, no delivery log, no bulk actions/search in the inbox, no admin UI for page rules.

Scope (phased)

  • P0 — SHIPPED with this task's opening commit: "Notifications" entry in the universal sidebar (NAVIGATION_RESOURCES + ALLOWED_MENU_KEYS + AppShell tab order/icon + open page rule + pageKey gate). Visible on every page for every authenticated user; page title already "Notifications".
  • P1 — Gather the existing surfaces into the page (tabbed):
  • Inbox (existing table) ·
  • Email Routing — move/embed SystemEmailRoutingTool (admin-gated tab), retire the Tools row ·
  • System Health — watchdog panel: per-pipeline scrape markers, reconciliation freshness, last/next ping, recent watchdog alerts.
  • P2 — Per-user preferences (the new engine): users/{uid} (or subcollection) preference matrix — per topic/type: in-app / email / both / off (+ future digest). Enforce in notify.ts recipient resolution (in-app create skip + email filter) with safe defaults (current behavior). Preferences UI as a matrix tab. System-critical alerts (watchdogs → super_admin) exempt from opt-out.
  • P3 — Delivery visibility: Resend webhook handler (bounce/complaint — the dormant T-041 extra)
  • a sent/failed email log tab.

Notes

  • The T-037T-041 cluster (wiring producers on AUTO events) is complementary, not overlapping: they make events fire; this page governs who sees/receives them. Do P2 with those in mind.
  • Owner may route this to the User Management agent (notification-center brief exists in that agent's onboarding scope); board entry deliberately self-contained for handoff.

Decision log

2026-07-07 — opened; P0 shipped

  • ✅ Read AGENTS.md. Scope-checked the board: T-037–T-041 cover producer wiring only; no task covers a Notifications Center page/preferences — new scope. (README next-free pointer was stale at T-160 while T-160/T-161 were taken; took T-162, pointer → T-163.)
  • P0 shipped in the same commit: sidebar entry via the universal nav (5 files: lib/navigation/resources.ts, components/layout/AppShell.tsx [BellOutlined + tab order], lib/rbac/pageRules.ts [pre-existing open notifications: {} rule kept — no rule change needed], app/notifications/page.tsx [pageKey gate], components/profile/ProfileApp.tsx [access-preview label]). tsc clean.
  • Sidebar-universality finding (owner's companion question): the sidebar is ALREADY one universal object — every one of the 28 AppShell mounts passes the identical NAVIGATION_RESOURCES + useAllowedMenuKeys() RBAC filter; visibility differences are per-USER rule evaluation (subsidiary/role/badge), not per-page configuration. No refactor warranted; concrete "item vanishes between pages" repros should be chased as rule bugs or the page-rules loading flash.
  • Source: EOP Local Assistance (fork) · session edb0abc7-6373-449b-8e31-379fd38d391b

2026-07-07 — routed to the User Management agent (P1–P3)

  • ✅ Read AGENTS.md. The owner delegated the routing call (verbatim): "I'll let you decide to whether pass it on to the 'User Management' agent or if you wanna handle it yourself. I'm fine either way." Decision (EOP Local Assistance (fork)): hand P1–P3 to User Management. Rationale: (a) the notification center is in that agent's onboarding charter; (b) P2's per-user preference engine and the inbox's live Firestore listener overlap that agent's own T-025 (Admin-SDK → client-SDK migration) — single ownership avoids collisions; (c) load-balancing (this agent holds T-147 close-out, T-154, T-047).
  • P0 remains shipped (44dd3a34); this task is handoff-ready as written. User Management: start at the "Current state" + "Scope" sections above; coordinate P1's SystemEmailRoutingTool move with your T-025 file-touch plan.
  • Source: EOP Local Assistance (fork) · session edb0abc7-6373-449b-8e31-379fd38d391b

2026-07-09 — accepted by User Management; status → doing (planning, pre-build)

  • ✅ Read AGENTS.md. Scope-checked the board: no existing task covers a Notifications Center page / per-user preferences — T-037–T-041 wire producers (complementary), T-025 is the client-SDK migration (overlaps only where noted below). Not a dup. Accepting P1–P3 from EOP Local Assistance (fork); P0 already shipped (44dd3a34).
  • Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW

Current-state reconciliation (grounds the T-025 sequencing): - The inbox listener useSharedNotifications is ALREADY on the client SDK (firebase/firestore onSnapshot via useSharedFirestoreQuery, gated by useFirebaseAuth). So P1's Inbox tab reuses an already-migrated read path — no self-collision with T-025 on reads. - The genuine T-025 interaction is P2's preference write path. Decision: route user pref edits through a self-scoped API route (mirroring the T-161 P3 /api/profile/name-preferences pattern — resolves the caller's own record, no id param), not a client-SDK write. This stays forward-compatible with T-025 without racing its not-yet-written client-SDK write rules. notify.ts enforcement stays server-side (Admin SDK) — the privileged/derived layer T-025 explicitly reserves, so it's unaffected by the migration.

Planned sequence (build starts on the owner's go): - P1 — tabbed page: Inbox (existing table) · Email Routing (move SystemEmailRoutingTool in, admin-gated; retire the Tools row) · System Health (read systemHealth/* markers). Lowest risk; I own SystemEmailRoutingTool from T-161 P4. - P2 — per-user preference matrix (type/topic × in-app / email / both / off), enforced in notify.ts recipient resolution with current behaviour as the default and watchdog→owner alerts exempt from opt-out. Matrix shape designed so the T-037T-041 producer types slot in when wired. Granularity (per-type vs per-topic) + digest deferral → owner sign-off at P2 planning, quoted per AGENTS.md. - P3 — Resend bounce/complaint webhook + delivery log tab.

House rules acknowledged: never persist what can be derived (the bell count already derives from the page query — preferences store only genuine user choices, not computed reach); no versioned structure labels; owner approvals quoted in this log.

Status = doing (owned + planning), pre-build: awaiting the owner's go to start P1, and a call on whether to deploy the stacked T-160/T-161 work first. No code yet.

2026-07-09 — UI-preview gate added (owner instruction); preview v1 published

  • ✅ Read AGENTS.md. Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW
  • Owner instruction (verbatim, 2026-07-09): "Before you enter, I'd like you to make sure that there's a step where you'd integrate the UI. And we get to that point, please provide me a re-design preview so that I know how it can look like, and have things tweaked before really having the newly created features deployed."
  • Gate recorded: every UI-integrating phase of this task ships preview → owner tweaks → approval → build (commit-only, PR flow) → deploy only on the owner's word. Same rhythm as the T-048 subsidiary-tab / contact-drawer redesigns.
  • Preview v1 published (label notifications-center-v1): https://claude.ai/code/artifact/22f87672-7b76-4ed2-afd6-76657bd579be — the full page mock on the app's design language: sidebar with the P0 Notifications entry + unread badge; tabs Inbox (real topic taxonomy, severity, filters, mark-read/delete) · Preferences (P2 matrix — all 25 types under the 8 topics, per-row In-app/Email/Both/Off, defaults = today's behaviour, data_integrity watchdog-exempt lock) · Email Routing (the Tools tool relocated, incl. the T-161 greeting selects) · System Health (4 real watchdog pipelines with thresholds/remedies + recent activity). Admin/Member toggle demonstrates tab gating; light/dark.
  • Awaiting owner feedback on v1 before any build.

2026-07-09 — owner feedback round 1 → preview v2 published

  • ✅ Read AGENTS.md. Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW
  • Owner feedback on v1 (verbatim, 2026-07-09): "1. With the Preferences tab, instead of showing In-app, Email, both and off, show just in-app and email and allow the tabs to be turned on and off. If both are on, then it's 'both', while if both are off, then it's 'off' 2. I'm unsure whether 'System Health' should be in Notifications 3. Don't show push notification numbers on the sidebar tab"
  • v2 changes (same artifact URL, label toggles-no-badge-health-option-v2):
  • Preference rows = two independent toggles (In-app / Email); effective state derives (both on = both, both off = off/muted, shown red). Watchdog-exempt row: both locked on.
  • System Health placement made comparable in-preview — a "Health tab: in Notifications / in Tools" control shows both options (option B removes the tab + notes it lives in Tools). PM recommendation logged: keep it in Notifications (the alerts it explains land in this inbox; Tools is simultaneously LOSING Email Routing to this page, so sending surfaces both directions is confusing) — but the owner decides; decision pending.
  • Sidebar Notifications item shows no unread-count badge (count stays on the Inbox tab label inside the page).
  • Awaiting owner verdict on v2 + the System Health placement call.

2026-07-09 — round 2: preferences approved · rename proposals (v3) · hide-n-seek notifications fixed

  • ✅ Read AGENTS.md. Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW
  • Owner (verbatim): "Preferences looks good." — the two-switch (In-app / Email) matrix design is APPROVED for P2.
  • Rename ask (verbatim): "maybe if we rename the Notifications tab to something more like Notifications, Alert Control & Warner Preferences... Something similar to that or if not similar, more appropriate than that." Preview v3 (same URL, label rename-notification-center-v3) shows the recommended "Notification Center" (sidebar + H1); alternatives offered: "Notifications & Alerts", "Alerts & Preferences". Owner to pick; the real nav rename ships with the P1 build.
  • Hide-n-seek fix — SHIPPED (3e11ce2a, PR #878). Owner (verbatim): "There're a few notifications that prompts user to click for more detail, but then when the user arrives the Notification page, the notification prompts the user to check system log, which turned it into a whole kind of hide-n-seek. Could you find those conversation and make it so that the entire notification could show up in on notifications page?" Inventory + fix:
  • Offenders: the reconciliation summaries (daily cron pages/api/cron/reconciliation.ts + manual pages/api/accounting/reconciliation/run.ts) said "… detected. Open the reconciliation report for details." → now itemized via shared buildReconciliationSummaryMessage (ids/amounts/reasons, 12-per-category cap, exact "…and N more" overflow; 3 unit tests).
  • Rendering half: the inbox clamped every message to ONE ellipsized row (crushing even good multi-line messages like the DMARC coverage lists) → now 2-line collapsed, expandable in place, newline-aware (app/notifications/client.tsx).
  • Reviewed, already self-contained: scrape-heartbeat (remedy included), dmarc-heartbeat (itemized), rotation-reminder (runbook included). Excluded: ocbc/velocity/debug (dev tool pointing at a NAS folder — nothing to inline).
  • Still open for the owner: rename verdict · System Health placement (in Notifications vs Tools).

2026-07-09 — naming RESOLVED: "Notifications" stays; owner grants a one-off deploy

  • ✅ Read AGENTS.md. Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW
  • Owner (verbatim): "Let's just stick to notifications and merge it, then deploy the web app (one off deploy approval)". After weighing "Notification Center", "Notifications & Alerts", "Alerts" (preview v4) and the escalation ladder (alerts/alarms/warnings/signals/watchtower), the page and sidebar keep the existing name "Notifications" — zero code change needed (P0 already ships that label). Naming discussion closed.
  • One-off deploy approved and executed by User Management via the Vercel deploy hook (main 🟡🟡 = Vercel, manual — this is the owner's explicit per-instance order the policy requires). Ships everything stacked on main incl. the T-160/T-161 signing-name work, the contact-drawer redesign, and this task's hide-n-seek fix (3e11ce2a).
  • Still open (last design decision before the P1 build): System Health placement — in Notifications vs in Tools; the comparison toggle stays live in the preview.

2026-07-09 — owner's 7-point restructure directive → "Notification Hub"; preview v5

  • ✅ Read AGENTS.md. Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW
  • Owner directive (verbatim, 2026-07-09): "1. Rename it Notification Hub 2. a) Create a sub-tab named Push Notifications in the sidebar, and for that page, b) shows only the inbox tab and the Preference tab 3. a) Create a sub-tab named System Emails. b) Migrate Email Templates from the Tools tab and c) without dividing each in it's own tab on the System Emails page, allow the template of 'Data-integrity & watchdog alerts', 'Invoice delivery failures', 'Invoice overdue alerts' & 'Invoice re-issue needed' to be previewed there while attached the Email routing into each email type as well 4. Move 'System Health' as a sub-tab under the Settings Tab in the sidebar 5. Create a sub-tab under Tools named 'Typesetting', and migrate 'Invoice Previewer', 'Payment Receipt Preview (MEL)', 'Payment Request Preview (MEL)', & 'Payment Confirmation Previewer' to it. 6. a) Create a 'Features Preview' tab under Settings, b) migrate 'IR56M — sign & seal test' to it, and c) create a 'WOPC — sign & seal test' for the WOPC referencing the features of the 'IR56M — sign & seal test' 7. Move 'DMARC Reports' as a tab on the 'System Health' page"
  • This SUPERSEDES the earlier one-page/4-tab P1 sketch and the "Notifications stays" naming verdict; System Health placement is now RESOLVED: Settings (item 4), with DMARC as a tab on it (item 7). Read-back confirmed; all migrate-sources verified in code (components/tools/ToolsApp.tsx rows + /admin/dmarc).
  • UX review delivered (owner asked): endorsed the audience-split IA as better than the 4-tab sketch; strongest idea = template-preview-beside-routing (item 3c). Flags recorded: (a) the name "Push Notifications" technically implies OS/browser push the app doesn't do — alternatives "My Notifications"/"Inbox" offered, owner's word stands, a live name-compare toggle is in the preview; (b) watchdog alerts' View action must DEEP-LINK to Settings ▸ System Health to offset the extra navigation hop; (c) System Emails page lazy-renders/collapses previews (weight).
  • Preview v5 published (same URL, label hub-restructure-v5): nested sidebar (Notification Hub ▸ Push Notifications · System Emails / Tools ▸ Typesetting / Settings ▸ System Health · Features Preview), Push Notifications = Inbox + Preferences (approved designs carried over), System Emails = per-kind routing beside a LIVE template mock (greeting select re-renders the preview), System Health with Watchdogs + DMARC tabs, Typesetting + Features Preview card pages (WOPC sign-&-seal marked NEW — doubles as the T-160 verification rig). Admin/Member gating: member sees only Push Notifications.
  • Assumptions logged: sidebar parents expand with children (parent click → first child); /notifications and /admin/dmarc redirect to their new homes. Scope split proposed: items 1–4+7 stay T-162; items 5–6 (Tools Typesetting + Features Preview + the new WOPC harness) to a companion task — awaiting the owner's OK.
  • Awaiting owner tweaks/approval on v5 before the build.

2026-07-09 — sub-tab HOUSING comparison (owner ask) → preview v6

  • ✅ Read AGENTS.md. Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW
  • Owner asked how else the sub-tabs could be housed in the UI. Survey delivered (sidebar children · always-open groups · in-page tabs · card landings · second rail) with the dividing rule: own audience/permission or deep-link target → sidebar child; same audience, different view → in-page tab. Recommendation: children under parents, never collapsed (open groups) — hierarchy + per-child gating with nothing hidden; card landing noted as the upgrade path for Tools if it keeps growing.
  • Preview v6 (same URL, label sidebar-housing-options-v6): a Sidebar control cycles the SAME content through the three candidate housings — open groups (recommended, as drawn in v5) → collapsible parents (▸/▾ toggle, children can hide) → card landings (sidebar shows only parents; Notification Hub / Tools / Settings become card doorway pages). Housing decision pending the owner.

2026-07-10 — housing + taxonomy verdicts; Rehearsals live-front spec; preview v7

  • ✅ Read AGENTS.md. Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW
  • Housing RESOLVED — collapsible. Owner (verbatim): "Sidebar: collapsible is definitely what I envisioned." (v7 defaults to it; the compare toggle stays for reference.)
  • Taxonomy review delivered (owner asked whether the parent↔child pairings are categorically appropriate). Full Tools inventory (12 rows) grounded it. Governing rule recorded: use it to do/check work → Tools · set or observe the system → Settings · the system talking to people → Notification Hub. Suggested deltas: (1) keep the invoice family whole — "Full Invoice Preview" + "Invoice Sections" join Typesetting (6 tools); (2) sign-&-seal harnesses are rehearsal rigs by FUNCTION → Tools ▸ Rehearsals (categorize by what a thing IS, not its maturity — status shelves force re-homing); hold "Features Preview" for real feature toggles; (3) "Deployments — build status" is system observation → propose third tab on Settings ▸ System Health; (4) design references (Sidebar Icon Reference, Component Showcase) stay flat in Tools for now.
  • Owner verdicts (verbatim): "I agree with the invoice section and full invoice preview merging to Typesetting." → delta (1) RESOLVED. On IR56M: "we could make it so that there's a front that allows a live preview of user inputting information on inline fields and those info gets shown immediately on the form, then provide the '[Open the sign & seal test here] (https://eop.theestablishers.com/ir56m/sign-test)' at the bottom of the IR56M section or the web app also allows user to user demo pre-filled information to do the test, and we'll include the Send Demo email button as well."
  • Rehearsals live-front SPEC (from the owner's words): each rehearsal (IR56M · WOPC) is a live form front — type into inline fields placed ON a form facsimile and the values render immediately; Use demo data pre-fills; Send demo email exercises the real email pipeline; footer link opens the full sign-&-seal flow (/ir56m/sign-test; WOPC gets a new /wopc/sign-test). WOPC front's closing block renders via the T-160 signing-name resolution — the harness doubles as that pipeline's verification rig.
  • Preview v7 (same URL, label rehearsals-live-fronts-v7): collapsible default · Typesetting = 6 cards · Tools ▸ Rehearsals with BOTH live form fronts working (type-in fields, demo fill, demo-email button, sign-&-seal links) · Settings ▸ System Health gains a “Deployments (proposed)” third tab so delta (3) can be judged in place · Features Preview removed.
  • Still open: owner's word on Rehearsals-under-Tools naming/parent (v7 shows the recommendation) and on the Deployments tab (marked “proposed” in v7).

2026-07-10 — v7 feedback round → v8: expandable rows, collapsible mail kinds, "Pipeline Preview", Deployments own sub-tab

  • ✅ Read AGENTS.md. Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW
  • Owner feedback on v7 (verbatim): "1. For Typesetting a) Show everything as expandable rows just like how they were in the Tools page, b) Aside making the following collapsible as well, group Invoice Previewer, Full Invoice Preview & Invoice Sections into one ALSO collapsible parent row c) Specify the name as 'Project Invoice Previewer' d) Don't show 'Open previewer →', show what's in the previewer in the collapsible row (as mentioned in 1a)) 2. Same with the cards in System Emails, make them collapsible 3. I don't like how it's called 'Rehearsals', change it to something else (Pipeline Preview/ Walkthrough...) 4. Deployment should have it's own sub tab under Settings"
  • Resolved by this round: Typesetting = expandable rows Tools-style, expanding shows the previewer inline (no link-outs), with the three invoice tools under one collapsible parent "Project Invoice Previewer" · System Emails kinds are collapsible (first open by default) · the live-front page is named "Pipeline Preview" (owner's first candidate; contrasts with Typesetting: document vs process) — Tools parent stands · Deployments = its own Settings sub-tab (not a System Health tab); System Health keeps Watchdogs · DMARC.
  • Preview v8 published (same URL, label expandable-rows-pipeline-preview-v8) implementing all four, with inline previewer mocks (invoice controls + sheet, sections px table, WOPC closing block, MEL receipt/request) marked "…the existing tool embeds here unchanged."
  • Awaiting the owner's v8 verdict — if clean, the design is CLOSED and the build starts (T-162 items 1–4+7; companion task for the Tools estate: Typesetting · Pipeline Preview · Deployments move).

2026-07-10 — Typesetting row renames (owner) → preview v9

  • ✅ Read AGENTS.md. Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW
  • Owner (verbatim): "Rename Payment Receipt Preview (MEL) to Coaching Payment Receipt Preview and Payment Request Preview (MEL) to Coaching Payment Request (Invoice) Preview" — these become the BUILD-TIME row labels for the migrated tools. Applied in preview v9 (same URL, label coaching-preview-renames-v9), incl. the facsimile sheet titles.

2026-07-10 — owner: "Go" — BUILD STARTED. Wave A shipped (P2 preferences engine + tabbed Push Notifications)

  • ✅ Read AGENTS.md. Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW
  • Design v9 approved by the owner's "Go". Build waves: A P2 engine + Push Notifications tabs (this entry) · B Hub sidebar nesting + System Emails page · C System Health + DMARC + redirects · D–F Tools/Settings estate → split to T-165 (created now, per the recorded proposal).
  • Wave A shipped:
  • lib/notifications/topics.ts — taxonomy moved out of the inbox client and made EXHAUSTIVE over the full 28-value NotificationType union (TYPE_META: Record<NotificationType,…> = compile-checked; new projects topic; inbox topic filter now derives membership from it).
  • lib/notifications/preferences.ts + prefs.server.ts — the P2 engine: sparse per-user overrides on users/{uid}.notificationPrefs (only non-default entries persisted — derive rule), two independent channel switches per kind, watchdog exemption (data_integrity × admin-tier can't be muted), batch reads fail-open to deliver.
  • Default = {inApp:on, email:on} for EVERY kind — a deliberate deviation from the preview's per-row default markers: email fan-out only exists where the system already emails (severity ≥ warning via notifyRoles + routed categories), and e.g. the DMARC coverage-gap alert rides type system at warning — a per-kind email-off default would have silently muted owner-critical mail. Behaviour-preserving beats preview-cosmetic; matrix explainer says so.
  • /api/profile/notification-preferences (GET catalog+overrides / PUT validated, self-scoped — T-161 P3 posture; locked kinds force-dropped server-side).
  • Enforcement in lib/notifications/notify.ts: notifyUsers gates the in-app channel (every broadcast helper funnels through it); notifyRoles' email block gates the email channel after the category policy; channels independent; pref-read failures deliver-all.
  • Push Notifications page: /notifications retitled, now Inbox | Preferences tabs; the Preferences matrix is the approved two-switch UI (topic collapses, both-off shown red "off", watchdog-exempt lock, per-click optimistic saves).
  • Heavy unfiltered tsc clean · eslint clean on new/changed code (pre-existing stub anys untouched) · vitest 645 green (+21: taxonomy exhaustiveness, defaults, normalize/derive-drop, exemption, channel filter).

2026-07-10 — Wave B shipped: Notification Hub sidebar nesting + System Emails page

  • ✅ Read AGENTS.md. Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW
  • Sidebar: notifications relabelled "Notification Hub" and made a collapsible parent via the house pattern (resources + DEFAULT_TAB_ORDER parent + per-child page rules): children Push Notifications (/notifications, open rule — parent+child share the route, the proven bank precedent) and System Emails (/notifications/system-emails, admin rule inheriting the Tools gate vocabulary). Icons wired; saved sidebar orders self-reconcile (existing AppShell reparenting logic).
  • System Emails page (owner spec 3c): ONE page, four collapsible kinds, each pairing routing (enable / recipients / greeting — same /api/system/email-policy model + Save semantics as the Tools row) with a live template preview rendered by the sender's own markup: the pure template extracted to integrityEmailTemplate.ts (sender rewired, zero behaviour change except one honest fix: multi-line alert messages now render as lines in the HTML email too — white-space:pre-line — closing the email-side twin of the inbox clamp), and /api/tools/email-preview extended with the four alert kinds + a greeting sample param. Changing a kind's greeting re-renders its preview via the T-161 naming lib (sample person). The full Email Templates storyboard embeds below (the migrated tool, unchanged).
  • Tools slimmed: the "Email Templates" and "System Email Routing" rows removed (pointer comment left in place).
  • Heavy unfiltered tsc clean · eslint: only a pre-existing pageRules any (identical at baseline) · vitest 648 green (+3 template tests: greeting escape/omission, pre-line, text variant).

2026-07-10 — Wave C shipped: Settings ▸ System Health (Watchdogs · DMARC) + Deployments sub-tab

  • ✅ Read AGENTS.md. Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW
  • Settings children (collapsible, house pattern): System Health (/settings/system-health, new) and Deployments (points at the existing /deployments page — its Tools link-card retired); hk-banks stays; the standalone dmarc sidebar entry is gone (its badge vocabulary moved INTO the system-health rule; the dmarc PageKey retired, one rule test updated).
  • System Health page: Watchdogs tab reads /api/system/health — a new READ-ONLY admin endpoint assembling the SAME freshness facts the dead-man's-switch crons check (reconciliation latest report; systemHealth/* markers via the cron's own lastIngestAt field incl. the legacy fallback; workspace entries max updateTime) with thresholds/remedies mirrored and keep-in-sync comments pointing at the crons (deliberately no refactor of the watchdog owner's files). Recent watchdog activity derives client-side from the shared notifications listener (viewer's own data_integrity copies — no new index). DMARC Reports tab embeds the former /admin/dmarc content (DmarcReportsContent exported); ?tab=dmarc deep-link; tab switch updates the URL.
  • Redirect: /admin/dmarc/settings/system-health?tab=dmarc (bookmarks + stale actionUrls keep working).
  • Watchdog deep-links (the recorded mitigation): scrape-heartbeat + reconciliation-heartbeat alerts now actionUrl: /settings/system-health; dmarc-heartbeat ×2 → /settings/system-health?tab=dmarc. (The reconciliation CRON's daily summary keeps /admin/reconciliation — that's the report page, still the right target for report content.)
  • Heavy unfiltered tsc clean · vitest 648 green · the 53 eslint hits in the three touched legacy files are byte-identical at baseline (pre-existing).
  • T-162 build COMPLETE for its estate (P0 + waves A/B/C = the fork's P1+P2 re-scoped through the owner's v9 design). Remaining in-task: P3 (Resend webhook + delivery log) — parked pending the owner's word; the Tools estate continues in T-165.

2026-07-10 — coordination note from Accounting (Diagnostics): new type match_suggestion (T-041)

  • ✅ Read AGENTS.md. T-041 shipped the auto-matcher suggestion notifications with a NEW NotificationType: match_suggestion (topic books, label "Match suggestion", info severity, producer notifyMatchSuggestion). Registered in TYPE_META + labels + topic map + bell/inbox icon maps, so the preferences matrix and inbox grouping pick it up automatically — no action needed unless you want a custom row treatment. Also note T-038 reuses payment_status and T-040 reuses bank_sync (no new rows).
  • Source: Accounting (Diagnostics) · https://claude.ai/code/session_01G58Y71noihrYCDEDMexmea

2026-07-20 — owner: the Hub must be THE single source of truth; recipient model v2 (dynamic ∪ specific) → preview v10

  • ✅ Read AGENTS.md. Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW
  • Trigger — owner, reviewing the deployed System Emails page, asked about the invoice alert kinds (verbatim): "For the Invoice related alerts, instead of allowing configuration for user to decide whom exactly to send the alerts to, I'm wondering a) shouldn't these emails sent to whomever created the invoice and/ or system administrator or IT Specialist etc… b) Do we currently record whoever that creates the invoice so that we could have the system contact the invoice creator for follow-up alerts/notificaitons?"
  • Facts reported for (b) (verified in code, not changed yet):
  • Creator IS recorded: invoice writes stamp createdBy with the session identity (email ≫ name ≫ "unknown") — pages/api/projects/by-id/[projectId]/invoices/index.ts (create :204, amend :304). It's an EMAIL string, so routing to the creator needs an email→uid resolve at send time.
  • The Hub is NOT the single source of truth today: the three invoice email kinds route via a hardcoded INVOICE_NOTIFICATION_ROLES = ['admin','project_admin'] in lib/notifications/notify.ts (:301), and only the 4 alert categories are governed by /api/system/email-policy at all — e.g. the DMARC coverage-gap alert rides kind system with no Hub row.
  • Liveness: only invoice_reissue_needed actually fires today (2 call sites); invoice_overdue has a helper but NO cron; email_bounce awaits the parked P3 Resend webhook.
  • Owner directive (verbatim): "Regarding (b), are you saying that the configuration provided in Notification Hub isn't THE single source of truth? If so, we need to make it so. As for (a), I think we'll have to integrate all other alert configuration to allow including personnel like the original creator while allowing adding specific people"
  • Recipient model v2 (agreed direction, pending v10 preview sign-off):
  • Hub = authoritative for EVERY email-capable kind. The policy store becomes keyed per kind; the hidden code role lists (INVOICE_NOTIFICATION_ROLES, and the accounting twin) are retired to SEED DEFAULTS only — first read materializes them as stored, visible, editable config; notify.ts's email path reads the Hub policy for every kind (incl. system).
  • Mixed Send-to entries per kind: dynamic personnel tokens (✦ Original creator, All admins, Project admins, Finance team…) resolved at send time from producer-attached context (creator = createdBy email → uid), freely mixed with specific users. Per-user Preferences still win for the individual (their "Off" stays off).
  • Preview v10 published (same artifact URL, label recipient-model-v10): mixed-chip Send-to rows (dashed ✦ chips vs solid people), invoice kinds defaulting to ✦ Original creator + ✦ All admins + ✦ Project admins, a 5th demonstration row system_notice (DMARC coverage gap) proving all-kinds governance, and the notebar rewritten to state the single-source-of-truth rule. Build starts on the owner's v10 approval per the standing preview gate; open build questions flagged to the owner: fold in the missing invoice_overdue cron, and/or unpark P3 (bounce webhook) with creator routing built in.

2026-07-21 — owner: "Sure" → recipient model v2 BUILT & MERGED (628a7154 / #911)

  • ✅ Read AGENTS.md. Source: User Management · https://claude.ai/code/session_01GGT5n9vCxKWoUQSRAfMSiW
  • Owner approved v10 with "Sure" — read per the stated default as build as shown; the overdue cron and P3 (bounce webhook) stay parked until the owner says otherwise.
  • Shipped (SHA 628a7154, PR #911):
  • emailPolicy.ts v2 — EMAIL_CAPABLE_KINDS (all 18 kinds any producer dispatches through notifyRoles, incl. system and access_expiry_warning from the super-admin lane), mixed RecipientEntry model (✦ dynamic tokens creator / admins / project_admins / finance / super_admins + specific uids), compile-time-exhaustive SEED_KIND_POLICIES (the retired hidden role lists as stored-visible defaults), send-time resolution mirroring notifyRoles matching, stored → migrated-v1-category → seed precedence, v2 write validation (creator token only on kinds with producer context). Legacy categories field read-only for in-place migration; never written by v2 (rollback-safe).
  • notify.ts — email path reads the per-kind Hub policy for EVERY kind; unclassified kinds warn + fall back to the role audience (the sync tripwire for future notifyRoles call sites). INVOICE_NOTIFICATION_ROLES / ACCOUNTING_NOTIFICATION_ROLES demoted to in-app audience + seed material. The 3 invoice alert producers take creatorEmail.
  • Creator resolution — resolveInvoiceCreatorIdentityServer (earliest updateLogs entry's editedBy = the creation write, mirroring the invoice timeline's derivation; legacy doc-level createdBy fallback; null on failure, backstop tokens still cover). Both LIVE invoice_reissue_needed call sites (invoice PATCH + /api/invoices/send) attach it.
  • /api/system/email-policy v2 — per-kind GET (effective config + stored flag + role-token membership so the UI shows the people behind a ✦ chip) and validated PUT (merge:true preserves the legacy field). First Save materializes all seeds as stored config.
  • System Emails UI — every governed kind a collapsible row (topic chips; "seed default — not saved yet" tag), mixed Send-to picker with dashed ✦ chips + resolved-membership hint, per-kind live preview via new alert-generic&kind= sample (all alert emails share the one sender template, so the preview is honest), v10 single-source-of-truth notebar.
  • Removed the orphaned SystemEmailRoutingTool (unreferenced since wave B; consumed the retired v1 API shape).
  • Owner-visible seed deltas (all editable in the Hub): the 3 invoice alert kinds seed ✦ creator + ✦ admins + ✦ project admins (the directive's (a)); system seeds ✦ All admins per the approved v10 (widened from super_admin-only — super_admin is inside the tier); access_expiry_warning stays ✦ Super admins; payment_status seeds the union of its two dispatch estates (admins + project admins + finance).
  • Unchanged by design: severity ≥ warning email gate, per-user preference gating (a person's "Off" still wins), greeting resolution (T-161), in-app delivery & audiences.
  • Heavy unfiltered tsc clean · vitest 673 green (emailPolicy suite rewritten for v2: seeds exhaustive, resolver incl. case-insensitive creator matching, precedence/migration, T-161 greeting grammar preserved) · eslint on touched files byte-identical to stash baseline (25 pre-existing, 0 new).
  • Blast radius: email routing only — every kind that emails now resolves through the Hub policy; producers/in-app untouched; Records' seams untouched. NOT yet deployed (merged ≠ deployed; deploy on the owner's word per house rules).