Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
24 changes: 24 additions & 0 deletions e2e/live-demo.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
8 changes: 7 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -448,6 +449,9 @@ function SiteShell() {
</div>
<div className="hero-glow" />
<InteractivePhone />
<button type="button" className="demo-openapp" onClick={() => setAppOpen(true)}>
Open the app full screen <ArrowRight size={14} />
</button>
<GuidedDemoSteps />
</div>
</div>
Expand All @@ -470,6 +474,8 @@ function SiteShell() {

<MobileDemoFAB />

<FullscreenApp open={appOpen} onClose={() => setAppOpen(false)} />

<footer className="site-footer">
<a className="logo" href="#top">
<SunMedium size={17} /><span>LUMEN</span>
Expand Down
Loading
Loading