Tenant Admin Console · Surface walkthrough

Notifications

A per-surface walkthrough of the Tenant Admin Console tenant-admin surface: layout, states, interactions, data, and cross-references.

drafted
8sections2 minread

On this page

Context. surface tenant-admin · domain operations · route /notifications · auth signed-in + tenant:admin (getTenantServerSession) · source apps/oshun/tenant-admin/src/app/notifications/page.tsx

Last walked.

Purpose#

Read-only inspection of how tenant communications are evaluated before sending: per-channel delivery decisions against user preferences, localized template rendering, message-center inbox state, and digest batching. Backs the notifications workspace (configuration group) in libs/oshun/navigation/src/tenant-ia.ts.

Entry points#

  • Shell sidebar (TenantSidebar) — "Messages" (shortLabel) under the Configuration group, link to /notifications (data-workspace-id="notifications")
  • Workspace grid on / (home) — list item data-workspace-id="notifications"
  • Direct URL /notifications (bookmarkable; middleware-gated)

Layout regions#

Inside TenantShell:

  • Header: TenantHeader shows tenant · <tenantId>"Notifications"
  • Main: <section data-testid="tenant-notifications-panel" data-tenant-id={tenantId}>
    • <h1>Notifications</h1> + subtitle paragraph
    • .summaryGrid — three .summaryItem tiles: Deliveries (count of decisions with verdict === 'deliver'), Inbox unread (inbox.length), Digest items (digest?.totalItems ?? 0)
    • .table — per-channel decision table: columns Channel, Preference (minSeverity or "override"), Decision (badge: deliverbadgeGood, else badgeNeutral)

States#

  • Loading — N/A; server component renders fixtures in one pass
  • Empty — N/A; fixture prefs.perChannel always yields rows (email, push, in-app). "Digest items" can show 0 when no digest is built
  • Populated (short) — three channel rows render; summary tiles reflect decideDelivery verdicts, inboxFor unread count, buildDigest items
  • Populated (long) — N/A; fixture fixed at 3 channels
  • Error (recoverable) — N/A; no runtime query
  • Error (unrecoverable) — no session → redirect('/unauthorized?reason=missing-session&returnTo=/notifications')
  • Offline — N/A for data (SSR-rendered)
  • GatedgetTenantServerSession() null → redirect to /unauthorized; sidebar Locked when canEnterTenantWorkspace is false
  • Standalone PWA — verify; no manifest/SW confirmed in source

Interactions#

The <main> content is a static report — no buttons, inputs, toggles, or template editor. All interactivity is shell chrome:

  • Skip to main content (link, TenantShell) → anchors #tenant-main
  • Sidebar workspace links (links, TenantSidebar) → navigate; current aria-current="page"; Locked when gated
  • Channel decision rows — non-interactive; each row shows channel, resolved preference minSeverity, and a verdict badge

Data & contracts#

  • Reads: no BFF/network read. The page composes pure functions from @oshun/tenant-console on in-page fixtures:
    • decideDelivery({ request, prefs: UserNotificationPreferences }) → per-channel verdict
    • renderTemplate({ template: NotificationTemplate, variables, locale, fallbackLocale })rendered.subject / rendered.body
    • appendEntry(...) then inboxFor({ entries, userId, onlyUnread, minSeverity, limit })inbox
    • enqueueForDigest(...) then buildDigest({ userId, tenantId, cadence, nowUnixSeconds, queue })digest
    • NOW = Date.UTC(2026, 4, 11, 15) (seconds); user fixture op-jane
  • Writes: None.
  • Realtime: None.
  • Caching: SSR (server component); deterministic per tenantId
  • Auth/role check: getTenantServerSession() in page; middleware gate

Contracts: NotificationTemplate, UserNotificationPreferences from @oshun/tenant-console (libs/oshun/tenant-console). Inbox/digest entries carry deepLinkPath values /policy and /members pointing at sibling routes.

Cross-references#

Open questions / known gaps#

  • No live notifications BFF route is wired — page renders a static fixture of one delivery request, one template, one inbox entry, one digest item.
  • No template editor, send action, or preference toggles exist on this route (read-only). Verify where tenant template management is intended to live.
  • "Preference" column shows override when no per-channel minSeverity is found; confirm this is the intended fallback label.