Admin P1 Sprint Plan — Structural IA Collapse (All Products)
Seven ordered slices (was six — added Train AI shell). Each slice is independently shippable and testable. No slice breaks the admin dashboard if the next slices don't ship — we're additive, not rewrite.
Scope correction — 2026-04-18 (second pass)
The first version of this plan treated Pro Website as the primary use-case and proposed retiring "Training" as a top-level concept. That was wrong. The admin dashboard at /admin/[token] is a SINGLE unified dashboard serving all ChurchWiseAI product customers:
cwa_starter_chat/cwa_pro_chat— chatbot onlycwa_starter_voice/cwa_pro_voice— voice agent onlycwa_starter_both/cwa_pro_both/cwa_suite_both/cwa_bundle— chat + voice bundlecwa_pro_website— Pro Website (includes built-in chatbot perpricing.yaml)- Legacy:
ps_pro_website,pro_website,ps_premium
Every paying customer has a chatbot (Pro Website bundles one). Training the AI takes significant real estate — knowledge base, FAQs, agent personality, safety rules, per-tradition vocabulary, simulator. Collapsing it into a Settings slide-over would be a 25-section nightmare.
The correct IA is 4 top-level tabs, universal, gated per plan + capability:
Home · Inbox · Train AI · Website ⚙️ 🔔 [Upgrade]
- Home — universal (all customers).
- Inbox — universal; content adapts per plan (calls chip only if voice, prayer chip only if chat, etc.).
- Train AI — universal (every customer trains a chatbot at minimum).
- Website — gated: visible only if plan predicate
hasProWebsite(plan)is true. - Settings / Upgrade — header controls, not tabs.
All slices below now include cross-cutting acceptance criteria for plan + capability gating (see end of doc).
Slice 1 — Extract Settings to a header-triggered slide-over (4 hrs)
Why first: Settings is the easiest to extract (least cross-dependency) and surfaces the header pattern we'll use for everything else.
Scope:
- Move
SettingsTab.tsxrendering into a slide-over panel triggered by a gear icon in the admin header. - Slide-over uses the existing
SetupSlideOver.tsxcomponent but with sub-tabs inside: Account / Team / Notifications / Integrations (+ Billing sub-tab for admin-only, gated onbilling:view). - Remove
settingsfromALL_TABSinAdminDashboard.tsx. - Add
SettingsPanelcomponent that renders inside the slide-over, lazy-loaded. - Keep all existing hash-based deep links working (e.g.,
#notifications) — open the slide-over to the matching sub-tab.
Files touched:
src/app/admin/[token]/components/AdminDashboard.tsx(remove tab, add gear button + state)src/app/admin/[token]/components/SettingsTab.tsx(already exists, wrap for slide-over)src/app/admin/[token]/components/SettingsPanel.tsx(new — wraps sub-tabs)
Cross-cutting acceptance: Settings gear icon visibility gated on hasAnySettingsCap(member) — hidden if user has no settings-namespace capabilities. Billing sub-tab gated on billing:view.
Test (Playwright): Clicking gear opens Settings slide-over; URL hash #notifications opens to Notifications sub-tab; treasurer without settings:church_profile:edit sees Billing-only sub-tab; care_team without any settings cap sees no gear icon.
Rollback: revert the tab hide + gear button; SettingsTab continues to work as a full tab until rollback reverts.
Slice 2 — Merge Calls + Requests + Care into unified Inbox tab with adaptive filters (7 hrs — was 6)
Why second: Largest UX consolidation. Best next step once Settings is out of the way (clears tab budget).
Scope:
- New
InboxTab.tsxcomponent with filter chips conditioned on plan:All— alwaysCalls— only ifhasVoice(plan)Prayer Requests— only ifhasChat(plan) || hasVoice(plan)(effectively all paying customers)Visitors— only ifhasChat(plan)Callbacks— only ifhasVoice(plan)Safety— only if user hasinbox:safety:readAND any chat/voice channel
- Unified chronological stream (most-recent first) from all four sources, joined in memory on the client.
- Role-gated filters: Prayer Team sees only
Prayer Requestschip; Treasurer sees Inbox empty-state with "You don't have Inbox permissions" message; care_team sees prayer + visitor only. - Plan-gated empty states: chat-only customer sees "No calls — your plan is chat-only. [Learn about voice →]" instead of a blank Calls pane.
- Existing per-item drill-downs (call transcript modal, prayer detail, visitor card) preserved.
- Remove
calls,requests,carefromALL_TABS. - Hash deep links (
#calls,#requests/prayer, etc.) redirect to Inbox with the matching filter active.
Files touched:
src/app/admin/[token]/components/InboxTab.tsx(new, ~500 LOC with adaptive filter logic)src/app/admin/[token]/components/AdminDashboard.tsx(remove 3 tabs, add Inbox, rewire hash routing)src/lib/premium-queries.ts(addgetInboxStream({ planPredicate, roleCaps })that queries enabled tables + merges)- Existing
CallsTab.tsx,RequestsTab.tsx,CareTab.tsxkept initially but unmounted from nav; mark for deletion after 1 release cycle.
Cross-cutting acceptance:
- Chat-only customer → no Calls/Callback chips rendered.
- Voice-only customer → no Visitors chip rendered.
- Bundle customer → all 5 chips rendered.
- Prayer Team role → only Prayer chip visible regardless of plan.
- Treasurer role → Inbox shows "No Inbox permissions" empty state.
Test: Playwright covers 6 permutations: {chat-only, voice-only, bundle} × {admin, prayer_team}, plus treasurer-sees-empty.
Rollback: re-add the 3 tabs to ALL_TABS; Inbox stays as a parallel path for opt-in.
Slice 2.5 — Train AI tab shell (4 hrs — NEW)
Why added: Train AI is universal (every customer trains a chatbot), but it never had a first-class tab. Previously spread across Training tab + Settings sub-panels + knowledge-base modal. Collapse into one tab with left-rail sub-navigation.
Scope:
- New
TrainAITab.tsxwith left-rail section navigation (no live preview — Train is an editor, not a renderer). - 5 sub-sections minimum:
- Church Knowledge — knowledge base CRUD (existing
KnowledgeBaseEditor.tsxreused), document upload, chunk viewer, re-index button - Theology & Tradition — TheoLens picker (existing) + per-tradition vocabulary boost list
- Agent Personality — tone, pastor voice template, greeting script, escalation rules
- FAQs — curated Q&A list (existing FAQ editor)
- Safety Rules — crisis keywords, escalation contacts, moderation thresholds (existing
ModerationDashboard.tsxadapted)
- Church Knowledge — knowledge base CRUD (existing
- Sixth sub-section optional: Chat Simulator — test the agent with test prompts, see which knowledge chunks are retrieved
- Left-rail pattern mirrors Website editor visually (active = gold dot, stone-100 hover).
- Each sub-section lazy-loads.
Files touched:
src/app/admin/[token]/components/TrainAITab.tsx(new)src/app/admin/[token]/components/train/{ChurchKnowledge,Theology,Personality,FAQs,Safety,Simulator}.tsx(new — mostly wrappers over existing editors)src/app/admin/[token]/components/AdminDashboard.tsx(add Train AI to ALL_TABS)src/lib/premium-queries.ts(reuse existing KB + FAQ + moderation queries; no new DB)
Cross-cutting acceptance:
- Every paying customer sees Train AI tab (no plan gate — Pro Website includes chat).
- Sub-sections gated per capability:
train:church_knowledge:edit,train:theology:edit,train:agents:edit,train:faqs:edit,train:safety:edit,train:simulator:use,train:documents:upload. - Volunteer coordinator without any
train:*caps → Train AI tab hidden. - Admin → all sub-sections visible.
Test: Playwright covers 4 roles × Train AI tab visibility; for admin, verify all 5 sub-sections load.
Rollback: revert the tab addition; Training sub-content remains accessible via existing modals/panels.
Slice 3 — Pre-loaded sample data on fresh accounts (3 hrs)
Why third: Small scope but massive first-run impact. Independent of structural changes.
Scope:
- On
/admin/[token]first load (detected bypremium.created_at< 24 hours ANDvoice_prayer_requests.count === 0), seed an in-memoryexampleDataarray with:- 1 sample prayer request
- 1 sample visitor contact
- 1 sample call log summary (gated — only if
hasVoice(plan))
- Each example renders with a visible ribbon
Example · tap ✕ to remove. - "Remove all examples" button at top of Inbox stream.
- Examples are client-side only — never written to DB, never real data.
Files touched:
src/app/admin/[token]/components/InboxTab.tsx(conditional example injection)src/app/admin/[token]/components/examples/ExampleRibbon.tsx(new small component)src/lib/admin-examples.ts(new — exports sample data arrays, one per plan variant)
Cross-cutting acceptance: example set varies by plan — chat-only sees prayer + visitor; voice-only sees prayer + call; bundle sees all 3; Pro Website-only sees prayer + visitor.
Test: Playwright: fresh admin token → inbox shows plan-appropriate example items tagged "Example"; click ✕ on one removes it locally; clicking Remove All clears all examples.
Rollback: remove the <ExampleRibbon> injection; inbox goes back to empty state for new customers.
Slice 4 — Persistent setup checklist rail on Home (5 hrs — was 4)
Why fourth: Once Inbox + Train AI are live and empty state is solved, Home becomes the obvious place for the checklist.
Scope:
- Convert
DashboardOverview.tsxto use a 2-column grid: left = activity summary + share links, right = setup checklist rail. - Rail uses existing
SetupGuide.tsxsteps but renders as a dense vertical list — no gold cards, no slide-over triggers from the rail itself (those still open from clicking). - Progress bar at top of rail: "3 of N done · 43%" — N varies by plan.
- Plan-specific checklist steps:
- Universal: Church name, Pastor tone, Theology tradition, Write 3 FAQs, Send share link, Invite team
- Chat-only addition: Upload chatbot logo, Configure widget colors
- Voice addition: Record greeting, Set business hours, Test call
- Pro Website addition: Upload hero photo, Add service times, Pick template, Publish site
- "Hide this checklist" button collapses to a thin badge on the header ("Setup 43% ▸ ") that re-expands.
- Rail dismissal state persists in
localStorage(cwai-setup-rail-collapsed=true). - When all required steps complete, rail auto-collapses with a "Setup complete" pill.
Files touched:
src/app/admin/[token]/components/DashboardOverview.tsx(2-column layout)src/components/admin/SetupChecklistRail.tsx(new — condensed, plan-aware version of SetupGuide)src/lib/setup-checklist-steps.ts(new — maps plan key to step list)src/components/admin/SetupGuide.tsx(keep for legacy, but the rail becomes the primary surface)
Cross-cutting acceptance: step count varies per plan; chat-only customer never sees "Record greeting"; Pro Website-only never sees "Set business hours" unless their plan also has voice.
Test: Playwright: rail renders with correct steps per plan, clicking step opens slide-over, progress updates on save, collapse/expand persists across reloads, bundle customer sees union of steps.
Rollback: remove 2-column layout; DashboardOverview reverts to single-column with old SetupGuide.
Slice 5 — 3-question onboarding shortcut (6-8 hrs)
Why fifth: High-impact but requires Slices 1-4 to land first — otherwise there's nowhere for the "generated defaults" to live intelligibly.
Scope:
- New route
/onboard/quickstart?plan=<plan_key>(accessed from post-Stripe-payment redirect instead of the current/onboard/return). - Three questions in sequence (not a multi-step wizard — one screen, three dropdowns/radios):
- Denomination (re-use the cascade from OnboardForm)
- Tone: Warm / Professional / Casual (radio group)
- Primary goal: Prayer Support / First-Time Visitors / Staying in Touch (radio group)
- Defaults generated vary by plan:
- Chat-only: sample FAQ seeded, widget config filled, 3 example Q&As populated
- Voice-only: greeting script template written, business hours pre-filled (Sun 9-12, Mon-Fri 9-5), crisis keywords seeded
- Bundle: all of the above
- Pro Website: above + hero video per denomination + section stubs (About, Services, Beliefs, Contact)
- Denomination → theological lens (already wired via webhook in P0)
- Tone → agent personality default (warm + 1-2 paragraphs of pastor voice template)
- Primary goal → enabled tools set + sample FAQ seeded
- Redirect to
/admin/[token]?from=quickstartwith a celebratory toast. - Pastor lands in a working dashboard — checklist items show as 'auto-filled — tap to customize' state instead of 'not done'.
Files touched:
src/app/onboard/quickstart/page.tsx(new)src/app/onboard/quickstart/QuickstartForm.tsx(new client component)src/app/api/onboard/quickstart/route.ts(new — receives 3 answers + plan, writes plan-specific defaults)src/lib/onboarding-defaults.ts(new — maps 3 answers + plan to default state)src/components/admin/SetupStepCard.tsx(add 'auto-filled' status with amber ring)src/app/onboard/return/ReturnContent.tsx(redirect to /onboard/quickstart instead of /thank-you for first-time activations)
Cross-cutting acceptance:
- Plan-appropriate defaults populated; chat-only customer does NOT get business hours seeded.
- Quickstart skipped for existing subscriptions (gate on
premium.created_at < 5 minutes).
Test: Playwright: submit quickstart with Baptist + Warm + Prayer × 4 plans → verify plan-specific default fields populated.
Rollback: revert the redirect in ReturnContent.tsx — new customers go back to /thank-you → admin. Quickstart page stays live as an opt-in path.
Slice 6 — Mobile bottom tab bar with conditional Website tab (3 hrs)
Why last: Once IA is collapsed to 4 tabs (Home / Inbox / Train AI / Website) + header Settings, mobile becomes trivial. Doing this earlier would require changing it again as slices 1-2.5 land.
Scope:
- New
MobileBottomNav.tsx— renders only onsm:breakpoint (<640px). - Up to 4 icon+label items — Website conditional on
hasProWebsite(plan):- Always: Home / Inbox / Train AI
- If Pro Website: + Website (4-tab mobile nav)
- Active tab has gold top-border + gold icon; inactive tabs are stone-500.
- Fixed to bottom with
env(safe-area-inset-bottom)padding. - Desktop nav hidden on
sm:; mobile nav hidden onmd:and up. - Settings gear moves to the top-right corner (sticky mobile header, unchanged).
- Upgrade CTA becomes a contextual prompt on Home (removable toast), not nav-persistent.
Files touched:
src/app/admin/[token]/components/MobileBottomNav.tsx(new)src/app/admin/[token]/components/AdminDashboard.tsx(conditional render, pass plan to mobile nav)src/app/admin/[token]/components/AdminHeader.tsx(mobile layout tweaks — new small component if not already separated)
Cross-cutting acceptance:
- Chat-only customer → 3-tab bottom nav (Home / Inbox / Train AI).
- Pro Website customer → 4-tab bottom nav.
- Bundle customer without Pro Website add-on → 3-tab bottom nav.
Test: Playwright --project=mobile (iPhone 12 viewport): bottom nav renders with correct tab count per plan, tap Inbox switches tab, active state visible, Settings accessible from header gear.
Rollback: remove the conditional render — mobile goes back to horizontal tabs.
Cross-cutting acceptance criteria (ALL slices)
Every slice above MUST, as part of its Playwright spec:
- Capability-gate every interactive element. If a cap is missing, the element is absent from the DOM (not visually hidden — absent). Test matrix: for each element, at least one passing role + one failing role.
- Plan-gate every tab and plan-dependent feature. Website tab absent for non-Pro-Website plans. Calls chip absent for chat-only plans. Etc.
- Fallback to
legacyRoleToCapabilities()for pre-RBAC users — verify admin role works even with emptygroup_ids. - No broken deep links. Every legacy hash (
#settings,#calls,#care) must redirect correctly. - Test across 4 representative plan permutations:
cwa_starter_chat,cwa_starter_voice,cwa_pro_both,cwa_pro_website. - Test across 4 representative roles: admin (all caps), care_team (pastoral + care caps), prayer_team (prayer-only), treasurer (financial only).
- Visual regression: every new/changed screen gets a Playwright screenshot baseline.
Sprint execution order + time
| Day | Slice | Hours | Cumulative |
|---|---|---|---|
| Mon AM | Slice 1 (Settings slide-over) | 4 | 4 |
| Mon PM | Slice 2 part A (Inbox skeleton + adaptive filter chips) | 4 | 8 |
| Tue AM | Slice 2 part B (role-gating + hash redirects + plan permutations) | 3 | 11 |
| Tue PM | Slice 2.5 (Train AI tab shell) | 4 | 15 |
| Wed AM | Slice 3 (plan-adaptive sample data) | 3 | 18 |
| Wed PM | Slice 4 (plan-aware checklist rail) | 5 | 23 |
| Thu AM | Slice 5 part A (quickstart form + per-plan defaults lib) | 4 | 27 |
| Thu PM | Slice 5 part B (webhook hooks + redirect + 4-plan tests) | 4 | 31 |
| Fri AM | Slice 6 (mobile bottom nav with conditional Website) | 3 | 34 |
| Fri PM | Playwright test pass across plan × role matrix + visual QA | 4 | 38 |
Total: ~38 hrs across 5.5 working days.
Branch strategy
- One long-lived branch
feat/admin-p1-ia-collapseoff main. - Each slice lands as a commit + self-contained Playwright test.
- PR stays draft until Slice 6 is green.
- Before merge: run full admin regression (existing admin.spec.ts) against preview URL × 4 plan permutations.
- Single squash-merge on Friday end-of-day.
Pre-flight checklist (do before Monday AM)
- P0 PR #60 merged to main (or critical-path-override applied)
- RBAC DB migration applied to Supabase (
pewsearch/migrations/20260418_admin_rbac_groups_and_capabilities.sql+ seed DO block) - 4 test admin tokens exist covering plan matrix: one each for chat-only, voice-only, bundle, Pro Website (all with directory_visible=false)
- Test team_members exist for each test token covering 4 representative roles
-
knowledge/architecture/admin-nav-capability-map.mdexists and is the source of truth for visibility rules (see companion doc produced same day) - Verify Supabase schema matches what each slice assumes
- Read the canonical acceptance spec
knowledge/acceptance/pro-website-setup-wizard.mdend-to-end
Out of scope for P1 (deferred to P2)
- Strikingly-style Website editor redesign (section-based left panel + explicit Publish). Bigger scope, benefits from customer usage data from P0/P1 before committing.
- Role-gated Inbox filters extending to call transcripts (privacy-pass over the unified stream). Requires policy decisions with founder.
- Checklist rail persistence across devices (server-side rather than localStorage). Nice-to-have.
- Self-serve custom domain flow (FA-### filed from earlier). Unblocked by Vercel Domains API work, not IA.
- Train AI Simulator as interactive feature (not shell only). Shell lands in Slice 2.5; interactive simulator with real RAG retrieval in P2.
- Dashboard branding per product (SermonWise dashboard integration). Separate project.
Risk register
| Risk | Impact | Mitigation |
|---|---|---|
| Slice 2 merges calls+care — if founder wants to split these later, unwinding is expensive | med | Keep CallsTab.tsx + CareTab.tsx in codebase as "not routed but importable" so re-adding a tab is 1-line |
| Slice 2.5 Train AI shell proves insufficient for power users (ChMS admins used to drilling into FAQs) | med | Left-rail nav + sub-section lazy-load preserves depth; add breadcrumbs if needed |
| Slice 5 pre-fills data users may want explicit control over | low | Every auto-filled value shows an "edit" link on the checklist card |
| Mobile bottom nav with 4 tabs too tight on small screens (iPhone SE 320px) | med | Fall back to 3-tab nav + Website in overflow menu on viewports <360px |
| Plan downgrade mid-session (customer cancels) — tabs disappear abruptly | low | Poll premium.status every 60s; soft-redirect to Home if user is on a now-gated tab |
3-question quickstart confuses existing-subscription customers hitting /admin post-P1 | low | Gate on premium.created_at < 5 minutes — existing customers bypass quickstart entirely |
| Multi-product dashboard feels bloated to single-product customers | med | Plan gates hide unused tabs; upsell CTAs offer path to expand, not clutter the nav |
Dependencies on other work streams
- admin-nav-capability-map.md (companion doc, same day) — single source of truth for visibility rules. If that doc and this plan disagree, the nav-map doc wins.
- DESIGN_SPEC_FOR_CLAUDE_DESIGN.md (design artifact for external UI designer, same day) — visual mockups for all 7 slices. If that spec and this plan disagree, the spec owns pixels, this plan owns code.
- admin-rbac-2026-04-18.md — RBAC spec. Never violate it; slices must gate with
can()fromrbac.ts.