diff --git a/apps/desktop-shell/package.json b/apps/desktop-shell/package.json index 8d38f6e25..a1d579fbb 100644 --- a/apps/desktop-shell/package.json +++ b/apps/desktop-shell/package.json @@ -4,7 +4,7 @@ "version": "0.1.0", "type": "module", "scripts": { - "dev": "tauri dev", + "dev": "WEB_PORT=3000 tauri dev", "build": "tauri build", "typecheck": "node scripts/check-config.mjs" }, diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index f7b564c5f..38a157e3a 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -629,7 +629,7 @@ assertNoBlockedDesktopSdkSnippets(); assertDesktopSourceLayout(); for (const [scriptName, expected] of Object.entries({ - dev: 'tauri dev', + dev: 'WEB_PORT=3000 tauri dev', build: 'tauri build', typecheck: 'node scripts/check-config.mjs', })) { @@ -2091,6 +2091,15 @@ if (config.build?.beforeDevCommand !== 'npm --prefix ../.. run dev:web') { throw new Error('desktop shell dev command must run the root H5 Vite dev server'); } +if ( + packageConfig.scripts?.dev !== 'WEB_PORT=3000 tauri dev' || + config.build?.devUrl !== 'http://127.0.0.1:3000/' +) { + throw new Error( + 'desktop shell dev script must pin WEB_PORT=3000 to match Tauri devUrl', + ); +} + const [mainWindow] = config.app?.windows ?? []; if (!mainWindow || mainWindow.create !== false) { throw new Error('desktop shell must create the main window from Rust setup'); diff --git a/apps/mobile-shell/app.json b/apps/mobile-shell/app.json index 97638a3cb..d6f1b2631 100644 --- a/apps/mobile-shell/app.json +++ b/apps/mobile-shell/app.json @@ -98,6 +98,7 @@ "allowBackup": false, "softwareKeyboardLayoutMode": "resize", "permissions": [ + "android.permission.POST_NOTIFICATIONS", "android.permission.RECORD_AUDIO" ], "blockedPermissions": [ diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index 390510f8c..c543fa117 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -1312,10 +1312,11 @@ if (appConfig.android?.softwareKeyboardLayoutMode !== 'resize') { if ( !Array.isArray(appConfig.android?.permissions) || - appConfig.android.permissions.length !== 1 || - appConfig.android.permissions[0] !== 'android.permission.RECORD_AUDIO' + appConfig.android.permissions.length !== 2 || + appConfig.android.permissions[0] !== 'android.permission.POST_NOTIFICATIONS' || + appConfig.android.permissions[1] !== 'android.permission.RECORD_AUDIO' ) { - throw new Error('mobile shell Android package must request only RECORD_AUDIO for same-origin H5 microphone gameplay'); + throw new Error('mobile shell Android package must request only POST_NOTIFICATIONS and RECORD_AUDIO for real local notifications and same-origin H5 microphone gameplay'); } if (appConfig.android?.permissions?.includes('android.permission.CAMERA')) { throw new Error('mobile shell Android CAMERA permission must come from real camera plugins only'); @@ -1336,6 +1337,9 @@ for (const permission of blockedAndroidPermissions) { if (appConfig.android?.blockedPermissions?.includes('android.permission.RECORD_AUDIO')) { throw new Error('mobile shell Android package must not block RECORD_AUDIO needed by same-origin H5 microphone gameplay'); } +if (appConfig.android?.blockedPermissions?.includes('android.permission.POST_NOTIFICATIONS')) { + throw new Error('mobile shell Android package must not block POST_NOTIFICATIONS needed by local notification.showLocal delivery'); +} if ( appConfig.android?.adaptiveIcon?.foregroundImage !== './assets/icon.png' || diff --git a/apps/mobile-shell/scripts/check-expo-config.mjs b/apps/mobile-shell/scripts/check-expo-config.mjs index 5826b3deb..702d899e0 100644 --- a/apps/mobile-shell/scripts/check-expo-config.mjs +++ b/apps/mobile-shell/scripts/check-expo-config.mjs @@ -293,7 +293,11 @@ assertEqual( ); assertSameSet( expoConfig.android?.permissions ?? [], - ['android.permission.CAMERA', 'android.permission.RECORD_AUDIO'], + [ + 'android.permission.CAMERA', + 'android.permission.POST_NOTIFICATIONS', + 'android.permission.RECORD_AUDIO', + ], 'Android explicit permissions', ); if (appConfig.android?.permissions?.includes('android.permission.CAMERA')) { @@ -312,6 +316,9 @@ assertIncludes( if (expoConfig.android?.blockedPermissions?.includes('android.permission.RECORD_AUDIO')) { throw new Error('Expo config Android permissions must not block RECORD_AUDIO needed by same-origin H5 microphone gameplay'); } +if (expoConfig.android?.blockedPermissions?.includes('android.permission.POST_NOTIFICATIONS')) { + throw new Error('Expo config Android permissions must not block POST_NOTIFICATIONS needed by local notification.showLocal delivery'); +} assertIncludes( expoConfig.android?.blockedPermissions, 'android.permission.REQUEST_INSTALL_PACKAGES', diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 86bbe27a7..9f4bbf4d7 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -142,7 +142,7 @@ - 2026-06-18 H5 背景音乐接入宿主生命周期:`useBackgroundMusic` 通过 `useHostLifecycleActive()` 消费 `subscribeHostAppLifecycle()` 的归一结果,宿主进入后台、inactive 或桌面窗口失焦时降低音量并暂停音频循环,同时 `suspend` WebAudio context;回到 `active + focused` 且用户原本开启音乐时再恢复播放,不改变用户音量设置。 - 2026-06-18 固定玩法音频接入宿主生命周期:前端新增 `useHostLifecycleActive()` 统一消费 `subscribeHostAppLifecycle()`,`useBackgroundMusic`、拼图运行态和抓大鹅运行态都只依赖该归一状态判断音频可播放性;宿主 inactive、background 或窗口失焦时暂停 `