From 71d3d6c0b77b3ee72e4c8380439516ff50943f5b Mon Sep 17 00:00:00 2001 From: hqhq1025 <1506751656@qq.com> Date: Sun, 19 Apr 2026 12:40:48 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui):=20PhoneFrame=20v2=20=E2=80=94=20realis?= =?UTF-8?q?tic=20body=20color=20+=20dynamic=20island=20+=20thinner=20bezel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User feedback on #76: sizing was correct but the device still looked melted into the cream canvas, the bezel was thick enough to clip iframe content, and the notch read as a bump rather than a real iPhone cut-out. Changes: - New tokens (--color-phone-body / --color-phone-island / --border-width-phone-bezel / --size-preview-mobile-island-{w,h}), light + dark variants - Single-layer deep space-gray body (no more outer cream + inner black double bezel) with a 3px bezel so artifact corners are not eaten - Centered Dynamic Island pill replaces the notch - Hide the click-to-comment hint pill in mobile viewport — it was overlapping the device chrome and the user has already opted into the small frame - Test contract updated: asserts body uses dedicated phone token (not --color-surface / --color-background) and exposes a stable test id + island token names --- .../src/components/PhoneFrame.test.ts | 24 ++++-- .../renderer/src/components/PhoneFrame.tsx | 82 +++++++++++-------- .../renderer/src/components/PreviewPane.tsx | 1 - packages/ui/src/tokens.css | 14 +++- 4 files changed, 77 insertions(+), 44 deletions(-) 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 (
- {/* Notch */} -