Skip to main content

Overview

Jarvis uses the wry crate to embed web content into tiling panes. Every pane displaying web content gets its own WebView instance, served through a custom jarvis:// protocol.

Custom Protocol

Assets served via jarvis://localhost/ URLs

Bidirectional IPC

Type-safe JSON messages between Rust and JavaScript

Child Windows

WebViews positioned by tiling layout engine

Theme Bridge

CSS variables injected from config

Custom Protocol

URL Format

All bundled panel assets are loaded via the jarvis:// scheme:
jarvis://localhost/{path-to-asset}

Panel Mapping

Each pane type maps to a specific URL:
Pane TypeURL
Terminaljarvis://localhost/terminal/index.html
Assistantjarvis://localhost/assistant/index.html
Chatjarvis://localhost/chat/index.html
Settingsjarvis://localhost/settings/index.html
Presencejarvis://localhost/presence/index.html
Gamesjarvis://localhost/games/{game}.html
Pluginjarvis://localhost/plugins/{id}/index.html
Every response includes Access-Control-Allow-Origin: * to enable CORS for dynamic content loading.

JavaScript IPC API

Sending Messages to Rust

The window.ipc global object provides message-passing APIs:
interface JarvisIpc {
  postMessage(message: string): void;
  on(handler: (message: string) => void): void;
}

declare global {
  interface Window {
    ipc: JarvisIpc;
  }
}

Example: Panel Focus

window.ipc.postMessage(JSON.stringify({
  kind: "panel_focus",
  payload: {}
}));

Example: PTY Input

window.ipc.postMessage(JSON.stringify({
  kind: "pty_input",
  payload: { data: "ls -la\n" }
}));

Rust to JavaScript Messages

Rust sends messages by calling webview.evaluate_script() with a JavaScript snippet:
let message = serde_json::to_string(&message)?;
let js = format!("window.ipcReceive({})", message);
webview.evaluate_script(&js)?;

Event Handler Setup

JavaScript panels register an event handler:
window.ipcReceive = (message) => {
  console.log('Received from Rust:', message);
  
  switch (message.kind) {
    case 'config_updated':
      updateTheme(message.payload.theme);
      break;
    case 'terminal_output':
      term.write(message.payload.data);
      break;
    case 'palette_show':
      showCommandPalette(message.payload.items);
      break;
  }
};

IPC Message Reference

JavaScript → Rust

Notifies Rust that this panel wants focus.
{ "kind": "panel_focus", "payload": {} }
Triggers a keybind action by name.
{ "kind": "keybind", "payload": { "action": "OpenCommandPalette" } }
Clipboard operations.
{ "kind": "clipboard_copy", "payload": { "text": "copied text" } }
{ "kind": "clipboard_paste", "payload": {} }
Sends input to the terminal PTY.
{ "kind": "pty_input", "payload": { "data": "echo hello\n" } }
Command palette interaction.
{ "kind": "palette_click", "payload": { "index": 0 } }
{ "kind": "palette_hover", "payload": { "index": 2 } }
{ "kind": "palette_dismiss", "payload": {} }
Logs a debug event for troubleshooting.
{ "kind": "debug_event", "payload": { "message": "xterm ready" } }

Rust → JavaScript

Command palette lifecycle.
{ "kind": "palette_show", "payload": { "items": [...] } }
{ "kind": "palette_update", "payload": { "items": [...], "filter": "te" } }
{ "kind": "palette_hide", "payload": {} }
Theme or config changed.
{ "kind": "config_updated", "payload": { "theme": {...} } }
PTY output for xterm.js.
{ "kind": "terminal_output", "payload": { "data": "\u001b[32mhello\u001b[0m" } }
AI assistant response chunk.
{ "kind": "assistant_message", "payload": { "text": "Sure, I can help..." } }
Online users changed.
{ "kind": "presence_update", "payload": { "users": [...] } }

Theme Bridge

Rust injects CSS custom properties into every WebView based on the current theme:
const style = document.createElement('style');
style.textContent = `
  :root {
    --jarvis-bg: #1a1a1a;
    --jarvis-fg: #e0e0e0;
    --jarvis-accent: #8b5cf6;
    --jarvis-border: #3a3a3a;
    /* ...17 total color variables */
  }
`;
document.head.appendChild(style);
When config changes, Rust sends a config_updated message and panels re-inject the theme.
Use var(--jarvis-*) in your CSS to match the global theme automatically.
Navigation is restricted by an allowlist:
pub const ALLOWED_PREFIXES: &[&str] = &[
    "jarvis://localhost/",
    "http://localhost:",
    "http://127.0.0.1:",
    "https://",
];
Any URL not matching these prefixes is blocked with:
Navigation to {url} blocked by allowlist
Plugins loaded from the filesystem have additional restrictions—they can only navigate to their own plugin:// URLs.

Keyboard Interception

WebViews can prevent Rust from processing keyboard shortcuts by setting:
window.jarvisPreventDefaultKeys = true;
When true, Rust does not interpret keybinds for that WebView—useful for terminals and text editors.

Source References

  • jarvis-rs/crates/jarvis-webview/src/ipc.rs - IPC message types
  • jarvis-rs/crates/jarvis-webview/src/manager.rs - WebView lifecycle
  • jarvis-rs/crates/jarvis-webview/src/theme_bridge/mod.rs - CSS injection
  • jarvis-rs/crates/jarvis-app/src/app_state/webview_bridge/ - IPC dispatch
  • docs/manual/06-webview-ipc.md - Complete reference