From f23553f82584b1d50a6923e56a33318d00a82e7a Mon Sep 17 00:00:00 2001 From: rcourtman <8825017+rcourtman@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:40:26 +0100 Subject: [PATCH] Link the demo banner back to the install steps pulserelay.pro sends curious visitors to the public demo, and the demo was a dead end: once inside, the only way back to installing Pulse was the browser's history. The demo-mode banner now ends with "Run Pulse on your own hardware", opening the site's setup steps in a new tab. It is install guidance rather than an upsell, so it belongs in demo mode where commercial surfaces are otherwise hidden. The read-only notice keeps its own element so the existing text lookup and dismiss behaviour are unchanged. Verified on a DEMO_MODE=true mock backend behind Vite at 1280x800 and 390x844: the link renders after the notice, wraps cleanly on a phone, and carries target _blank with rel noopener noreferrer. Contract-Neutral: demo banner copy and link only; no public API, config, or contract surface changes --- frontend-modern/browser-verification.json | 29 ++++++------------- frontend-modern/src/components/DemoBanner.tsx | 16 +++++++++- .../components/__tests__/DemoBanner.test.tsx | 11 +++++++ 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index 00f6b678a..557dd5566 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,20 +1,16 @@ { "version": 1, - "base_sha": "f4dc1e69a0e214dd0085c7696f42444b8fd91fa3", - "verified_at": "2026-09-06T16:58:13Z", + "base_sha": "df7eca7913c72f913fe196f37acec4d3b104c726", + "verified_at": "2026-09-06T19:40:26Z", "result": "passed", "changed_paths": [ - "frontend-modern/src/components/Login.tsx", - "frontend-modern/src/useAppRuntimeState.ts", - "frontend-modern/src/utils/localStorage.ts" + "frontend-modern/src/components/DemoBanner.tsx" ], "content_sha256": { - "frontend-modern/src/components/Login.tsx": "621f0b97d775aacaa521a3c72ed02db3fde5ad3eda06d55950a32107723eb44c", - "frontend-modern/src/useAppRuntimeState.ts": "5a1d343d92c441e1302dab129be7256dd9a287c6a60fb0e17c6f9fa13fe20900", - "frontend-modern/src/utils/localStorage.ts": "182ed45685228781fd32725db50611b1115f9ee4fb1af23aeedcf41ca707bdbc" + "frontend-modern/src/components/DemoBanner.tsx": "13fd8dea552d97f1df866438f7ba38eec01f25907dc68a0bec672e1793c3fc97" }, "routes": [ - "/" + "/proxmox/overview" ], "viewports": [ { @@ -27,18 +23,11 @@ } ], "states": [ - "demo-mode login page with the \"Signing you in to the demo\u2026\" status while the automatic sign-in is in flight", - "application shell after the automatic demo sign-in (Demo instance banner, Proxmox overview)", - "login form with printed demo credentials after an explicit Logout, session marker \"suppressed\"", - "login form still shown after a reload following the Logout", - "login form with the \"Invalid username or password\" error after a rejected automatic sign-in (401 stub), Sign in button enabled" + "demo-mode banner at 1280x800 with the read-only notice followed by the \"Run Pulse on your own hardware\" link", + "demo-mode banner at 390x844 wrapping onto two lines with the link visible and the dismiss control intact" ], "interactions": [ - "load / in a fresh browser context at 1280x800 and at 390x844; automatic POST /api/login with demo/demo, no typing", - "click the Logout control in the app header at both widths", - "reload the page after Logout", - "type demo/demo into the form and submit after Logout", - "open / in a second fresh context: automatic sign-in again", - "stub /api/login with 401 and load /: form fallback" + "load /proxmox/overview on a DEMO_MODE=true mock backend behind Vite in fresh contexts at both widths", + "read the link attributes from the live DOM: href https://pulserelay.pro/#setup, target _blank, rel noopener noreferrer" ] } diff --git a/frontend-modern/src/components/DemoBanner.tsx b/frontend-modern/src/components/DemoBanner.tsx index 7bfa9b6de..08c173968 100644 --- a/frontend-modern/src/components/DemoBanner.tsx +++ b/frontend-modern/src/components/DemoBanner.tsx @@ -3,6 +3,8 @@ import { createSignal, onMount, Show } from 'solid-js'; import { InlineNotice } from '@/components/shared/InlineNotice'; import { presentationPolicyIsDemoMode } from '@/stores/sessionPresentationPolicy'; +const DEMO_INSTALL_URL = 'https://pulserelay.pro/#setup'; + export function DemoBanner() { const [dismissed, setDismissed] = createSignal(false); @@ -29,7 +31,19 @@ export function DemoBanner() { dismissLabel="Dismiss demo banner" dismissTitle="Dismiss" > - Demo instance with mock data (read-only) + Demo instance with mock data (read-only) + + {/* The public demo is where pulserelay.pro sends curious visitors, and + without this it was a dead end: no way back except browser history. + Install guidance, not an upsell, so it stays in demo mode. */} + + Run Pulse on your own hardware + ); diff --git a/frontend-modern/src/components/__tests__/DemoBanner.test.tsx b/frontend-modern/src/components/__tests__/DemoBanner.test.tsx index abc1a361e..4d96920ed 100644 --- a/frontend-modern/src/components/__tests__/DemoBanner.test.tsx +++ b/frontend-modern/src/components/__tests__/DemoBanner.test.tsx @@ -57,6 +57,17 @@ describe('DemoBanner', () => { expect(screen.getByText('Demo instance with mock data (read-only)')).toBeInTheDocument(); }); + it('links the demo back to the install steps in a new tab', async () => { + presentationPolicyIsDemoModeMock.mockReturnValue(true); + + await renderBanner(); + + const link = screen.getByRole('link', { name: 'Run Pulse on your own hardware' }); + expect(link).toHaveAttribute('href', 'https://pulserelay.pro/#setup'); + expect(link).toHaveAttribute('target', '_blank'); + expect(link).toHaveAttribute('rel', 'noopener noreferrer'); + }); + it('stays hidden when demo mode is disabled', async () => { await renderBanner();