Commit9c8776b0Recorded24 Jul 2026Repositorylantern

app: origin-aware nav handler for URL-origin apps (keep the app in-view)

Message

lantern-run installed default-nav-handler unconditionally, which returns 'external for any non-lantern:/about:/data:/blob: URI. For a URL-origin app (assets: an http(s) URL) that externalizes the app's OWN first load — the page opens in the system browser instead of the webview. The is-url path loaded a URL but never kept it in-view, so it was never actually usable.

Add make-url-nav-handler (allow the app's own origin + lantern:/about:/data:/blob:, externalize only genuinely different origins) + url-origin, and select it in lantern-run when is-url. This is the pivot-required "the Shell renders the node's origin" behavior: slate-shell --url http://localhost:<port> now renders the node PWA in the native window and only sends off-origin links to the browser.

Changed
 lantern/src/lantern/app.sgl | 48 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 46 insertions(+), 2 deletions(-)
Diff
lantern/src/lantern/app.sglmodified
@@ -173,6 +173,47 @@
173
(starts-with? assets "https://")
174
(starts-with? assets "file://"))))
175
+176
;; The origin (scheme://host[:port], no path) of a URL — everything up to the
+177
;; first '/' after the "scheme://". For a URL-origin app this is the string a
+178
;; same-origin navigation shares as a prefix.
+179
(define (url-origin url)
+180
(let ((sep (find-substring url "://")))
+181
(if (not sep)
+182
url
+183
(let ((rest-start (+ sep 3)))
+184
(let loop ((i rest-start))
+185
(cond
+186
((>= i (string-length url)) url)
+187
((char=? (string-ref url i) #\/) (substring url 0 i))
+188
(else (loop (+ i 1)))))))))
+189
+190
(define (find-substring s sub)
+191
(let ((n (string-length s)) (m (string-length sub)))
+192
(let loop ((i 0))
+193
(cond
+194
((> (+ i m) n) #f)
+195
((string=? (substring s i (+ i m)) sub) i)
+196
(else (loop (+ i 1)))))))
+197
+198
;; The nav handler for a URL-ORIGIN app (assets: an http(s)/file URL). The
+199
;; default handler externalizes every non-lantern:/about:/data:/blob: URI —
+200
;; which, for a URL app, wrongly sends the app's OWN first load to the system
+201
;; browser (a node-origin Slate would open in the user's browser instead of
+202
;; the window). This keeps the app's own origin (and its in-page navigations)
+203
;; in the view, and externalizes only genuinely different origins — the
+204
;; pivot's "the Shell renders the node's origin" behavior.
+205
(define (make-url-nav-handler app-url)
+206
(let ((origin (url-origin app-url)))
+207
(lambda (uri)
+208
(if (or (starts-with? uri "lantern:")
+209
(starts-with? uri "about:")
+210
(starts-with? uri "data:")
+211
(starts-with? uri "blob:")
+212
(= (string-length uri) 0)
+213
(starts-with? uri origin))
+214
'allow
+215
'external))))
+216
217
;; ---- the runner -------------------------------------------------------
218
219
(define (lantern-run app (keys: (title #f) (width 1024) (height 768)
@@ -216,8 +257,11 @@
257
(when (procedure? init) (init app))))
258
(dict-ref app plugins: (list)))
259
−219
;; Origin gate + load + show.
−220
(webview-set-nav-handler wv win default-nav-handler)
+260
;; Origin gate + load + show. A URL-origin app keeps its OWN origin in
+261
;; the view (make-url-nav-handler); the embedded lantern://app case uses
+262
;; the default handler (external http links open in the browser).
+263
(webview-set-nav-handler wv win
+264
(if is-url (make-url-nav-handler assets) default-nav-handler))
265
266
;; Opt-in page-driven close: hand a WM close-request to the page (an
267
;; `app.close-requested` event) and VETO the immediate close, so the page