diff --git a/frontend/public/kiosk-manifest.webmanifest b/frontend/public/kiosk-manifest.webmanifest
new file mode 100644
index 0000000..4bdbf1b
--- /dev/null
+++ b/frontend/public/kiosk-manifest.webmanifest
@@ -0,0 +1,13 @@
+{
+ "name": "HVAC Kiosk",
+ "short_name": "HVAC Kiosk",
+ "start_url": "/hvac/kiosk",
+ "scope": "/hvac/kiosk",
+ "display": "standalone",
+ "theme_color": "#c1440e",
+ "background_color": "#c1440e",
+ "icons": [
+ { "src": "/hvac/icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
+ { "src": "/hvac/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }
+ ]
+}
diff --git a/frontend/src/pages/Kiosk.tsx b/frontend/src/pages/Kiosk.tsx
index b9c8629..dc7047f 100644
--- a/frontend/src/pages/Kiosk.tsx
+++ b/frontend/src/pages/Kiosk.tsx
@@ -17,6 +17,23 @@ function liveDevicesOf(zone: ZoneStatus): DeviceSummary[] {
return zone.devices.filter(d => KIOSK_DEVICE_TYPES.includes(d.device_type))
}
+// The whole hvac SPA shares one index.html, so there's only one in the document — "Install" from any route otherwise always
+// installs the main app (start_url '/hvac/', staff login). Swap it to a
+// kiosk-specific manifest (start_url/scope '/hvac/kiosk') while this route is
+// mounted, so installing from here opens straight back into the kiosk and its
+// own password prompt instead — restore it on unmount in case something ever
+// navigates away client-side rather than via a full page load.
+function useKioskManifest() {
+ useEffect(() => {
+ const link = document.querySelector('link[rel="manifest"]')
+ if (!link) return
+ const original = link.href
+ link.href = '/hvac/kiosk-manifest.webmanifest'
+ return () => { link.href = original }
+ }, [])
+}
+
// The wall-panel kiosk view — a dedicated route/login, deliberately outside the
// normal staff AuthGate/Layout (see App.tsx). One master panel: one tile per
// ROOM/ZONE (same as the Dashboard's ZoneCard — a room's TRV and AC show
@@ -25,6 +42,8 @@ function liveDevicesOf(zone: ZoneStatus): DeviceSummary[] {
// across zones. See project_hvac_kiosk memory for the design decisions behind
// this (access model, layout, bulk-edit mechanism).
export default function Kiosk() {
+ useKioskManifest()
+
const [authState, setAuthState] = useState<'checking' | 'login' | 'authed'>('checking')
const [password, setPassword] = useState('')
const [loginError, setLoginError] = useState('')