wsb-daemon 0.275.7622

wsb's daemon: the workbench, the GitHub mirror's schedule and peer continuity, for every workspace on the machine
<script lang="ts">
  interface Props {
    title: string;
    html: string;
    messages: unknown[];
  }

  let { title, html, messages }: Props = $props();
  let iframeEl: HTMLIFrameElement | undefined = $state();

  // Build closing script tag without writing it literally (Svelte parser would choke)
  const SCRIPT_CLOSE = "<" + "/script>";

  /**
   * VS Code API shim injected into <head>.
   * Provides acquireVsCodeApi() so extension webviews can call it.
   * Messages from the parent arrive via the standard window 'message' event.
   */
  const vscodeApiShim = [
    "<script>",
    "window.acquireVsCodeApi = function acquireVsCodeApi() {",
    "  let state = null;",
    "  return {",
    "    postMessage(message) {",
    "      window.parent.postMessage({ type: '__wsb_webview_message__', payload: message }, '*');",
    "    },",
    "    setState(value) {",
    "      state = value;",
    "      return value;",
    "    },",
    "    getState() {",
    "      return state;",
    "    },",
    "  };",
    "};",
    // Forward keybindings to parent so the app-level shortcut handler can process them.
    // Only forwards meta/ctrl combos — normal typing stays in the iframe.
    "document.addEventListener('keydown', function(e) {",
    "  if (e.metaKey || e.ctrlKey) {",
    "    window.parent.postMessage({",
    "      type: '__wsb_keydown__',",
    "      key: e.key,",
    "      code: e.code,",
    "      metaKey: e.metaKey,",
    "      ctrlKey: e.ctrlKey,",
    "      shiftKey: e.shiftKey,",
    "      altKey: e.altKey",
    "    }, '*');",
    "  }",
    "}, true);",
    SCRIPT_CLOSE,
  ].join("\n");

  let srcdoc = $derived.by(() => {
    let doc = html;
    if (html.includes("acquireVsCodeApi")) {
      if (html.includes("<head>")) {
        doc = html.replace("<head>", `<head>${vscodeApiShim}`);
      } else {
        doc = `${vscodeApiShim}${html}`;
      }
    }
    return doc;
  });

  /**
   * When the iframe loads, post each pending message into it.
   * This is how VS Code delivers messages: the parent posts into the
   * iframe's contentWindow. The extension's message listener receives
   * them via the standard window 'message' event.
   */
  function onIframeLoad() {
    const win = iframeEl?.contentWindow;
    if (!win || messages.length === 0) return;
    for (const message of messages) {
      // Round-trip through JSON to ensure the data is plain and cloneable.
      // Messages originate from serde_json::Value and may carry non-cloneable artifacts.
      win.postMessage(JSON.parse(JSON.stringify(message)), "*");
    }
  }
</script>

<div class="webview-panel">
  <iframe
    bind:this={iframeEl}
    class="webview-frame"
    title={title}
    sandbox="allow-scripts allow-same-origin"
    srcdoc={srcdoc}
    onload={onIframeLoad}
  ></iframe>
</div>

<style>
  .webview-panel {
    width: 100%;
    height: 100%;
    min-height: 0;
    background: var(--vscode-tab-activeBackground);
  }

  .webview-frame {
    width: 100%;
    height: 100%;
    border: 0;
    background: white;
  }
</style>