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 itemdata-workspace-id="notifications" - Direct URL
/notifications(bookmarkable; middleware-gated)
Layout regions#
Inside TenantShell:
- Header:
TenantHeadershowstenant · <tenantId>› "Notifications" - Main:
<section data-testid="tenant-notifications-panel" data-tenant-id={tenantId}><h1>Notifications</h1>+ subtitle paragraph.summaryGrid— three.summaryItemtiles: Deliveries (count of decisions withverdict === 'deliver'), Inbox unread (inbox.length), Digest items (digest?.totalItems ?? 0).table— per-channel decision table: columns Channel, Preference (minSeverityor "override"), Decision (badge:deliver→badgeGood, elsebadgeNeutral)
States#
- Loading — N/A; server component renders fixtures in one pass
- Empty — N/A; fixture
prefs.perChannelalways yields rows (email, push, in-app). "Digest items" can show0when no digest is built - Populated (short) — three channel rows render; summary tiles reflect
decideDeliveryverdicts,inboxForunread count,buildDigestitems - 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)
- Gated —
getTenantServerSession()null → redirect to/unauthorized; sidebar Locked whencanEnterTenantWorkspaceis 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; currentaria-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-consoleon in-page fixtures:decideDelivery({ request, prefs: UserNotificationPreferences })→ per-channelverdictrenderTemplate({ template: NotificationTemplate, variables, locale, fallbackLocale })→rendered.subject/rendered.bodyappendEntry(...)theninboxFor({ entries, userId, onlyUnread, minSeverity, limit })→inboxenqueueForDigest(...)thenbuildDigest({ userId, tenantId, cadence, nowUnixSeconds, queue })→digestNOW = Date.UTC(2026, 4, 11, 15)(seconds); user fixtureop-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#
- Workspace definition:
libs/oshun/navigation/src/tenant-ia.ts(notifications— groupconfiguration,requiredScopes: ['tenant:admin:*'],iconToken: 'bell',shortLabel: 'Messages') - Shell:
TenantShell.tsx,TenantSidebar.tsx,TenantHeader.tsx - Deep-link targets: policy.md, members.md
- Sibling routes: integrations.md, roles.md, status.md
- Conventions: ../WALKTHROUGH/00-conventions.md
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
overridewhen no per-channelminSeverityis found; confirm this is the intended fallback label.