From b921c46afe3f393e8fbb1c931e3b7a8950ac8bed Mon Sep 17 00:00:00 2001 From: kdletters Date: Sat, 20 Jun 2026 09:27:41 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=81=E5=AE=9A=E7=A7=BB=E5=8A=A8=E5=A3=B3?= =?UTF-8?q?=E5=AE=89=E5=85=A8=E5=8C=BA=E5=9B=9B=E8=BE=B9=E8=BE=B9=E7=95=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移动壳 safeArea 单测补齐四边固定覆盖断言 移动壳配置门禁反查安全区测试边界 宿主壳方案和共享决策同步安全区验收口径 --- apps/mobile-shell/scripts/check-config.mjs | 17 +++++++++++++++++ apps/mobile-shell/src/shell/safeArea.test.ts | 10 ++++++++++ .../shared-memory/decision-log.md | 2 +- ...„】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md | 2 +- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index f95524bc2..1ef235446 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -145,6 +145,8 @@ const loadFailurePath = new URL('../src/shell/loadFailure.ts', import.meta.url); const loadFailureSource = fs.readFileSync(loadFailurePath, 'utf8'); const runtimePath = new URL('../src/shell/runtime.ts', import.meta.url); const runtimeSource = fs.readFileSync(runtimePath, 'utf8'); +const safeAreaTestPath = new URL('../src/shell/safeArea.test.ts', import.meta.url); +const safeAreaTestSource = fs.readFileSync(safeAreaTestPath, 'utf8'); const sharedContractPath = new URL( '../../../packages/shared/src/contracts/hostBridge.ts', import.meta.url, @@ -1375,6 +1377,21 @@ for (const snippet of [ } } +for (const snippet of [ + "describe('mobile shell safe area'", + "test('protects the WebView from every device edge'", + "test('keeps the edge list fixed for shell layout usage'", + 'expect(MOBILE_SHELL_SAFE_AREA_EDGES).toHaveLength(4)', + "'bottom',", + "'left',", + "'right',", + "'top',", +]) { + if (!safeAreaTestSource.includes(snippet)) { + throw new Error(`mobile shell safe-area tests missing ${snippet}`); + } +} + for (const snippet of [ 'type HostBridgeEventName', 'isHostBridgeEventName', diff --git a/apps/mobile-shell/src/shell/safeArea.test.ts b/apps/mobile-shell/src/shell/safeArea.test.ts index 09c70c82e..ba3a55ebc 100644 --- a/apps/mobile-shell/src/shell/safeArea.test.ts +++ b/apps/mobile-shell/src/shell/safeArea.test.ts @@ -11,4 +11,14 @@ describe('mobile shell safe area', () => { 'left', ]); }); + + test('keeps the edge list fixed for shell layout usage', () => { + expect(MOBILE_SHELL_SAFE_AREA_EDGES).toHaveLength(4); + expect([...MOBILE_SHELL_SAFE_AREA_EDGES].sort()).toEqual([ + 'bottom', + 'left', + 'right', + 'top', + ]); + }); }); diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index a337a51db..cce212641 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -85,7 +85,7 @@ - 2026-06-19 移动二维码扫描:Expo 壳新增 `scanner.scanQrCode` HostBridge capability,通过 `expo-camera` 请求相机权限并打开真实扫码 overlay,成功只返回共享契约清洗后的二维码文本和 `qr_code` 格式,空值、控制字符和超长文本按 `normalizeHostBridgeQrCodeValue` 处理;HostBridge 成功响应包装统一收口在 `apps/mobile-shell/src/host-bridge/scanner.ts`,`dispatch.ts` 只委托扫码请求。用户关闭扫码返回 `cancelled`,H5 个人中心扫码入口不再连带弹出浏览器摄像头权限。Tauri 桌面壳只把 `scanner.scanQrCode` 保留在 method 白名单中返回 `unsupported_method`,不声明 capability、不伪造桌面扫码;宿主缺能力或非法结果时 H5 继续走原浏览器扫码 fallback。 - 2026-06-20 移动壳扫码单测边界:`apps/mobile-shell/src/host-bridge/scanner.test.ts` 直接覆盖扫码 helper 的订阅状态初始值、进行中 requestKey、并发扫码拒绝、非法完成不清 pending、成功完成的二维码值清洗、用户取消 `cancelled`、宿主失败 `host_error`、无 pending 时的空操作和 HostBridge 成功响应包装;根级 `npm run check:native-shells` 会把该测试文件列入移动桥接层结构清单,避免扫码状态机只靠完整 bridge 流程或 overlay 测试间接覆盖。 - 2026-06-18 H5 图片上传接入宿主导入:`CreativeImageInputPanel` 在 `native_app` 且声明 `file.importImage` / `file.captureImage` 时,主图上传和描述参考图上传可分别调用 `importHostImageFile()` / `captureHostImageFile()`,并把宿主返回的 base64 图片转换为现有 `File` 回调;浏览器、小程序和未声明能力的裁剪壳继续走原生 `` 路径,不新增玩法侧上传分叉。 -- 2026-06-18 移动壳安全区:Expo 壳根布局使用 `react-native-safe-area-context` 的 `SafeAreaProvider` 与四边 `SafeAreaView` 保护 WebView,避免 H5 主站内容贴进 iOS 刘海、底部 Home Indicator、Android 状态栏或横屏边缘;该能力属于宿主壳布局保护,不新增 H5 占位 UI,不改变玩法 runtime 或 HostBridge capability。 +- 2026-06-18 移动壳安全区:Expo 壳根布局使用 `react-native-safe-area-context` 的 `SafeAreaProvider` 与四边 `SafeAreaView` 保护 WebView,避免 H5 主站内容贴进 iOS 刘海、底部 Home Indicator、Android 状态栏或横屏边缘;该能力属于宿主壳布局保护,不新增 H5 占位 UI,不改变玩法 runtime 或 HostBridge capability。`safeArea.test.ts` 必须证明 top / right / bottom / left 四边固定覆盖,移动壳配置检查会反查该测试边界。 - 2026-06-18 移动壳方向策略:Expo 壳 `orientation` 固定为 `default`,不锁竖屏或横屏;后续固定玩法和 AI H5 sandbox 的方向需求由设备方向、H5 响应式布局和玩法自身画布适配承接,壳层只负责安全区、WebView 容器和 HostBridge。移动壳配置检查和 Expo public config smoke 会拒绝重新锁定 portrait / landscape。 - 2026-06-18 移动壳键盘布局:Expo Android 壳 `softwareKeyboardLayoutMode` 固定为 `resize`,让系统键盘打开时真实调整 WebView 可视高度;H5 继续使用已有 viewport / 输入法聚焦适配承接创作表单、聊天输入和玩法输入框,壳层不新增键盘遮挡补偿 UI、不伪造键盘状态。移动壳配置检查和 Expo public config smoke 会拒绝该字段缺失或漂移。 - 2026-06-18 移动壳媒体策略:Expo WebView 允许内联媒体播放和用户触发的全屏视频,但保留 `mediaPlaybackRequiresUserAction`,不允许无手势自动播放;固定玩法和 AI H5 sandbox 的音频仍由 H5 用户开关、运行态状态和宿主生命周期控制,壳层不注入额外播放器或假播放状态。移动壳配置检查会拒绝 WebView 媒体策略漂移。 diff --git a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md index 8a93541b8..9250c7ff7 100644 --- a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md +++ b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md @@ -348,7 +348,7 @@ GameBridge 禁止: 2026-06-18 追加:移动壳 WebView 每次同源主站页面成功加载后都会补发当前 `app.lifecycle` 与 `network.statusChanged` 状态,覆盖首载、`app.reloadWebView`、进程恢复 reload 和 H5 自身刷新后的新 JS 上下文。补发仍只使用 HostBridge event,不新增 capability,不向 `about:blank`、外域或错误页注入宿主状态。 -2026-06-18 追加:移动壳根布局接入 `react-native-safe-area-context`,用 `SafeAreaProvider` 和四边 `SafeAreaView` 承接 iOS 刘海、底部 Home Indicator、Android 状态栏和横屏边缘安全区;WebView 仍加载同一 H5 主站,不改变 H5 路由、玩法 runtime 或 HostBridge capability。该能力属于宿主壳布局保护,不在 H5 内补额外占位 UI。 +2026-06-18 追加:移动壳根布局接入 `react-native-safe-area-context`,用 `SafeAreaProvider` 和四边 `SafeAreaView` 承接 iOS 刘海、底部 Home Indicator、Android 状态栏和横屏边缘安全区;WebView 仍加载同一 H5 主站,不改变 H5 路由、玩法 runtime 或 HostBridge capability。该能力属于宿主壳布局保护,不在 H5 内补额外占位 UI。移动壳 `safeArea.test.ts` 必须证明 `MOBILE_SHELL_SAFE_AREA_EDGES` 固定覆盖 top / right / bottom / left 四边,配置检查会反查该测试,避免后续只保护竖屏常见边缘。 2026-06-18 追加:移动壳不锁定竖屏,Expo `orientation` 固定为 `default`,由 iOS / Android 设备方向和 H5 响应式布局共同承接竖屏、横屏与后续横屏玩法;壳层只负责安全区、WebView 容器和 HostBridge,不为某一类玩法硬编码方向。`npm run mobile-shell:typecheck` 与 `npm run mobile-shell:config` 会拒绝重新锁回 portrait / landscape。