From 8e6fe4142fcad69e933deba3c37bcfda170cb4f2 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sat, 11 Apr 2026 21:54:43 +0100 Subject: [PATCH] Hide whats-new modal on public demo --- .../subsystems/frontend-primitives.md | 4 ++++ .../SharedPrimitives.guardrails.test.ts | 2 ++ .../shared/__tests__/WhatsNewModal.test.tsx | 24 ++++++++++++++++++- .../shared/useWhatsNewModalState.ts | 10 +++++++- 4 files changed, 38 insertions(+), 2 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index d2747dc95..1b5390a40 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -1130,6 +1130,10 @@ product copy, or external links back into the shared shell. Internal product navigation from that shell should still route through canonical shared helpers such as `frontend-modern/src/routing/resourceLinks.ts` rather than freezing raw `/recovery?...` route strings into the modal itself. +That state owner now also owns public-demo suppression: the modal must stay +closed until `sessionPresentationPolicyResolved()` is true and must fail closed +when `presentationPolicyIsDemoMode()` resolves true, so the public demo does +not front-load product migration onboarding ahead of the actual surface. Canonical customer disclosures inside those shared shells now route through `frontend-modern/src/utils/docsLinks.ts`, so settings and what's-new privacy links resolve to shipped `/docs/...` assets instead of hard-coded GitHub diff --git a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts index 6a8da3ef1..b35587aa4 100644 --- a/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts +++ b/frontend-modern/src/components/shared/SharedPrimitives.guardrails.test.ts @@ -1134,6 +1134,8 @@ describe('shared primitive guardrails', () => { expect(whatsNewModalStateSource).toContain('createLocalStorageBooleanSignal'); expect(whatsNewModalStateSource).toContain('createSignal'); expect(whatsNewModalStateSource).toContain('STORAGE_KEYS.WHATS_NEW_NAV_V2_SHOWN'); + expect(whatsNewModalStateSource).toContain('sessionPresentationPolicyResolved'); + expect(whatsNewModalStateSource).toContain('presentationPolicyIsDemoMode'); expect(whatsNewModalStateSource).toContain('handleClose'); expect(whatsNewModalModelSource).toContain('WHATS_NEW_FEATURE_CARDS'); diff --git a/frontend-modern/src/components/shared/__tests__/WhatsNewModal.test.tsx b/frontend-modern/src/components/shared/__tests__/WhatsNewModal.test.tsx index 8adcd8449..3dfdeefa6 100644 --- a/frontend-modern/src/components/shared/__tests__/WhatsNewModal.test.tsx +++ b/frontend-modern/src/components/shared/__tests__/WhatsNewModal.test.tsx @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { cleanup, fireEvent, render, screen, waitFor } from '@solidjs/testing-library'; import { WhatsNewModal } from '@/components/shared/WhatsNewModal'; import whatsNewModalSource from '@/components/shared/WhatsNewModal.tsx?raw'; @@ -6,9 +6,19 @@ import whatsNewModalModelSource from '@/components/shared/whatsNewModalModel.ts? import whatsNewModalStateSource from '@/components/shared/useWhatsNewModalState.ts?raw'; import { STORAGE_KEYS } from '@/utils/localStorage'; +const presentationPolicyIsDemoModeMock = vi.hoisted(() => vi.fn(() => false)); +const sessionPresentationPolicyResolvedMock = vi.hoisted(() => vi.fn(() => true)); + +vi.mock('@/stores/sessionPresentationPolicy', () => ({ + presentationPolicyIsDemoMode: presentationPolicyIsDemoModeMock, + sessionPresentationPolicyResolved: sessionPresentationPolicyResolvedMock, +})); + describe('WhatsNewModal', () => { beforeEach(() => { localStorage.clear(); + presentationPolicyIsDemoModeMock.mockReturnValue(false); + sessionPresentationPolicyResolvedMock.mockReturnValue(true); }); afterEach(() => { @@ -31,6 +41,8 @@ describe('WhatsNewModal', () => { expect(whatsNewModalStateSource).toContain('createLocalStorageBooleanSignal'); expect(whatsNewModalStateSource).toContain('createSignal'); expect(whatsNewModalStateSource).toContain('STORAGE_KEYS.WHATS_NEW_NAV_V2_SHOWN'); + expect(whatsNewModalStateSource).toContain('sessionPresentationPolicyResolved'); + expect(whatsNewModalStateSource).toContain('presentationPolicyIsDemoMode'); expect(whatsNewModalStateSource).toContain('handleClose'); expect(whatsNewModalModelSource).toContain('WHATS_NEW_FEATURE_CARDS'); @@ -56,6 +68,16 @@ describe('WhatsNewModal', () => { expect(screen.getByText('Welcome to the New Navigation!')).toBeInTheDocument(); }); + it('stays hidden for public demo sessions', async () => { + presentationPolicyIsDemoModeMock.mockReturnValue(true); + + render(() => ); + + await waitFor(() => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + }); + it('closes on backdrop click and records the modal as seen by default', async () => { render(() => ); diff --git a/frontend-modern/src/components/shared/useWhatsNewModalState.ts b/frontend-modern/src/components/shared/useWhatsNewModalState.ts index 59ffab50f..3d438f0ad 100644 --- a/frontend-modern/src/components/shared/useWhatsNewModalState.ts +++ b/frontend-modern/src/components/shared/useWhatsNewModalState.ts @@ -1,5 +1,9 @@ import { createSignal } from 'solid-js'; import { createLocalStorageBooleanSignal, STORAGE_KEYS } from '@/utils/localStorage'; +import { + presentationPolicyIsDemoMode, + sessionPresentationPolicyResolved, +} from '@/stores/sessionPresentationPolicy'; export function useWhatsNewModalState() { const [hasSeen, setHasSeen] = createLocalStorageBooleanSignal( @@ -9,7 +13,11 @@ export function useWhatsNewModalState() { const [dontShowAgain, setDontShowAgain] = createSignal(true); const [dismissedForSession, setDismissedForSession] = createSignal(false); - const isOpen = () => !hasSeen() && !dismissedForSession(); + const isOpen = () => + sessionPresentationPolicyResolved() && + !presentationPolicyIsDemoMode() && + !hasSeen() && + !dismissedForSession(); const handleClose = () => { if (dontShowAgain()) {