-
- Privacy note
+
+
+ {WHATS_NEW_TELEMETRY_LABEL}
-
+
{WHATS_NEW_TELEMETRY_TITLE}
-
- {WHATS_NEW_TELEMETRY_COPY[0]}
+
+ {WHATS_NEW_TELEMETRY_COPY[0]}{' '}
+
+ {WHATS_NEW_TELEMETRY_PRIVACY_LABEL}
+
+
+ {WHATS_NEW_TELEMETRY_COPY[1]}
+
+
+
+ {WHATS_NEW_TELEMETRY_SETTINGS_PATH}
+
+
+ {WHATS_NEW_TELEMETRY_ENV_VAR}
+
+
-
- {WHATS_NEW_TELEMETRY_COPY[1]} You can disable it any time in{' '}
- {WHATS_NEW_TELEMETRY_SETTINGS_PATH} or by
- setting{' '}
-
- {WHATS_NEW_TELEMETRY_ENV_VAR}
-
- .{' '}
-
- {WHATS_NEW_TELEMETRY_PRIVACY_LABEL}
-
-
diff --git a/frontend-modern/src/components/shared/__tests__/WhatsNewModal.test.tsx b/frontend-modern/src/components/shared/__tests__/WhatsNewModal.test.tsx
index 12d0eeb62..0d7addc1d 100644
--- a/frontend-modern/src/components/shared/__tests__/WhatsNewModal.test.tsx
+++ b/frontend-modern/src/components/shared/__tests__/WhatsNewModal.test.tsx
@@ -56,9 +56,11 @@ describe('WhatsNewModal', () => {
expect(whatsNewModalModelSource).toContain('WHATS_NEW_PRIVACY_URL');
expect(whatsNewModalModelSource).toContain('MIGRATION_GUIDE_DOC_URL');
expect(whatsNewModalModelSource).toContain('PRIVACY_DOC_URL');
- expect(whatsNewModalModelSource).toContain('rotating install ID');
- expect(whatsNewModalModelSource).toContain('normalized release identity');
- expect(whatsNewModalModelSource).toContain('IP addresses are not stored in telemetry rows');
+ expect(whatsNewModalModelSource).toContain('anonymous daily ping');
+ expect(whatsNewModalModelSource).toContain('nothing is gone');
+ expect(whatsNewModalModelSource).toContain('WHATS_NEW_KICKER_LABEL');
+ expect(whatsNewModalModelSource).toContain('WHATS_NEW_STEP_MAP_LABEL');
+ expect(whatsNewModalModelSource).toContain('WHATS_NEW_TELEMETRY_LABEL');
expect(whatsNewModalModelSource).not.toContain('https://github.com/rcourtman/Pulse/blob/main/docs/README.md');
expect(whatsNewModalModelSource).not.toContain('https://github.com/rcourtman/Pulse/blob/main/docs/PRIVACY.md');
expect(whatsNewModalModelSource).toContain('WHATS_NEW_DOCS_LABEL');
@@ -71,8 +73,9 @@ describe('WhatsNewModal', () => {
const dialog = await screen.findByRole('dialog');
expect(dialog).toBeInTheDocument();
expect(within(dialog).getByText('Welcome to Pulse v6')).toBeInTheDocument();
+ expect(within(dialog).getByText(/nothing is gone/i)).toBeInTheDocument();
expect(within(dialog).getByText('Step 1 of 5')).toBeInTheDocument();
- expect(within(dialog).getByText('In This Tour')).toBeInTheDocument();
+ expect(within(dialog).getByText('Where Things Moved')).toBeInTheDocument();
expect(within(dialog).queryByText(/Stop 1/i)).not.toBeInTheDocument();
expect(within(dialog).getAllByText('Dashboard')).toHaveLength(2);
});
@@ -127,26 +130,26 @@ describe('WhatsNewModal', () => {
it('advances through the guided tour and finishes on the last step', async () => {
render(() => );
- expect(await screen.findByText(/problem-focused summary/i)).toBeInTheDocument();
+ expect(await screen.findByText(/overall picture: health, alerts, capacity/i)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
- expect(await screen.findByText(/platform roots live here/i)).toBeInTheDocument();
+ expect(await screen.findByText(/systems themselves: Proxmox nodes, Docker hosts/i)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
- expect(await screen.findByText(/docker update status now share/i)).toBeInTheDocument();
+ expect(await screen.findByText(/guests or Docker workloads in v5/i)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
- expect(await screen.findByText(/Storage is now a top-level destination/i)).toBeInTheDocument();
+ expect(await screen.findByText(/Datastores, pools, disks, and capacity moved here/i)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
- expect(await screen.findByText(/Recovery events \(backups, snapshots, and replication\)/i)).toBeInTheDocument();
+ expect(await screen.findByText(/Backups, snapshots, and replication moved here/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: "Let's go" })).toBeInTheDocument();
});
it('lets the user jump to a tour stop directly from the stop map', async () => {
render(() => );
- expect(await screen.findByText(/problem-focused summary/i)).toBeInTheDocument();
+ expect(await screen.findByText(/overall picture: health, alerts, capacity/i)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /Workloads/i }));
- expect(await screen.findByText(/docker update status now share/i)).toBeInTheDocument();
+ expect(await screen.findByText(/guests or Docker workloads in v5/i)).toBeInTheDocument();
expect(screen.getByText('Step 3 of 5')).toBeInTheDocument();
});
diff --git a/frontend-modern/src/components/shared/whatsNewModalModel.ts b/frontend-modern/src/components/shared/whatsNewModalModel.ts
index 4f681e643..d658cb5bb 100644
--- a/frontend-modern/src/components/shared/whatsNewModalModel.ts
+++ b/frontend-modern/src/components/shared/whatsNewModalModel.ts
@@ -15,7 +15,7 @@ export const WHATS_NEW_FEATURE_CARDS: WhatsNewFeatureCard[] = [
{
accent: 'border-indigo-200 bg-indigo-50 dark:border-indigo-800 dark:bg-indigo-900',
description:
- 'Start here for a problem-focused summary. This is the landing page now, not the old Proxmox overview.',
+ 'Start here first when you want the overall picture: health, alerts, capacity, and recent activity across your estate.',
icon: 'dashboard',
target: 'dashboard',
title: 'Dashboard',
@@ -23,7 +23,7 @@ export const WHATS_NEW_FEATURE_CARDS: WhatsNewFeatureCard[] = [
{
accent: 'border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-900',
description:
- 'Platform roots live here: Proxmox nodes, Docker hosts, Kubernetes clusters, PBS, PMG, TrueNAS, and more.',
+ 'Use this when you want the systems themselves: Proxmox nodes, Docker hosts, Kubernetes clusters, PBS, PMG, TrueNAS, and other platform roots.',
icon: 'infrastructure',
target: 'infrastructure',
title: 'Infrastructure',
@@ -31,14 +31,15 @@ export const WHATS_NEW_FEATURE_CARDS: WhatsNewFeatureCard[] = [
{
accent: 'border-purple-200 bg-purple-50 dark:border-purple-800 dark:bg-purple-900',
description:
- 'VMs, containers, pods, and Docker update status now share one unified workloads surface.',
+ 'VMs, containers, and pods live here now. If you used to drill into guests or Docker workloads in v5, this is the new starting point.',
icon: 'workloads',
target: 'workloads',
title: 'Workloads',
},
{
accent: 'border-emerald-200 bg-emerald-50 dark:border-emerald-800 dark:bg-emerald-900',
- description: 'Storage is now a top-level destination across all systems.',
+ description:
+ 'Datastores, pools, disks, and capacity moved here so storage is one destination across platforms.',
icon: 'storage',
target: 'storage',
title: 'Storage',
@@ -46,20 +47,25 @@ export const WHATS_NEW_FEATURE_CARDS: WhatsNewFeatureCard[] = [
{
accent: 'border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-900',
description:
- 'Recovery events (backups, snapshots, and replication) are now first-class pages.',
+ 'Backups, snapshots, and replication moved here. Open this when you want restore posture or recent recovery activity.',
icon: 'recovery',
target: 'recovery',
title: 'Recovery',
},
];
+export const WHATS_NEW_KICKER_LABEL = 'V5 to V6 Guide';
+export const WHATS_NEW_CURRENT_STEP_LABEL = 'Now showing';
+export const WHATS_NEW_STEP_MAP_LABEL = 'Where Things Moved';
+export const WHATS_NEW_STEP_MAP_HELPER = 'Jump ahead or follow the highlighted path.';
export const WHATS_NEW_TITLE = 'Welcome to Pulse v6';
export const WHATS_NEW_SUBTITLE =
- 'Everything is now organized by what you want to do, not where the data comes from.';
-export const WHATS_NEW_TELEMETRY_TITLE = 'Anonymous outbound telemetry';
+ "If you're coming from v5, nothing is gone. Pulse is now grouped by task so you can find things faster.";
+export const WHATS_NEW_TELEMETRY_LABEL = 'Telemetry note';
+export const WHATS_NEW_TELEMETRY_TITLE = 'Anonymous telemetry';
export const WHATS_NEW_TELEMETRY_COPY = [
- 'Pulse now sends a lightweight anonymous ping once a day — just a rotating install ID, normalized release identity, platform, resource counts, and feature flags. No hostnames, credentials, or personal information are sent, and IP addresses are not stored in telemetry rows.',
- 'This helps the developer understand how Pulse is used and prioritise what to build next.',
+ 'Pulse also sends a lightweight anonymous daily ping. No hostnames, credentials, or personal information are sent.',
+ 'You can turn it off any time in Settings → System → General or with PULSE_TELEMETRY=false.',
];
export const WHATS_NEW_TELEMETRY_SETTINGS_PATH = 'Settings → System → General';
export const WHATS_NEW_TELEMETRY_ENV_VAR = 'PULSE_TELEMETRY=false';
diff --git a/tests/integration/tests/19-telemetry-disclosure.spec.ts b/tests/integration/tests/19-telemetry-disclosure.spec.ts
index 46e2d1459..bf902a459 100644
--- a/tests/integration/tests/19-telemetry-disclosure.spec.ts
+++ b/tests/integration/tests/19-telemetry-disclosure.spec.ts
@@ -153,18 +153,15 @@ test.describe('Telemetry disclosure', () => {
);
await expect(dialog.getByText('Welcome to Pulse v6')).toBeVisible();
await expect(dialog.getByText('Step 1 of 5')).toBeVisible();
+ await expect(dialog.getByText(/nothing is gone/i)).toBeVisible();
await expect(assistantLauncher).toBeHidden();
await expect(spotlight).toHaveAttribute('data-tour-step', 'dashboard');
await expect(dialog).toHaveAttribute('data-tour-step', 'dashboard');
await expectSpotlightAround(spotlight, dashboardTab);
- await expect(
- dialog.getByText(
- /rotating install ID, normalized release identity, platform, resource counts, and feature flags/i,
- ),
- ).toBeVisible();
- await expect(
- dialog.getByText(/IP addresses are not stored in telemetry rows/i),
- ).toBeVisible();
+ await expect(dialog.getByText('Where Things Moved')).toBeVisible();
+ await expect(dialog.getByText('Telemetry note')).toBeVisible();
+ await expect(dialog.getByText(/lightweight anonymous daily ping/i)).toBeVisible();
+ await expect(dialog.getByText('Settings → System → General', { exact: true })).toBeVisible();
const privacyLink = dialog.getByRole('link', { name: 'Full details' });
await expect(privacyLink).toHaveAttribute('href', '/docs/PRIVACY.md');