diff --git a/apps/desktop/src/renderer/src/components/PhoneFrame.test.ts b/apps/desktop/src/renderer/src/components/PhoneFrame.test.ts index 56f68268..e4955a28 100644 --- a/apps/desktop/src/renderer/src/components/PhoneFrame.test.ts +++ b/apps/desktop/src/renderer/src/components/PhoneFrame.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { PHONE_FRAME_SIZING } from './PhoneFrame'; +import { PHONE_FRAME_SIZING, PHONE_FRAME_TEST_IDS } from './PhoneFrame'; describe('PhoneFrame sizing contract', () => { it('uses iPhone-reference 375x812 screen dimensions', () => { @@ -7,14 +7,28 @@ describe('PhoneFrame sizing contract', () => { expect(PHONE_FRAME_SIZING.expectedScreenHeightPx).toBe(812); }); - it('keeps total frame size near iPhone 396x844 (within 8px bezel)', () => { - expect(PHONE_FRAME_SIZING.expectedFrameWidthPx).toBe(391); - expect(PHONE_FRAME_SIZING.expectedFrameHeightPx).toBe(828); + it('uses a thin bezel so artifacts are not visually clipped', () => { + expect(PHONE_FRAME_SIZING.expectedBezelWidthPx).toBe(3); + expect(PHONE_FRAME_SIZING.expectedFrameWidthPx).toBe(381); + expect(PHONE_FRAME_SIZING.expectedFrameHeightPx).toBe(818); }); it('references shared design tokens, not hard-coded pixels', () => { expect(PHONE_FRAME_SIZING.screenWidthVar).toBe('--size-preview-mobile-width'); expect(PHONE_FRAME_SIZING.screenHeightVar).toBe('--size-preview-mobile-height'); - expect(PHONE_FRAME_SIZING.borderWidthVar).toBe('--border-width-strong'); + expect(PHONE_FRAME_SIZING.bezelWidthVar).toBe('--border-width-phone-bezel'); + }); + + it('paints the body with a dedicated phone-body token, not the app surface', () => { + expect(PHONE_FRAME_SIZING.bodyColorVar).toBe('--color-phone-body'); + expect(PHONE_FRAME_SIZING.bodyColorVar).not.toBe('--color-surface'); + expect(PHONE_FRAME_SIZING.bodyColorVar).not.toBe('--color-background'); + }); + + it('exposes a dynamic island element via tokens and a stable test id', () => { + expect(PHONE_FRAME_SIZING.islandWidthVar).toBe('--size-preview-mobile-island-width'); + expect(PHONE_FRAME_SIZING.islandHeightVar).toBe('--size-preview-mobile-island-height'); + expect(PHONE_FRAME_SIZING.islandColorVar).toBe('--color-phone-island'); + expect(PHONE_FRAME_TEST_IDS.dynamicIsland).toBe('phone-frame-dynamic-island'); }); }); diff --git a/apps/desktop/src/renderer/src/components/PhoneFrame.tsx b/apps/desktop/src/renderer/src/components/PhoneFrame.tsx index 6d1f770b..b1f2825f 100644 --- a/apps/desktop/src/renderer/src/components/PhoneFrame.tsx +++ b/apps/desktop/src/renderer/src/components/PhoneFrame.tsx @@ -6,66 +6,58 @@ interface PhoneFrameProps { /** * Pure-data sizing contract for the iPhone-style bezel. Exported so unit - * tests can verify the frame stays at iPhone-reference dimensions without - * needing a DOM environment. + * tests can verify the frame stays at iPhone-reference dimensions and + * uses the correct design tokens without needing a DOM environment. */ export const PHONE_FRAME_SIZING = { screenWidthVar: '--size-preview-mobile-width', screenHeightVar: '--size-preview-mobile-height', - borderWidthVar: '--border-width-strong', + bezelWidthVar: '--border-width-phone-bezel', + bodyColorVar: '--color-phone-body', + islandColorVar: '--color-phone-island', + islandWidthVar: '--size-preview-mobile-island-width', + islandHeightVar: '--size-preview-mobile-island-height', expectedScreenWidthPx: 375, expectedScreenHeightPx: 812, - expectedBorderWidthPx: 8, + expectedBezelWidthPx: 3, get expectedFrameWidthPx(): number { - return this.expectedScreenWidthPx + this.expectedBorderWidthPx * 2; + return this.expectedScreenWidthPx + this.expectedBezelWidthPx * 2; }, get expectedFrameHeightPx(): number { - return this.expectedScreenHeightPx + this.expectedBorderWidthPx * 2; + return this.expectedScreenHeightPx + this.expectedBezelWidthPx * 2; }, } as const; +export const PHONE_FRAME_TEST_IDS = { + body: 'phone-frame-body', + dynamicIsland: 'phone-frame-dynamic-island', +} as const; + /** - * Renders an iPhone-style bezel around its child iframe. - * All measurements are derived from design tokens (CSS custom properties). - * No px or color hard-codes — see packages/ui/src/tokens.css. + * Renders an iPhone-style device shell around its child iframe. * - * The screen area has fixed pixel dimensions; child iframes should fill - * 100% of that area (do not set their own pixel width/height). + * Single-layer body in deep space-gray (intentionally distinct from the + * cream app background so the device reads as a physical object), a thin + * bezel, and a centered Dynamic Island. The screen rounds inward by + * (radius-phone − bezel) so artifact content isn't clipped at the corners. */ export function PhoneFrame({ children }: PhoneFrameProps): ReactElement { return (