Shrink welcome tour into compact coachmark

Refs #1429
This commit is contained in:
rcourtman 2026-04-19 22:21:50 +01:00
parent a34bfcc037
commit 3dbe638f70
7 changed files with 124 additions and 211 deletions

View file

@ -1321,6 +1321,11 @@ Its primary job is rapid v5-to-v6 reorientation. The modal should explain
where familiar v5 destinations moved and what each v6 top-level area is for,
so operators who feel briefly lost after upgrading can rebuild their mental
map in one pass.
That guided welcome surface should stay compact. The canonical shape is a
coachmark-sized card centered on the current destination with one short
step-specific sentence, a small clickable step strip, and minimal footer
controls. It must not grow back into a large sectioned explainer when one
sentence would do the job.
The guided stop map inside that welcome surface is interactive, not decorative:
operators must be able to jump directly to any tour step from the stop list,
and desktop layouts may widen the panel enough to keep step labels readable
@ -1334,8 +1339,9 @@ use bordered flat fills and normal app radii instead of gradient washes,
glassmorphism, or other marketing-style promo chrome that drifts from the rest
of the product.
Secondary disclosures such as telemetry must stay subordinate to that
orientation job: keep them compact, link to the canonical privacy/settings
surfaces, and do not let them crowd out the migration wayfinding copy.
orientation job: keep them as footer-level links into the canonical
privacy/settings surfaces, and do not let them crowd out the migration
wayfinding copy.
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

View file

@ -1143,11 +1143,11 @@ describe('shared primitive guardrails', () => {
expect(whatsNewModalStateSource).toContain('spotlightStyle');
expect(whatsNewModalModelSource).toContain('WHATS_NEW_FEATURE_CARDS');
expect(whatsNewModalModelSource).toContain('WHATS_NEW_TELEMETRY_TITLE');
expect(whatsNewModalModelSource).toContain('WHATS_NEW_DOCS_URL');
expect(whatsNewModalModelSource).toContain('WHATS_NEW_PRIVACY_URL');
expect(whatsNewModalModelSource).toContain('WHATS_NEW_DOCS_LABEL');
expect(whatsNewModalModelSource).toContain('MIGRATION_GUIDE_DOC_URL');
expect(whatsNewModalModelSource).toContain('Telemetry details');
expect(whatsNewModalModelSource).toContain("title: 'Dashboard'");
});

View file

@ -5,13 +5,10 @@ import ServerIcon from 'lucide-solid/icons/server';
import BoxesIcon from 'lucide-solid/icons/boxes';
import HardDriveIcon from 'lucide-solid/icons/hard-drive';
import ShieldCheckIcon from 'lucide-solid/icons/shield-check';
import ChartBarIcon from 'lucide-solid/icons/chart-bar';
import ExternalLinkIcon from 'lucide-solid/icons/external-link';
import XIcon from 'lucide-solid/icons/x';
import {
WHATS_NEW_BACK_LABEL,
WHATS_NEW_CLOSE_LABEL,
WHATS_NEW_CURRENT_STEP_LABEL,
WHATS_NEW_DOCS_LABEL,
WHATS_NEW_DOCS_URL,
WHATS_NEW_DO_NOT_SHOW_LABEL,
@ -19,17 +16,9 @@ import {
WHATS_NEW_KICKER_LABEL,
WHATS_NEW_NEXT_LABEL,
WHATS_NEW_PRIMARY_ACTION_LABEL,
WHATS_NEW_PROGRESS_PREFIX,
WHATS_NEW_PRIVACY_URL,
WHATS_NEW_SKIP_LABEL,
WHATS_NEW_STEP_MAP_HELPER,
WHATS_NEW_STEP_MAP_LABEL,
WHATS_NEW_SUBTITLE,
WHATS_NEW_TELEMETRY_COPY,
WHATS_NEW_TELEMETRY_ENV_VAR,
WHATS_NEW_TELEMETRY_LABEL,
WHATS_NEW_TELEMETRY_PRIVACY_LABEL,
WHATS_NEW_TELEMETRY_SETTINGS_PATH,
WHATS_NEW_TELEMETRY_TITLE,
WHATS_NEW_TELEMETRY_LINK_LABEL,
WHATS_NEW_TITLE,
type WhatsNewFeatureCard,
} from './whatsNewModalModel';
@ -86,24 +75,31 @@ export function WhatsNewModal() {
data-tour-step={step().target}
role="dialog"
aria-modal="true"
aria-labelledby="whats-new-title"
aria-label={WHATS_NEW_TITLE}
tabindex="-1"
class="fixed z-[1001] max-h-[min(90vh,44rem)] overflow-y-auto rounded-md border border-border bg-surface shadow-xl focus:outline-none"
class="fixed z-[1001] max-h-[min(90vh,32rem)] overflow-y-auto rounded-md border border-border bg-surface shadow-xl focus:outline-none"
style={state.panelStyle()}
onClick={(event) => event.stopPropagation()}
>
<div class="flex items-start justify-between border-b border-border bg-surface px-6 py-5">
<div>
<div class="inline-flex items-center rounded-full border border-border bg-surface-hover px-2.5 py-1 text-[10px] font-semibold uppercase tracking-[0.18em] text-muted">
{WHATS_NEW_KICKER_LABEL}
<div class="flex items-start justify-between border-b border-border bg-surface px-5 py-4">
<div class="min-w-0">
<div class="flex items-center gap-2 text-[10px] font-semibold uppercase tracking-[0.18em] text-muted">
<span class="inline-flex items-center rounded-full border border-border bg-surface-hover px-2 py-1">
{WHATS_NEW_KICKER_LABEL}
</span>
<span>
{WHATS_NEW_PROGRESS_PREFIX} {state.stepIndex() + 1} of {state.stepCount()}
</span>
</div>
<div class="mt-3 text-xs font-semibold uppercase tracking-[0.18em] text-muted">
Step {state.stepIndex() + 1} of {state.stepCount()}
<div class="mt-3 flex items-start gap-3">
<div class={`flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-md border bg-surface ${step().accent}`}>
<WhatsNewFeatureIcon card={step()} />
</div>
<div class="min-w-0">
<div class="text-lg font-semibold text-base-content">{step().title}</div>
<p class="mt-1 text-sm leading-6 text-muted">{step().description}</p>
</div>
</div>
<h2 id="whats-new-title" class="mt-1 text-xl font-semibold text-base-content">
{WHATS_NEW_TITLE}
</h2>
<p class="mt-1 text-sm text-muted">{WHATS_NEW_SUBTITLE}</p>
</div>
<button
onClick={state.handleClose}
@ -115,148 +111,81 @@ export function WhatsNewModal() {
</button>
</div>
<div class="space-y-5 px-6 py-5">
<div class={`rounded-md border p-4 ${step().accent}`}>
<div class="flex items-start gap-4">
<div class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-md border border-white/40 bg-surface text-inherit dark:border-slate-800">
<WhatsNewFeatureIcon card={step()} />
</div>
<div class="min-w-0">
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] opacity-70">
{WHATS_NEW_CURRENT_STEP_LABEL}
</div>
<div class="mt-1 text-base font-semibold text-inherit">{step().title}</div>
<p class="mt-2 text-sm leading-6 text-inherit">{step().description}</p>
</div>
</div>
</div>
<div class="space-y-2.5">
<div class="flex items-center gap-3">
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-muted">
{WHATS_NEW_STEP_MAP_LABEL}
</div>
<div class="h-px flex-1 bg-border"></div>
</div>
<p class="text-xs text-muted">{WHATS_NEW_STEP_MAP_HELPER}</p>
<div class="grid grid-cols-2 gap-2.5">
<For each={WHATS_NEW_FEATURE_CARDS}>
{(card, index) => (
<button
type="button"
onClick={() => state.handleSelectStep(index())}
aria-current={index() === state.stepIndex() ? 'step' : undefined}
class={`min-h-[3.25rem] rounded-md border px-3 py-2.5 text-left transition-colors ${
<div class="space-y-4 px-5 py-4">
<div class="flex flex-wrap gap-2">
<For each={WHATS_NEW_FEATURE_CARDS}>
{(card, index) => (
<button
type="button"
onClick={() => state.handleSelectStep(index())}
aria-current={index() === state.stepIndex() ? 'step' : undefined}
class={`inline-flex items-center gap-2 rounded-md border px-2.5 py-1.5 text-xs font-medium transition-colors ${
index() === state.stepIndex()
? 'border-blue-300 bg-blue-50 text-blue-900 dark:border-blue-700 dark:bg-blue-950 dark:text-blue-100'
: 'border-border bg-surface-hover text-base-content hover:border-slate-300 hover:bg-surface dark:hover:border-slate-700'
}`}
>
<span
class={`inline-flex h-5 w-5 items-center justify-center rounded-sm border text-[10px] font-semibold font-mono ${
index() === state.stepIndex()
? 'border-blue-300 bg-blue-50 text-blue-900 dark:border-blue-700 dark:bg-blue-950 dark:text-blue-100'
: 'border-border bg-surface-hover text-base-content hover:border-slate-300 hover:bg-surface dark:hover:border-slate-700'
? 'border-blue-200 bg-surface text-blue-700 dark:border-blue-800 dark:bg-slate-900 dark:text-blue-200'
: 'border-border bg-surface text-muted'
}`}
>
<div class="flex items-center gap-3">
<div
class={`flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md border text-[11px] font-semibold font-mono ${
index() === state.stepIndex()
? 'border-blue-200 bg-surface text-blue-700 dark:border-blue-800 dark:bg-slate-900 dark:text-blue-200'
: 'border-border bg-surface text-muted'
}`}
>
{String(index() + 1).padStart(2, '0')}
</div>
<div class="min-w-0 flex-1 truncate text-sm font-semibold">{card.title}</div>
</div>
</button>
)}
</For>
</div>
</div>
<div class="rounded-md border border-border bg-surface-hover p-3.5">
<div class="flex items-start gap-3">
<div class="mt-0.5 flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md border border-border bg-surface text-muted">
<ChartBarIcon class="h-4 w-4" />
</div>
<div class="min-w-0">
<div class="text-[11px] font-semibold uppercase tracking-[0.16em] text-muted">
{WHATS_NEW_TELEMETRY_LABEL}
</div>
<div class="mt-1 text-sm font-medium text-base-content">
{WHATS_NEW_TELEMETRY_TITLE}
</div>
<p class="mt-1 text-xs leading-5 text-muted">
{WHATS_NEW_TELEMETRY_COPY[0]}{' '}
<a
href={WHATS_NEW_PRIVACY_URL}
target="_blank"
rel="noopener noreferrer"
class="underline hover:text-base-content"
>
{WHATS_NEW_TELEMETRY_PRIVACY_LABEL}
</a>
</p>
<p class="mt-1 text-xs leading-5 text-muted">
{WHATS_NEW_TELEMETRY_COPY[1]}
</p>
<div class="mt-2 flex flex-wrap items-center gap-2 text-[10px] text-muted">
<span class="rounded bg-surface px-1.5 py-0.5 font-medium">
{WHATS_NEW_TELEMETRY_SETTINGS_PATH}
{String(index() + 1).padStart(2, '0')}
</span>
<code class="rounded bg-surface px-1.5 py-0.5 font-mono">
{WHATS_NEW_TELEMETRY_ENV_VAR}
</code>
</div>
</div>
</div>
<span>{card.title}</span>
</button>
)}
</For>
</div>
<div class="flex flex-col gap-3 rounded-md border border-border bg-surface-hover p-3.5 sm:flex-row sm:items-center sm:justify-between">
<label class="flex items-center gap-2 text-sm text-muted">
<input
type="checkbox"
checked={state.dontShowAgain()}
onChange={(event) => state.setDontShowAgain(event.currentTarget.checked)}
class="h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-2 focus:ring-blue-500"
/>
{WHATS_NEW_DO_NOT_SHOW_LABEL}
</label>
<div class="flex items-center gap-4">
<div class="flex flex-col gap-3 border-t border-border pt-3 sm:flex-row sm:items-center sm:justify-between">
<div class="flex flex-wrap items-center gap-3 text-xs text-muted">
<label class="flex items-center gap-2">
<input
type="checkbox"
checked={state.dontShowAgain()}
onChange={(event) => state.setDontShowAgain(event.currentTarget.checked)}
class="h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-2 focus:ring-blue-500"
/>
{WHATS_NEW_DO_NOT_SHOW_LABEL}
</label>
<a
href={WHATS_NEW_DOCS_URL}
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-1 rounded-md border border-border bg-surface px-3 py-1.5 text-sm font-medium text-base-content transition-colors hover:bg-surface-hover"
class="underline hover:text-base-content"
>
{WHATS_NEW_DOCS_LABEL}
<ExternalLinkIcon class="h-4 w-4" />
</a>
<a
href={WHATS_NEW_PRIVACY_URL}
target="_blank"
rel="noopener noreferrer"
class="underline hover:text-base-content"
>
{WHATS_NEW_TELEMETRY_LINK_LABEL}
</a>
</div>
</div>
</div>
<div class="flex items-center justify-between border-t border-border bg-surface-hover px-6 py-4">
<button
type="button"
onClick={state.handleClose}
class="text-sm font-medium text-muted transition-colors hover:text-base-content"
>
{WHATS_NEW_SKIP_LABEL}
</button>
<div class="flex items-center gap-2">
<button
type="button"
onClick={state.handlePrevious}
disabled={state.isFirstStep()}
class="rounded-md border border-border px-3.5 py-2 text-sm font-medium text-base-content transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-50"
>
{WHATS_NEW_BACK_LABEL}
</button>
<button
onClick={state.handleNext}
class="rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white transition-colors hover:bg-blue-700"
type="button"
>
{state.isLastStep() ? WHATS_NEW_PRIMARY_ACTION_LABEL : WHATS_NEW_NEXT_LABEL}
</button>
<div class="flex items-center gap-2 sm:justify-end">
<button
type="button"
onClick={state.handlePrevious}
disabled={state.isFirstStep()}
class="rounded-md border border-border px-3 py-1.5 text-sm font-medium text-base-content transition-colors hover:bg-surface disabled:cursor-not-allowed disabled:opacity-50"
>
{WHATS_NEW_BACK_LABEL}
</button>
<button
onClick={state.handleNext}
class="rounded-md bg-blue-600 px-3.5 py-1.5 text-sm font-semibold text-white transition-colors hover:bg-blue-700"
type="button"
>
{state.isLastStep() ? WHATS_NEW_PRIMARY_ACTION_LABEL : WHATS_NEW_NEXT_LABEL}
</button>
</div>
</div>
</div>
</div>

View file

@ -51,16 +51,14 @@ describe('WhatsNewModal', () => {
expect(whatsNewModalStateSource).toContain('handleClose');
expect(whatsNewModalModelSource).toContain('WHATS_NEW_FEATURE_CARDS');
expect(whatsNewModalModelSource).toContain('WHATS_NEW_TELEMETRY_TITLE');
expect(whatsNewModalModelSource).toContain('WHATS_NEW_DOCS_URL');
expect(whatsNewModalModelSource).toContain('WHATS_NEW_PRIVACY_URL');
expect(whatsNewModalModelSource).toContain('MIGRATION_GUIDE_DOC_URL');
expect(whatsNewModalModelSource).toContain('PRIVACY_DOC_URL');
expect(whatsNewModalModelSource).toContain('anonymous daily ping');
expect(whatsNewModalModelSource).toContain('nothing is gone');
expect(whatsNewModalModelSource).toContain('Telemetry details');
expect(whatsNewModalModelSource).toContain('WHATS_NEW_KICKER_LABEL');
expect(whatsNewModalModelSource).toContain('WHATS_NEW_STEP_MAP_LABEL');
expect(whatsNewModalModelSource).toContain('WHATS_NEW_TELEMETRY_LABEL');
expect(whatsNewModalModelSource).toContain('WHATS_NEW_PROGRESS_PREFIX');
expect(whatsNewModalModelSource).toContain("WHATS_NEW_PRIMARY_ACTION_LABEL = 'Done'");
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');
@ -70,14 +68,14 @@ describe('WhatsNewModal', () => {
it('renders when the navigation modal has not been seen yet', async () => {
render(() => <WhatsNewModal />);
const dialog = await screen.findByRole('dialog');
const dialog = await screen.findByRole('dialog', { name: 'Welcome to Pulse v6' });
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('Where Things Moved')).toBeInTheDocument();
expect(within(dialog).queryByText(/Stop 1/i)).not.toBeInTheDocument();
expect(within(dialog).getAllByText('Dashboard')).toHaveLength(2);
expect(within(dialog).getByText('V5 to V6')).toBeInTheDocument();
expect(within(dialog).getByText(/overview for health, alerts, capacity/i)).toBeInTheDocument();
expect(within(dialog).queryByText('Where Things Moved')).not.toBeInTheDocument();
expect(within(dialog).getByRole('link', { name: 'Migration guide' })).toBeInTheDocument();
expect(within(dialog).getByRole('link', { name: 'Telemetry details' })).toBeInTheDocument();
});
it('stays hidden for public demo sessions', async () => {
@ -130,26 +128,26 @@ describe('WhatsNewModal', () => {
it('advances through the guided tour and finishes on the last step', async () => {
render(() => <WhatsNewModal />);
expect(await screen.findByText(/overall picture: health, alerts, capacity/i)).toBeInTheDocument();
expect(await screen.findByText(/overview for health, alerts, capacity/i)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
expect(await screen.findByText(/systems themselves: Proxmox nodes, Docker hosts/i)).toBeInTheDocument();
expect(await screen.findByText(/Systems live here: nodes, hosts, clusters/i)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
expect(await screen.findByText(/guests or Docker workloads in v5/i)).toBeInTheDocument();
expect(await screen.findByText(/If you looked for guests in v5/i)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
expect(await screen.findByText(/Datastores, pools, disks, and capacity moved here/i)).toBeInTheDocument();
expect(await screen.findByText(/Datastores, pools, disks, and capacity live here/i)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Next' }));
expect(await screen.findByText(/Backups, snapshots, and replication moved here/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: "Let's go" })).toBeInTheDocument();
expect(await screen.findByText(/Backups, snapshots, and replication live here/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Done' })).toBeInTheDocument();
});
it('lets the user jump to a tour stop directly from the stop map', async () => {
render(() => <WhatsNewModal />);
expect(await screen.findByText(/overall picture: health, alerts, capacity/i)).toBeInTheDocument();
expect(await screen.findByText(/overview for health, alerts, capacity/i)).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: /Workloads/i }));
expect(await screen.findByText(/guests or Docker workloads in v5/i)).toBeInTheDocument();
expect(await screen.findByText(/If you looked for guests in v5/i)).toBeInTheDocument();
expect(screen.getByText('Step 3 of 5')).toBeInTheDocument();
});

View file

@ -169,9 +169,9 @@ export function useWhatsNewModalState() {
const rect = spotlightRect();
const panel = panelRef();
const desktopWidth = window.innerWidth >= 1024 ? 448 : 420;
const desktopWidth = window.innerWidth >= 1024 ? 376 : 344;
const panelWidth = Math.min(desktopWidth, window.innerWidth - 32);
const panelHeight = panel?.offsetHeight ?? 340;
const panelHeight = panel?.offsetHeight ?? 260;
if (!rect) {
return {

View file

@ -14,66 +14,48 @@ export const WHATS_NEW_PRIVACY_URL = PRIVACY_DOC_URL;
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 first when you want the overall picture: health, alerts, capacity, and recent activity across your estate.',
description: 'Your new overview for health, alerts, capacity, and recent activity.',
icon: 'dashboard',
target: 'dashboard',
title: 'Dashboard',
},
{
accent: 'border-blue-200 bg-blue-50 dark:border-blue-800 dark:bg-blue-900',
description:
'Use this when you want the systems themselves: Proxmox nodes, Docker hosts, Kubernetes clusters, PBS, PMG, TrueNAS, and other platform roots.',
description: 'Systems live here: nodes, hosts, clusters, and other platform roots.',
icon: 'infrastructure',
target: 'infrastructure',
title: 'Infrastructure',
},
{
accent: 'border-purple-200 bg-purple-50 dark:border-purple-800 dark:bg-purple-900',
description:
'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.',
description: 'VMs, containers, and pods live here. If you looked for guests in v5, start here.',
icon: 'workloads',
target: 'workloads',
title: 'Workloads',
},
{
accent: 'border-emerald-200 bg-emerald-50 dark:border-emerald-800 dark:bg-emerald-900',
description:
'Datastores, pools, disks, and capacity moved here so storage is one destination across platforms.',
description: 'Datastores, pools, disks, and capacity live here across platforms.',
icon: 'storage',
target: 'storage',
title: 'Storage',
},
{
accent: 'border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-900',
description:
'Backups, snapshots, and replication moved here. Open this when you want restore posture or recent recovery activity.',
description: 'Backups, snapshots, and replication live here.',
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_KICKER_LABEL = 'V5 to V6';
export const WHATS_NEW_TITLE = 'Welcome to Pulse v6';
export const WHATS_NEW_SUBTITLE =
"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 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';
export const WHATS_NEW_TELEMETRY_PRIVACY_LABEL = 'Full details';
export const WHATS_NEW_PROGRESS_PREFIX = 'Step';
export const WHATS_NEW_BACK_LABEL = 'Back';
export const WHATS_NEW_CLOSE_LABEL = 'Close';
export const WHATS_NEW_DOCS_LABEL = 'Migration guide';
export const WHATS_NEW_DO_NOT_SHOW_LABEL = "Don't show again";
export const WHATS_NEW_NEXT_LABEL = 'Next';
export const WHATS_NEW_PRIMARY_ACTION_LABEL = "Let's go";
export const WHATS_NEW_SKIP_LABEL = 'Skip tour';
export const WHATS_NEW_PRIMARY_ACTION_LABEL = 'Done';
export const WHATS_NEW_TELEMETRY_LINK_LABEL = 'Telemetry details';

View file

@ -151,19 +151,17 @@ test.describe('Telemetry disclosure', () => {
const infrastructureTab = page.locator(
'[role="tab"][title="All agents and nodes across platforms"]',
);
await expect(dialog.getByText('Welcome to Pulse v6')).toBeVisible();
await expect(dialog).toHaveAttribute('aria-label', 'Welcome to Pulse v6');
await expect(dialog.getByText('Step 1 of 5')).toBeVisible();
await expect(dialog.getByText(/nothing is gone/i)).toBeVisible();
await expect(dialog.getByText('V5 to V6')).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('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();
await expect(dialog.getByText(/overview for health, alerts, capacity/i)).toBeVisible();
await expect(dialog.getByRole('link', { name: 'Telemetry details' })).toBeVisible();
const privacyLink = dialog.getByRole('link', { name: 'Full details' });
const privacyLink = dialog.getByRole('link', { name: 'Telemetry details' });
await expect(privacyLink).toHaveAttribute('href', '/docs/PRIVACY.md');
await expectPopupDoc(
page,
@ -189,7 +187,7 @@ test.describe('Telemetry disclosure', () => {
for (let step = 0; step < 3; step += 1) {
await dialog.getByRole('button', { name: 'Next' }).click();
}
await dialog.getByRole('button', { name: "Let's go" }).click();
await dialog.getByRole('button', { name: 'Done' }).click();
await expect(dialog).not.toBeVisible();
await expect(assistantLauncher).toBeVisible();