diff --git a/README.md b/README.md index 024eca9..6bfc6bc 100644 --- a/README.md +++ b/README.md @@ -301,6 +301,7 @@ The browser bootstrap includes contacts, calls, messages, mail, invoices, transa System overlays are available through dedicated preview parameters: +- Lock screen: `http://localhost:5174/?apiPort=3002&lockScreenPreview=1` - SIM picker: `http://localhost:5174/?apiPort=3002&simPickerPreview=1` - Payphone: `http://localhost:5174/?apiPort=3002&payphonePreview=1` (dial `5551110001` for a connected call or `5550000000` for a busy line) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index d7f4f4d..348d9f1 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -244,6 +244,9 @@ const PHONE_PORTRAIT_WIDTH = 390 const PHONE_PORTRAIT_HEIGHT = 844 const MIN_PRODUCTION_PHONE_ZOOM = 260 / PHONE_PORTRAIT_WIDTH const isDevelopment = import.meta.env.DEV +const developmentParameters = new URLSearchParams(window.location.search) +const developmentLockScreenPreview = + isDevelopment && developmentParameters.has('lockScreenPreview') const phone = usePhoneStore() const account = useAccountStore() @@ -1177,7 +1180,6 @@ onMounted(() => { } }, 1000) if (isDevelopment) { - const developmentParameters = new URLSearchParams(window.location.search) void hydrateDevelopmentPhone() if (developmentParameters.has('simPickerPreview')) { simPicker.value = { @@ -1251,7 +1253,7 @@ watch( } return } - isLocked.value = true + isLocked.value = !isDevelopment || developmentLockScreenPreview unlockedServicesLoaded.value = false controlCenterOpened.value = false weather.start() @@ -1268,7 +1270,8 @@ watch( startPasscodeLock(passcodeRetrySeconds.value) } phone.setLaunchOrigin(null) - void router.replace('/') + if (isLocked.value) void router.replace('/') + else loadUnlockedPhoneData() }, ) diff --git a/frontend/src/AppDevelopmentPreview.contract.test.ts b/frontend/src/AppDevelopmentPreview.contract.test.ts new file mode 100644 index 0000000..c37a00b --- /dev/null +++ b/frontend/src/AppDevelopmentPreview.contract.test.ts @@ -0,0 +1,21 @@ +import { readFileSync } from 'node:fs' + +import { describe, expect, it } from 'vitest' + +const source = readFileSync(new URL('./App.vue', import.meta.url), 'utf8') + +describe('browser development preview contract', () => { + it('starts unlocked while preserving an explicit lock screen preview', () => { + expect(source).toContain( + "developmentParameters.has('lockScreenPreview')", + ) + expect(source).toContain( + 'isLocked.value = !isDevelopment || developmentLockScreenPreview', + ) + }) + + it('loads authenticated app data without replacing direct app routes', () => { + expect(source).toContain("if (isLocked.value) void router.replace('/')") + expect(source).toContain('else loadUnlockedPhoneData()') + }) +})