diff --git a/website/components/founder-portal/founder-chat.tsx b/website/components/founder-portal/founder-chat.tsx index 1e54c20..1707e08 100644 --- a/website/components/founder-portal/founder-chat.tsx +++ b/website/components/founder-portal/founder-chat.tsx @@ -60,6 +60,7 @@ export function FounderChat({ context }: FounderChatProps) { const activeChannel = CHANNELS.find((c) => c.id === activeChannelId) || CHANNELS[0]; const isRestrictedChannel = activeChannel.requiresOfficer && !isOfficer; + const hasExampleMessages = messages.length > 0 && messages.every((m) => m.isExample); useEffect(() => { setError(null); @@ -67,7 +68,7 @@ export function FounderChat({ context }: FounderChatProps) { if (isDemoFixture) { const channelMsgs = FIXTURE_CHAT_MESSAGES[activeChannelId] || []; - setMessages(channelMsgs.map((m) => ({ ...m, clubId: club.id, channelId: activeChannelId }))); + setMessages(channelMsgs.map((m) => ({ ...m, clubId: club.id, channelId: activeChannelId, isExample: true }))); } else { unsubscribe = subscribeClubChat( club.id, @@ -143,10 +144,17 @@ export function FounderChat({ context }: FounderChatProps) {

Squad Live Chat

- - - Live Connected - + {hasExampleMessages ? ( + + visibility + Example thread + + ) : ( + + + Live Connected + + )}

Real-time squad channels for ride organization, road condition warnings, and administrative officer discussions. @@ -179,6 +187,16 @@ export function FounderChat({ context }: FounderChatProps) { + {hasExampleMessages && ( +

+ info + + These are example messages — this + channel has no posts yet. Send a message to start the real conversation. + +
+ )} + {error && (
diff --git a/website/components/founder-portal/founder-routes.tsx b/website/components/founder-portal/founder-routes.tsx index 9c5c2b5..d4c0a13 100644 --- a/website/components/founder-portal/founder-routes.tsx +++ b/website/components/founder-portal/founder-routes.tsx @@ -178,6 +178,8 @@ export function FounderRoutes({ context }: FounderRoutesProps) { const canCurate = permissions.canPlanRides || permissions.canManageSettings; + const hasExampleRoutes = routes.length > 0 && routes.every((r) => r.isExample); + return (
{/* Top Header Card */} @@ -187,8 +189,14 @@ export function FounderRoutes({ context }: FounderRoutesProps) {

Curated Club Routes

- - {routes.length} Available + + {hasExampleRoutes ? `${routes.length} Examples` : `${routes.length} Available`}

@@ -207,6 +215,16 @@ export function FounderRoutes({ context }: FounderRoutesProps) { )}

+ {hasExampleRoutes && ( +
+ info + + These are example routes — your club + hasn’t curated any yet. Curate a route to replace them with your own. + +
+ )} + {error && (
@@ -314,9 +332,16 @@ export function FounderRoutes({ context }: FounderRoutesProps) {
{/* Card Header & Badge */}
-

- {route.title} -

+
+

+ {route.title} +

+ {route.isExample && ( + + Example + + )} +
{ if (options?.allowDemoPreview) { - return FIXTURE_CLUB_ROUTES.map((r) => ({ ...r, clubId })); + return FIXTURE_CLUB_ROUTES.map((r) => ({ ...r, clubId, isExample: true })); } callerUid(); const routesRef = collection(firestore, 'clubs', clubId, 'routes'); const q = query(routesRef, orderBy('createdAt', 'desc'), limit(50)); const snap = await getDocs(q); if (snap.empty) { - // Return sample starter routes if no custom routes have been uploaded - return FIXTURE_CLUB_ROUTES.map((r) => ({ ...r, clubId })); + // No custom routes uploaded yet: surface sample starter routes, explicitly flagged as + // examples so the UI can label them and they are never mistaken for real club routes. + return FIXTURE_CLUB_ROUTES.map((r) => ({ ...r, clubId, isExample: true })); } return snap.docs.map((d) => { const data = d.data(); @@ -358,6 +361,8 @@ export interface ChatMessage { authorRole: ClubRole; content: string; createdAt: string; + /** True when this is MotorSignal sample/example content, not a real posted message. */ + isExample?: boolean; } export const FIXTURE_CHAT_MESSAGES: Record = { @@ -441,9 +446,10 @@ export function subscribeClubChat( q, (snap) => { if (snap.empty) { - // Fallback to fixture messages if channel has no messages yet + // No messages in this channel yet: surface sample messages, explicitly flagged as + // examples so the UI can label them and they are never mistaken for real posts. const initial = FIXTURE_CHAT_MESSAGES[channelId] || []; - onMessages(initial.map((m) => ({ ...m, clubId, channelId }))); + onMessages(initial.map((m) => ({ ...m, clubId, channelId, isExample: true }))); return; } const msgs: ChatMessage[] = snap.docs.map((d) => { diff --git a/website/tests/founder-routes-chat.test.tsx b/website/tests/founder-routes-chat.test.tsx index 625c276..fbb481b 100644 --- a/website/tests/founder-routes-chat.test.tsx +++ b/website/tests/founder-routes-chat.test.tsx @@ -250,6 +250,18 @@ describe('Founder Routes Management', () => { expect(mocks.deleteRoute).toHaveBeenCalledWith('ClubID', 'r1'); }); }); + + // WEB-W-routes regression: fixture fallback routes must be labelled as examples so a + // rider can never mistake sample content for the club's real curated routes. + it('labels example routes when the club has none of its own', async () => { + mocks.fetchRoutes.mockResolvedValue(sampleRoutes.map((r) => ({ ...r, isExample: true }))); + render(); + await screen.findByText('Blood Mountain Gap Loop'); + + expect(screen.getByText(/example routes/i)).toBeDefined(); + expect(screen.getByText(/Examples$/)).toBeDefined(); + expect(screen.getAllByText('Example').length).toBeGreaterThanOrEqual(1); + }); }); describe('Founder Chat Management', () => { @@ -296,6 +308,36 @@ describe('Founder Chat Management', () => { }); }); + // WEB-W-chat regression: fixture fallback messages must be labelled as examples and the + // header must not claim a live connection when only sample content is shown. + it('labels example messages when the channel has no real posts', async () => { + mocks.subscribeChat.mockImplementation((_clubId, _channelId, onMessages) => { + onMessages([ + { + id: 'example-msg-1', + clubId: 'ClubID', + channelId: 'general', + authorUid: 'owner', + authorDisplayName: 'Morgan Vance', + authorHandle: '@morgan_v', + authorDisplayRank: 'President', + authorRole: 'owner', + content: 'Welcome to squad chat!', + createdAt: '2026-06-01T12:00:00Z', + isExample: true, + }, + ]); + return vi.fn(); + }); + + render(); + await screen.findByText('Welcome to squad chat!'); + + expect(screen.getByText('Example thread')).toBeDefined(); + expect(screen.getByText(/example messages/i)).toBeDefined(); + expect(screen.queryByText('Live Connected')).toBeNull(); + }); + it('restricts Officers Lounge channel for non-officers', async () => { const memberContext: FounderClubContext = { ...mockContext,