Adding a New Vertical
Internal-only guide for the WiseAI Agency template pattern. Forks of churchwiseai-web become new verticals (FuneralWiseAI, VetWiseAI, ShopWiseAI, ...). This doc captures the 5-step pattern that the Phase 2 sprint (2026-05-01) made systematic.
Summary
The admin dashboard is vertical-aware. The shape (tab nav, Inbox stream, Home tiles, Settings slide-over) is shared; the content (labels, examples, integrations, action buttons, terminology) is driven by a VerticalProfile registry entry. To add a new vertical you populate one profile object, plus a brand kit and a few small components, then register the new key. No core admin code changes.
5-step pattern
Step 1 — Create src/lib/verticals/<vertical>.ts
A VerticalProfile exporting:
export const <vertical>Profile: VerticalProfile = {
key: '<vertical>', // matches Vertical enum in tenant-config.ts
brand: <vertical>Brand, // imported from src/lib/brands/<vertical>.ts (Step 2)
hostname: '<vertical>wiseai.com',
adminPath: '/admin/[token]',
legacyAdminPath: undefined, // or '/admin/<vertical>/[token]' for grandfathered URLs
tabs: <VERTICAL>_TABS, // ordered TabSpec[] — display order in nav
adminTabs: [], // legacy, leave empty until AdminShell migration
terminology: {
visitor: 'family' | 'client' | ...,
callback: 'at-need callback' | 'consult callback' | ...,
teamMember: 'staff' | 'associate' | ...,
director: 'director' | 'veterinarian' | ...,
organization: 'funeral home' | 'practice' | ...,
audience: 'families' | 'pet owners' | ..., // 2E
intakePageLabel: 'intake page' | 'appointment page' | ..., // 2E
inboxRowTypes: { ... }, // 2E
inboxActionLabels: { ... }, // 2E
officeHoursHelpText: '...', // 2F
printArtifact: 'service programs' | ..., // 2F
shareableHubTitle: 'Family Hub' | ..., // 2F
shareableHubDescription: '...', // 2F
},
planKeys: ['<vertical>_starter', ...], // matches stripe metadata + premium_<vertical>s.plan CHECK
rbacRoleLabels: { admin: 'Director / Admin', ... },
knowledgeTaxonomy: KNOWLEDGE_TAXONOMY.<vertical>, // Step 3
crisisCopy: CRISIS_COPY.<vertical>, // Step 3
accountForm: '<Vertical>InfoForm', // Step 4 — must match component name in Step 4
inboxFilters: <VERTICAL>_INBOX_FILTERS, // const FilterChipSpec[]
metricsConfig: <VERTICAL>_METRICS, // const MetricSpec[]
integrations: <VERTICAL>_INTEGRATIONS, // const IntegrationSpec[]
// Server-only data accessors (import supabase) — return [] for stub
callbackQueueQuery: <vertical>CallbackQueueQuery,
voiceCallsQuery: <vertical>VoiceCallsQuery,
inboxFeedQuery: <vertical>InboxFeedQuery,
};
Patterns to copy from: church.ts for full-detail, funeral.ts for vertical-specific tabs, vet.ts for scaffold-only.
Step 2 — Create src/lib/brands/<vertical>.ts
Brand profile (palette, typography, voice, ecosystem). Shape pinned by BrandProfile type. Keep palette tokens narrow — match the vertical's existing marketing identity.
Step 3 — Add knowledge taxonomy + crisis copy
In src/lib/verticals/knowledge-taxonomy.ts: add a KNOWLEDGE_TAXONOMY.<vertical> entry. Empty array OK initially — falls back to church taxonomy via getKnowledgeTaxonomy().
In src/lib/verticals/crisis-copy.ts: add a CRISIS_COPY.<vertical> entry with at minimum a 988-baseline US suicide hotline. Vertical-specific resources optional. 988 is enforced by test — no vertical can omit it.
Step 4 — Add per-vertical components
Under src/components/admin/<vertical>/:
<Vertical>InfoForm.tsx— Settings → Account → Profile sub-tab form. MirrorsChurchInfoForm.tsx/FuneralHomeInfoForm.tsxshape. Field set is vertical-specific (e.g. funeral has Lead Director Name + Number of Licensed Directors + Business Hours).
Add the new form key to AccountFormKey type in src/lib/verticals/types.ts, and to ACCOUNT_FORM_MAP in src/app/admin/[token]/components/SettingsTab.tsx.
Vertical-specific tabs (At-Need, Pre-Planning, Service Catalog for funeral) live alongside.
Step 5 — Tests + acceptance spec
- Add a profile-shape test in
src/lib/verticals/__tests__/<vertical>-profile.test.tsmirroringphase2e-bleed-sweep.test.tsinvariants. - Verify the 988 baseline crisis test passes.
- Create or extend
knowledge/acceptance/<vertical>-admin.mdwith the tier-aware expected-output spec. - Register the new vertical in
src/lib/verticals/registry.tsso hostname routing resolves it.
Code files
See frontmatter code-files:. Derive check validates they exist.
Tests
The 988-baseline crisis test (src/lib/verticals/__tests__/crisis-copy.test.ts) blocks any new vertical that omits the suicide hotline. The Phase 2E + 2F tests (phase2e-bleed-sweep.test.ts, phase2f-hours-sharing-copy.test.ts) pin the contract on shared shapes — extending those with the new vertical's expected values is recommended but not required to ship.
Gotchas
tenant_iddoes NOT exist onvoice_callback_requests,voice_call_logs,moderation_violations. Filter bychurch_ideven when the row semantically belongs to a non-church tenant. P2 to add a realtenant_idmigration later.- Plan key CHECK constraints on
premium_<vertical>s.plan. Use the existing^<vertical>_regex pattern; align theplanKeysarray in the profile to the constraint. - Settings is a gear icon, NOT a tab. Per IA design Principle 6, do not add a Settings entry to the new vertical's
tabsarray — Settings is universally accessible via the gear in the header chrome. - Subscription/Upgrade tab IS a tab. Wire it via
{ key: 'upgrade', label: 'Subscription', capability: 'billing:view', componentPath: '@/app/admin/[token]/components/UpgradeTab' }. The funeral vertical missed this in PR #266 and customers couldn't manage their plan from the dashboard until PR #280 fixed it. audiencefield is plural ('families','pet owners'). Church'saudienceis'people'for backward compatibility — UI text that needs plural-natural copy may need anisChurch ? 'congregation' : terminology.audiencewrapper.- Brand chrome (
brandKey,brandName,brandAccentColor) drives the admin header. Funeral uses copper#B45309; church uses navy#1B365D. Match the marketing site palette.
Recent activity
- 2026-04-28 (PR #263 + #266) — Funeral vertical scaffolded —
funeralwise-demo-token-2026first onboarding - 2026-05-01 — Vet vertical scaffolded (PR #273)
- 2026-05-01 — Phase 2A–2F (PRs #275–#281) — vertical-aware shapes for AccountForm, Inbox filters, Home tiles, Integrations, bleed sweeps, Hours/Sharing copy