feat(ui): add SPA-side support for WebView2 native bridge#69633
feat(ui): add SPA-side support for WebView2 native bridge#69633AlexAlves87 wants to merge 10 commits intoopenclaw:mainfrom
Conversation
cb0f814 to
54b3d31
Compare
|
Codex review: needs real behavior proof before merge. Summary Reproducibility: not applicable. this is a feature PR, not a bug report. The JS-side behavior has high-confidence source and test coverage, but the native WebView2 host smoke path still needs real proof. Real behavior proof Next step before merge Security Review detailsBest possible solution: Keep the narrow bridge, but merge only after the contributor adds redacted real WebView2 host proof for draft injection, reconnect cleanup, and regular-browser no-op behavior. Do we have a high-confidence way to reproduce the issue? Not applicable: this is a feature PR, not a bug report. The JS-side behavior has high-confidence source and test coverage, but the native WebView2 host smoke path still needs real proof. Is this the best way to solve the issue? Yes for the code direction: the draft-text/ready-only SPA bridge is the narrow maintainable counterpart to the native-side contract. It is not merge-ready until the external real behavior proof gate is satisfied. What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 695d4ccd1b3d. |
7be00dd to
2aefa11
Compare
Greptile SummaryThis PR wires a WebView2 native bridge into the SPA lifecycle, adding The surface is correctly scoped to Confidence Score: 4/5Safe to merge — no logic errors or security issues; one minor style note about redundant getWebview() lookup. The change is small, well-tested, and handles all the relevant edge cases (no-op outside WebView2, cleanup on disconnect, malformed message guards). Only a P2 style observation remains. No files require special attention. Prompt To Fix All With AIThis is a comment left during a code review.
Path: ui/src/ui/app-native-bridge.ts
Line: 57
Comment:
**Redundant `getWebview()` lookup via `sendToNative`**
`initNativeBridge` already has a confirmed non-null `bridge` reference, but calling `sendToNative` causes a second `getWebview()` / `window.chrome?.webview` lookup for the same handshake. Consider posting directly on `bridge` to make the intent explicit:
```suggestion
bridge.postMessage({ type: "ready" });
```
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat(ui): wire WebView2 bridge — draft-t..." | Re-trigger Greptile |
6bcfa44 to
aabd9ab
Compare
|
I dug into the CI failures and they don't come from the bridge itself. Both visible errors ( The root cause was a stale fixture in I've rebased the branch onto current |
|
@BunsDev sorry for the noise. The root cause was on my side: Pushing a fix now that adds the suppress in |
Adds app-native-bridge.ts and wires it into OpenClawApp lifecycle.
Surface (minimal, parity-aligned with openclaw-windows-node#159):
- inbound: draft-text { payload: { text: string } }
- outbound: ready handshake
Implementation:
- NativeBridgeHost requires only handleChatDraftChange(next).
recording-start/stop and voice-start/stop excluded — no handler or
UI surface today, and recording follows the parity decision in
openclaw-windows-node#159.
- handleNativeMessage validates event.data as unknown: guards object,
type string, and payload.text string; malformed messages are silently
ignored.
- draft-text routes through handleChatDraftChange so native-injected
text resets input-history navigation state, same as a user edit.
- initNativeBridge called in connectedCallback; cleanup in
disconnectedCallback via private nativeBridgeCleanup field.
Tests (15):
- isWebView2 present/absent
- sendToNative posts message, no-op outside WebView2
- ready handshake sent on init, listener registered first
- no-op outside WebView2
- draft-text happy path calls handleChatDraftChange
- draft-text with missing payload, non-string text — ignored
- unknown types, null, primitives, missing type — ignored
- cleanup removes listener; post-cleanup messages ignored
- integration: draft-text resets active history navigation state
Native side: openclaw-windows-node#192 (c7630fa).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Avoids redundant getWebview() lookup via sendToNative. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bridge.postMessage() triggers oxlint missing-target-origin rule. sendToNative uses optional chaining (?.postMessage) which is exempt. The Greptile style suggestion was not lint-safe. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eliminates the eslint-disable-next-line comment so the lint-suppressions allowlist stays unmodified. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
sendToNative called ?.postMessage without a lint suppress and unicorn/require-post-message-target-origin is heuristic — it flags any .postMessage with a single argument regardless of receiver type. Adds the eslint-disable-next-line comment at the one callsite where it applies and removes the duplicate ready handshake that had been inserted before the listener registration. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
?.postMessage is exempt from unicorn/require-post-message-target-origin. The rule was firing on the bare bridge.postMessage() call that was removed in the previous commit, not on the optional-chaining path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
b171ab0 to
0224a82
Compare
|
Maintainer update: I pushed Verification:
Remaining blocker: @clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
Summary
Wires the WebView2 bridge into the SPA lifecycle with a minimal, parity-aligned surface.
Context
The native side landed in \openclaw-windows-node#192\ (\c7630fa) with origin validation, dispatcher marshaling, closed-window guards, sanitized logging, and payload JSON validation. This PR closes the SPA side of that minimal bridge surface.
Test plan
Real behavior proof
Environment: OpenClaw Windows Tray (WinUI 3, WebView2 host), local gateway at
127.0.0.1:18789, branchfeat/webview2-bridge-spa.Recording: Grabacion.de.pantalla.2026-05-08.160245.mp4
window.chrome?.webview?.dispatchEvent(new MessageEvent('message',{data:{type:'draft-text',payload:{text:'bridge test'}}}))returningtrue. At 00:16, reopening the widget shows"bridge test"injected into the input field."bridge test"persists across every cycle. Additionally, rapid-fire spam of the same event (00:28–00:32) proves idempotency — no duplication or corruption on reconnect.window.chrome?.webviewand returnsundefined, proving the bridge API is absent in a standard browser and the code paths safely no-op.Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com