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()) {