Commit6f1e55fcRecorded16 Jul 2026Repositorylantern

lantern: host-injected wasm streaming->buffer fallback so wasm apps boot over lantern://

Message

WebKitGTK will not stream-compile wasm over the custom lantern:// scheme: WebAssembly.compileStreaming/instantiateStreaming reject with 'Unexpected response MIME type. Expected application/wasm' even though the asset backend serves an explicit Content-Type: application/wasm. Every Sigil-wasm app (Slate included) boot-died blank as a result.

Registering the scheme as secure + cors-enabled via WebKitSecurityManager did NOT fix it (WebKit ignores a custom scheme MIME for streaming compile, confirmed empirically). Kept the 'secure' registration for secure-context hygiene; dropped cors.

Fix: (lantern js) wasm-streaming-shim-js patches WebAssembly.compileStreaming/ instantiateStreaming to clone the response, try streaming, and buffer-compile on failure. app.sgl injects it at document-start before the app boots. This makes the fallback a HOST responsibility (every web build gets it) rather than relying on each build to carry its own. It is deliberately LOUD: one console.warn on first fallback, pointing at the durable fix. See topics/lantern-wasm-streaming-fallback.

Also: run-slate.sh now takes a Slate repo folder (resolves build/web), launches with --transparent, and passes LANTERNSOFTWARERENDER through.

Changed
 lantern/src/lantern/app.sgl         |  5 +++++
 lantern/src/lantern/backend/gtk.sgl | 23 +++++++++++++++++++++--
 lantern/src/lantern/js.sgl          | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 run-slate.sh                        | 44 ++++++++++++++++++++++++++++++++++----------
 4 files changed, 133 insertions(+), 13 deletions(-)
Diff
lantern/src/lantern/app.sglmodified
@@ -132,6 +132,11 @@
132
(unless is-url
133
(webview-register-scheme wv "lantern" (make-asset-resolver assets)))
134
+135
;; Inject the wasm streaming->buffer fallback FIRST (before the app boots
+136
;; and calls compileStreaming): WebKitGTK will not stream-compile wasm over
+137
;; the lantern:// scheme, so this host-injected shim patches WebAssembly to
+138
;; buffer-compile on failure. It is loud (warns once) — see (lantern js).
+139
(webview-inject-script wv win wasm-streaming-shim-js)
140
;; Inject the bridge glue at document-start, then any plugin glue.
141
(webview-inject-script wv win lantern-js)
142
(for-each (lambda (p)
lantern/src/lantern/backend/gtk.sglmodified
@@ -69,6 +69,8 @@
69
(define gdk-display-get-default #f)
70
71
(define webkit-web-context-get-default #f)
+72
(define webkit-web-context-get-security-manager #f)
+73
(define webkit-security-manager-register-scheme-secure #f)
74
(define webkit-web-context-register-uri-scheme #f)
75
(define webkit-uri-scheme-request-get-uri #f)
76
(define webkit-uri-scheme-request-get-path #f)
@@ -257,6 +259,10 @@
259
(set! gdk-display-get-default (c-function libgtk "gdk_display_get_default" (list) ffi/pointer))
260
261
(set! webkit-web-context-get-default (c-function libwebkit "webkit_web_context_get_default" (list) ffi/pointer))
+262
(set! webkit-web-context-get-security-manager
+263
(c-function libwebkit "webkit_web_context_get_security_manager" (list ffi/pointer) ffi/pointer))
+264
(set! webkit-security-manager-register-scheme-secure
+265
(c-function libwebkit "webkit_security_manager_register_uri_scheme_as_secure" (list ffi/pointer ffi/string) ffi/void))
266
(set! webkit-web-context-register-uri-scheme
267
(c-function libwebkit "webkit_web_context_register_uri_scheme"
268
(list ffi/pointer ffi/string ffi/pointer ffi/pointer ffi/pointer) ffi/void))
@@ -372,8 +378,21 @@
378
379
(define (register-scheme scheme resolver)
380
(set! *scheme-resolver* resolver)
375
(webkit-web-context-register-uri-scheme
376
(webkit-web-context-get-default) scheme scheme-cb NULL NULL))
+381
(let ((ctx (webkit-web-context-get-default)))
+382
;; Register the app scheme as a SECURE context (like https) before the
+383
;; first load: a custom scheme is not a secure context by default, which
+384
;; gates web features an app may rely on (SubtleCrypto, service workers,
+385
;; etc.). Good hygiene for the app origin.
+386
;;
+387
;; NB: secure + cors-enabled was ALSO tried as a fix for wasm streaming
+388
;; compile over lantern:// and does NOT work — WebKitGTK still ignores a
+389
;; custom scheme MIME for WebAssembly.compileStreaming (empirically
+390
;; confirmed 2026-07-16). That is handled instead by the injected
+391
;; streaming->buffer shim (see (lantern js) wasm-streaming-shim-js and
+392
;; topics/lantern-wasm-streaming-fallback). CORS was dropped as useless.
+393
(webkit-security-manager-register-scheme-secure
+394
(webkit-web-context-get-security-manager ctx) scheme)
+395
(webkit-web-context-register-uri-scheme ctx scheme scheme-cb NULL NULL)))
396
397
(define (register-messages window name on-message)
398
(set! *msg-handler* on-message)
lantern/src/lantern/js.sglmodified
@@ -7,9 +7,81 @@
7
8
(define-library (lantern js)
9
(import (sigil core))
10
(export lantern-js)
+10
(export lantern-js
+11
wasm-streaming-shim-js)
12
(begin
13
+14
;; WebAssembly streaming-compile fallback (injected at document-start, BEFORE
+15
;; the app boots so its compileStreaming call is already patched).
+16
;;
+17
;; WHY THIS EXISTS — READ BEFORE DELETING: WebKitGTK will NOT stream-compile
+18
;; wasm over Lantern's custom lantern:// scheme. `WebAssembly.compileStreaming`
+19
;; /`instantiateStreaming` reject with "Unexpected response MIME type. Expected
+20
;; application/wasm" even though the backend serves an explicit
+21
;; `Content-Type: application/wasm` (via finish_with_response) AND the scheme is
+22
;; registered secure + cors-enabled — WebKit simply does not consult a custom
+23
;; scheme response MIME for streaming compile (confirmed 2026-07-16). Without a
+24
;; fallback, EVERY Sigil-wasm app (Slate included) boot-dies blank in Lantern.
+25
;;
+26
;; This shim makes the fallback a HOST responsibility (Lantern injects it into
+27
;; every page), not a per-web-build one — so no app has to remember the
+28
;; workaround. It is a WORKAROUND, not a fix: it is deliberately LOUD (a single
+29
;; console.warn the first time it fires) so we do not forget. The durable fix is
+30
;; to serve assets over a loopback http origin where wasm streams natively; see
+31
;; topics/lantern-wasm-streaming-fallback. Remove this shim if/when that lands
+32
;; (or if a future WebKitGTK honors custom-scheme MIME for streaming).
+33
;;
+34
;; String constraints (same as lantern-js): NO double quotes, NO backslashes,
+35
;; NO apostrophes inside the single-quoted JS strings — it embeds in a Sigil
+36
;; double-quoted string literal.
+37
(define wasm-streaming-shim-js
+38
"(function () {
+39
if (!(window.WebAssembly)) return;
+40
var WA = window.WebAssembly;
+41
if (WA.__lanternWasmShim) return;
+42
WA.__lanternWasmShim = true;
+43
var warned = false;
+44
function warnOnce(kind) {
+45
if (warned) return;
+46
warned = true;
+47
if (!(window.console) || !console.warn) return;
+48
console.warn(
+49
'lantern: WebAssembly.' + kind + ' was rejected over this origin ' +
+50
'(WebKitGTK does not honor a custom lantern:// scheme MIME for streaming ' +
+51
'wasm compile). lantern is buffer-compiling as a HOST-SIDE FALLBACK. This ' +
+52
'is a workaround, NOT a fix. Durable fix: serve assets over a loopback http ' +
+53
'origin so wasm streams natively. See topics/lantern-wasm-streaming-fallback.');
+54
}
+55
function patch(name, onFail) {
+56
var orig = WA[name];
+57
if (typeof orig !== 'function') return;
+58
WA[name] = function (src, importObject) {
+59
return Promise.resolve(src).then(function (resp) {
+60
var canClone = resp && typeof resp.clone === 'function';
+61
var attempt = canClone ? resp.clone() : resp;
+62
return Promise.resolve(orig.call(WA, attempt, importObject))
+63
.catch(function (e) {
+64
warnOnce(name);
+65
return onFail(resp, importObject);
+66
});
+67
});
+68
};
+69
}
+70
patch('compileStreaming', function (resp) {
+71
return resp.arrayBuffer().then(function (buf) { return WA.compile(buf); });
+72
});
+73
patch('instantiateStreaming', function (resp, importObject) {
+74
return resp.arrayBuffer().then(function (buf) {
+75
return WA.compile(buf).then(function (mod) {
+76
return WA.instantiate(mod, importObject).then(function (inst) {
+77
return { module: mod, instance: inst };
+78
});
+79
});
+80
});
+81
});
+82
})();
+83
")
+84
85
(define lantern-js
86
"(function () {
87
if (window.lantern) return;
run-slate.shmodified
@@ -1,25 +1,49 @@
1
#!/usr/bin/env bash
2
# Launch Slate in a native Lantern window with REAL local fs + pty access.
+2
# Launch Slate in a native Lantern window with TRANSPARENCY on + REAL local
+3
# fs + pty access.
4
#
4
# ./run-slate.sh [slate-web-build-dir]
+5
# ./run-slate.sh [slate-repo-dir] [extra lantern args...]
6
#
6
# Defaults to the sibling slate worktree's web build. Wraps the invocation in an
7
# ephemeral `guix shell` (the GTK4 + WebKitGTK + Mesa graphics stack, from
8
# gl-manifest.scm) — no persistent profile. If the window never maps, re-run with
9
# LANTERN_SOFTWARE_RENDER=1 (the Mesa GL loader-path gotcha).
+7
# Give it a path to a Slate REPO folder (the one holding build/web/index.html
+8
# after `sigil build`). Defaults to the sibling `slate` checkout. It resolves the
+9
# web build, wraps the run in an ephemeral `guix shell` (the GTK4 + WebKitGTK +
+10
# Mesa graphics stack from gl-manifest.scm — no persistent profile), and launches
+11
# with --transparent so the window is transparency-CAPABLE.
+12
#
+13
# Transparency is opt-in-safe: with Slate's own translucency OFF (the default) the
+14
# window looks completely normal; toggle Slate translucency ON (command palette:
+15
# "Transparency: on") and the desktop shows through the frosted content.
+16
#
+17
# If the window never maps, re-run with LANTERN_SOFTWARE_RENDER=1 (the Mesa GL
+18
# loader-path gotcha) — it is passed through to the child if set in your env.
19
set -euo pipefail
20
21
HERE="$(cd "$(dirname "$0")" && pwd)"
13
SLATE_BUILD="${1:-$HERE/../task-lantern-system-slate/build/web}"
+22
SLATE_REPO="${1:-$HERE/../slate}"
+23
shift || true # remaining args pass through to `lantern run`
24
LANTERN_BIN="$HERE/build/native/bin/lantern"
25
26
[ -x "$LANTERN_BIN" ] || { echo "missing $LANTERN_BIN — run: sigil build lantern-cli --config native" >&2; exit 1; }
17
[ -f "$SLATE_BUILD/index.html" ] || { echo "no web build at $SLATE_BUILD" >&2; exit 1; }
27
+28
# Accept either a repo folder (…/build/web/index.html) or a web build dir directly.
+29
if [ -f "$SLATE_REPO/build/web/index.html" ]; then SLATE_BUILD="$SLATE_REPO/build/web"
+30
elif [ -f "$SLATE_REPO/index.html" ]; then SLATE_BUILD="$SLATE_REPO"
+31
else
+32
echo "no Slate web build found under: $SLATE_REPO" >&2
+33
echo " expected $SLATE_REPO/build/web/index.html (build Slate first: sigil build)" >&2
+34
exit 1
+35
fi
+36
# Normalize to an absolute path so the guix-shell child resolves it correctly.
+37
SLATE_BUILD="$(cd "$SLATE_BUILD" && pwd)"
+38
+39
echo "==> Slate repo: $SLATE_REPO"
40
echo "==> Slate build: $SLATE_BUILD"
41
echo "==> lantern: $LANTERN_BIN"
+42
echo "==> transparency: ON (toggle Slate translucency from its command palette)"
+43
44
exec guix shell -m "$HERE/gl-manifest.scm" -- bash -c '
45
export LD_LIBRARY_PATH="$GUIX_ENVIRONMENT/lib"
46
export LANTERN_LIB_DIR="$GUIX_ENVIRONMENT/lib"
24
exec "$0" run "$1" --title Slate
25
' "$LANTERN_BIN" "$SLATE_BUILD"
+47
bin="$1"; build="$2"; shift 2
+48
exec "$bin" run "$build" --title Slate --transparent "$@"
+49
' _ "$LANTERN_BIN" "$SLATE_BUILD" "$@"