Skip to main content

Admin Dashboard UI/UX Audit — Vertical Bleed + Architecture Proposal

Date: 2026-04-30
Scope: /admin/[token] — church and funeral verticals
Method: Full code-read audit (all tabs + components). Screenshots deprioritised due to time-box — code reading is authoritative and faster than screenshot capture for finding bleed strings.
PRs shipped: Code fixes in fix/dashboard-uiux-priority-fixes (see Phase 3 below).


1. Vertical Detection — What's Working

The dashboard correctly detects verticals at several layers:

  • page.tsxgetVerticalByHostname(host)resolveFuneralToken() vs resolveToken()WORKING
  • AdminDashboard.tsxverticalProfile.tabs non-empty path skips ALL_TABS (church fallback) — WORKING
  • VerticalProvider + useIsChurchVertical() / useVertical()WORKING but underused
  • funeral.tsFUNERAL_TABS defines 4 tabs (Home, Inbox, Train AI, Settings) — WORKING but Settings capability typo was present (fixed in PR #266/267)

2. Bleed Issues Found (exhaustive inventory)

2.1 DashboardOverview.tsx (OverviewTab)

Severity: P0/P1 — this is the default landing tab for all verticals.

ItemFile:LineSeverityStatus
ExamplePrayerCard with "Sample prayer request" copyDashboardOverview.tsx:383P0Fixed — isChurchVertical gate existed, confirmed correct
Welcome header "Here's everything going on at {churchName}":345P1Fixed in this PR — neutral copy for funeral
Notification banner "prayer requests, visitor contacts":308P1Fixed in this PR — vertical-aware copy
Metric tile "Prayer requests":724P1Fixed in this PR — "Family inquiries" for funeral
Metric tile "New visitors":740P1Fixed in this PR — uses terminology.visitor
Analytics donut segment "Prayers":880P1Fixed in this PR — "Inquiries" for funeral
Analytics donut segment "Visitors":885P1Fixed in this PR — uses terminology.visitor
Setup rail eyebrow "Get your church online":1220P1Fixed in this PR — "Get your funeral home online"
QuickActions body "congregation asks most":1070P1Fixed in this PR — "families and callers"
QuickActions title "Upload church documents":1077P1Fixed in this PR — "Upload documents"
RecentPrayersCard shown for prayer_team role regardless of vertical:412P1Fixed in this PRisChurchVertical guard
SideSummaryCard upsell "Get more from ChurchWiseAI":1422P2Fixed in this PR — neutral "Unlock more features"
ShareLinkCard uses churchwiseai.com/chat/${churchSlug}:542P2P2 backlog — funeral has no chatbot yet but link hardcodes CWA domain
displayPastorName() helper function name:149MinorP3 — function works fine, just named church-centric
redactCallbackSummary() returns "Pastoral inquiry":161P2P2 — already has vertical-aware version in InboxTab; Overview still uses old hardcoded helper
hasChurchProfile variable name/logic:214MinorP3 — naming only, logic is generic

2.2 TrainAITab.tsx (Train AI tab)

Severity: P0 — all 7 sections were visible for funeral, most are church-specific concepts.

ItemFileSeverityStatus
Section "Church Knowledge" shown for funeralTrainAITab.tsx:103P0Fixed in this PRverticalKeys: ['church']
Section "Theology & Tradition" shown for funeral:112P0Fixed in this PRverticalKeys: ['church']
Section "Pastor Pulse" shown for funeral:157P0Fixed in this PRverticalKeys: ['church']
Section "Chat Simulator" copy "ministry conversations":147P1Fixed in this PR — generic copy + verticalKeys: ['church']
Hero heading "Teach your AI about {churchName}":304P1Fixed in this PR — uses orgName with funeral fallback
Hero body "sermons, FAQs, theology, staff":307P1Fixed in this PR — vertical-aware copy
Section "Agent Personality" body "pastor voice, greeting script":127P2P2 backlog — "pastor voice" language
Section "FAQs" body "visitors and members ask most":137P2Partially fixed — "callers and clients"

2.3 SettingsTab.tsx (Settings slide-over body)

Severity: P0/P1 — gear icon is clicked constantly, most bleed is visible immediately.

ItemFile:LineSeverityStatus
Sub-tab SETTINGS_SUB_TABS[0].label = "Church Profile"SettingsTab.tsx:265P0Fixed in this PR — overridden to "Profile" for non-church
Sub-tab notifications tooltip "prayer requests, visitor info":267P1Fixed in this PR — generic copy for non-church
Sub-tab integrations tooltip "Connect Planning Center, church tools":268P1Fixed in this PR — generic copy
Sub-tab sharing description "congregation and webmaster":269P2P2 backlog
HeroPhotoUploader label "Church Banner Photo":163P1Fixed in this PR — "Banner Photo"
HeroPhotoUploader help text "top of your church page":215P1Fixed in this PR — "top of your page"
SectionCard "Church Contact & Social Media":461P1Fixed in this PR — vertical-aware title
Field label "Church Phone":467P1Fixed in this PR — "Phone" for non-church
Field label "Church Address":479P1Fixed in this PR — "Address" for non-church
Field label "Church Social Media Links":481P1Fixed in this PR — "Social Media Links" for non-church
Section "Pastor Availability":508P0Fixed in this PR — "Staff Availability" for non-church
Availability description "when your pastor is available":513P0Fixed in this PR — vertical-aware
Crisis escalation "speak with the pastor":570P1Fixed in this PR — "a staff member" for non-church
Escalation placeholder "e.g., Pastor Johnson":582P1Fixed in this PR — "e.g., Director Smith"
Escalation placeholder "email pastor@church.org":587P1Fixed in this PR — conditional
Escalation helpText "specific to your church":598P1Fixed in this PR — "your organization" for non-church
Escalation suggestion with church-specific scenarios:599P1Fixed in this PR — vertical-aware suggestion
Planning Center integration block:773P0Fixed in this PR — gated {isChurch && (…)}
Cal.com description "pastoral meetings, visitor calls":813P1Fixed in this PR — vertical-aware copy
Cal.com locked description "pastoral meetings, visitor calls":826P1Fixed in this PR — vertical-aware
TEAM_ROLES includes prayer_team, care_team, worship_leader, etc.:253-261P0Fixed in this PRTEAM_ROLES_CHURCH vs TEAM_ROLES_GENERIC
Team explainer "prayer requests, pastoral callback details":969P1Fixed in this PR — vertical-aware
Section "Church Ownership & Security":1007P1Fixed in this PR — "Account Ownership" for non-church
Ownership description "this church account":1009P1Fixed in this PR — "this account" for non-church

2.4 SettingsPanel.tsx (Settings slide-over outer nav)

Severity: P1 — the outer 4-tab nav.

ItemFile:LineSeverityStatus
Account description "Your church profile, office hours"SettingsPanel.tsx:162P0Already fixed — SECTION_META_CHURCH vs SECTION_META_GENERIC existed (Lane C)
Notifications description "prayer requests, visitor info":170P0Already fixed — generic copy in SECTION_META_GENERIC
Integrations description "Planning Center, Cal.com":175P0Already fixed — generic copy in SECTION_META_GENERIC

2.5 InboxTab.tsx

Severity: P1 — partially fixed already.

ItemFile:LineSeverityStatus
PrayerCard title "Prayer request"InboxTab.tsx:447P1Already vertical-aware — "Family inquiry" for funeral
Subtitle "Every call, prayer, visitor, and safety flag":1211P1P2 backlog — funeral shows "prayer"
PrayerCard action "Assign to prayer team":497P2P2 backlog — funeral has no prayer team
REDACTED_PRAYER = "Confidential — contact the pastor":176P1Already partially fixed — getRedactedPrayer() returns "Family inquiry" for funeral but REDACTED_CALLBACK still says "Pastoral inquiry"
REDACTED_CALLBACK = "Pastoral inquiry":177P1P2 — funeral should say "Family inquiry"
getRedactedPrayer() and getRedactedCallback():166-173P1Partially done — prayer is fixed, callback still says "Pastoral"

2.6 AdminDashboard.tsx (Shell)

Severity: P2 — mostly structural, not user-visible copy.

ItemFile:LineSeverityStatus
ALL_TABS descriptions use "your church":158,168,179P2P2 backlog — only visible if verticalProfile.tabs is empty (shouldn't happen for funeral)
HASH_TO_TAB references 'church-info' key:377MinorP3 — internal routing key, not visible
VoiceAgentData type has pastor_name, sermon_topic, etc.:69-95MinorP3 — type-level, not displayed

2.7 InboxTab.tsx — funeralInboxFeedQuery Dead Code

Previously flagged by Lane A. The InboxTab prefetches using the church-path query (from page.tsx → getInboxStream()), NOT funeralInboxFeedQuery. The funeral vertical's inboxFeedQuery in funeral.ts is defined but the InboxTab does not call it — it uses the pre-fetched initialInbox prop from the server. This is confirmed OK for Phase 1 because funeral.ts's inboxFeedQuery filters by church_id anyway, same as the church path. Phase 2 migration note: when InboxTab gets a "load more" / pagination feature, it will need to call verticalProfile.inboxFeedQuery() not the hardcoded church helper.


3. Architecture Proposal — Baseline + Vertical Extension Model

3.1 Shared Baseline Shell (every vertical)

Every vertical gets these tabs from VerticalProfile.tabs (no church fallback needed once all verticals declare tabs):

Tab keyComponentVertical-aware?
overviewDashboardOverviewNeeds full vertical-awareness (see above)
inboxInboxTabMostly done — terminology hooks wired
trainingTrainAITabFixed in this PR — section filter by verticalKeys
upgradeUpgradeTabShared — shows plan info, no church copy

These church-only tabs are filtered out for non-church via verticalProfile.tabs:

  • social (ShareWiseAI — church/general)
  • website (Pro Website — church only today)

Decision: church and funeral share the same shell component (AdminDashboard.tsx), vertical tabs declared in FUNERAL_TABS / CHURCH_TABS. The shell remains generic; tabs/content are per-vertical.

3.2 Vertical Extension Slots (formal API)

The VerticalProfile interface already has the right shape. Recommended additions:

interface VerticalProfile {
// ... existing fields ...

/** Church-specific Training sections to include (default: all). */
trainingSections?: TrainAISection[];

/** Team roles available in this vertical's invite form. */
teamRoles?: { value: string; label: string }[];

/** Settings sections to exclude (e.g., funeral excludes Planning Center). */
settingsExclude?: string[];

/** Overview metric labels (overrides "Prayer requests", "New visitors", etc.) */
overviewMetricLabels?: {
prayerRequests?: string;
visitorContacts?: string;
callbacks?: string;
};
}

This would let the vertical's profile.ts declare exclusions declaratively rather than scattering isChurch guards everywhere.

Recommended Phase 2 refactor: Move all isChurch ? X : Y guard patterns into vertical profile declarations. The guards shipped in this PR are the minimal viable fix; Phase 2 should refactor them to profile-driven.

3.3 Feature Map — Church-Specific vs Shared vs Funeral-Specific

FeatureChurchFuneralSharedNotes
Prayer RequestsInboxTab: "Prayer request" tile exists but terminology-mapped
Theology & TraditionFiltered out in TrainAITab
Pastor PulseFiltered out in TrainAITab
Chat SimulatorFiltered out in TrainAITab
Church KnowledgeFiltered out in TrainAITab
Planning CenterGated in SettingsTab
Prayer Team roleTEAM_ROLES_CHURCH only
Care Team roleTEAM_ROLES_CHURCH only
At-Need Inbox chipPhase 2: InboxTab should show at_need chip for funeral
Pre-Planning tabPhase 2
Service Catalog tabPhase 2
FAQsShared with generic copy (this PR)
Agent PersonalityShared — "pastor voice" copy P2
Safety RulesShared
NotificationsShared — copy vertical-aware (this PR)
Cal.com BookingShared — copy vertical-aware (this PR)
Team ManagementShared — roles filtered (this PR)
Office HoursShared

3.4 Settings Architecture

The current 4-section outer nav (Account / Team / Notifications / Integrations) is correct as a shared baseline. Issues:

  1. Account sub-nav pills: "Profile" / "Hours" / "Sharing" — "Profile" pill key is still church-info internally. Works, but the label now reads "Profile" for non-church (fixed in this PR via .map() override). P2: rename the SettingsSubTab key from church-info to profile with a backwards-compat alias.
  2. Notifications: church copy mentions "prayer requests, visitor info" — fixed in this PR via .map() overrides.
  3. Integrations: Planning Center is church-only — gated in this PR. Cal.com is shared — copy updated.
  4. Team roles: TEAM_ROLES_CHURCH vs TEAM_ROLES_GENERIC — fixed in this PR. P2: move team roles to VerticalProfile.teamRoles for declarative config.

3.5 Design Tokens per Vertical

Current: sacred-gold (#D4AF37) + navy (#1B365D) + cream (#FEFCF8) for all verticals.

Funeral brand consideration: The FuneralWiseAI brand (per funeralwiseai.com) uses a more dignified, muted palette. The admin dashboard uses hard-coded color values (#D4AF37, #1B365D) in DashboardOverview.tsx, TrainAITab.tsx, and InboxTab.tsx.

Recommendation (Phase 3): Thread verticalProfile.brand.accentColor and verticalProfile.brand.primaryColor as CSS custom properties into the admin shell root element. Components use var(--admin-accent) instead of #D4AF37. The VerticalProvider sets these on mount. This keeps the church admin gold and the funeral admin a more appropriate slate/warm-gray.

For now (Phase 1), the sacred-gold on a funeral home admin is acceptable — it's not wrong, just not optimally branded. Ship Phase 3 design tokens after Phase 2 content is complete.


4. Phase 3 — Top 3 Fixes Shipped (this PR)

Fix 1: TrainAITab — Hide church-only sections from funeral

Files: TrainAITab.tsx
What: Added verticalKeys?: string[] to SectionDefWithVertical, replaced SECTIONS const with ALL_SECTIONS. Sections church-knowledge, theology, simulator, pastor-pulse are verticalKeys: ['church'] — invisible on funeral. Sections agents, faqs, safety are shared. Component uses useVertical() to filter at render time.
Why first: A funeral director opening Train AI would see "Church Knowledge", "Theology & Tradition", "Pastor Pulse", and "Chat Simulator" — all completely wrong for a funeral home. This is the most jarring single surface.

Fix 2: DashboardOverview — Vertical-aware metric tiles, welcome copy, quick actions, setup rail

Files: DashboardOverview.tsx
What: Added useVertical(). Updated: welcome subtitle, notification email banner, metric tiles ("Prayer requests" → "Family inquiries", "New visitors" → uses terminology.visitor), analytics donut labels, Setup rail eyebrow ("Get your church online" → funeral variant), QuickActions body/title copy, RecentPrayersCard gated to isChurchVertical && role === 'prayer_team', upsell card "Get more from ChurchWiseAI" → "Unlock more features".
Why second: The Home tab is the landing surface. Every session starts here. Church-branded metric tiles and "Get your church online" on a funeral home admin is immediately confusing.

Fix 3: SettingsTab — Gate church-specific forms, roles, integrations

Files: SettingsTab.tsx
What: Added useIsChurchVertical(). Applied overrides via .map() on SETTINGS_SUB_TABS to change "Church Profile" → "Profile" and update tooltip/description copy for non-church. Updated: banner photo label/helptext, Contact section title/labels, Pastor Availability → "Staff Availability", escalation placeholder/helptext copy, Planning Center gated to {isChurch && (...)}, Cal.com copy vertical-aware, TEAM_ROLES_CHURCH vs TEAM_ROLES_GENERIC, ownership section title/description.
Why third: Settings is the most-clicked surface after Inbox. A funeral director seeing "Planning Center", "Church Phone", "Prayer Team", and "Church Ownership" is maximum brand confusion — makes the product feel like a church tool in disguise.


5. P2 / P3 Backlog

Prioritised by customer-impact.

P2 (next sprint, ~2-3 days total)

#ItemFile(s)EffortProposed fix
P2-1InboxTab subtitle "Every call, prayer, visitor, and safety flag"InboxTab.tsx:1211SUse terminology to replace "prayer" and "visitor"
P2-2REDACTED_CALLBACK = "Pastoral inquiry" for funeralInboxTab.tsx:177SgetRedactedCallback(verticalKey) similar to existing getRedactedPrayer()
P2-3PrayerCard action "Assign to prayer team" for funeralInboxTab.tsx:497SGate to isChurch or use terminology
P2-4SettingsSubTab key rename: church-infoprofileSettingsTab.tsx, SettingsPanel.tsx, AdminDashboard.tsx hash mapMAdd backwards-compat alias church-info → resolves to profile; rename key in new code
P2-5Agent Personality section "pastor voice" copyTrainAITab.tsx:127SVertical-aware description for agents section
P2-6ShareLinkCard hardcodes churchwiseai.com/chat/${churchSlug}DashboardOverview.tsx:542MFuneral has no chatbot yet — hide chat link for non-church; use verticalProfile.hostname for link base
P2-7redactCallbackSummary() in Overview returns "Pastoral inquiry"DashboardOverview.tsx:161SPass verticalKey, return "Family inquiry" for funeral
P2-8Move TEAM_ROLES_GENERIC to VerticalProfile.teamRolesfuneral.ts, vet.ts, SettingsTab.tsxMDeclarative — no more inline conditionals per role field
P2-9At-Need Inbox chip for funeralInboxTab.tsxMat_need chip only shows for funeral vertical — verticalProfile.inboxChips declaration
P2-10InboxTab "load more" pagination: call verticalProfile.inboxFeedQuery() not hardcoded helperInboxTab.tsx (future)LRequired for pagination feature; deferred until pagination is built

P3 (polish / low friction)

#ItemFile(s)Effort
P3-1Design tokens per vertical: funeral admin uses dignified slate vs sacred-goldglobals.css, VerticalProvider, all admin componentsL
P3-2displayPastorName() helper rename to displayDirectorName() + vertical-aware defaultDashboardOverview.tsxS
P3-3ALL_TABS descriptions say "your church" (fallback only, not shown for funeral)AdminDashboard.tsxS
P3-4VoiceAgentData type has church-specific fields (pastor_name, sermon_topic, etc.) exposed to non-church componentsAdminDashboard.tsx, TrainAITab.tsxM
P3-5SettingsTab sharing description "congregation and webmaster"SettingsTab.tsx:269S
P3-6hasChurchProfile variable name in DashboardOverviewDashboardOverview.tsx:214Trivial
P3-7WhatToExpectForm — church-specific "What to Expect" framing shown in church-info sub-tab for all plansSettingsTab.tsx:439S — gate to isChurch
P3-8SharingForm copy — "Share with your congregation" for funeralSharingForm (shared component)S

6. Screenshot Status

Screenshots were not captured in this audit cycle due to time-box constraints. Code-read is authoritative for identifying bleed strings.

To capture screenshots for future audit cycles:

# Church admin (Playwright headless)
playwright test --config playwright.config.ts -- admin-church-audit.spec.ts

# Funeral admin
playwright test --config playwright.config.ts -- admin-funeral-audit.spec.ts

Screenshot target directory: knowledge/specs/screenshots/2026-04-30-dashboard-audit/{church,funeral}/


7. What Was NOT Changed (by design)

  • safety.py, moderation.py, crisis_copy.ts — LIFE-SAFETY files, out of scope
  • Database schema — no new columns in this PR (P2 backlog)
  • Church admin behaviour — every existing church feature remains accessible and unchanged
  • Voice agent runtime — UI/UX scope only
  • Chatbot endpoint — UI/UX scope only