# Native OS Notification — Testing Status Companion to `TAURI_CEF_FINDINGS_AND_CHANGES.md`. This file is a quick-reference checklist: what is done, what is still needed, and how to test. --- ## What Is Done ### tauri-cef (vendored submodule) | Change | File | |--------|------| | `NotifyRenderProcessHandler` wired into `TauriApp::render_process_handler` | `vendor/tauri-cef/crates/tauri-runtime-cef/src/cef_impl.rs` | | `run_cef_helper_process()` uses `NotifyApp` (not `None`) | `vendor/tauri-cef/crates/tauri-runtime-cef/src/lib.rs` | | `notification::unregister(browser_id)` called on browser close | `vendor/tauri-cef/crates/tauri-runtime-cef/src/cef_impl.rs` | | Dispatch logs added (`[cef-notify] dispatch` / dropped) | `vendor/tauri-cef/crates/tauri-runtime-cef/src/notification.rs` | | `on_context_created` install logs in runtime shim | `vendor/tauri-cef/crates/tauri-runtime-cef/src/notification.rs` | | `on_context_created` install logs in helper shim | `vendor/tauri-cef/cef-helper/src/notification.rs` | | Debug markers: `window.__OPENHUMAN_CEF_NOTIFICATION_SHIM`, `__OPENHUMAN_CEF_NOTIFICATION_ORIGIN` | `vendor/tauri-cef/cef-helper/src/notification.rs` | | Manual test entry point: `window.__openhumanFireNotification(title, opts)` | `vendor/tauri-cef/cef-helper/src/notification.rs` | | `ensure-tauri-cli.sh` reinstalls vendored CLI when tauri-cef sources are newer | `scripts/ensure-tauri-cli.sh` | ### tauri-plugin-notification (vendored to stop init-script conflict) | Change | File | |--------|------| | Plugin vendored at `vendor/tauri-plugin-notification/` | `app/src-tauri/Cargo.toml` | | Plugin dependency switched from git rev to local path | `app/src-tauri/Cargo.toml` | | `.js_init_script(...)` call removed from plugin `init()` | `vendor/tauri-plugin-notification/src/lib.rs` | **Why this mattered:** Without this change, the plugin injected a JS shim that forwarded `new Notification(...)` to `http://ipc.localhost/plugin:notification|notify`. That IPC always fails with 500 in third-party webviews (Slack), overwriting the CEF shim and blocking all notification delivery. ### openhuman-cursor app shell | Change | File | |--------|------| | Default notification toggle set to `true` | `src/notification_settings/mod.rs` | | `OPENHUMAN_DISABLE_SLACK_SCANNER=1` env bypass for DevTools inspection | `src/webview_accounts/mod.rs` | | Platform-specific OS notification with click detection added | `src/webview_accounts/mod.rs` | | macOS: `mac-notification-sys` + `wait_for_click` + `std::thread::spawn` | `src/webview_accounts/mod.rs` | | Linux: `notify-rust` + `wait_for_action` + `std::thread::spawn` | `src/webview_accounts/mod.rs` | | Windows: fire-and-forget fallback via `NotificationExt` | `src/webview_accounts/mod.rs` | | `notification:click` Tauri event emitted with `{ account_id, provider }` | `src/webview_accounts/mod.rs` | | `[notify-click]` success logs promoted from `debug` to `info` | `src/webview_accounts/mod.rs` | | `mac-notification-sys = "0.6"` added to macOS dependencies | `app/src-tauri/Cargo.toml` | | `notify-rust` added to Linux dependencies | `app/src-tauri/Cargo.toml` | | `NotificationExt` import scoped to `#[cfg(all(feature = "cef", windows))]` | `src/webview_accounts/mod.rs` | | `tokio::task::spawn_blocking` replaced with `std::thread::spawn` (fixes tokio panic from CEF callback thread) | `src/webview_accounts/mod.rs` | | Scanner fallback: per-channel unread baseline, delta-based notification synthesis | `src/slack_scanner/mod.rs` | --- ## What Is Still Needed ### 1. ✅ Slack scanner registry re-enabled **File:** `app/src-tauri/src/lib.rs` This fix has already been applied in this PR. `ScannerRegistry` is now registered in the Tauri app state, so the scanner-driven fallback notification path is active. The following was added to `lib.rs` inside `tauri::Builder::default()...manage(...)`: ```rust // already applied .manage(Arc::new(slack_scanner::ScannerRegistry::new())) ``` With this change: - The scanner tracks per-channel unread counts - When a channel's unread count increases, the scanner synthesizes a native OS notification - This is the fallback path because Slack's embedded session does not call `new Notification(...)` for real incoming messages ### 2. Verify end-to-end with a real incoming Slack message Once the scanner registry is registered: 1. Run the app: `cd app && pnpm dev:app` 2. Open the Slack webview — wait for Slack to load fully 3. Have someone send you a Slack message from another device 4. Watch the log: ```bash tail -f /tmp/openhuman-dev-app.log | grep --line-buffered "notify-cef\|notify-click\|scanner\|unread" ``` 5. Expected log sequence: ``` [scanner] unread delta channel=... prev=N new=M [notify-cef][] source=... tag=... title_chars=N body_chars=M ``` 6. OS toast should appear 7. Click the toast → expected: ``` [notify-click][] clicked provider=slack ``` 8. Slack webview should come into focus (frontend routes `notification:click` → `setActiveAccount` → `activate_main_window`) ### 3. Verify the CEF shim installs in Slack's page context Before relying on real messages, confirm the helper shim is active via DevTools: 1. Open `brave://inspect` 2. Find the Slack page target → click **Inspect** 3. In Console, run: ```js window.__OPENHUMAN_CEF_NOTIFICATION_SHIM // should be true window.__OPENHUMAN_CEF_NOTIFICATION_ORIGIN // should be the Slack URL ``` 4. If both are present, the CEF helper shim installed correctly ### 4. Verify the manual helper trigger path end-to-end With DevTools open on the Slack target: ```js window.__openhumanFireNotification("Slack CEF test", { body: "Manual trigger" }) ``` Expected log: ``` [cef-notify] dispatch browser_id=N source=Window title="Slack CEF test" origin=... [notify-cef][] source=Window tag= silent=false title_chars=14 body_chars=13 ``` And an OS notification toast should appear. If no toast appears, the blocker is in `forward_native_notification` in `webview_accounts/mod.rs`. ### 5. Clean up debug instrumentation (post-verification) Once notifications are working reliably, remove: - `window.__OPENHUMAN_CEF_NOTIFICATION_SHIM` global marker - `window.__OPENHUMAN_CEF_NOTIFICATION_ORIGIN` global marker - `window.__openhumanFireNotification` manual trigger - `window.__OPENHUMAN_CEF_NOTIFICATION_CONSTRUCTOR` saved reference - `[cef-helper-notify]` `eprintln!` calls in `cef-helper/src/notification.rs` (or replace with proper `log::debug!`) --- ## How To Run The App Correctly The app **must** be started with `dev:app`, not `tauri dev` directly: ```bash cd app && pnpm dev:app ``` `dev:app` sets `CEF_PATH=$HOME/Library/Caches/tauri-cef` and ensures the vendored `cargo-tauri` is installed. Without it, the app panics at startup in `cef::library_loader` with `No such file or directory`. Live log location: ```bash tail -f /tmp/openhuman-dev-app.log | grep --line-buffered "notify-cef\|notify-click\|scanner\|unread\|cef-notify" ``` --- ## Key Log Prefixes | Prefix | Where | Meaning | |--------|-------|---------| | `[cef-helper-notify] on_context_created` | renderer subprocess | shim callback fired for a new JS context | | `[cef-helper-notify] installed shims` | renderer subprocess | shim installed in that context | | `[cef-helper-notify] execute` | renderer subprocess | `new Notification(...)` called by page JS | | `[cef-notify] dispatch` | browser process | notification IPC received from renderer, handler called | | `[cef-notify] dropped` | browser process | notification IPC received but no handler registered | | `[notify-cef][id]` | `webview_accounts` | notification payload reached app, OS toast being sent | | `[notify-click][id] clicked` | `webview_accounts` | user clicked OS toast, emitting `notification:click` | | `[webview-accounts] slack ScannerRegistry not in app state` | `webview_accounts` | scanner registry is missing — add `.manage(ScannerRegistry::new())` in `lib.rs` | --- ## Frontend Side (Already Wired, No Changes Needed) `app/src/services/webviewAccountService.ts` already listens for `notification:click`: ```ts listen('notification:click', ({ payload }) => { dispatch(setActiveAccount(payload.account_id)); invoke('activate_main_window'); }); ``` No frontend changes are needed. The click routing will work once the Rust side emits the event.