Skip to content

feat(ui): add SPA-side support for WebView2 native bridge#69633

Draft
AlexAlves87 wants to merge 1 commit intoopenclaw:mainfrom
AlexAlves87:feat/webview2-bridge-spa
Draft

feat(ui): add SPA-side support for WebView2 native bridge#69633
AlexAlves87 wants to merge 1 commit intoopenclaw:mainfrom
AlexAlves87:feat/webview2-bridge-spa

Conversation

@AlexAlves87
Copy link
Copy Markdown

@AlexAlves87 AlexAlves87 commented Apr 21, 2026

Summary

Wires the WebView2 bridge into the SPA lifecycle with a minimal, parity-aligned surface.

  • app-native-bridge.ts — reduces NativeBridgeMessage to draft-text (inbound) and ready (outbound handshake). Removes recording-start/stop and voice-start/stop, since there is no corresponding SPA handler or UI surface today, and recording follows the parity decision in openclaw-windows-node#159. NativeBridgeHost now requires only handleChatDraftChange(next).
  • app.ts — imports initNativeBridge, calls it in connectedCallback, and cleans it up in disconnectedCallback.
  • draft-text goes through handleChatDraftChange so native-injected text resets input-history navigation the same way a real user edit does.

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

  • In a WebView2 host: send {"type":"draft-text","payload":{"text":"hello"}} → chat input updates and input-history navigation resets
  • Disconnect/reconnect component → no listener leak
  • Outside WebView2 (regular browser) → initNativeBridge no-ops without errors

@clawsweeper
Copy link
Copy Markdown

clawsweeper Bot commented Apr 28, 2026

Codex review: keeping this open for maintainer follow-up; there is still a little grit to resolve.

Keep this PR open. Current main still has no Control UI SPA-side WebView2 bridge or lifecycle wiring, while the draft PR adds a focused bridge module, app hookup, and tests for the draft-text and ready handshake surface. The linked Windows host-side bridge has merged in openclaw-windows-node#192, so this remains a live implementation candidate rather than obsolete cleanup.

Best possible solution:

Keep this PR open for normal maintainer review while it is draft. The best path is to review the focused Control UI bridge, confirm the merged Windows host bridge with a WebView2 smoke, keep the runtime validation and lifecycle cleanup tests, and land it if maintainers accept SPA-side WebView2 support.

What I checked:

  • current_main_no_spa_bridge: A targeted current-main search in ui/src/ui and docs/web finds no initNativeBridge, app-native-bridge, chrome.webview, draft-text, NativeBridge, or WebView2 Control UI implementation. (fd2625a16252)
  • app_lifecycle_lacks_bridge_wiring: OpenClawApp.connectedCallback currently installs the global keydown handler, calls handleConnected, and starts web push state; disconnectedCallback only removes the keydown handler and calls handleDisconnected. There is no native bridge init or cleanup on current main. (ui/src/ui/app.ts:585, fd2625a16252)
  • draft_change_hook_exists: The existing draft-change helper sets chatMessage and resets input-history navigation, which matches the PR's intended route for native-injected draft text. (ui/src/ui/chat/input-history.ts:121, fd2625a16252)
  • chat_render_uses_draft_hook: The chat render path passes the current draft and routes user draft edits through state.handleChatDraftChange, so a native draft-text message would integrate with the existing composer path. (ui/src/ui/app-render.ts:2316, fd2625a16252)
  • docs_do_not_document_current_bridge: The Control UI docs describe a Vite + Lit SPA served by the Gateway and using the Gateway WebSocket; the WebChat docs describe Gateway WebSocket chat behavior. Neither documents WebView2 native bridge support on current main. Public docs: docs/web/control-ui.md. (docs/web/control-ui.md:10, fd2625a16252)
  • pr_diff_is_focused_candidate: The PR head adds ui/src/ui/app-native-bridge.ts, ui/src/ui/app-native-bridge.test.ts, and five lines of lifecycle wiring in ui/src/ui/app.ts. The bridge surface is inbound draft-text plus outbound ready, with runtime guards for malformed messages and cleanup tests. (ui/src/ui/app-native-bridge.ts:1, 2aefa119696e)

Likely related people:

  • BunsDev: Remote history shows BunsDev authored the original Control UI dashboard shell and the later chat infrastructure slice that introduced chat/input-history.ts, directly adjacent to the draft handling and app/render surfaces this PR wires into. (role: original Control UI and chat-infrastructure owner; confidence: high; commits: 3bbbe33a1b91, c5ea6134d041, e3ad82d86df0; files: ui/src/ui/app.ts, ui/src/ui/app-render.ts, ui/src/ui/chat/input-history.ts)
  • Ivocin: Commit 8200d87 specifically hardened WebChat input history behavior, including draft/navigation consistency, which is the exact SPA behavior the native draft-text bridge intends to reuse. (role: recent input-history maintainer; confidence: medium; commits: 8200d878a340; files: ui/src/ui/chat/input-history.ts, ui/src/ui/app.ts)
  • steipete: Recent Control UI history includes browser-safety and browser realtime Talk work by steipete, both adjacent to adding a browser/native bridge and reviewing its trust assumptions. (role: recent Control UI browser-surface maintainer; confidence: medium; commits: 9e149519fed8, 04066d246abc, c6ebd99a461d; files: ui/src/ui/app.ts, ui/src/ui/app-render.ts, docs/web/control-ui.md)

Remaining risk / open question:

  • The PR introduces a new native-message trust boundary. The SPA-side diff validates payload shape, and the linked Windows host-side bridge validates origin, but maintainers should still run a real WebView2 smoke before merge.
  • This was a read-only cleanup review, so no local tests or WebView2 runtime smoke were run.

Codex review notes: model gpt-5.5, reasoning high; reviewed against fd2625a16252.

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>
@AlexAlves87 AlexAlves87 force-pushed the feat/webview2-bridge-spa branch from 7be00dd to 2aefa11 Compare April 29, 2026 00:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant