Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131<!doctype html><html> <head> <meta charset="utf-8" /> <title>Aesel session host</title> </head> <body> <!-- Nothing is drawn here. This page exists so the shared session can run inside a WKWebView the iOS app keeps off-screen, while SwiftUI does every pixel.
The webview is not a shortcut around writing Swift — it is what supplies the web platform the shared bridge is written against. `ac-server.mjs` streams with `response.body.getReader()` and decodes with `TextDecoder`, and JavaScriptCore has neither, nor ES modules, nor `fetch`. Oskiewar's Mac app pays that bill (it hands JSC a bundled script and drives Metal), because a webview cannot draw its game. Aesel's renderer is text, and its piece preview has to be a webview on every platform anyway — so hosting the session in one costs an object nobody sees.
Swift → JS is `window.aesel.*`. JS → Swift is one message handler named `aesel`, carrying the session's events verbatim. --> <script> const hostError = (text) => window.webkit?.messageHandlers?.aesel?.postMessage({type: "hostError", text}); window.addEventListener("error", event => hostError(event.message || "The session module could not load.")); window.addEventListener("unhandledrejection", event => hostError(String(event.reason?.message || event.reason))); </script> <script src="/easel/phone/shim/globals.js"></script> <script type="importmap"> { "imports": { "/easel/src/source-parser.mjs": "/easel/src/vendor/acorn.mjs", "node:events": "/easel/phone/shim/events.mjs", "node:fs": "/easel/phone/shim/fs.mjs", "node:path": "/easel/phone/shim/path.mjs", "node:url": "/easel/phone/shim/url.mjs", "node:crypto": "/easel/phone/shim/crypto.mjs", "node:os": "/easel/phone/shim/absent.mjs", "node:http": "/easel/phone/shim/absent.mjs", "node:child_process": "/easel/phone/shim/absent.mjs", "node:readline": "/easel/phone/shim/absent.mjs", "/easel/src/preview-frame.mjs": "/easel/phone/shim/preview-frame.mjs", "/easel/src/jev-advisor.mjs": "/easel/phone/shim/jev-advisor.mjs", "/easel/src/revisions.mjs": "/easel/phone/shim/revisions.mjs" } } </script> <script type="module"> import { createSession } from "/easel/phone/session.mjs";
const native = globalThis.webkit?.messageHandlers?.aesel; const post = (event) => { try { native?.postMessage(event); } catch (error) { console.error("[aesel] could not reach the host", error); } };
// The host owns persistence: Documents on iOS, so a session survives the // app being killed. `seed` is written by Swift before this page loads. const storage = { get: (key) => globalThis.__aeselSeed?.[key] ?? null, set: (key, value) => { globalThis.__aeselSeed = { ...(globalThis.__aeselSeed || {}), [key]: value }; post({ type: "persist", key, value }); }, };
const hostRPC = globalThis.webkit?.messageHandlers?.aeselHost ? (method, params) => globalThis.webkit.messageHandlers.aeselHost.postMessage({method, params}) : null; const renewToken = globalThis.webkit?.messageHandlers?.aeselAccount ? async ({force = false} = {}) => { const result = await globalThis.webkit.messageHandlers.aeselAccount.postMessage({method:"token",force}); if(result.error)throw Object.assign(new Error(result.error),{status:result.status}); return result.token; } : null; const session = createSession({ storage, emit: post, hostRPC, renewToken });
// Every call is wrapped so a thrown error reaches Swift as an event // rather than vanishing into an unhandled rejection the host cannot see. const guard = (name, fn) => async (...args) => { try { return await fn(...args); } catch (error) { post({ type: "bad", text: `${name}: ${error.message}` }); } };
globalThis.aesel = { ask: guard("ask", (text, context = "") => session.ask(String(text), String(context).slice(0, 6000))), stop: () => session.stop(), refreshCredits: () => session.refreshCredits(), signOut: () => session.signOut(), newPiece: guard("newPiece", (medium = "piece") => session.newSession(medium)), resumeSession: guard("resumeSession", (id) => session.resumeSession(String(id))), save: () => session.saveCurrent(), exportNotebook: guard("exportNotebook", () => session.exportNotebook()), importNotebook: guard("importNotebook", text => session.importNotebook(text)), editSource: guard("editSource", (source,id) => session.editSource(source,id)), previewRevision: guard("previewRevision", (version,id) => session.previewRevision(version,id)), restoreRevision: guard("restoreRevision", (version,id) => session.restoreRevision(version,id)), selectRevision: guard("selectRevision", (version,id) => session.selectRevision(version,id)), setAutoPublish: guard("setAutoPublish", enabled => session.setAutoPublish(enabled)), setDraft: guard("draft", (text, threadID) => session.setDraft(String(text), String(threadID))), setModel: guard("model", (model) => session.setModel(String(model))), setProvider: guard("provider", (id) => session.setProvider(String(id))), refreshProviders: () => session.refreshProviders(), setMcpAutoAllow: guard("permissions", value => session.setMcpAutoAllow(value === true)), resumeTurn: guard("reconnect", () => session.resumeTurn()), respondToApproval: guard("approval", (id, decision) => session.respondToApproval(String(id), String(decision))), publish: guard("publish", () => session.publish()), adoptToken: async (token) => { await session.adoptToken(String(token)); await session.open(); }, restore: guard("restore", async () => { const restoring = session.restore(); await session.open(); const ok = await restoring; post({ type: "restored", signedIn: ok }); }), };
await session.begin(); </script> </body></html>