"use strict";
import { paint } from "./scene.js";
import { applyPatch } from "./diff.js";
import { BrowserGlassesClient, defaultPose } from "./glasses.js";
import { intentFromEmit } from "./intent.js";
import { bootPwa } from "./pwa.js";
import { postIntent, openSession } from "./session.js";
const SESSION_RESOURCE = "demo";
const SESSION_PANE = "pane-main";
const BOOTSTRAP_SCENE = {
kind: "scene/stack",
dir: "column",
children: [
{
kind: "scene/box",
role: "summary",
children: [
{ kind: "scene/text", text: "SIM Web-UI shell" },
{ kind: "scene/badge", status: "ok", label: "interpreter loaded" },
],
},
],
};
export function renderSessionError(doc, message) {
const error = doc.createElement("div");
error.className = "session-error";
error.setAttribute("role", "alert");
error.textContent = String(message || "session error");
return error;
}
function applyReducedMotion() {
const prefersReduced =
typeof window.matchMedia === "function" &&
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (prefersReduced || window.__SIM_REDUCED_MOTION__) {
document.body.dataset.reducedMotion = "true";
}
}
function installKeyboardSpine(mount) {
document.addEventListener("keydown", (e) => {
if ((e.ctrlKey || e.metaKey) && e.key === "k") {
e.preventDefault();
const focusable = mount.querySelector(
'input, button, [tabindex="0"]',
);
if (focusable && typeof focusable.focus === "function") {
focusable.focus();
}
}
});
}
function boot() {
const mount = document.getElementById("shell");
if (!mount) return;
bootPwa();
applyReducedMotion();
installKeyboardSpine(mount);
let scene = window.__SIM_SCENE__ || BOOTSTRAP_SCENE;
let sessionError = null;
let tick = 0;
let pose = window.__SIM_GLASSES_POSE__ || defaultPose();
const glasses = window.__SIM_GLASSES_CAPS__
? new BrowserGlassesClient(window.__SIM_GLASSES_CAPS__)
: null;
if (glasses) document.body.dataset.glassesMode = glasses.mode;
const receiveScene = (next) => {
scene = next;
if (!glasses) return;
try {
glasses.receive(scene);
sessionError = null;
} catch (error) {
sessionError = error instanceof Error ? error.message : String(error);
}
};
const emit = (event) => {
tick += 1;
const intent = intentFromEmit(event, "pane-main", "human", tick);
if (intent) {
document.dispatchEvent(new CustomEvent("sim-intent", { detail: intent }));
}
};
const repaint = () => {
let visible = scene;
if (glasses && glasses.contentReceipts > 0) {
try {
visible = glasses.frame(pose);
} catch (error) {
sessionError = error instanceof Error ? error.message : String(error);
}
}
paint(document, mount, visible, emit);
if (sessionError) {
mount.appendChild(renderSessionError(document, sessionError));
}
};
receiveScene(scene);
repaint();
document.addEventListener("sim-scene-patch", (e) => {
receiveScene(applyPatch(scene, e.detail));
repaint();
});
document.addEventListener("sim-glasses-pose", (e) => {
pose = e.detail || defaultPose();
if (!glasses || !glasses.usesAdaptLoop()) repaint();
});
document.addEventListener("sim-intent", async (e) => {
const result = await postIntent(e.detail);
if (!result.ok) {
sessionError = result.error;
repaint();
return;
}
sessionError = null;
if (result.patches.length === 0) {
repaint();
return;
}
for (const patch of result.patches) {
document.dispatchEvent(new CustomEvent("sim-scene-patch", { detail: patch }));
}
});
openSession(SESSION_RESOURCE, SESSION_PANE).then((result) => {
if (result.ok && result.scene) {
receiveScene(result.scene);
repaint();
return;
}
if (!result.ok) {
sessionError = result.error;
repaint();
}
});
if (glasses && glasses.usesAdaptLoop() && typeof window.requestAnimationFrame === "function") {
const adapt = () => {
repaint();
window.requestAnimationFrame(adapt);
};
window.requestAnimationFrame(adapt);
}
console.log("sim-web-shell: scene painter booted");
}
if (typeof document !== "undefined") {
boot();
}
export { BOOTSTRAP_SCENE, boot };