Skip to main content

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. Mirrors ChurchInfoForm.tsx / FuneralHomeInfoForm.tsx shape. 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.ts mirroring phase2e-bleed-sweep.test.ts invariants.
  • Verify the 988 baseline crisis test passes.
  • Create or extend knowledge/acceptance/<vertical>-admin.md with the tier-aware expected-output spec.
  • Register the new vertical in src/lib/verticals/registry.ts so 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_id does NOT exist on voice_callback_requests, voice_call_logs, moderation_violations. Filter by church_id even when the row semantically belongs to a non-church tenant. P2 to add a real tenant_id migration later.
  • Plan key CHECK constraints on premium_<vertical>s.plan. Use the existing ^<vertical>_ regex pattern; align the planKeys array 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 tabs array — 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.
  • audience field is plural ('families', 'pet owners'). Church's audience is 'people' for backward compatibility — UI text that needs plural-natural copy may need an isChurch ? 'congregation' : terminology.audience wrapper.
  • 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-2026 first 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