Repository navigation
fix(wework): recover frozen webview after lock screen - #2216
Conversation
📝 WalkthroughWalkthroughThe desktop app adds a frontend resume probe and Tauri recovery bridge. After prolonged window unfocus or application resume, the backend checks frontend rendering and recreates the main window on timeout while preserving its placement and keeping task execution active. ChangesFrontend recovery bridge
Native recovery lifecycle
Lifecycle wiring and documentation
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant DesktopLifecycle
participant FrontendBridge
participant TauriCommands
participant MainWindow
DesktopLifecycle->>FrontendBridge: schedule frontend resume probe
FrontendBridge->>TauriCommands: acknowledge probe after rendering frames
TauriCommands-->>DesktopLifecycle: return acknowledged probe id
DesktopLifecycle->>MainWindow: recreate window if acknowledgement times out
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
wework/src-tauri/src/lib.rs (1)
2535-2549: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueIgnored
run_on_main_threaderrors can latchfrontend_probe_in_flight. Both worker threads discard the dispatch result withlet _; if dispatch fails, the closure that resetsfrontend_probe_in_flightnever runs and all future probes are skipped for the process lifetime.
wework/src-tauri/src/lib.rs#L2535-L2549: onErrfromrun_on_main_thread, resetfrontend_probe_in_flighttofalse(and log) instead of dropping the result.wework/src-tauri/src/lib.rs#L2492-L2505: apply the same fallback reset in the recreation thread.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wework/src-tauri/src/lib.rs` around lines 2535 - 2549, The two worker threads must handle failed run_on_main_thread dispatches so frontend_probe_in_flight cannot remain latched. In wework/src-tauri/src/lib.rs:2535-2549, update the probe thread around run_on_main_thread to reset frontend_probe_in_flight to false and log the failure when dispatch returns Err; apply the same fallback in the recreation thread at wework/src-tauri/src/lib.rs:2492-2505, while preserving the existing successful closure behavior.wework/src/lib/frontendRecovery.ts (1)
14-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider an install-once guard for consistency.
installAppLogging/installDebugPanelLogCaptureboth guard with aninstalledflag; a secondinstallFrontendRecoveryBridge()call would re-register the bridge and re-invoke the native command. Cheap to align.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@wework/src/lib/frontendRecovery.ts` around lines 14 - 30, Add an install-once guard to installFrontendRecoveryBridge so repeated calls return without replacing the probe handler or re-invoking REGISTER_FRONTEND_RECOVERY_COMMAND. Set the guard only when installation proceeds, while preserving the existing isTauriRuntime check and recovery behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/zh/wegent/user-guide/settings/desktop-app-preferences.md`:
- Line 68: Update the recovery description at
docs/zh/wegent/user-guide/settings/desktop-app-preferences.md:68-68 to describe
the behavior as applying to all desktop platforms rather than only macOS, with
screen locking presented as a typical trigger. Apply the equivalent wording
change at docs/en/wegent/user-guide/settings/desktop-app-preferences.md:68-68,
replacing “On macOS” with desktop-wide wording; no code changes are needed.
In `@wework/src-tauri/src/lib.rs`:
- Around line 2474-2505: Clear destroy_to_tray_in_progress when the delayed
recreation flow completes successfully or fails, after create_main_window is
attempted in the run_on_main_thread closure. Update the recovery path around
create_main_window and retain the existing cleanup for destroy errors, ensuring
the flag cannot remain latched when no ExitRequested event is emitted.
---
Nitpick comments:
In `@wework/src-tauri/src/lib.rs`:
- Around line 2535-2549: The two worker threads must handle failed
run_on_main_thread dispatches so frontend_probe_in_flight cannot remain latched.
In wework/src-tauri/src/lib.rs:2535-2549, update the probe thread around
run_on_main_thread to reset frontend_probe_in_flight to false and log the
failure when dispatch returns Err; apply the same fallback in the recreation
thread at wework/src-tauri/src/lib.rs:2492-2505, while preserving the existing
successful closure behavior.
In `@wework/src/lib/frontendRecovery.ts`:
- Around line 14-30: Add an install-once guard to installFrontendRecoveryBridge
so repeated calls return without replacing the probe handler or re-invoking
REGISTER_FRONTEND_RECOVERY_COMMAND. Set the guard only when installation
proceeds, while preserving the existing isTauriRuntime check and recovery
behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: fbce04f3-ba25-48ab-b335-dd60872ed69c
📒 Files selected for processing (6)
docs/en/wegent/user-guide/settings/desktop-app-preferences.mddocs/zh/wegent/user-guide/settings/desktop-app-preferences.mdwework/src-tauri/src/lib.rswework/src/lib/frontendRecovery.test.tswework/src/lib/frontendRecovery.tswework/src/main.tsx
|
|
||
| ## 锁屏后的界面恢复 | ||
|
|
||
| 在 macOS 上,如果 Wework 主窗口连续失去焦点超过 1 分钟,例如电脑长时间锁屏,窗口重新获得焦点时会检查 WebView 是否已经恢复绘制。正常恢复的界面不会重新加载;如果 WebView 在 5 秒内仍无法完成绘制回执,Wework 会自动重建主窗口,并保留原来的窗口位置、大小、最大化和全屏状态。 |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Recovery is #[cfg(desktop)], not macOS-only. Both docs scope the behavior to macOS, but the probe/recreate path in wework/src-tauri/src/lib.rs is gated on desktop, so Windows and Linux builds behave the same way.
docs/zh/wegent/user-guide/settings/desktop-app-preferences.md#L68-L68: 将“在 macOS 上”改为桌面版通用描述(或说明该行为在所有桌面平台生效,锁屏是最常见触发场景)。docs/en/wegent/user-guide/settings/desktop-app-preferences.md#L68-L68: replace "On macOS" with desktop-wide wording, mentioning screen lock only as the typical trigger.
📍 Affects 2 files
docs/zh/wegent/user-guide/settings/desktop-app-preferences.md#L68-L68(this comment)docs/en/wegent/user-guide/settings/desktop-app-preferences.md#L68-L68
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/zh/wegent/user-guide/settings/desktop-app-preferences.md` at line 68,
Update the recovery description at
docs/zh/wegent/user-guide/settings/desktop-app-preferences.md:68-68 to describe
the behavior as applying to all desktop platforms rather than only macOS, with
screen locking presented as a typical trigger. Apply the equivalent wording
change at docs/en/wegent/user-guide/settings/desktop-app-preferences.md:68-68,
replacing “On macOS” with desktop-wide wording; no code changes are needed.
| state.frontend_recovery_ready.store(false, Ordering::SeqCst); | ||
| state | ||
| .destroy_to_tray_in_progress | ||
| .store(true, Ordering::SeqCst); | ||
|
|
||
| log::warn!("Recreating unresponsive main WebView after resume probe timed out"); | ||
| if let Err(error) = window.destroy() { | ||
| state | ||
| .destroy_to_tray_in_progress | ||
| .store(false, Ordering::SeqCst); | ||
| state | ||
| .frontend_probe_in_flight | ||
| .store(false, Ordering::SeqCst); | ||
| state.frontend_recovery_ready.store(true, Ordering::SeqCst); | ||
| log::warn!("Failed to destroy unresponsive main WebView: {error}"); | ||
| return; | ||
| } | ||
|
|
||
| std::thread::spawn(move || { | ||
| std::thread::sleep(MAIN_WINDOW_RECREATE_DELAY); | ||
| let app_for_create = app.clone(); | ||
| let _ = app.run_on_main_thread(move || { | ||
| let state = app_for_create.state::<MainWindowLifecycleState>(); | ||
| if let Err(error) = create_main_window(&app_for_create, None, Some(placement)) { | ||
| log::warn!("Failed to recreate unresponsive main WebView: {error}"); | ||
| set_dock_icon_visible(&app_for_create, true); | ||
| } | ||
| state | ||
| .frontend_probe_in_flight | ||
| .store(false, Ordering::SeqCst); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
destroy_to_tray_in_progress can stay latched after a successful recovery.
The flag is set before window.destroy() and is only cleared in the destroy-error branch or by the RunEvent::ExitRequested handler (Line 4279). If no ExitRequested fires — e.g. another webview window (embedded browser / appshot) is still open, so destroying the main window is not an app-exit trigger — the flag remains true and the user's next real quit is silently prevented once. Clear it once the recreation attempt completes.
🐛 Proposed fix
if let Err(error) = create_main_window(&app_for_create, None, Some(placement)) {
log::warn!("Failed to recreate unresponsive main WebView: {error}");
set_dock_icon_visible(&app_for_create, true);
}
+ state
+ .destroy_to_tray_in_progress
+ .store(false, Ordering::SeqCst);
state
.frontend_probe_in_flight
.store(false, Ordering::SeqCst);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| state.frontend_recovery_ready.store(false, Ordering::SeqCst); | |
| state | |
| .destroy_to_tray_in_progress | |
| .store(true, Ordering::SeqCst); | |
| log::warn!("Recreating unresponsive main WebView after resume probe timed out"); | |
| if let Err(error) = window.destroy() { | |
| state | |
| .destroy_to_tray_in_progress | |
| .store(false, Ordering::SeqCst); | |
| state | |
| .frontend_probe_in_flight | |
| .store(false, Ordering::SeqCst); | |
| state.frontend_recovery_ready.store(true, Ordering::SeqCst); | |
| log::warn!("Failed to destroy unresponsive main WebView: {error}"); | |
| return; | |
| } | |
| std::thread::spawn(move || { | |
| std::thread::sleep(MAIN_WINDOW_RECREATE_DELAY); | |
| let app_for_create = app.clone(); | |
| let _ = app.run_on_main_thread(move || { | |
| let state = app_for_create.state::<MainWindowLifecycleState>(); | |
| if let Err(error) = create_main_window(&app_for_create, None, Some(placement)) { | |
| log::warn!("Failed to recreate unresponsive main WebView: {error}"); | |
| set_dock_icon_visible(&app_for_create, true); | |
| } | |
| state | |
| .frontend_probe_in_flight | |
| .store(false, Ordering::SeqCst); | |
| }); | |
| }); | |
| state.frontend_recovery_ready.store(false, Ordering::SeqCst); | |
| state | |
| .destroy_to_tray_in_progress | |
| .store(true, Ordering::SeqCst); | |
| log::warn!("Recreating unresponsive main WebView after resume probe timed out"); | |
| if let Err(error) = window.destroy() { | |
| state | |
| .destroy_to_tray_in_progress | |
| .store(false, Ordering::SeqCst); | |
| state | |
| .frontend_probe_in_flight | |
| .store(false, Ordering::SeqCst); | |
| state.frontend_recovery_ready.store(true, Ordering::SeqCst); | |
| log::warn!("Failed to destroy unresponsive main WebView: {error}"); | |
| return; | |
| } | |
| std::thread::spawn(move || { | |
| std::thread::sleep(MAIN_WINDOW_RECREATE_DELAY); | |
| let app_for_create = app.clone(); | |
| let _ = app.run_on_main_thread(move || { | |
| let state = app_for_create.state::<MainWindowLifecycleState>(); | |
| if let Err(error) = create_main_window(&app_for_create, None, Some(placement)) { | |
| log::warn!("Failed to recreate unresponsive main WebView: {error}"); | |
| set_dock_icon_visible(&app_for_create, true); | |
| } | |
| state | |
| .destroy_to_tray_in_progress | |
| .store(false, Ordering::SeqCst); | |
| state | |
| .frontend_probe_in_flight | |
| .store(false, Ordering::SeqCst); | |
| }); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wework/src-tauri/src/lib.rs` around lines 2474 - 2505, Clear
destroy_to_tray_in_progress when the delayed recreation flow completes
successfully or fails, after create_main_window is attempted in the
run_on_main_thread closure. Update the recovery path around create_main_window
and retain the existing cleanup for destroy errors, ensuring the flag cannot
remain latched when no ExitRequested event is emitted.
What changed
Why
After macOS remains locked for a while, the Wework WebView can occasionally stay on its last rendered frame and stop accepting input after unlock. The native application and local executor remain alive, and manually closing and reopening the window restores the interface.
The previous window lifecycle had no way to distinguish a healthy resumed WebView from a frozen one. This change verifies that rendering actually advances and rebuilds only the unresponsive interface process.
Impact
Validation
pnpm --filter wework test— 218 test files, 2167 tests passedcargo test --manifest-path wework/src-tauri/Cargo.toml— 91 tests passedSummary by CodeRabbit
New Features
Documentation
Tests