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)} /> +