Glaze builds desktop applications with a Racket backend and a Web frontend
rendered inside a native OS window. Windows uses WebView2, macOS uses
WKWebView, and Linux uses WebKitGTK.
Human-first. Agent-native. Local by design. New projects include an
agent instruction contract and native-window verification script. The CLI
exposes machine-readable project inspection and runtime diagnostics without
changing Glaze’s human-readable workflow.
$ raco pkg install --auto glaze |
$ raco glaze init myapp |
$ cd myapp |
$ raco glaze inspect --json |
$ raco glaze doctor --json |
$ raco glaze verify |
$ racket main.rkt |
# or: raco glaze dev |
A Glaze application is native-GUI only. If its native WebView cannot
start, application startup fails with platform-specific installation or repair
guidance. Glaze never substitutes a system-browser tab for the desktop window.
2 Application Lifecycle🔗ℹ
| (run-app | [ | #:public-dir public-dir | | | | | #:api api | | | | | #:port port | | | | | #:title title | | | | | #:width width | | | | | #:height height | | | | | #:events events | | | | | #:api-token api-token | | | | | #:capability capability | | | | | #:max-body-size max-body-size | | | | | #:on-close on-close | | | | | #:on-error on-error | | | | | #:check-update check-update | | | | | #:current-version current-version | | | | | #:app-id app-id | | | | | #:window-state window-state | | | | | #:transport transport | | | | | #:on-ready on-ready]) | |
|
|
| public-dir : (or/c string? path?) = "public" |
| api : (listof route?) = '() |
| port : (or/c #f exact-nonnegative-integer?) = #f |
| title : string? = "Glaze" |
| width : exact-positive-integer? = 1024 |
| height : exact-positive-integer? = 768 |
| events : (or/c #f event-bus?) = #f |
| api-token : (or/c #f string? #t) = #t |
| capability : (or/c #f capability?) = #f |
| max-body-size : exact-nonnegative-integer? = 8388608 |
| on-close : (-> any) = (lambda () (void)) |
| on-error : (or/c #f procedure?) = #f |
| check-update : (or/c #f string?) = #f |
| current-version : string? = "0.0.0" |
| app-id : (or/c #f string?) = #f |
| window-state : (or/c #f #t path-string?) = #f |
| transport : (or/c 'http 'native) = 'http |
| on-ready : procedure? = (lambda (wv url) (void)) |
The one-call application entry point. It selects a free loopback port unless
#:port is supplied, starts the static/API server, opens the native
WebView window, invokes on-ready with the webview? handle and
clean application URL, and blocks until the window closes.
When the native window closes, the local server is stopped and the procedure
returns (values 'webview shutdown). If native WebView startup fails,
Glaze first stops the local server and then propagates an actionable startup
error. There is intentionally no browser-fallback option.
The bridge is token-protected by default: #:api-token defaults to
#t, which generates a random capability token and uses a one-time
bootstrap URL to set an HttpOnly cookie for the embedded frontend, so a loopback
port is never an open API for other local processes or web pages. Pass an
explicit string to choose the token, or #:api-token #f to opt out
(dev tooling, trusted kiosk setups). #:max-body-size caps the request
body the server will buffer (413 above the limit). #:on-error receives
API handler failures. #:check-update wires an update manifest into the
application lifecycle.
#:transport 'native serves the page and API entirely inside the
WebView’s process boundary (Native Transport): the page loads
from the controlled glaze://app/ origin, invokes ride the WebView’s
message handler, no TCP port is bound, and no token is minted — the message
handler is the trust boundary. #:port is an error in this mode.
The default 'http transport keeps the loopback server and is the
development vehicle.
The lifecycle is exception-safe: an #:on-ready callback that raises
tears the window and the server down before propagating, and an
#:on-close hook that raises is reported through
#:on-error while the close still completes — a callback bug can
neither leak the server nor wedge the application.
When #:capability is supplied, run-app automatically creates
an API token if necessary. Every API route then requires a declared and granted
permission; this mode is default-deny.
With #:window-state #t, #:app-id selects a platform config
path where Glaze saves outer position, size, and maximized state at close and
restores them on the next launch. An explicit path may be supplied instead.
Stale geometry is clamped to the current virtual desktop so a disconnected
monitor cannot strand the window.
(make-api-token) → string?
|
Returns a random 32-hex-character capability token.
(current-api-token) → string?
|
| (current-api-token token) → void? |
| token : string? |
Bound by run-app so callbacks can read the active API token; the value
is the empty string when API-token protection is disabled.
The application is a state machine over a custodian:
'starting → 'ready → 'running →
'stopping → 'stopped. Every app owns a custodian, so
resources spawned for it (background threads such as the update check) are
reaped deterministically at quit. State transitions broadcast an
'app-state event on the bus when one is wired. With
#:quit-on-last-window? #f the app survives its last window closing
(tray-resident mode) and ends only when app-quit! is called;
additional windows attach through open-app-window.
(glaze-app? v) → boolean?
|
| v : any/c |
Recognizes the app handle.
(current-app) → (or/c #f glaze-app?)
|
| (current-app app) → void? |
| app : (or/c #f glaze-app?) |
Bound by run-app for the duration of the application; readable in
#:on-ready, callbacks, and tray menus.
(app-state [app]) → symbol?
|
| app : glaze-app? = (current-app) |
The lifecycle state.
(app-id [app]) → string?
|
| app : glaze-app? = (current-app) |
The application identity (#:app-id or generated).
(app-quit! [app]) → void?
|
| app : glaze-app? = (current-app) |
Quits the app: transitions to 'stopping, closes leftover windows,
stops the server, and reaps the app custodian.
| (open-app-window | | url | | | | | | | [ | #:title title | | | | | | | | #:width width | | | | | | | | #:height height | | | | | | | | #:window-state window-state | | | | | | | | #:on-close on-close]) | | → | | webview? |
|
| url : string? |
| title : string? = "Glaze" |
| width : exact-positive-integer? = 1024 |
| height : exact-positive-integer? = 768 |
| window-state : (or/c #f path-string?) = #f |
| on-close : (-> any) = (lambda () (void)) |
Opens a window attached to the running app. Requires current-app
to be bound (inside #:on-ready or a callback of a running app).
| (start-server | [ | #:port port | | | | | #:public-dir public-dir | | | | | #:api api | | | | | #:events events | | | | | #:api-token api-token | | | | | #:capability capability | | | | | #:serve-api-client? serve-api-client? | | | | | #:max-body-size max-body-size]) | |
|
| | → | | | exact-nonnegative-integer? | | procedure? |
|
|
| port : exact-nonnegative-integer? = 8080 |
| public-dir : (or/c string? path?) = "public" |
| api : (listof route?) = '() |
| events : (or/c #f event-bus?) = #f |
| api-token : (or/c #f string?) = #f |
| capability : (or/c #f capability?) = #f |
| serve-api-client? : boolean? = #t |
| max-body-size : exact-nonnegative-integer? = 8388608 |
Starts the loopback HTTP server that powers the embedded frontend. Static
resources, SPA index fallback, JSON routes, generated API client, and optional
SSE event stream share the same origin. The return values are the actual port
and a shutdown procedure. start-dev-server remains a compatibility
alias for this low-level server primitive; it does not define a browser-based
application mode.
The low-level server requires an explicit, non-empty #:api-token whenever
#:capability is supplied, keeping runtime authority bound to the
embedded WebView rather than an unauthenticated loopback caller.
Every request passes a Host-header check (DNS rebinding), a request-body
limit, and — for API routes and the event stream — a cross-site guard: a
foreign Origin header or a Sec-Fetch-Site value other than
"same-origin"/"none" is rejected with 403, which stops
other web pages from posting to the loopback bridge. Static file serving is
contained to #:public-dir, including through percent-encoded
.. segments and symlinks; route names that cannot be expressed in
the generated JavaScript client (collisions, invalid identifiers) fail at
startup. Handler exceptions answer a generic 500 body — the full exception
goes to the current-glaze-error-reporter, never to the page.
(stop-server shutdown-proc) → void?
|
| shutdown-proc : procedure? |
Stops the server.
(open-browser url) → void?
|
| url : string? |
Explicitly opens an external URL in the user’s default browser. This helper is
appropriate for documentation, OAuth, support pages, and similar external
resources. run-app, open-window, and open-webview
do not use it as a fallback.
4 JavaScript Bridge🔗ℹ
The embedded frontend calls Racket through ordinary same-origin HTTP requests.
This keeps the bridge easy to inspect and test with normal developer tools.
| (GET | | path | | | | | | | | handler | | | | | | | [ | #:permission permission | | | | | | | | #:resource resource]) | | → | | route? |
|
| path : string? |
| handler : procedure? |
| permission : (or/c #f symbol? string?) = #f |
| resource : (or/c #f procedure?) = #f |
| (POST | | path | | | | | | | | handler | | | | | | | [ | #:permission permission | | | | | | | | #:resource resource]) | | → | | route? |
|
| path : string? |
| handler : procedure? |
| permission : (or/c #f symbol? string?) = #f |
| resource : (or/c #f procedure?) = #f |
| (PUT | | path | | | | | | | | handler | | | | | | | [ | #:permission permission | | | | | | | | #:resource resource]) | | → | | route? |
|
| path : string? |
| handler : procedure? |
| permission : (or/c #f symbol? string?) = #f |
| resource : (or/c #f procedure?) = #f |
| (DELETE | | path | | | | | | | | handler | | | | | | | [ | #:permission permission | | | | | | | | #:resource resource]) | | → | | route? |
|
| path : string? |
| handler : procedure? |
| permission : (or/c #f symbol? string?) = #f |
| resource : (or/c #f procedure?) = #f |
The resource procedure receives the same request and captured path parameters
as the route handler. Its result is checked against the active scoped
permission before the handler can run. define-api-routes accepts the
same #:permission and #:resource options after its route path.
5 Runtime Capabilities🔗ℹ
(make-capability id permissions) → capability?
|
| id : string? |
| permissions : list? |
Creates a named authority from permission identifiers and scoped permissions.
(allow-permission id) → any/c
|
| id : (or/c symbol? string?) |
Creates an
unscoped permission grant. A bare symbol or string in
make-capability has the same meaning.
(scoped-permission id authorize) → any/c
|
| id : (or/c symbol? string?) |
| authorize : procedure? |
Creates a generic
resource permission. authorize receives the route resource and must
return a true value to authorize the request.
| (path-permission | | id | | | | | | | | #:allow allow-roots | | | | | | | [ | #:deny deny-roots]) | | → | | any/c |
|
| id : (or/c symbol? string?) |
| allow-roots : list? |
| deny-roots : list? = '() |
Allows resources inside the listed roots except those inside a denied root.
Paths are simplified through existing symlinks; deny entries take precedence.
The checked resource may be one path or a non-empty list of paths; every path
in a list must be authorized.
| (url-permission | | id | | | | | | | | #:allow allowed-patterns | | | | | | | [ | #:deny denied-patterns]) | | → | | any/c |
|
| id : (or/c symbol? string?) |
| allowed-patterns : list? |
| denied-patterns : list? = '() |
Allows URL resources that match an exact string or an explicitly supplied
regular expression. Deny patterns take precedence. Strings never gain implicit
wildcards.
| (command-permission | | id | | | | | | | | #:allow allow-programs | | | | | | | [ | #:deny deny-programs | | | | | | | | #:arguments arguments-ok?]) | | → | | any/c |
|
| id : (or/c symbol? string?) |
| allow-programs : list? |
| deny-programs : list? = '() |
| arguments-ok? : procedure? = (lambda (arguments) #t) |
Allows exact executable names or paths and optionally validates their argument
list. Deny entries take precedence.
(command-resource program arguments) → command-resource?
|
| program : path-string? |
| arguments : list? |
Constructs the
resource returned by a command route’s #:resource procedure.
| (accelerator-permission | | id | | | | | | | | #:allow allowed-patterns | | | | | | | | #:deny denied-patterns) | | → | | any/c |
|
| id : (or/c symbol? string?) |
| allowed-patterns : list? |
| denied-patterns : '() |
Allows global-shortcut accelerators that match an exact string or an
explicitly supplied regular expression. Both the declared patterns and the
requested accelerator are matched in canonical form, so
CmdOrCtrl+Shift+D and COMMANDORCONTROL+shift+d authorize
the same hotkey on the same platform. Plain strings must parse as
accelerators at construction time; deny entries take precedence.
| (capability-authorized? | | capability | | | | | | | | permission | | | | | | | [ | resource]) | | → | | boolean? |
|
| capability : capability? |
| permission : (or/c symbol? string?) |
| resource : any/c = #f |
Checks runtime
authority without invoking a route.
(current-capability-id) → (or/c #f string?)
|
| (current-capability-id id) → void? |
| id : (or/c #f string?) |
Bound to the active
capability ID while a protected route handler or resource extractor runs.
(current-capability-authorizer) → (or/c #f procedure?)
|
| (current-capability-authorizer authorize) → void? |
| authorize : (or/c #f procedure?) |
Bound to the active capability’s authorizer while a protected route resource
extractor or handler runs. This permits a handler to authorize resources that
are only discovered during execution, such as HTTP redirect targets.
| (current-capability-authorized? | | permission | | | | | | | [ | resource]) | | → | | boolean? |
|
| permission : (or/c symbol? string?) |
| resource : any/c = #f |
Checks
the dynamically active capability. Returns false outside a protected route.
6 Scoped Filesystem🔗ℹ
(make-filesystem-routes [#:prefix prefix]) → (listof route?)
|
| prefix : string? = "api/fs" |
Creates frontend routes for text and binary file I/O, directories, metadata,
existence checks, file copy, move, and removal. The routes declare
'fs:read or 'fs:write; use path-permission grants in
the active capability. Copy and move expose both paths as one resource, so both
source and destination must be in scope.
The default prefix generates client functions such as
glaze.api.fsReadText, glaze.api.fsWriteFile, and
glaze.api.fsMove. Binary payloads use base64 strings in JSON.
(fs-read-text path) → string?
|
| path : path-string? |
(fs-write-text! path text) → void?
|
| path : path-string? |
| text : string? |
Writes via a
same-directory temporary file and atomic replacement.
(fs-read-bytes path) → bytes?
|
| path : path-string? |
(fs-write-bytes! path data) → void?
|
| path : path-string? |
| data : bytes? |
Writes via a
same-directory temporary file and atomic replacement.
(fs-read-dir path) → (listof hash?)
|
| path : path-string? |
| (fs-create-dir! | | path | | | | | | | [ | #:recursive? recursive?]) | | → | | void? |
|
| path : path-string? |
| recursive? : boolean? = #t |
(fs-remove! path [#:recursive? recursive?]) → void?
|
| path : path-string? |
| recursive? : boolean? = #f |
| (fs-copy! | | source | | | | | | | | destination | | | | | | | [ | #:replace? replace?]) | | → | | void? |
|
| source : path-string? |
| destination : path-string? |
| replace? : boolean? = #f |
| (fs-move! | | source | | | | | | | | destination | | | | | | | [ | #:replace? replace?]) | | → | | void? |
|
| source : path-string? |
| destination : path-string? |
| replace? : boolean? = #f |
(fs-stat path) → hash?
|
| path : path-string? |
(fs-exists? path) → boolean?
|
| path : path-string? |
7 Scoped Shell and Child Processes🔗ℹ
| (make-shell-routes | [ | #:prefix prefix | | | | | #:max-output max-output | | | | | #:max-processes max-processes | | | | | #:cwd-roots cwd-roots | | | | | #:allow-environment allowed-environment | | | | | #:retention-seconds retention-seconds]) | |
|
| → (listof route?) |
| prefix : string? = "api/shell" |
| max-output : exact-positive-integer? = (* 1024 1024) |
| max-processes : exact-positive-integer? = 32 |
| cwd-roots : (listof path-string?) = '() |
| allowed-environment : list? = '() |
| retention-seconds : positive-real? = 300 |
Creates frontend routes for direct command execution. 'shell:execute
authorizes command-resource values before synchronous or background
execution; 'shell:manage authorizes background status, stdin, and kill
operations. Handles are additionally bound to the capability that spawned
them. Programs are never passed through a system shell. Frontend working
directories and environment-variable names are denied by default; applications
must opt in with cwd-roots and allowed-environment. The
registry retains at most max-processes handles, evicting completed
handles first.
The default prefix generates glaze.api.shellOutput,
shellSpawn, shellStatus, shellWrite,
shellCloseStdin, and shellKill. Completed handles remain
available for retention-seconds.
| (shell-output | | program | | | | | | | [ | arguments | | | | | | | | #:cwd cwd | | | | | | | | #:env environment | | | | | | | | #:timeout timeout | | | | | | | | #:max-output max-output]) | | → | | hash? |
|
| program : path-string? |
| arguments : (listof string?) = '() |
| cwd : (or/c #f path-string?) = #f |
| environment : (or/c #f hash?) = #f |
| timeout : (or/c #f positive-real?) = 30 |
| max-output : exact-positive-integer? = (* 1024 1024) |
Executes a program directly, closes stdin, waits up to the
timeout, and returns exit status plus bounded UTF-8 stdout and stderr.
| (shell-spawn! | | program | | | | | | | [ | arguments | | | | | | | | #:cwd cwd | | | | | | | | #:env environment | | | | | | | | #:max-output max-output]) | | → | | shell-process? |
|
| program : path-string? |
| arguments : (listof string?) = '() |
| cwd : (or/c #f path-string?) = #f |
| environment : (or/c #f hash?) = #f |
| max-output : exact-positive-integer? = (* 1024 1024) |
Starts a background child process without invoking a
system shell.
(shell-process-id child) → string?
|
| child : shell-process? |
(shell-process-pid child) → exact-integer?
|
| child : shell-process? |
(shell-process-info child) → hash?
|
| child : shell-process? |
(shell-process-write! child data) → void?
|
| child : shell-process? |
| data : (or/c string? bytes?) |
(shell-process-close-input! child) → void?
|
| child : shell-process? |
(shell-process-kill! child [force?]) → void?
|
| child : shell-process? |
| force? : boolean? = #t |
(shell-process-wait child [timeout]) → boolean?
|
| child : shell-process? |
| timeout : (or/c #f nonnegative-real?) = #f |
8 Persistent JSON Store🔗ℹ
| (make-store-routes | | #:root root | | | | | | | [ | #:prefix prefix | | | | | | | | #:defaults defaults | | | | | | | | #:auto-save auto-save | | | | | | | | #:events events]) | | → | | (listof route?) |
|
| root : path-string? |
| prefix : string? = "api/store" |
| defaults : hash? = (hasheq) |
| auto-save : (or/c boolean? nonnegative-real?) = 100 |
| events : (or/c #f event-bus?) = #f |
Creates capability-gated frontend routes for persistent JSON key-value stores.
Read operations declare 'store:read and mutations declare
'store:write. Frontend paths must be relative and cannot escape the
required root. Mutations optionally publish
'store:change on events. Auto-save uses a debounce interval in
milliseconds; #f disables it.
The generated client includes storeLoad, storeGet,
storeSet, storeHas, storeDelete,
storeClear, storeReset, storeKeys,
storeValues, storeEntries, storeLength,
storeSave, storeReload, and storeClose.
| (load-store | | path | | | | | | | [ | #:defaults defaults | | | | | | | | #:auto-save auto-save | | | | | | | | #:create-new? create-new? | | | | | | | | #:override-defaults? override-defaults?]) | | → | | store? |
|
| path : path-string? |
| defaults : hash? = (hasheq) |
| auto-save : (or/c boolean? nonnegative-real?) = 100 |
| create-new? : boolean? = #f |
| override-defaults? : boolean? = #f |
Loads a JSON object from disk or creates an in-memory store
from defaults. The file is created on the first save.
(store-get storage key [default]) → any/c
|
| storage : store? |
| key : (or/c string? symbol?) |
| default : any/c = #f |
(store-set! storage key value) → void?
|
| storage : store? |
| key : (or/c string? symbol?) |
| value : jsexpr? |
(store-has-key? storage key) → boolean?
|
| storage : store? |
| key : (or/c string? symbol?) |
(store-delete! storage key) → boolean?
|
| storage : store? |
| key : (or/c string? symbol?) |
(store-clear! storage) → void?
|
| storage : store? |
(store-reset! storage) → void?
|
| storage : store? |
(store-keys storage) → (listof string?)
|
| storage : store? |
(store-values storage) → list?
|
| storage : store? |
(store-entries storage) → list?
|
| storage : store? |
(store-count storage) → exact-nonnegative-integer?
|
| storage : store? |
(store-snapshot storage) → hash?
|
| storage : store? |
(store-save! storage) → void?
|
| storage : store? |
| (store-reload! | | storage | | | | | | | [ | #:ignore-defaults? ignore-defaults?]) | | → | | void? |
|
| storage : store? |
| ignore-defaults? : boolean? = #f |
(store-close! storage) → void?
|
| storage : store? |
9 Capability-Gated System Plugins🔗ℹ
(make-system-routes [#:prefix prefix]) → (listof route?)
|
| prefix : string? = "api/system" |
Creates frontend routes for clipboard text, desktop notifications, opening or
revealing paths, opening URLs, and OS information. The routes use distinct
permissions: 'clipboard:read, 'clipboard:write,
'notification:send, 'opener:open-path,
'opener:reveal-path, 'opener:open-url, 'os:read,
and 'os:hostname. Path operations should use path-permission
grants and URL opening should use url-permission. Resource scopes are
checked before native handlers run.
The default prefix generates systemClipboardRead,
systemClipboardWrite, systemNotificationSend,
systemOpenerOpenPath, systemOpenerRevealPath,
systemOpenerOpenUrl, systemOsInfo, and
systemOsHostname.
(system-information) → hash?
|
Returns platform, OS type, family,
architecture, executable extension, locale, and the runtime-reported system
version string.
(system-hostname) → string?
|
Returns the local host name. Frontend access
uses the separate 'os:hostname permission.
10 Global Shortcuts🔗ℹ
System-wide hotkeys with Tauri-style accelerators such as
CmdOrCtrl+Shift+D. The native backends are Win32
RegisterHotKey on Windows, Carbon RegisterEventHotKey on
macOS, and X11 XGrabKey on Linux (XWayland sessions included; a
Wayland session without X fails registration rather than raising). Linux
grabs use exact modifiers, so pressed lock keys such as Caps Lock suppress
the event there.
(string->hotkey s) → (or/c hotkey? #f)
|
| s : string? |
Parses an accelerator. Modifier spellings are case-insensitive:
Command/Cmd/Super/Meta,
Control/Ctrl, Option/Alt,
Shift, and the per-platform CommandOrControl/
CmdOrCtrl (cmd on macOS, ctrl elsewhere). Keys accept bare and
KeyA/Digit7/Code-style spellings, F1 through
F24, numpad keys, and named keys such as Space or
BracketLeft. Returns #f for any malformed input.
(hotkey->string hk) → string?
|
| hk : hotkey? |
Renders the canonical form,
for example Ctrl+Shift+D on Windows and Linux.
(hotkey-register! hk thunk) → boolean?
|
| hk : hotkey? |
| thunk : procedure? |
Registers a system-wide hotkey; the thunk runs each time the combination is
pressed anywhere in the session. Duplicate registrations report #f.
hotkey-unregister!, hotkey-unregister-all!, and
hotkey-registered? manage the lifecycle.
| (make-global-shortcut-routes | [ | #:prefix prefix | | | | | #:backend backend | | | | | #:events event-bus]) | |
|
| → (listof route?) |
| prefix : string? = "api/global-shortcut" |
| backend : hotkey-backend? = default-hotkey-backend |
| event-bus : (or/c event-bus? #f) = #f |
Creates register, unregister, unregister-all, and is-registered routes under
the permissions 'global-shortcut:register,
'global-shortcut:unregister,
'global-shortcut:unregister-all, and
'global-shortcut:is-registered; pair them with
accelerator-permission scopes so a page can only grab the
combinations the app declared. Triggers reach the page as
global-shortcut SSE events carrying the canonical accelerator. The
generated client contains glaze.api.globalShortcutRegister,
glaze.api.globalShortcutUnregister,
glaze.api.globalShortcutUnregisterAll, and
glaze.api.globalShortcutIsRegistered.
11 Structured Logging🔗ℹ
Tauri-plugin-log-style logging: one logger fans structured records out to
sinks with per-sink minimum levels, keeps a bounded in-memory history, and
pushes records to the page as log SSE events when an event bus is
attached.
| (make-glaze-logger | [ | #:min-level min-level | | | | | | | | #:sinks sinks | | | | | | | | #:file-root file-root | | | | | | | | #:events event-bus | | | | | | | | #:history history-limit]) | | → | | glaze-logger? |
|
| min-level : symbol? = 'info |
| sinks : list? = '() |
| file-root : (or/c #f path-string?) = #f |
| event-bus : (or/c event-bus? #f) = #f |
| history-limit : (or/c #f exact-positive-integer?) = 1000 |
Creates a logger with a stderr sink plus the optional rotating file sink
(#:file-root), SSE push (#:events), and custom sinks.
Records below min-level are dropped everywhere; each sink can
additionally filter by its own minimum level. log-trace,
log-debug, log-info, log-warn, and
log-error emit records with optional #:data jsexprs.
(glaze-logger-history logger [limit]) → (listof log-record?)
|
| logger : glaze-logger? |
| limit : exact-positive-integer? = 100 |
Returns the most recent records, oldest first.
| (make-file-log-sink | | root | | | | | | | [ | #:name name | | | | | | | | #:max-bytes max-bytes | | | | | | | | #:keep keep | | | | | | | | #:min-level min-level]) | | → | | log-sink? |
|
| root : path-string? |
| name : string? = "glaze.log" |
| max-bytes : exact-positive-integer? = (* 1024 1024) |
| keep : exact-nonnegative-integer? = 3 |
| min-level : symbol? = 'trace |
A rotating file sink: when a line would push the current file past
max-bytes, files shift (name.k-1 becomes
name.k, the oldest is dropped) instead of truncating, so a crash
mid-write never destroys earlier logs.
(make-log-routes logger [#:prefix prefix]) → (listof route?)
|
| logger : glaze-logger? |
| prefix : string? = "api/log" |
Creates a 'log:write-gated write route and a
'log:read-gated bounded history route. Frontend records are
tagged 'frontend with the active capability ID, so a page cannot
forge backend log lines, and they respect the logger’s minimum level
exactly like backend records. The generated client contains
glaze.api.logWrite and glaze.api.logHistory.
12 Application and Resource Paths🔗ℹ
| (make-path-resolver | | app-id | | | | | #:resource-root resource-root | | | | [ | #:app-directories-override overrides]) | |
|
| → path-resolver? |
| app-id : string? |
| resource-root : path-string? |
| overrides : (or/c #f path-string? hash?) = #f |
Creates a resolver for Tauri-style application directories. A path override
uses one portable root: config/data/local-data use the root, cache uses its
caches child, and log uses logs. A hash may override
'config, 'data, 'localData, 'cache, and
'log individually. Overrides accept the documented $HOME,
$DATA, $LOCALDATA, and other base-directory variables.
| (make-path-routes | | #:app-id app-id | | | | | #:resource-root resource-root | | | | [ | #:app-directories-override overrides | | | | | #:prefix prefix]) | |
|
| → (listof route?) |
| app-id : string? |
| resource-root : path-string? |
| overrides : (or/c #f path-string? hash?) = #f |
| prefix : string? = "api/path" |
Creates granular, default-deny frontend routes for application/user/resource
directories and path utilities. Resource resolution is confined to the
configured resource root even through existing symbolic links.
(app-config-dir resolver) → path?
|
| resolver : path-resolver? |
(app-data-dir resolver) → path?
|
| resolver : path-resolver? |
(app-local-data-dir resolver) → path?
|
| resolver : path-resolver? |
(app-cache-dir resolver) → path?
|
| resolver : path-resolver? |
(app-log-dir resolver) → path?
|
| resolver : path-resolver? |
(resource-dir resolver) → path?
|
| resolver : path-resolver? |
(resolve-resource resolver relative-path) → path?
|
| resolver : path-resolver? |
| relative-path : path-string? |
(path-join path ...) → path?
|
| path : path-string? |
(path-resolve path ...) → path?
|
| path : path-string? |
(path-normalize path) → path?
|
| path : path-string? |
(path-basename path) → string?
|
| path : path-string? |
(path-dirname path) → string?
|
| path : path-string? |
(path-extname path) → string?
|
| path : path-string? |
(path-absolute? path) → boolean?
|
| path : path-string? |
13 Scoped HTTP Client🔗ℹ
| (http-request | | url | | | | | | | [ | #:method method | | | | | | | | #:headers headers | | | | | | | | #:body body | | | | | | | | #:timeout timeout | | | | | | | | #:max-request-bytes max-request-bytes | | | | | | | | #:max-response-bytes max-response-bytes | | | | | | | | #:max-redirects max-redirects | | | | | | | | #:authorize-url? authorize-url?]) | | → | | hash? |
|
| url : string? |
| method : (or/c string? symbol? bytes?) = "GET" |
| headers : hash? = (hasheq) |
| body : (or/c #f string? bytes?) = #f |
| timeout : real? = 30 |
| max-request-bytes : exact-positive-integer? = (* 10 1024 1024) |
| | max-response-bytes | | : | | exact-positive-integer? | | | | | = | | (* 10 1024 1024) |
|
| max-redirects : exact-nonnegative-integer? = 5 |
| authorize-url? : procedure? = (lambda (candidate) #t) |
Performs a bounded HTTP or HTTPS request and returns the status, final URL,
headers, UTF-8 replacement-decoded text, and base64 body. The timeout is a
total deadline across redirects. Every URL, including every redirect target,
is checked before connecting. Cross-origin redirects remove
Authorization and Cookie.
| (make-http-routes | [ | #:prefix prefix | | | | | #:max-request-bytes max-request-bytes | | | | | #:max-response-bytes max-response-bytes | | | | | #:timeout timeout | | | | | #:max-redirects max-redirects]) | |
|
| → (listof route?) |
| prefix : string? = "api/http" |
| max-request-bytes : exact-positive-integer? = (* 10 1024 1024) |
| | max-response-bytes | | : | | exact-positive-integer? | | | | | = | | (* 10 1024 1024) |
|
| timeout : real? = 30 |
| max-redirects : exact-nonnegative-integer? = 5 |
Creates 'http:request-protected frontend access. The generated client
contains glaze.api.httpRequest. Initial and redirected URLs are
checked against the active URL-scoped capability before connecting. Frontend
callers may lower configured limits but cannot raise them. Connection-managed
headers such as Host and Content-Length are rejected.
14 Scoped SQLite🔗ℹ
| (open-sqlite-database | | path | | | | [ | #:busy-retry-limit busy-retry-limit]) | |
|
| → sql-database? |
| path : path-string? |
| busy-retry-limit : exact-nonnegative-integer? = 1000 |
Opens or creates a SQLite database. The parent
directory must already exist.
| (sql-select | | database | | | | | | | | statement | | | | | | | [ | parameters | | | | | | | | #:max-rows max-rows | | | | | | | | #:max-cell-bytes max-cell-bytes]) | | → | | (listof hash?) |
|
| database : sql-database? |
| statement : string? |
| parameters : list? = '() |
| max-rows : exact-positive-integer? = 10000 |
| max-cell-bytes : exact-positive-integer? = (* 10 1024 1024) |
Runs a parameterized SELECT or WITH query. Results are
bounded by row count. SQL NULL becomes JSON null and BLOB values become hashes
containing 'blobBase64.
(sql-execute! database statement [parameters]) → hash?
|
| database : sql-database? |
| statement : string? |
| parameters : list? = '() |
Runs a parameterized statement and returns
'rowsAffected and 'lastInsertId.
(sql-close! database) → void?
|
| database : sql-database? |
Closes the connection.
Repeated close calls are harmless.
| (make-sql-routes | | #:root root | | | | [ | #:prefix prefix | | | | | #:max-connections max-connections | | | | | #:max-rows max-rows | | | | | #:max-cell-bytes max-cell-bytes | | | | | #:busy-retry-limit busy-retry-limit]) | |
|
| → (listof route?) |
| root : path-string? |
| prefix : string? = "api/sql" |
| max-connections : exact-positive-integer? = 16 |
| max-rows : exact-positive-integer? = 10000 |
| max-cell-bytes : exact-positive-integer? = (* 10 1024 1024) |
| busy-retry-limit : exact-nonnegative-integer? = 1000 |
Creates 'sql:load, 'sql:select, 'sql:execute, and
'sql:close protected routes. Database paths are relative to
root, are checked through existing symbolic links, and connections
are cached separately for each active capability. The generated client
contains sqlLoad, sqlSelect, sqlExecute, and
sqlClose.
A route handler receives the web-server request followed by any captured
:param path values. Returning a jsexpr produces a JSON 200 response;
a full response value may also be returned, including a streaming response
(streaming-response / event-stream-response below).
(request-json-body req) → jsexpr?
|
| req : request? |
Parses a JSON request body. Missing, empty, or malformed input yields an empty
hash so route validation can produce a clean client error. JSON object keys in
Racket jsexprs are symbols, for example (hash-ref body 'delta).
(json-response data) → response?
|
| data : jsexpr? |
(api-response data) → response?
|
| data : jsexpr? |
(error-response status message) → response?
|
| status : exact-nonnegative-integer? |
| message : string? |
| (streaming-response | | writer | | | | | | | [ | #:mime mime | | | | | | | | #:headers headers]) | | → | | response? |
|
| writer : (-> output-port? any) |
| mime : bytes? = #"application/octet-stream" |
| headers : (listof header?) = '() |
A chunked 200 response. writer runs on the connection thread after
the status line and headers go out — the same mechanism as the built-in
/glaze/events SSE endpoint. Write to the port and call
(flush-output out) after each chunk that should be delivered
immediately; returning from the writer ends the response.
A handler that raises before returning still maps to a 500 JSON (nothing is
on the wire yet); an exception inside the writer closes the
connection mid-stream, so wrap your own errors there if truncation is
unacceptable. The typical use is proxying a streaming LLM endpoint — tokens
reach the page as they arrive, API keys stay in Racket, and the page never
makes a cross-origin call:
| (GET "api/llm/chat" |
| (lambda (req) |
| (streaming-response |
| #:mime #"application/x-ndjson" |
| (lambda (out) |
| (for ([delta (in-llm-deltas (request-json-body req))]) |
| (displayln (jsexpr->string (hasheq 'delta delta)) out) |
| (flush-output out)))))) |
| (event-stream-response | | sender | | | | | | | [ | #:headers headers]) | | → | | response? |
|
| sender : procedure? |
| headers : (listof header?) = '() |
The SSE flavor of streaming-response: sender receives a
send callback, and each (send name data) emits one
event: name\ndata: <json> |
frame and flushes. Consuming with
EventSource in the page works out of the box;
Cache-Control: no-cache is added automatically.
| (GET "api/sse/demo" |
| (lambda (req) |
| (event-stream-response |
| (lambda (send) |
| (send 'delta (hasheq 'text "Hel")) |
| (send 'delta (hasheq 'text "lo")) |
| (send 'done (hasheq 'ok #t)))))) |
(define-api-routes id clause ...)
|
Declares a callable Racket procedure, a validated HTTP route, and a generated
JavaScript client entry from one route clause.
| (define-api-routes api |
| [(POST "api/counter/bump") |
| (bump [delta exact-nonnegative-integer? 1]) |
| (hasheq 'count (add1 delta))]) |
The generated /glaze/api.js exposes route-specific functions plus
glaze.call(...) and glaze.on(...).
Glaze uses same-origin Server-Sent Events for backend-to-frontend push.
(make-event-bus) → event-bus?
|
Creates a broadcast event bus.
(bus-broadcast! bus name data) → void?
|
| bus : event-bus? |
| name : (or/c symbol? string?) |
| data : jsexpr? |
Broadcasts an event without blocking the producer. When a slow subscriber’s
backlog (256 events) is full, the subscriber’s OLDEST event is dropped to
make room — a lagging UI wants the freshest state. Drops are never silent:
bus-dropped-count reports the total and the first drop in a burst is
reported through current-event-drop-reporter (at most once every
five seconds per bus).
(bus-subscribe! bus) → async-channel?
|
| bus : event-bus? |
(bus-unsubscribe! bus channel) → void?
|
| bus : event-bus? |
| channel : async-channel? |
(bus-wait channel [seconds]) → any/c
|
| channel : async-channel? |
| seconds : real? = 10 |
(bus-dropped-count bus) → exact-nonnegative-integer?
|
| bus : event-bus? |
(current-event-drop-reporter)
|
| → (-> exact-nonnegative-integer? (or/c symbol? string?) any) |
| (current-event-drop-reporter reporter) → void? |
| reporter : (-> exact-nonnegative-integer? (or/c symbol? string?) any) |
16 GLZ1 Bridge Protocol🔗ℹ
GLZ1 is Glaze’s versioned bridge protocol: a request envelope with an id, a
closed typed-error taxonomy, cancellation, and timeouts, wrapped around the
registered routes. The HTTP endpoints are GET /glaze/hello,
POST /glaze/invoke, and POST /glaze/cancel; the generated api.js
exposes glaze.hello() / glaze.invoke(path, args, opts) /
glaze.cancel(id). The envelope layer is transport-agnostic — the native
message-handler transport (Native Transport) dispatches the same
envelopes through the WebView’s message handler, with no TCP port.
The protocol is aligned with keel,
the transport-neutral application contract shared with Rivet: every invoke
response names its terminal 'outcome
("succeeded" / "failed" / "cancelled" /
"timed-out"); the error object is keel-shaped (kebab-case code,
message, data); /glaze/hello carries a keel app-schema rendering of
the route surface; and the app lifecycle state machine validates every
transition against keel’s lifecycle contract.
glz1-version : exact-nonnegative-integer?
|
The protocol version (currently 1).
(bridge-error-code? v) → boolean?
|
| v : any/c |
Recognizes the closed error-taxonomy codes.
(raise-bridge-error code [message data]) → any
|
| code : bridge-error-code? |
| message : (or/c #f string?) = #f |
| data : any/c = #f |
Raise inside an invoke handler to answer a typed bridge error; data
carries structured, non-sensitive detail — never exception text.
(struct exn:fail:glaze:bridge (code data))
|
| code : bridge-error-code? |
| data : any/c |
(bridge-cancel-event) → (or/c #f semaphore?)
|
The cancel event of the in-flight request, or #f outside an invoke.
Syncing on it blocks until POST /glaze/cancel fires for this request.
(bridge-cancelled?) → boolean?
|
Non-blocking cancellation poll. A handler that accepts the request should
raise (raise-bridge-error 'cancelled "request cancelled") so its
invoke response owns the "cancelled" terminal outcome. Returning a
normal value remains a successful completion even if cancellation was
observed.
16.1 Streaming responses🔗ℹ
A streaming handler (streaming-response /
event-stream-response) cannot be one invoke envelope — its bytes
arrive over time. GLZ1 delivers it as a frame sequence on the same channel:
an open frame marks the request id as a stream, one chunk frame carries each
piece of decoded text (window.__glz1Chunk(id, text)), and a terminal
frame (window.__glz1End(id)) settles the invoking promise with the
full joined text. A handler that fails before returning the
streaming response still answers a normal typed-error envelope, so both
terminal shapes resolve the same page-side promise.
The page client exposes glaze.invokeStream(path, args, opts):
opts.onDelta fires per chunk as it arrives, and opts.signal (an
AbortSignal) rejects the promise immediately and sends the cancel op, which
closes the stream’s pipe server side. Streams carry no timeout — they
settle when the writer finishes. Chunk boundaries respect UTF-8: a
multi-byte character split across internal reads is held back until it
completes rather than delivered corrupted. A writer exception truncates the
stream (the terminal frame still arrives), mirroring the HTTP adapter’s
connection close.
Every frame travels in both carriers like a full response: a
self-contained JavaScript snippet for evaluateJavaScript-style hosts
(WKWebView, WebKitGTK), and a {glz, stream, id[, text]} JSON object
for PostWebMessageAsJson-style hosts (WebView2).
17 Layer Modules🔗ℹ
Beyond the curated glaze/core face, every facade name is
reachable through its aggregation layer. Applications that want one
surface without the full glaze facade require the layer
directly; each layer’s export set is guarded by CI, so drift fails
instead of widening silently.
Windows, webview handles, and window state
(
glaze/webview/main and
glaze/window-state
re-exported).
The capability services: filesystem scopes,
the key-value store, SQL, shell, the HTTP proxy, logging, dialogs, tray,
system commands, global shortcuts, hotkeys, system information, and the
browser opener.
Packaging, signing, updates, and bundled
assets — everything that carries an application from a source tree to an
installed, self-updating product.
Development-time tooling: the loopback dev
server (the HTTP adapter for hot reload and curl) and FFI discovery
diagnostics.
Capabilities that are shipped but NOT
frozen:
glaze/deeplink (OS URL-scheme handover) and
glaze/autolaunch (login items). The surface may change in
any release without a compat baseline; a module graduates into its layer
after surviving a release cycle unchanged under real product usage.
18 Typed Command Declarations🔗ℹ
Typed declarations give a route’s parameters and results one expression
that both types them in the keel command schema and validates the wire
value — the declaration is the validation. Descriptors are accepted
anywhere define-api-routes takes a predicate:
| (define Item (grecord 'Item "id" GInt64 "name" GString)) |
| |
| (define-api-routes api |
| [(POST "api/counter/bump") |
| (bump [delta GInt64 1]) |
| (begin (bump! delta) (hasheq 'count (unbox count)))] |
| [(GET "api/items/:id" #:returns Item) |
| (item id) |
| (hasheq 'id id)]) |
The scalar descriptors are GNull, GBool,
GInt64, GFloat64, GString, GBytes
(a base64 string on the wire), and GValue (any jsexpr).
Combinators are glist, goptional, and gmap.
Named types — (grecord 'Item "id" GInt64 ...) and
(genum 'Color 'red 'green 'blue) — are referenced by value:
using Item inside another declaration is the reference,
and named types render as TypeScript interfaces and unions.
Wire conventions: null is JSON null, unknown object keys are
tolerated so newer clients keep working against older apps, and a wire
value that fails its descriptor is a typed invalid-args error
naming the parameter — both transports share the same validator
(validate-type / validate-jsexpr).
(grecord name field type ... ...) → grecord?
|
| name : symbol? |
| field : string? |
| type : type-descriptor? |
(genum name case ...) → genum?
|
| name : symbol? |
| case : symbol? |
(glist element) → type-descriptor?
|
| element : type-descriptor? |
(goptional element) → type-descriptor?
|
| element : type-descriptor? |
(gmap value-type) → type-descriptor?
|
| value-type : type-descriptor? |
| (validate-type descriptor value) | | → | | |
|
| descriptor : type-descriptor? |
| value : jsexpr? |
Wire check; on failure the second value names the offending position.
| (validate-jsexpr type value) | | → | | |
|
| type : any/c |
| value : jsexpr? |
The same check for plain keel types, used by the invoke pipeline for
schema commands declared without descriptors.
Declared types surface in three places automatically: /glaze/hello
carries real keel types for every typed command,
/glaze/api.d.ts (served by both transports) carries the generated
TypeScript client declaration —
generate-ts-declarations renders it — and
glaze/schema-compat classifies an old app-schema against a new
one:
(schema-compat? v) → boolean?
|
| v : any/c |
(schema-compat-report old new) → (hash/c symbol? any/c)
|
| old : any/c |
| new : any/c |
Deterministic compatibility report between two app schemas: every
breaking change (removed command, renamed parameter, changed wire type)
is named, so raco glaze schema check can gate a release.
(command-compatible? old new) → boolean?
|
| old : any/c |
| new : any/c |
19 Native Transport🔗ℹ
The native transport serves the app’s page and API entirely inside the
WebView’s process boundary: the page loads from a controlled origin
(glaze://app/ on macOS and Linux, http://glaze.localhost/ on
Windows, because WebView2 refuses top-level navigation to custom schemes)
and invokes the backend through the WebView’s message handler — production
binds no TCP port. GLZ1 envelopes ride the message handler in both
directions, so typed errors, timeouts, cancellation, capability checks, and
streaming work identically on both transports; the HTTP adapter remains the
development vehicle, and an app’s existing <script src="/glaze/api.js">
works unchanged because the scheme handler serves the native client at the
same path.
| (start-native-transport | | #:api routes | | | | [ | #:authority authority | | | | | #:events bus | | | | | #:public-dir public-dir]) | |
|
| → native-transport? |
| routes : (listof route?) |
| authority : (or/c #f capability?) = #f |
| bus : (or/c #f event-bus?) = #f |
| public-dir : path-string? = "public" |
Starts one transport. #:api routes dispatch through the GLZ1
envelope layer exactly as over HTTP. #:events subscribes the bus so
broadcasts reach the page as sequenced batches. #:public-dir backs
the controlled scheme’s static resources with the same symlink containment
and index.html fallback as the HTTP adapter. The dispatcher inherits
the caller’s parameterization (custodian, error reporter) and spawns one
thread per message, so a slow or timing-out invoke never blocks other
invokes or cancels.
(native-transport-message! t raw) → void?
|
| t : native-transport? |
| raw : string? |
Entry point the platform wiring calls with each message-handler payload,
from the UI thread; never blocks. Payloads queue on an async channel and
each dispatches on its own thread.
(struct native-delivery (js json))
|
| js : string? |
| json : string? |
One delivery to the page in both carriers: native-delivery-js is a
self-contained JavaScript snippet for evaluateJavaScript-style hosts
(WKWebView, WebKitGTK); native-delivery-json is the bare JSON text
for PostWebMessageAsJson-style hosts (WebView2). Host wiring picks its
carrier and hops it to the UI thread.
(native-transport-set-deliver! t deliver!) → void?
|
| t : native-transport? |
| deliver! : (-> native-delivery? any) |
Installs the host’s delivery hook once a WebView handle exists.
deliver! runs on transport threads and must hop to the UI thread.
(native-transport-stats t)
|
| → (hash/c symbol? exact-nonnegative-integer?) |
| t : native-transport? |
Counters: resources-served, messages-received,
responses-delivered, events-delivered.
(native-transport-count-resource! t) → void?
|
| t : native-transport? |
Called by the host’s scheme handler for each served resource, so tests can
observe that the page really loaded through the controlled origin.
(native-transport-shutdown! t) → void?
|
| t : native-transport? |
Stops the dispatcher and event threads. Idempotent.
(native-app-origin) → string?
|
The controlled origin the page runs on, per platform — for tests and
assertions.
(native-scheme-segments url-str) → (or/c #f (listof string?))
|
| url-str : string? |
Parses a controlled-scheme URL
(glaze://app/sub/x.js?query#frag) into percent-decoded path segments,
accepting the Windows http://glaze.localhost/... shape as well. A
hostile or malformed URL produces #f, which hosts map to their 404
path.
native-bootstrap-js : string?
|
The page-side bootstrap, injected at document start by every platform’s
wiring. Defines the delivery plumbing (__glz1Deliver, the streaming
hooks) and a transport-level client (__glz1.invoke /
invokeStream / cancel / hello / on); platform
differences are confined to the sink selection (WebKit message handlers
versus WebView2 postMessage).
(native-api-client-js routes) → string?
|
| routes : (listof route?) |
The native twin of the HTTP api.js, served by the scheme handler at
/glaze/api.js: binds glaze.call / glaze.invoke /
glaze.invokeStream / glaze.cancel / glaze.hello /
glaze.on and one generated function per route over the transport
bindings.
(generate-api-declarations routes) → string?
|
| routes : (listof route?) |
Renders the TypeScript declarations for the app’s typed routes (routes that
declare #:body-types/#:returns/#:errors schemas and their
glaze/types descriptors), served by the scheme handler at
/glaze/api.d.ts. Function names match the generated client.
20 Native WebView🔗ℹ
The native WebView is an application prerequisite, not an optional rendering
mode. The backends are WebView2 on Windows, WKWebView on macOS, and WebKitGTK
on Linux.
| (open-window | | url | | | | | | | [ | #:title title | | | | | | | | #:width width | | | | | | | | #:height height | | | | | | | | #:devtools? devtools? | | | | | | | | #:window-state window-state | | | | | | | | #:on-close on-close]) | | → | | webview? |
|
| url : string? |
| title : string? = "Glaze" |
| width : exact-positive-integer? = 1024 |
| height : exact-positive-integer? = 768 |
| devtools? : boolean? = #f |
| window-state : (or/c #f path-string?) = #f |
| on-close : (-> any) = (lambda () (void)) |
Opens a native desktop window and loads url. If the backend or its
runtime dependency is unavailable, this procedure raises. Before raising in
an interactive desktop process, Glaze also attempts to show an OS-level error
dialog so packaged GUI applications without a console still give the user an
actionable explanation. CI environments suppress the dialog and retain the
exception text in logs.
There is no #:fallback-browser? keyword.
| (open-webview | | url | | | | | | | [ | #:title title | | | | | | | | #:width width | | | | | | | | #:height height | | | | | | | | #:devtools? devtools? | | | | | | | | #:window-state window-state | | | | | | | | #:on-close on-close]) | | → | | webview? |
|
| url : string? |
| title : string? = "Glaze" |
| width : exact-positive-integer? = 1024 |
| height : exact-positive-integer? = 768 |
| devtools? : boolean? = #f |
| window-state : (or/c #f path-string?) = #f |
| on-close : (-> any) = (lambda () (void)) |
Lower-level synonym of open-window with the same fail-fast contract.
(webview-window-state webview) → (or/c #f window-state?)
|
| webview : webview? |
Returns the native window’s outer position, size, and maximized state.
(webview-set-window-state! webview state) → any
|
| webview : webview? |
| state : window-state? |
Moves/resizes the native window and restores its maximized state.
(webview-save-state! webview path) → (or/c #f path?)
|
| webview : webview? |
| path : path-string? |
(webview-restore-state! webview path) → any
|
| webview : webview? |
| path : path-string? |
Reads a saved state, clamps it to webview-screen-area, and applies it.
Malformed or missing state files are ignored.
(webview-supported?) → boolean?
|
Non-throwing capability probe for the current platform backend. Actual window
creation remains the authoritative runtime check.
(webview-last-error) → any/c
|
Returns the most recent backend probe/startup error.
(webview-install-guidance) → string?
|
Returns platform-specific dependency guidance. Windows guidance names the
Microsoft Edge WebView2 Evergreen Runtime; Linux guidance names GTK 3 and
WebKitGTK packages; macOS explains that WKWebView is part of the OS.
(webview-diagnostic) → string?
|
Formats the current error and guidance.
(webview-navigate wv url) → void?
|
| wv : webview? |
| url : string? |
(webview-close wv) → void?
|
| wv : webview? |
(webview-title wv) → (or/c #f string?)
|
| wv : webview? |
(webview-url wv) → (or/c #f string?)
|
| wv : webview? |
(webview-capture! wv [dest]) → (or/c #f path?)
|
| wv : webview? |
| dest : (or/c #f string? path?) = #f |
(webview-set-title! wv title) → void?
|
| wv : webview? |
| title : string? |
(webview-set-size! wv width height) → void?
|
| wv : webview? |
| width : exact-positive-integer? |
| height : exact-positive-integer? |
(webview-set-fullscreen! wv on?) → void?
|
| wv : webview? |
| on? : boolean? |
(webview-focus! wv) → void?
|
| wv : webview? |
(webview-set-menu! wv menus) → void?
|
| wv : webview? |
| menus : list? |
(webview-closed? wv) → boolean?
|
| wv : webview? |
(all-webviews) → (listof webview?)
|
(close-all-webviews!) → void?
|
(wait-for-webviews [timeout-seconds]) → boolean?
|
| timeout-seconds : (or/c #f real?) = #f |
20.1 Startup Dependency Feedback🔗ℹ
When native startup fails, Glaze reports the backend error and remediation.
Typical guidance includes:
Windows: install or repair Microsoft Edge WebView2 Runtime (Evergreen),
with a winget command and Microsoft’s official download page.
Debian/Ubuntu: sudo apt install libgtk-3-0 libwebkit2gtk-4.1-0.
Fedora: sudo dnf install gtk3 webkit2gtk4.1.
Arch: sudo pacman -S gtk3 webkit2gtk-4.1.
macOS: WKWebView is built in; use a logged-in graphical session and
report the preserved backend error if initialization still fails.
Set environment variable GLAZE_NO_STARTUP_DIALOG to 1 to
suppress the interactive error dialog while retaining the exception. Dialogs
are also suppressed automatically under common CI environments.
21 System Integrations🔗ℹ
(sys-supported?) → boolean?
|
(clipboard-set! text) → boolean?
|
| text : string? |
(clipboard-get) → string?
|
(notify! title [body #:subtitle subtitle]) → boolean?
|
| title : string? |
| body : string? = "" |
| subtitle : string? = "" |
(open-path path-or-url) → boolean?
|
| path-or-url : (or/c path? string?) |
(reveal-path path) → boolean?
|
| path : (or/c path? string?) |
(single-instance? app-id) → boolean?
|
| app-id : any/c |
These helpers are best-effort integrations. Their failure semantics are
separate from the WebView startup contract: the WebView is required for the
application itself, while an optional integration may report failure without
changing the application’s rendering model.
(tray-supported?) → boolean?
|
| (make-tray | | #:icon icon | | | | | | | | #:tooltip tooltip | | | | | | | | #:menu menu | | | | | | | [ | #:on-event on-event]) | | → | | tray? |
|
| icon : any/c |
| tooltip : string? |
| menu : list? |
| on-event : procedure? = (lambda (e) (void)) |
(tray-set-tooltip! tray tooltip) → void?
|
| tray : tray? |
| tooltip : string? |
(tray-set-icon! tray icon) → void?
|
| tray : tray? |
| icon : any/c |
(tray-set-menu! tray menu) → void?
|
| tray : tray? |
| menu : list? |
(tray-close tray) → void?
|
| tray : tray? |
The tray remains an optional capability. If its native backend is unavailable,
Glaze may use an inert tray stub; this does not weaken the mandatory native
WebView contract for the main window.
The local server binds to loopback and validates Host headers against
127.0.0.1, localhost, and [::1] to reduce DNS
rebinding risk.
With #:api-token, API routes and the SSE stream require a capability.
run-app opens the native WebView at a one-time bootstrap URL; the
server exchanges the token for an HttpOnly cookie and redirects to the clean
path. Programmatic clients may use the X-Glaze-Token header.
With #:capability, the server additionally enforces named permissions
and resource scopes. Routes without #:permission, routes whose
permission is absent, and resources outside a granted scope return 403 before
the handler runs. Grant 'glaze:events to authorize the SSE endpoint.
This is defense in depth against casual local callers, not isolation from
other processes running as the same OS user.
24 Update Checks🔗ℹ
| (check-update | | manifest-url | | | | [ | #:current-version current-version]) | |
|
| → (or/c #f hash?) |
| manifest-url : string? |
| current-version : string? = "0.0.0" |
Checks a JSON manifest for a newer version. An optional sha256 field
is passed through for artifact verification. Absolute and relative redirects
are followed for at most ten hops.
(newer-version? candidate current) → boolean?
|
| candidate : string? |
| current : string? |
(verify-file-sha256 path expected-hex) → boolean?
|
| path : (or/c string? path?) |
| expected-hex : string? |
Returns #t only for a verified digest; #f also covers cases
where verification could not be performed.
For installed applications, the signed updater is the preferred path. An
update-manifest binds the application id, SemVer version, release
channel, minimum supported version, staged rollout percentage, and a set of
platform/architecture update-artifact values into one Ed25519-signed
payload. Artifact records include a bounded size, SHA-256 digest, installer
kind and arguments, and an optional second Ed25519 signature.
| (fetch-update-manifest | | manifest-url | | | | | public-key | | | | [ | #:key-id key-id | | | | | #:maximum-bytes maximum-bytes]) | |
|
| → update-manifest? |
| manifest-url : string? |
| public-key : path-string? |
| key-id : (or/c #f string?) = #f |
| maximum-bytes : exact-positive-integer? = (* 1024 1024) |
Downloads an HTTPS manifest with a hard byte limit and verifies its signature.
If key-id is provided, a valid signature from any other release key
is rejected. Redirects are followed for at most ten hops, and every target
must remain HTTPS.
(select-update config manifest) → (or/c #f update-candidate?)
|
| config : updater-config? |
| manifest : update-manifest? |
Checks application identity, channel, exact SemVer precedence, minimum version,
rollout bucket, platform, and architecture.
| (download-update | | config | | | | | | | | candidate | | | | | | | | destination) | | → | | path? |
|
| config : updater-config? |
| candidate : update-candidate? |
| destination : path-string? |
Downloads to a partial file over HTTPS, enforces the configured maximum and
signed artifact size, verifies SHA-256 plus the optional artifact signature,
then atomically renames the verified file into place. Redirects are followed
for at most ten hops, and every target must remain HTTPS.
| (make-install-plan | | candidate | | | | | | | | downloaded-path | | | | | | | [ | #:backup-path backup-path] | | | | | | | | #:install install | | | | | | | [ | #:restart restart | | | | | | | | #:rollback rollback]) | | → | | install-plan? |
|
| candidate : update-candidate? |
| downloaded-path : path-string? |
| backup-path : (or/c #f path-string?) = #f |
| install : procedure? |
| restart : procedure? = void |
| rollback : procedure? = void |
| (make-replace-install-plan | | candidate | | | | | downloaded-path | | | | | target-path | | | | | #:backup-path backup-path | | | | [ | #:restart restart]) | |
|
| → install-plan? |
| candidate : update-candidate? |
| downloaded-path : path-string? |
| target-path : path-string? |
| backup-path : path-string? |
| restart : procedure? = void |
Creates the atomic portable-artifact strategy, suitable for AppImage-style or
self-contained deployments. Native installers use make-install-plan
with a platform adapter that owns elevation and process handoff.
(execute-install-plan! plan) → any
|
| plan : install-plan? |
Runs install and restart. If installation raises and the signed manifest allows
rollback, the rollback callback runs before the original exception is raised.
Release automation can validate and sign a payload with
raco glaze manifest-sign, then verify the wrapper and pinned key id with
raco glaze manifest-verify.
25 File Dialogs🔗ℹ
(dialog-supported?) → boolean?
|
| (pick-file | [ | #:title title | | | | | | | | #:directory directory | | | | | | | | #:filters filters]) | | → | | (or/c #f path?) |
|
| title : (or/c #f string?) = #f |
| directory : (or/c #f path-string?) = #f |
| filters : list? = '() |
| (pick-files | [ | #:title title | | | | | | | | #:directory directory | | | | | | | | #:filters filters]) | | → | | (listof path?) |
|
| title : (or/c #f string?) = #f |
| directory : (or/c #f path-string?) = #f |
| filters : list? = '() |
| (pick-folder | [ | #:title title | | | | | | | | #:directory directory]) | | → | | (or/c #f path?) |
|
| title : (or/c #f string?) = #f |
| directory : (or/c #f path-string?) = #f |
| (save-file-dialog | [ | #:title title | | | | | #:default-name default-name | | | | | #:directory directory | | | | | #:filters filters]) | |
|
| → (or/c #f path?) |
| title : (or/c #f string?) = #f |
| default-name : (or/c #f string?) = #f |
| directory : (or/c #f path-string?) = #f |
| filters : list? = '() |
(dialog-message! title [body #:kind kind]) → boolean?
|
| title : string? |
| body : string? = "" |
| kind : symbol? = 'info |
Shows a native message box; the kind is
'info, 'warning, or 'error.
(dialog-ask! title [body #:kind kind]) → boolean?
|
| title : string? |
| body : string? = "" |
| kind : symbol? = 'info |
Shows a native yes/no box and returns the answer.
Dialogs are modal on the calling thread: while one is open, other
Racket-side request handling waits (the WebView itself stays responsive
in its own native loop).
| (make-dialog-routes | [ | #:prefix prefix | | | | | | | | #:backend backend]) | | → | | (listof route?) |
|
| prefix : string? = "api/dialog" |
| backend : dialog-backend? = default-dialog-backend |
Capability-gated frontend access: 'dialog:open and
'dialog:save — scope them with path-permission over the
starting directory; a request without a start directory has nothing to
authorize and is denied under a scoped grant — plus 'dialog:message
and 'dialog:ask. JSON filters take the shape
{"name": "Text", "extensions": ["txt", "*.md"]}; bare extensions
gain the *. prefix and cancels map to JSON null. The generated
client contains glaze.api.dialogOpen,
glaze.api.dialogSave, glaze.api.dialogMessage, and
glaze.api.dialogAsk. The backend is an injectable value so tests
and headless CI never open real dialogs.
26 Deep Links and Launch at Login🔗ℹ
| (ensure-url-scheme! | | scheme | | | | | | | [ | #:app-name app-name]) | | → | | any/c |
|
| scheme : string? |
| app-name : string? = scheme |
Windows registers a user-scope URL protocol, Linux writes a desktop entry and
uses xdg-mime when available, and macOS URL schemes are declared in the
bundle at build time.
(auto-launch-set! name enabled?) → void?
|
| name : string? |
| enabled? : boolean? |
(auto-launch-enabled? name) → any/c
|
| name : string? |
| (build-app | [ | #:entry entry | | | | | | | | #:name name | | | | | | | | #:version version | | | | | | | | #:publisher publisher | | | | | | | | #:identifier identifier | | | | | | | | #:icon icon | | | | | | | | #:out-dir out-dir | | | | | | | | #:embed-dlls? embed-dlls? | | | | | | | | #:installer? installer? | | | | | | | | #:sign sign | | | | | | | | #:entitlements entitlements | | | | | | | | #:no-hardened-runtime? no-hardened-runtime? | | | | | | | | #:timestamp-url timestamp-url | | | | | | | | #:notarize-profile notarize-profile | | | | | | | | #:url-schemes url-schemes]) | | → | | path? |
|
| entry : (or/c string? path?) = "main.rkt" |
| name : (or/c #f string?) = #f |
| version : (or/c #f string?) = #f |
| publisher : (or/c #f string?) = #f |
| identifier : (or/c #f string?) = #f |
| icon : any/c = #f |
| out-dir : (or/c string? path?) = "dist" |
| embed-dlls? : boolean? = #f |
| installer? : boolean? = #f |
| sign : (or/c #f string?) = #f |
| entitlements : any/c = #f |
| no-hardened-runtime? : boolean? = #f |
| timestamp-url : (or/c #f string?) = #f |
| notarize-profile : (or/c #f string?) = #f |
| url-schemes : list? = '() |
Builds and distributes the application with raco exe and
raco distribute. Windows GUI builds use raco exe --gui, so they
may not have a visible console; this is why native WebView startup failures
also attempt an OS-level error dialog.
Installer-toolchain absence may degrade an installer request to an archive
with a loud warning. That packaging fallback is unrelated to runtime startup:
the built application still requires its native WebView.
The publisher is written to native installer metadata. Keep the identifier
stable across releases: it deterministically defines the WiX UpgradeCode and
the NSIS Add/Remove Programs identity.
28 CLI Commands🔗ℹ
raco glaze init <name> Create a native desktop project |
raco glaze dev Run the project's native main.rkt |
raco glaze build Build a distributable / installer |
raco glaze help Show help |
There is intentionally no browser-mode dev or serve command.
Development and production use the same native WebView startup path.