From 0b9c8e275d374b62d4cf7a6bbce6b85faa64fb3d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Jul 2026 00:33:33 +0000 Subject: [PATCH] Add full-screen app mode, device planning, and scene effects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the faithful iOS simulator, driven by live feedback: - Full-screen app mode: a new "Open the app full screen" button launches FullscreenApp, the same interactive app (shared AppShell) filling the viewport — edge-to-edge on phones, a centered phone column on desktop — over one PhoneProvider, with Esc / Close to exit and body-scroll lock. - Rooms can add devices: the room-detail + opens an Add-Device sheet (mirrors AddDeviceView — name field + type grid from DeviceType). Added devices appear under a Planned section, are tappable into capability controls, and update the room/dashboard counts. - Scenes now visibly do something: applying any scene (approval or the ambient suggestion) writes its light preset to every light and tints the dashboard via activeSceneAmbient, with the subtitle noting the live scene. - Phone frame proportions: the hero phone read tablet-wide (320×490); it now uses a true phone aspect (264×544) and the responsive width override that the earlier rule's specificity had been silently shadowing. New styles live in src/simulator.css. Adds an e2e test covering full-screen open → plan a device → close, and updates CLAUDE.md / AGENTS.md (kept byte-for-byte identical) to document the mode. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01MwXGKcD6HtsThXsp5KzCMB --- AGENTS.md | 2 +- CLAUDE.md | 2 +- e2e/live-demo.e2e.js | 24 +++ src/App.jsx | 8 +- src/InteractivePhone.jsx | 385 ++++++++++++++++++++++++++++++++------- src/simulator.css | 242 ++++++++++++++++++++++++ 6 files changed, 598 insertions(+), 65 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2e1d469..a93f436 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -272,7 +272,7 @@ npm run e2e # Playwright end-to-end tests npm run ci # lint + test + build + e2e ``` -Single-page React/Vite app — no router, anchor-scroll only. Entry is `src/main.jsx`, which mounts `src/App.jsx` into `#root` in the top-level `index.html` and imports the page styles (`App.css`, `lumen-overrides.css`, `mobile-polish.css`; `App.jsx` also imports `theme.css` and `simulator.css`). `src/styles.css` is unused if present. The hero embeds an interactive iOS simulator (`src/InteractivePhone.jsx`) that mirrors the native app's surfaces — all five tabs (Home rhythm/stats/rooms/"Lumen noticed", Rooms→room→device drill-down, Intel device list, Auto scenes, Settings incl. the Sensory Profile) and the consent flow (Awareness → Reasoning → Action → Execution). Its markup deliberately reuses the app's copy, seeded scenes, and dark palette; `simulator.css` holds the styles the earlier demo didn't cover. Keep it in step with the Swift UI when that changes. Unit tests run on Vitest (`src/main.test.jsx`, setup in `src/test/setup.js`); end-to-end coverage uses Playwright (`playwright.config.js`, `testDir: ./e2e`). `public/privacy/index.html` is fully self-contained (no React); the `vercel.json` rewrite maps `/privacy` → `/privacy/index.html`. +Single-page React/Vite app — no router, anchor-scroll only. Entry is `src/main.jsx`, which mounts `src/App.jsx` into `#root` in the top-level `index.html` and imports the page styles (`App.css`, `lumen-overrides.css`, `mobile-polish.css`; `App.jsx` also imports `theme.css` and `simulator.css`). `src/styles.css` is unused if present. The hero embeds an interactive iOS simulator (`src/InteractivePhone.jsx`) that mirrors the native app's surfaces — all five tabs (Home rhythm/stats/rooms/"Lumen noticed", Rooms→room→device drill-down, Intel device list, Auto scenes, Settings incl. the Sensory Profile) and the consent flow (Awareness → Reasoning → Action → Execution). Its markup deliberately reuses the app's copy, seeded scenes, and dark palette; `simulator.css` holds the styles the earlier demo didn't cover. Keep it in step with the Swift UI when that changes. The same app UI (the shared `AppShell`) also powers a **full-screen mode** — `FullscreenApp`, launched from the hero's "Open the app full screen" button, fills the viewport (edge-to-edge on phones, a centered phone column on desktop) sharing one `PhoneProvider` state. Running a scene applies its light preset to every light and tints the dashboard (`activeSceneAmbient`), and the Rooms `+` opens an Add-Device sheet (mirrors `AddDeviceView`) that adds a planned device to the room. Unit tests run on Vitest (`src/main.test.jsx`, setup in `src/test/setup.js`); end-to-end coverage uses Playwright (`playwright.config.js`, `testDir: ./e2e`). `public/privacy/index.html` is fully self-contained (no React); the `vercel.json` rewrite maps `/privacy` → `/privacy/index.html`. Waitlist validation/delivery logic (`normalizeWaitlistPayload`, `isValidWaitlistEmail`, `postWebhook`/`deliverWaitlist`) is factored into `lib/waitlist.js` and shared by both sides of the submit path: `api/waitlist.js` (the Vercel serverless handler) imports it server-side, and `src/waitlistSubmit.js` (the browser-side submit + provider-fallback chain) imports `DEFAULT_TO_EMAIL` from it. `lib/waitlist.test.js` covers the shared module directly. diff --git a/CLAUDE.md b/CLAUDE.md index 2e1d469..a93f436 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -272,7 +272,7 @@ npm run e2e # Playwright end-to-end tests npm run ci # lint + test + build + e2e ``` -Single-page React/Vite app — no router, anchor-scroll only. Entry is `src/main.jsx`, which mounts `src/App.jsx` into `#root` in the top-level `index.html` and imports the page styles (`App.css`, `lumen-overrides.css`, `mobile-polish.css`; `App.jsx` also imports `theme.css` and `simulator.css`). `src/styles.css` is unused if present. The hero embeds an interactive iOS simulator (`src/InteractivePhone.jsx`) that mirrors the native app's surfaces — all five tabs (Home rhythm/stats/rooms/"Lumen noticed", Rooms→room→device drill-down, Intel device list, Auto scenes, Settings incl. the Sensory Profile) and the consent flow (Awareness → Reasoning → Action → Execution). Its markup deliberately reuses the app's copy, seeded scenes, and dark palette; `simulator.css` holds the styles the earlier demo didn't cover. Keep it in step with the Swift UI when that changes. Unit tests run on Vitest (`src/main.test.jsx`, setup in `src/test/setup.js`); end-to-end coverage uses Playwright (`playwright.config.js`, `testDir: ./e2e`). `public/privacy/index.html` is fully self-contained (no React); the `vercel.json` rewrite maps `/privacy` → `/privacy/index.html`. +Single-page React/Vite app — no router, anchor-scroll only. Entry is `src/main.jsx`, which mounts `src/App.jsx` into `#root` in the top-level `index.html` and imports the page styles (`App.css`, `lumen-overrides.css`, `mobile-polish.css`; `App.jsx` also imports `theme.css` and `simulator.css`). `src/styles.css` is unused if present. The hero embeds an interactive iOS simulator (`src/InteractivePhone.jsx`) that mirrors the native app's surfaces — all five tabs (Home rhythm/stats/rooms/"Lumen noticed", Rooms→room→device drill-down, Intel device list, Auto scenes, Settings incl. the Sensory Profile) and the consent flow (Awareness → Reasoning → Action → Execution). Its markup deliberately reuses the app's copy, seeded scenes, and dark palette; `simulator.css` holds the styles the earlier demo didn't cover. Keep it in step with the Swift UI when that changes. The same app UI (the shared `AppShell`) also powers a **full-screen mode** — `FullscreenApp`, launched from the hero's "Open the app full screen" button, fills the viewport (edge-to-edge on phones, a centered phone column on desktop) sharing one `PhoneProvider` state. Running a scene applies its light preset to every light and tints the dashboard (`activeSceneAmbient`), and the Rooms `+` opens an Add-Device sheet (mirrors `AddDeviceView`) that adds a planned device to the room. Unit tests run on Vitest (`src/main.test.jsx`, setup in `src/test/setup.js`); end-to-end coverage uses Playwright (`playwright.config.js`, `testDir: ./e2e`). `public/privacy/index.html` is fully self-contained (no React); the `vercel.json` rewrite maps `/privacy` → `/privacy/index.html`. Waitlist validation/delivery logic (`normalizeWaitlistPayload`, `isValidWaitlistEmail`, `postWebhook`/`deliverWaitlist`) is factored into `lib/waitlist.js` and shared by both sides of the submit path: `api/waitlist.js` (the Vercel serverless handler) imports it server-side, and `src/waitlistSubmit.js` (the browser-side submit + provider-fallback chain) imports `DEFAULT_TO_EMAIL` from it. `lib/waitlist.test.js` covers the shared module directly. diff --git a/e2e/live-demo.e2e.js b/e2e/live-demo.e2e.js index df58033..b5d8115 100644 --- a/e2e/live-demo.e2e.js +++ b/e2e/live-demo.e2e.js @@ -44,4 +44,28 @@ test.describe('Lumen live demo', () => { await expect(demo.getByText(/why lumen noticed/i)).toBeVisible(); }); + + test('opens full-screen app mode and plans a device', async ({ page }) => { + await page.goto('/'); + await page.getByRole('button', { name: /open the app full screen/i }).click(); + + const app = page.locator('.app-fullscreen'); + await expect(app).toBeVisible(); + + // Rooms → Office (empty) → add a planned device. + await app.getByRole('button', { name: /rooms tab/i }).click(); + await app.getByRole('button', { name: /office/i }).first().click(); + await app.getByRole('button', { name: /add a device/i }).click(); + await app.locator('.add-device-input').fill('Reading Lamp'); + const addButton = app.getByRole('button', { name: /^add device$/i }); + await expect(addButton).toBeEnabled(); + await addButton.click(); + + await expect(app.getByRole('button', { name: /reading lamp/i })).toBeVisible(); + await expect(app.locator('.planned-dot')).toBeVisible(); + + // Close returns to the marketing page. + await app.getByRole('button', { name: /close app preview/i }).click(); + await expect(page.locator('.app-fullscreen')).toHaveCount(0); + }); }); diff --git a/src/App.jsx b/src/App.jsx index c9e86bf..8583402 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -3,7 +3,7 @@ import { Activity, Apple, ArrowRight, Blinds, Check, DoorClosed, Droplets, ExternalLink, Home, Lightbulb, Lock, Mail, Menu, Moon, Sparkle, Sun, SunMedium, Thermometer, X, Zap, } from 'lucide-react'; -import { PhoneProvider, InteractivePhone, usePhone } from './InteractivePhone.jsx'; +import { PhoneProvider, InteractivePhone, FullscreenApp, usePhone } from './InteractivePhone.jsx'; import { submitWaitlist } from './waitlistSubmit.js'; import { trackWaitlistEvent } from './waitlistAnalytics.js'; import { TESTFLIGHT_URL } from './siteConfig.js'; @@ -380,6 +380,7 @@ function Waitlist() { function SiteShell() { const [menuOpen, setMenuOpen] = useState(false); + const [appOpen, setAppOpen] = useState(false); const { theme, toggleTheme } = useSiteTheme(); const close = () => setMenuOpen(false); const ambient = useAmbientController(AMBIENT_IDLE); @@ -448,6 +449,9 @@ function SiteShell() {
+
@@ -470,6 +474,8 @@ function SiteShell() { + setAppOpen(false)} /> +