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
This commit is contained in:
rcourtman
2026-09-06 20:40:26 +01:00
parent df7eca7913
commit f23553f825
3 changed files with 35 additions and 21 deletions
+9 -20
View File
@@ -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"
]
}
+15 -1
View File
@@ -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)
<span>Demo instance with mock data (read-only)</span>
<span aria-hidden="true"> · </span>
{/* 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. */}
<a
href={DEMO_INSTALL_URL}
target="_blank"
rel="noopener noreferrer"
class="font-medium underline underline-offset-2 hover:no-underline"
>
Run Pulse on your own hardware
</a>
</InlineNotice>
</Show>
);
@@ -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();