diff --git a/ui/src/App.test.tsx b/ui/src/App.test.tsx index 18b58404..23e24db3 100644 --- a/ui/src/App.test.tsx +++ b/ui/src/App.test.tsx @@ -14,6 +14,12 @@ vi.mock('./shared/routing/routing', () => ({ getAppUrl: vi.fn(), })) +vi.mock('./causestarter/shell/CauseShell', () => ({ + CauseShell: ({ children }: { children: React.ReactNode }) => ( +
{children}
+ ), +})) + vi.mock('./shared/components/AppShell', () => ({ AppShell: ({ branding, navigation, children }: { branding: { name: string }; navigation: { primaryNavigation: Array<{label: string; path: string}>; secondaryNavigation: Array<{label: string; path: string}>; footerText: string }; children: React.ReactNode }) => (
@@ -134,6 +140,28 @@ describe('App route composition', () => { expect(screen.getByText('Common Sense Majority')).toBeInTheDocument() }) + + it('sets the document title from domain branding', async () => { + mockGetActiveDomain.mockReturnValue(fakeDomain('CauseStarter', [], 'footer')) + + const { default: App } = await import('./App') + render(React.createElement(App)) + + expect(document.title).toBe('CauseStarter') + }) + + it('sets the document title when using CauseShell', async () => { + mockGetActiveDomain.mockReturnValue({ + ...fakeDomain('CauseStarter', [], 'footer'), + useCauseShell: true, + }) + + const { default: App } = await import('./App') + render(React.createElement(App)) + + expect(screen.getByTestId('cause-shell')).toBeInTheDocument() + expect(document.title).toBe('CauseStarter') + }) }) describe('primary navigation per domain', () => { diff --git a/ui/src/App.tsx b/ui/src/App.tsx index ea261909..b17eaa2a 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -9,6 +9,11 @@ import { CauseShell } from './causestarter/shell/CauseShell' function DomainChrome({ children }: { children: ReactNode }) { const domain = getActiveDomain() + + useEffect(() => { + document.title = domain.branding.name + }, [domain.branding.name]) + if (domain.useCauseShell) { return {children} } diff --git a/ui/src/shared/components/AppShell.tsx b/ui/src/shared/components/AppShell.tsx index 3a6a187c..0bb8afbe 100644 --- a/ui/src/shared/components/AppShell.tsx +++ b/ui/src/shared/components/AppShell.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState } from 'react' import type { MouseEvent, ReactNode } from 'react' import { AppBar, @@ -208,10 +208,6 @@ export function AppShell({ children, branding, navigation }: AppShellProps) { tagline: 'Find common ground and fund what matters.', } - useEffect(() => { - document.title = brand.name - }, [brand.name]) - const nav = navigation ?? { primaryNavigation: [ { label: 'Start Here', path: '/docs' }, diff --git a/ui/vite.config.ts b/ui/vite.config.ts index 756f060b..a30cd292 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -18,7 +18,7 @@ export default defineConfig(({ mode }) => { build: { outDir: `dist/${domain}`, }, - plugins: [react(), runtimeConfigPlugin(domain, env), endUserDocsPlugin({ domain })], + plugins: [react(), htmlTitlePlugin(domain), runtimeConfigPlugin(domain, env), endUserDocsPlugin({ domain })], worker: { format: 'es', }, @@ -102,6 +102,29 @@ function sdkSourceAliases(): Record { ) } +// Must match branding.name in ui/src/domains/*/manifest.tsx (Vite cannot import those TSX files here). +const DOMAIN_TITLES: Record = { + commonality: 'Commonality', + lazyGiving: 'LazyGiving', + alignment: 'Aligning', + tally: 'Tally', + 'content-funding': 'Content Funding', + civility: 'Civility', + 'common-sense-majority': 'Common Sense Majority', + conceptspace: 'Conceptspace', + causestarter: 'CauseStarter', +} + +function htmlTitlePlugin(buildDomain: string): Plugin { + const title = DOMAIN_TITLES[buildDomain] ?? 'Commonality' + return { + name: 'commonality-html-title', + transformIndexHtml(html) { + return html.replace(/[^<]*<\/title>/, `<title>${title}`) + }, + } +} + function runtimeConfigPlugin(buildDomain: string, env: Record): Plugin { return { name: 'commonality-runtime-config',