补齐桌面壳窗口状态失败观测
桌面外观角标和窗口标题记录系统异常日志 Rust 单测覆盖稳定错误和日志边界 配置门禁和文档同步窗口状态观测约束
This commit is contained in:
@@ -2613,18 +2613,23 @@ if (
|
||||
for (const snippet of [
|
||||
'desktop_appearance_color_scheme',
|
||||
'appearance_unavailable_response',
|
||||
'log_desktop_appearance_failure',
|
||||
'desktop appearance failed for {label}: {error}',
|
||||
'color_scheme_from_theme(theme)',
|
||||
'window.theme()',
|
||||
'log_desktop_appearance_failure("theme", &error.to_string())',
|
||||
'log_desktop_appearance_failure("window", "main window unavailable")',
|
||||
'"colorScheme"',
|
||||
'"appearance unavailable"',
|
||||
'appearance_unavailable_response_is_stable',
|
||||
'appearance_failures_are_logged_without_exposing_native_detail',
|
||||
]) {
|
||||
if (!desktopHostBridgeAppearanceSource.includes(snippet)) {
|
||||
throw new Error(`desktop shell appearance module is missing ${snippet}`);
|
||||
}
|
||||
}
|
||||
if (
|
||||
desktopHostBridgeAppearanceSource.includes('error.to_string()') ||
|
||||
desktopHostBridgeAppearanceSource.includes('failed(request.id.clone(), "host_error", error.to_string())') ||
|
||||
desktopHostBridgeAppearanceSource.includes('"main window not found"')
|
||||
) {
|
||||
throw new Error('desktop shell appearance module must hide native window errors');
|
||||
@@ -2645,17 +2650,22 @@ for (const snippet of [
|
||||
'fn badge_count_payload',
|
||||
'set_desktop_app_badge_count',
|
||||
'badge_unavailable_response',
|
||||
'log_desktop_badge_failure',
|
||||
'desktop app badge failed for {label}: {error}',
|
||||
'log_desktop_badge_failure("window", "main window unavailable")',
|
||||
'log_desktop_badge_failure("set", &error.to_string())',
|
||||
'ok(request.id.clone(), json!(true))',
|
||||
'"count must be an integer between 0 and 99999"',
|
||||
'"badge unavailable"',
|
||||
'badge_unavailable_response_is_stable',
|
||||
'badge_failures_are_logged_without_exposing_native_detail',
|
||||
]) {
|
||||
if (!desktopHostBridgeBadgeSource.includes(snippet)) {
|
||||
throw new Error(`desktop shell badge module is missing ${snippet}`);
|
||||
}
|
||||
}
|
||||
if (
|
||||
desktopHostBridgeBadgeSource.includes('error.to_string()') ||
|
||||
desktopHostBridgeBadgeSource.includes('failed(request.id.clone(), "host_error", error.to_string())') ||
|
||||
desktopHostBridgeBadgeSource.includes('"main window not found"')
|
||||
) {
|
||||
throw new Error('desktop shell badge module must hide native window errors');
|
||||
@@ -2678,20 +2688,25 @@ for (const snippet of [
|
||||
'normalize_window_title',
|
||||
'window_title_from_request',
|
||||
'window_title_unavailable_response',
|
||||
'log_desktop_window_title_failure',
|
||||
'desktop window title failed for {label}: {error}',
|
||||
'required_string_payload(request, "title")',
|
||||
'window.set_title(&title)',
|
||||
'log_desktop_window_title_failure("set", &error.to_string())',
|
||||
'log_desktop_window_title_failure("window", "main window unavailable")',
|
||||
'"title is required"',
|
||||
'"window title unavailable"',
|
||||
'window_title_request_rejects_missing_or_invalid_payload',
|
||||
'window_title_request_trims_and_truncates_shared_boundary',
|
||||
'window_title_unavailable_response_is_stable',
|
||||
'window_title_failures_are_logged_without_exposing_native_detail',
|
||||
]) {
|
||||
if (!desktopHostBridgeTitleSource.includes(snippet)) {
|
||||
throw new Error(`desktop shell title module is missing ${snippet}`);
|
||||
}
|
||||
}
|
||||
if (
|
||||
desktopHostBridgeTitleSource.includes('error.to_string()') ||
|
||||
desktopHostBridgeTitleSource.includes('failed(request.id.clone(), "host_error", error.to_string())') ||
|
||||
desktopHostBridgeTitleSource.includes('"main window not found"')
|
||||
) {
|
||||
throw new Error('desktop shell title module must hide native window errors');
|
||||
|
||||
@@ -10,9 +10,15 @@ pub(crate) fn desktop_appearance_color_scheme(
|
||||
match app.get_webview_window("main") {
|
||||
Some(window) => match window.theme() {
|
||||
Ok(theme) => ok_color_scheme(request, color_scheme_from_theme(theme)),
|
||||
Err(_) => appearance_unavailable_response(request),
|
||||
Err(error) => {
|
||||
log_desktop_appearance_failure("theme", &error.to_string());
|
||||
appearance_unavailable_response(request)
|
||||
}
|
||||
},
|
||||
None => appearance_unavailable_response(request),
|
||||
None => {
|
||||
log_desktop_appearance_failure("window", "main window unavailable");
|
||||
appearance_unavailable_response(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +35,14 @@ fn appearance_unavailable_response(request: &HostBridgeRequest) -> HostBridgeRes
|
||||
failed(request.id.clone(), "host_error", "appearance unavailable")
|
||||
}
|
||||
|
||||
fn log_desktop_appearance_failure(label: &str, error: &str) -> bool {
|
||||
if !error.is_empty() {
|
||||
eprintln!("desktop appearance failed for {label}: {error}");
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -56,4 +70,16 @@ mod tests {
|
||||
assert_eq!(error.code, "host_error");
|
||||
assert_eq!(error.message, "appearance unavailable");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appearance_failures_are_logged_without_exposing_native_detail() {
|
||||
assert!(!log_desktop_appearance_failure(
|
||||
"theme",
|
||||
"private appearance detail"
|
||||
));
|
||||
assert!(!log_desktop_appearance_failure(
|
||||
"window",
|
||||
"main window unavailable"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,12 +39,16 @@ pub(crate) fn set_desktop_app_badge_count(
|
||||
Err(response) => return response,
|
||||
};
|
||||
let Some(window) = app.get_webview_window("main") else {
|
||||
log_desktop_badge_failure("window", "main window unavailable");
|
||||
return badge_unavailable_response(request);
|
||||
};
|
||||
|
||||
match window.set_badge_count(count) {
|
||||
Ok(()) => ok(request.id.clone(), json!(true)),
|
||||
Err(_) => badge_unavailable_response(request),
|
||||
Err(error) => {
|
||||
log_desktop_badge_failure("set", &error.to_string());
|
||||
badge_unavailable_response(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +56,14 @@ fn badge_unavailable_response(request: &HostBridgeRequest) -> HostBridgeResponse
|
||||
failed(request.id.clone(), "host_error", "badge unavailable")
|
||||
}
|
||||
|
||||
fn log_desktop_badge_failure(label: &str, error: &str) -> bool {
|
||||
if !error.is_empty() {
|
||||
eprintln!("desktop app badge failed for {label}: {error}");
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -94,4 +106,16 @@ mod tests {
|
||||
assert_eq!(error.code, "host_error");
|
||||
assert_eq!(error.message, "badge unavailable");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn badge_failures_are_logged_without_exposing_native_detail() {
|
||||
assert!(!log_desktop_badge_failure(
|
||||
"set",
|
||||
"private badge detail"
|
||||
));
|
||||
assert!(!log_desktop_badge_failure(
|
||||
"window",
|
||||
"main window unavailable"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,9 +36,15 @@ pub(crate) fn set_desktop_host_bridge_window_title(
|
||||
match app.get_webview_window("main") {
|
||||
Some(window) => match window.set_title(&title) {
|
||||
Ok(()) => ok(request.id.clone(), json!(true)),
|
||||
Err(_) => window_title_unavailable_response(request),
|
||||
Err(error) => {
|
||||
log_desktop_window_title_failure("set", &error.to_string());
|
||||
window_title_unavailable_response(request)
|
||||
}
|
||||
},
|
||||
None => window_title_unavailable_response(request),
|
||||
None => {
|
||||
log_desktop_window_title_failure("window", "main window unavailable");
|
||||
window_title_unavailable_response(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +52,14 @@ fn window_title_unavailable_response(request: &HostBridgeRequest) -> HostBridgeR
|
||||
failed(request.id.clone(), "host_error", "window title unavailable")
|
||||
}
|
||||
|
||||
fn log_desktop_window_title_failure(label: &str, error: &str) -> bool {
|
||||
if !error.is_empty() {
|
||||
eprintln!("desktop window title failed for {label}: {error}");
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -112,4 +126,16 @@ mod tests {
|
||||
assert_eq!(error.code, "host_error");
|
||||
assert_eq!(error.message, "window title unavailable");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn window_title_failures_are_logged_without_exposing_native_detail() {
|
||||
assert!(!log_desktop_window_title_failure(
|
||||
"set",
|
||||
"private title detail"
|
||||
));
|
||||
assert!(!log_desktop_window_title_failure(
|
||||
"window",
|
||||
"main window unavailable"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3130,6 +3130,12 @@
|
||||
- 决策:`apps/desktop-shell/src-tauri/src/host_bridge/files.rs` 必须在导出路径转换、导出写入、导入路径转换和导入后台读取 join 失败时记录 `desktop file export failed for ...` 或 `desktop file import failed for ...` 日志。HostBridge 对 H5 仍只返回稳定错误语义,不透传本地路径、系统错误或线程细节;用户取消系统文件对话框仍返回 `cancelled`,不记录为异常。
|
||||
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::files`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-06-20 桌面壳窗口状态小能力系统异常必须可观测
|
||||
|
||||
- 背景:Tauri 桌面壳的外观、角标和窗口标题能力都依赖主窗口和平台系统 API;这些能力对 H5 必须保持稳定错误语义,但开发侧也需要知道是主窗口缺失、主题读取失败还是系统 API 调用失败。
|
||||
- 决策:`apps/desktop-shell/src-tauri/src/host_bridge/appearance.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/badge.rs` 和 `apps/desktop-shell/src-tauri/src/host_bridge/title.rs` 必须在主窗口缺失、主题读取失败、角标设置失败和窗口标题设置失败时分别记录 `desktop appearance failed for ...`、`desktop app badge failed for ...` 或 `desktop window title failed for ...` 日志。HostBridge 对 H5 仍只返回稳定 `appearance unavailable`、`badge unavailable` 或 `window title unavailable`,不透传系统错误、窗口内部信息或平台细节。
|
||||
- 验证方式:`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::appearance`、`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::badge`、`cargo test --manifest-path apps/desktop-shell/src-tauri/Cargo.toml host_bridge::title`、`npm run desktop-shell:typecheck`、`npm run check:native-shells`、`npm run check:encoding`、`git diff --check`。
|
||||
|
||||
## 2026-06-20 移动壳门禁脚本必须自登记自扫描
|
||||
|
||||
- 背景:Expo 移动壳单端检查已把 `apps/mobile-shell/scripts/` 纳入生产源码扫描入口,但 `check-config.mjs` 自身仍被排除在脚本清单和替身词扫描之外;这会让移动壳与桌面壳门禁结构不一致,也可能让后续门禁反查内容绕过生产替身词规则。
|
||||
|
||||
@@ -350,6 +350,8 @@ GameBridge 禁止:
|
||||
|
||||
2026-06-20 追加:桌面壳文件导入导出的系统异常必须可观测。Tauri dialog 路径转换失败、后台文件读写任务失败或任务 join 失败时,桌面壳必须分别记录 `desktop file export failed for ...` 或 `desktop file import failed for ...` 日志;HostBridge 回包仍只暴露稳定 `file export unavailable` 或 `file import unavailable` 语义,不透传本地路径、系统错误或线程细节。用户取消系统文件对话框仍返回 `cancelled`,不记录为异常。
|
||||
|
||||
2026-06-20 追加:桌面壳窗口状态小能力的系统异常必须可观测。Tauri 主窗口缺失、主题读取失败、角标设置失败或窗口标题设置失败时,桌面壳必须分别记录 `desktop appearance failed for ...`、`desktop app badge failed for ...` 或 `desktop window title failed for ...` 日志;HostBridge 回包仍只暴露稳定 `appearance unavailable`、`badge unavailable` 或 `window title unavailable` 语义,不透传系统错误、窗口内部信息或平台细节。
|
||||
|
||||
2026-06-18 追加:H5 账号状态刷新开始消费 `app.reloadWebView`。用户登录成功、退出登录、其它身份边界变化或登录状态异常页点击重新尝试时,`AuthGate` 会优先请求 Expo 壳刷新当前 WebView;宿主未声明或刷新失败时再回退浏览器刷新,避免在移动壳内绕过受控容器刷新入口。
|
||||
|
||||
2026-06-18 追加:移动壳 WebView 内容 / 渲染进程终止时复用同一受控刷新路径。iOS `onContentProcessDidTerminate` 和 Android `onRenderProcessGone` 只调用当前 `react-native-webview` 的 `reload()`,不改写 H5 URL、不注入额外脚本、不新增宿主恢复页面,避免系统回收 WebView 进程后留下空白容器。
|
||||
|
||||
Reference in New Issue
Block a user