Skip to content

[Proposal] Add a formal WebView2 bridge for native ↔ SPA communication #191

@AlexAlves87

Description

@AlexAlves87

Right now WebChatWindow and CanvasWindow both load gateway content in WebView2, but there's no formal communication channel between the native C# side and the embedded SPA. The two halves run disconnected, which shows up in a few places already:

Surface Current situation
WebChatWindow No bridge at all. Native code has no way to signal UI state to the SPA (recording indicator, draft text, activity spinner)
CanvasWindow One-way native→web channel via ExecuteScriptAsync with 11 heuristic fallbacks. No web→native channel
Voice PR #120 (NichUK) Works around the gap with DOM scraping (querySelector, MutationObserver, descriptor hacks). The author describes it as fragile
Screen record PR #159 No way to show a recording indicator in the SPA while the app is capturing
QuickSendDialog Can send messages over WebSocket but can't coordinate UI state with the embedded chat

The fix

WebView2 ships a proper bidirectional bridge that doesn't touch the DOM at all:

Native → SPA

// Inject bootstrap before any page scripts run
await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(BridgeInitScript);

// Send a message at any point
webView.CoreWebView2.PostWebMessageAsJson(JsonSerializer.Serialize(msg));

SPA → Native

webView.CoreWebView2.WebMessageReceived += (_, e) => {
    var msg = JsonSerializer.Deserialize<BridgeMessage>(e.WebMessageAsJson);
    HandleBridgeMessage(msg);
};

Gateway SPA side

// Receive from native
window.chrome.webview.addEventListener('message', e => {
    const msg = e.data; // { type, payload }
    // dispatch by type
});

// Send to native
window.chrome.webview.postMessage({ type: 'ready' });

Minimal message contract to start

{ "type": "recording-start", "payload": {} }
{ "type": "recording-stop",  "payload": {} }
{ "type": "voice-start",     "payload": {} }
{ "type": "voice-stop",      "payload": {} }
{ "type": "draft-text",      "payload": { "text": "..." } }
{ "type": "ready",           "payload": {} }

Scope

This touches two repos:

  1. This repo — wire the bridge in WebChatWindow.xaml.cs and CanvasWindow.xaml.cs
  2. Gateway — expose the window.chrome.webview listener in the SPA

The existing WebSocket transport (chat.send) stays as-is — it handles chat messages. The bridge is purely for UI state coordination.

Happy to put together a PR for the Windows side once there's alignment on the gateway contract.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions