diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index 96915388b..8c3cc89da 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -193,6 +193,14 @@ const desktopShellNavigationSource = fs.readFileSync( desktopShellNavigationPath, 'utf8', ); +const desktopShellLifecyclePath = new URL( + '../src-tauri/src/shell/lifecycle.rs', + import.meta.url, +); +const desktopShellLifecycleSource = fs.readFileSync( + desktopShellLifecyclePath, + 'utf8', +); const stageReleaseBinaryPath = new URL( '../scripts/stage-release-binary.mjs', import.meta.url, @@ -2595,6 +2603,7 @@ const requiredRustHostSnippets = [ 'resolve_desktop_lifecycle_window_flag', 'emit_current_desktop_lifecycle_event', 'desktop_lifecycle_window_flag_logs_failure_and_uses_default', + 'desktop_lifecycle_window_flag_logs_stable_label_only', '"background"', '"hidden"', '"minimized"', @@ -2619,6 +2628,7 @@ const requiredRustHostSnippets = [ 'PageLoadEvent::Finished', 'replay_desktop_webview_state', 'log_desktop_host_event_result', + 'desktop_host_event_result_logs_stable_label_only', 'resolve_desktop_lifecycle_window_flag("visible",', 'resolve_desktop_lifecycle_window_flag("minimized",', 'resolve_desktop_lifecycle_window_flag(\n "focused",', @@ -3347,3 +3357,14 @@ if ( ) { throw new Error('desktop deep-link diagnostics must not log plugin error details'); } + +if ( + desktopShellLifecycleSource.includes( + 'desktop host event failed for app.lifecycle.{label}: {error}', + ) || + desktopShellLifecycleSource.includes( + 'desktop host event failed for {label}: {error}', + ) +) { + throw new Error('desktop lifecycle diagnostics must not log Tauri error details'); +} diff --git a/apps/desktop-shell/src-tauri/src/shell/lifecycle.rs b/apps/desktop-shell/src-tauri/src/shell/lifecycle.rs index 0075311cc..5932e1028 100644 --- a/apps/desktop-shell/src-tauri/src/shell/lifecycle.rs +++ b/apps/desktop-shell/src-tauri/src/shell/lifecycle.rs @@ -80,8 +80,8 @@ fn resolve_desktop_lifecycle_window_flag( ) -> bool { match result { Ok(value) => value, - Err(error) => { - eprintln!("desktop host event failed for app.lifecycle.{label}: {error}"); + Err(_error) => { + eprintln!("desktop host event failed for app.lifecycle.{label}"); default_value } } @@ -125,8 +125,8 @@ pub(crate) fn log_desktop_host_event_result( ) -> bool { match result { Ok(()) => true, - Err(error) => { - eprintln!("desktop host event failed for {label}: {error}"); + Err(_error) => { + eprintln!("desktop host event failed for {label}"); false } } @@ -166,6 +166,16 @@ mod tests { )); } + #[test] + fn desktop_host_event_result_logs_stable_label_only() { + assert!(!log_desktop_host_event_result( + "navigation.canGoBack", + Err(tauri::Error::AssetNotFound( + "navigation?token=private".to_string() + )) + )); + } + #[test] fn desktop_lifecycle_window_flag_logs_failure_and_uses_default() { assert!(resolve_desktop_lifecycle_window_flag( @@ -180,6 +190,17 @@ mod tests { )); } + #[test] + fn desktop_lifecycle_window_flag_logs_stable_label_only() { + assert!(!resolve_desktop_lifecycle_window_flag( + "focused", + Err(tauri::Error::AssetNotFound( + "window?token=private".to_string() + )), + false + )); + } + #[test] fn desktop_lifecycle_payload_maps_hidden_and_minimized_to_background() { assert_eq!( diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 8724e0803..e96567ca1 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -2976,6 +2976,7 @@ - 背景:Tauri 桌面壳 `app.lifecycle` 事件会驱动 H5 游戏循环、背景音乐和固定玩法音频暂停 / 恢复;如果 `is_visible()`、`is_minimized()` 或 `is_focused()` 读取失败后静默使用默认值,生命周期状态可能错误且难以排查。 - 决策:`emit_current_desktop_lifecycle_event(...)` 必须通过 `resolve_desktop_lifecycle_window_flag(...)` 读取窗口可见、最小化和焦点状态;读取失败时记录 `desktop host event failed for app.lifecycle.`,再使用保守默认值。桌面壳配置检查拒绝重新出现 `window.is_visible().unwrap_or(...)`、`window.is_minimized().unwrap_or(...)` 或 `window.is_focused().unwrap_or(...)`。 +- 2026-06-21 调整:生命周期窗口状态读取失败和 `app.lifecycle` / `navigation.canGoBack` 等桌面宿主事件注入失败只记录固定阶段标签,不把 Tauri 错误详情写入可分发桌面壳 stderr;配置检查拒绝生命周期日志重新输出 `: {error}` 详情。 - 影响范围:`apps/desktop-shell/src-tauri/src/shell/lifecycle.rs`、`apps/desktop-shell/scripts/check-config.mjs`。 - 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml shell::lifecycle`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。 diff --git a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md index d072f5ec1..faddd4b44 100644 --- a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md +++ b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md @@ -443,7 +443,7 @@ GameBridge 禁止: 2026-06-18 追加:H5 账号状态刷新开始消费 `app.reloadWebView`。用户登录成功、退出登录、其它身份边界变化或登录状态异常页点击重新尝试时,`AuthGate` 会优先请求 Tauri 主 WebViewWindow 刷新;宿主未声明或刷新失败时再回退浏览器刷新,Tauri 仍只暴露 `host_bridge_request` 这一受控命令入口。 -2026-06-18 追加:桌面壳 WebView 每次页面加载完成后都会回放当前 `app.lifecycle`,覆盖托盘刷新、`app.reloadWebView` 和 H5 自刷新后的新 JS 上下文。回放会读取主窗口当前 `is_visible()`、`is_minimized()` 和 `is_focused()`,隐藏或最小化状态下不得误报 active;生命周期事件仍只通过 HostBridge message 注入,不开放 Tauri event 插件或额外 command。2026-06-20 调整:桌面壳不再声明 `network.statusChanged`,直到网络变化事件能由 Rust 侧真实网络源统一驱动;H5 仍可按 `network.status` 主动查询桌面网络可达性。 +2026-06-18 追加:桌面壳 WebView 每次页面加载完成后都会回放当前 `app.lifecycle`,覆盖托盘刷新、`app.reloadWebView` 和 H5 自刷新后的新 JS 上下文。回放会读取主窗口当前 `is_visible()`、`is_minimized()` 和 `is_focused()`,隐藏或最小化状态下不得误报 active;生命周期事件仍只通过 HostBridge message 注入,不开放 Tauri event 插件或额外 command。2026-06-20 调整:桌面壳不再声明 `network.statusChanged`,直到网络变化事件能由 Rust 侧真实网络源统一驱动;H5 仍可按 `network.status` 主动查询桌面网络可达性。2026-06-21 调整:生命周期窗口状态读取失败和生命周期 / 返回栈事件注入失败只记录 `desktop host event failed for app.lifecycle.` 或对应事件固定标签,不把 Tauri 错误详情写入可分发桌面壳 stderr。 2026-06-18 追加:桌面壳启用 Tauri 真实系统托盘,并复用品牌图标。托盘菜单只提供宿主壳级动作:显示主窗口、刷新主窗口和退出应用;左键点击托盘图标恢复并聚焦主窗口。该能力不进入 HostBridge capability 清单,不向 H5 暴露托盘 API、菜单 API、shell API 或任意窗口控制;如果当前桌面环境无法注册托盘,壳会继续启动主窗口。托盘注册成功时,用户点击主窗口关闭按钮会先通过 `app.lifecycle` 注入 `background` / `hidden`,再隐藏到托盘,必须通过托盘“退出”动作结束应用;托盘恢复、应用菜单显示、单实例唤醒和 deep link 唤醒都会重新显示、取消最小化、聚焦主窗口并回放当前生命周期状态。托盘注册失败时不拦截关闭,避免窗口消失后没有恢复入口;托盘显示和刷新主窗口动作失败必须记录桌面宿主事件日志,不能静默吞错。