Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
03811eb
Make native WebView failures actionable
turinglambdaai Sep 20, 2026
c67ac89
Make run-app GUI-first by default
turinglambdaai Sep 20, 2026
75b14d5
Make Glaze CLI GUI-first
turinglambdaai Sep 20, 2026
1e25bff
Make WebView demo cross-platform
turinglambdaai Sep 20, 2026
94c3147
Test WebView diagnostics API
turinglambdaai Sep 20, 2026
049052a
Make native WebView failures fatal
turinglambdaai Sep 20, 2026
4708d0f
Remove browser fallback from run-app
turinglambdaai Sep 20, 2026
c36cc7d
Make CLI desktop-only
turinglambdaai Sep 20, 2026
6171f20
Make WebView demo fail fast with cleanup
turinglambdaai Sep 20, 2026
e143a35
Remove browser fallback wording from API docs
turinglambdaai Sep 20, 2026
48eb61c
Align event docs with native-only app model
turinglambdaai Sep 20, 2026
8f16009
Test native-only WebView contract
turinglambdaai Sep 20, 2026
db1f04b
Document native-only example behavior
turinglambdaai Sep 20, 2026
53e4824
Document native-only GUI startup
turinglambdaai Sep 20, 2026
1764975
统一中文文档为原生 GUI 优先
turinglambdaai Sep 20, 2026
5a59194
Document GUI-first startup policy
turinglambdaai Sep 20, 2026
26b6e52
Add GUI-first policy marker
turinglambdaai Sep 20, 2026
579705a
Temporary marker
turinglambdaai Sep 20, 2026
5d243cd
Remove redundant GUI-first marker
turinglambdaai Sep 20, 2026
b443a88
Remove temporary GUI-first marker
turinglambdaai Sep 20, 2026
47513dd
Add native startup dependency feedback
turinglambdaai Sep 20, 2026
e46554c
Show native startup error dialog before exit
turinglambdaai Sep 20, 2026
e6a8f9a
Update API docs for native-only GUI contract
turinglambdaai Sep 20, 2026
e08fccd
Update maintainer guide for GUI-first contract
turinglambdaai Sep 20, 2026
80838d2
Document visible native startup feedback
turinglambdaai Sep 20, 2026
6767f74
Keep scaffold runnable after packaging
turinglambdaai Sep 20, 2026
fe3ba5c
Gate AppKit menu e2e to macOS
turinglambdaai Sep 20, 2026
3212ced
Align README scaffold with packaged entry
turinglambdaai Sep 20, 2026
23dfe22
同步中文脚手架与打包入口
turinglambdaai Sep 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
476 changes: 305 additions & 171 deletions AGENTS.md

Large diffs are not rendered by default.

213 changes: 74 additions & 139 deletions README.md

Large diffs are not rendered by default.

323 changes: 99 additions & 224 deletions README.zh-CN.md

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions docs/gui-first.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# GUI-first startup policy

Glaze is a desktop GUI framework. Application entry points (`run-app`, `open-window`, and `open-webview`) require a working native WebView backend and never fall back to the system browser.

If native startup fails, Glaze preserves the underlying backend error and adds platform-specific remediation:

- **Windows:** install or repair Microsoft Edge WebView2 Runtime (Evergreen). The diagnostic includes `winget install --id Microsoft.EdgeWebView2Runtime -e` and Microsoft's official WebView2 download page. Glaze itself ships `WebView2Loader.dll`.
- **macOS:** WKWebView is part of macOS; run from a logged-in graphical session and report the preserved backend error if initialization still fails.
- **Linux:** install GTK 3 + WebKitGTK for the distribution and run inside a graphical desktop session (or Xvfb in CI).

A packaged GUI application may not have a visible console, especially on Windows where Glaze builds with `raco exe --gui`. For that reason, an interactive startup failure also attempts to show an OS-level error dialog containing the same diagnosis before the exception terminates the application. CI/GitHub Actions suppress the dialog so unattended jobs cannot block. Set `GLAZE_NO_STARTUP_DIALOG=1` to suppress it explicitly.

This policy is intentional: a desktop application that unexpectedly becomes a browser tab is a different application model and hides dependency/backend failures during development. Optional helpers such as `open-browser` remain available for deliberately opening external documentation, OAuth pages, and similar URLs, but they are not part of native application startup.
6 changes: 4 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
| 示例 | 一句话 | 展示的能力 |
|---|---|---|
| [`showcase/`](showcase/) | **一屏看尽全部能力(推荐先看)** | 宏路由全形态(类型校验/400/:path/500)、SSE 事件流 + 后端错误回流(on-error)、系统功能(剪贴板/通知/Finder/窗口控制)、Agent 验证(title/url/capture + 截图回传)、API token(401 演示)、更新检查、托盘、单实例 |
| [`hello/`](hello/) | 8 行最小应用 | run-app 一键入口、静态页面 |
| [`hello/`](hello/) | 8 行最小应用 | run-app 一键入口、原生窗口、静态页面 |
| [`counter/`](counter/) | JS↔Racket 桥接主打 | define-api-routes、SSE 广播驱动 UI、api.js 生成客户端、模块可组合(provide api/bus) |
| [`webview-demo.rkt`](webview-demo.rkt) | WebView 生命周期 | 加载/导航/关闭/on-close/验证 API 实时打印、看门狗 |
| [`webview-demo.rkt`](webview-demo.rkt) | 跨平台 WebView 生命周期 | 原生窗口、加载/导航/关闭/on-close/验证 API 实时打印、看门狗 |
| [`agent-verify.rkt`](agent-verify.rkt) | 无人值守验证 | agent 工作流:轮询断言 + 截图 + 退出码 |
| [`tray-demo.rkt`](tray-demo.rkt) | 跨平台托盘 | make-tray/菜单/tooltip 动态更新 |

Expand All @@ -17,4 +17,6 @@ racket examples/counter/main.rkt # 桥接 + 事件
racket examples/hello/main.rkt # 最小应用
```

Glaze 示例只走原生桌面 WebView,不会退回系统浏览器。若 WebView2 / WebKitGTK 等运行时依赖缺失,启动会直接失败并打印对应平台的安装/修复指引。

所有示例均可 `raco glaze build` 打包为独立应用。
47 changes: 25 additions & 22 deletions examples/webview-demo.rkt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#lang racket/base

;; Glaze WebView demo (macOS). Serves two pages from a temp dir, opens a
;; native NSWindow + WKWebView, auto-navigates to page 2 after a while, and
;; exits when you close the window (red button) or after the watchdog —
;; exercising the whole surface: rendering, JS execution, webview-navigate,
;; verification APIs, and the on-close callback.
;; Glaze native WebView demo (Windows/macOS/Linux). Serves two pages from a
;; temp dir, opens a native desktop window with the platform WebView backend,
;; auto-navigates to page 2 after a while, and exits when you close the window
;; or after the watchdog — exercising rendering, JS execution,
;; webview-navigate, verification APIs, and the on-close callback.
;;
;; Run: racket examples/webview-demo.rkt

Expand All @@ -21,7 +21,7 @@
(lambda (o) (display
#<<HTML
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Glaze Demo</title><style>
body{font-family:-apple-system,sans-serif;background:#F4F3EE;color:#2d2a26;
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#F4F3EE;color:#2d2a26;
display:flex;align-items:center;justify-content:center;height:100vh;margin:0}
.card{max-width:560px;text-align:center;padding:48px}
h1{font-size:44px;font-weight:800;margin:0 0 8px}
Expand All @@ -33,12 +33,12 @@
#count{font-size:60px;font-weight:800;color:#C15F3C;margin:24px 0 4px}
.hint{margin-top:28px;font-size:13px;color:#9a958a}
</style></head><body><div class="card">
<h1>Glaze <em>on macOS</em></h1>
<p class="sub">NSWindow + WKWebView,由 Racket 纯 FFI 创建,无 C 编译</p>
<h1>Glaze <em>Native WebView</em></h1>
<p class="sub">Racket + system native window + embedded WebView</p>
<div id="count">0</div>
<button onclick="bump()">点我试试(JS 在跑)</button>
<p class="hint">约 20 秒后 Racket 会调用 webview-navigate 跳到第二页<br>
点红色关闭按钮,Racket 侧 on-close 将触发并退出</p>
<button onclick="bump()">Click me (JavaScript is running)</button>
<p class="hint">Racket will call webview-navigate and switch pages in about 20 seconds.<br>
Close the native window at any time to trigger the Racket on-close callback.</p>
</div><script>
let n = 0;
function bump(){ n++; document.getElementById('count').textContent = n; }
Expand All @@ -49,19 +49,19 @@ HTML
(lambda (o) (display
#<<HTML
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Navigated</title><style>
body{font-family:-apple-system,sans-serif;background:#1e1e2e;color:#eee;
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#1e1e2e;color:#eee;
display:flex;align-items:center;justify-content:center;height:100vh;margin:0}
.card{text-align:center}
h1{font-size:40px;font-weight:800;color:#89b4fa}
p{color:#a6adc8;font-size:16px}
code{background:#313244;padding:2px 8px;border-radius:6px;font-size:14px}
</style></head><body><div class="card">
<h1>webview-navigate ✓</h1>
<p>这一页是 Racket 在运行时调 <code>(webview-navigate wv url)</code> 载入的</p>
<p>This page was loaded by Racket calling <code>(webview-navigate wv url)</code>.</p>
<p id="t"></p>
</div><script>
document.getElementById('t').textContent =
'JS 时间: ' + new Date().toLocaleTimeString();
'JS time: ' + new Date().toLocaleTimeString();
</script></body></html>
HTML
o)) #:exists 'replace)
Expand All @@ -70,14 +70,17 @@ HTML
(say "[demo] server up on 127.0.0.1:~a\n" port)

(define closed? (box #f))
(define wv (open-window (format "http://127.0.0.1:~a/" port)
#:title "Glaze · macOS Demo"
#:width 960
#:height 680
#:on-close (lambda () (set-box! closed? #t))))
(unless wv
(error 'demo "webview backend unavailable — run this on macOS"))
(say "[demo] window opened (backend=~a)\n" (webview-backend wv))
(define wv
(with-handlers ([exn:fail? (lambda (e)
(stop)
(delete-directory/files dir)
(raise e))])
(open-window (format "http://127.0.0.1:~a/" port)
#:title "Glaze · Native WebView Demo"
#:width 960
#:height 680
#:on-close (lambda () (set-box! closed? #t)))))
(say "[demo] native window opened (backend=~a)\n" (webview-backend wv))

(define tmp (path->string (find-system-path 'temp-dir)))

Expand Down
74 changes: 39 additions & 35 deletions glaze-cli/cli.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,22 @@
racket/file
racket/string
racket/system
glaze/server
glaze/browser
glaze/build
glaze/license)

(define (init-project name)
(printf "Creating Glaze project: ~a\n" name)
(make-directory* name)
(make-directory* (build-path name "public"))
(write-file (build-path name "main.rkt")
(string-append "#lang racket/base\n\n"
"(require glaze)\n\n"
"(define-values (port server)\n"
" (start-dev-server #:public-dir \"public\"))\n\n"
"(printf \"Glaze app running at http://127.0.0.1:~a\\n\" port)\n"
"(open-browser (format \"http://127.0.0.1:~a\" port))\n\n"
"(with-handlers ([exn:break?\n"
" (lambda (e)\n"
" (stop-server server)\n"
" (printf \"Server stopped.\\n\"))])\n"
" (sync never-evt))\n"))
(write-file
(build-path name "main.rkt")
(string-append
"#lang racket/base\n\n"
"(require racket/runtime-path\n"
" glaze)\n\n"
"(define-runtime-path public \"public\")\n\n"
"(run-app #:public-dir public\n"
(format " #:title ~s)\n" name)))
(write-file
(build-path name "public" "index.html")
#"<!DOCTYPE html>
Expand Down Expand Up @@ -54,16 +49,24 @@
</body>
</html>
")
(printf "Done! Run:\n cd ~a\n racket main.rkt\n" name))
(printf "Done! Run:\n cd ~a\n racket main.rkt\n\nOr use:\n cd ~a\n raco glaze dev\n"
name name))

(define (dev-server)
(define-values (actual-port server) (start-dev-server #:port 8080 #:public-dir "public"))
(printf "Dev server running at http://127.0.0.1:~a\n" actual-port)
(open-browser (format "http://127.0.0.1:~a" actual-port))
(with-handlers ([exn:break? (lambda (e)
(stop-server server)
(printf "Server stopped.\n"))])
(sync never-evt)))
;; `dev` runs the project's real entry point, so routes/events/window options
;; in main.rkt are preserved. Glaze development follows the same native GUI
;; path as the shipped application; there is no browser-mode escape hatch.
(define (dev-app)
(define entry (build-path (current-directory) "main.rkt"))
(unless (file-exists? entry)
(error 'dev "main.rkt not found in ~a; run this command from a Glaze project"
(path->string (current-directory))))
(define racket-exe (find-executable-path "racket" #f))
(unless racket-exe
(error 'dev "racket executable not found on PATH"))
(printf "Starting Glaze native app: ~a\n" (path->string entry))
(define code (system*/exit-code racket-exe (path->string entry)))
(unless (zero? code)
(exit code)))

;; Parse the rest args for `build`. Recognized flags:
;; --name <name> app/bundle name (default: project dir name)
Expand All @@ -76,12 +79,11 @@
;; --sign <id> code-signing identity (macOS: codesign identity,
;; "-" = ad-hoc; Windows: cert SHA-1 thumbprint or
;; subject name for signtool)
;; --entitlements <p> macOS: .entitlements plist for codesign
;; --no-hardened-runtime macOS: disable hardened runtime (notarization
;; needs it; leave it on unless you know better)
;; --entitlements <p> macOS: path to a .entitlements plist
;; --no-hardened-runtime macOS: disable hardened runtime
;; --timestamp-url <u> Windows: RFC-3161 timestamp server for signtool
;; --notarize <profile> macOS: notarytool keychain profile; submits the
;; dmg/app for notarization and staples it
;; --notarize <profile> macOS: notarytool keychain profile
;; --url-scheme <name> deep-link URL scheme (repeatable)
(define (parse-build-opts rest)
(let loop ([args rest]
[name #f]
Expand Down Expand Up @@ -133,7 +135,7 @@
sign entitlements #t ts-url notarize schemes)]
[(and (equal? (car args) "--timestamp-url") (pair? (cdr args)))
(loop (cddr args) name version icon entry out embed installer
sign entitlements no-hardened (cadr args) notarize)]
sign entitlements no-hardened (cadr args) notarize schemes)]
[(and (equal? (car args) "--notarize") (pair? (cdr args)))
(loop (cddr args) name version icon entry out embed installer
sign entitlements no-hardened ts-url (cadr args) schemes)]
Expand Down Expand Up @@ -171,13 +173,17 @@
(displayln "Usage: raco glaze <command> [args]")
(displayln "")
(displayln "Commands:")
(displayln " init <name> Create a new Glaze project")
(displayln " dev Start dev server with auto-open browser")
(displayln " init <name> Create a native Glaze desktop project")
(displayln " dev Run this project's native Glaze desktop app")
(displayln " build Build a distributable (raco exe + raco distribute)")
(displayln " keygen Create an RSA keypair for license signing")
(displayln " license Sign or verify offline license files")
(displayln " help Show this help")
(displayln "")
(displayln "Glaze requires a working native WebView. If it is missing or broken, startup")
(displayln "fails with platform-specific installation/repair instructions; it never opens")
(displayln "the system browser as a fallback.")
(displayln "")
(displayln "build options:")
(displayln " --name <name> app/bundle name (default: project dir)")
(displayln " --version <v> app version (Info.plist / MSI metadata)")
Expand All @@ -197,13 +203,11 @@
(displayln " Info.plist entries; call (ensure-url-scheme! ...)")
(displayln " at app start on Windows/Linux)"))


(define (write-file path content)
(call-with-output-file path (lambda (out) (display content out)) #:exists 'replace))

;; ---- keygen: create an RSA keypair for license signing ----

;; raco glaze keygen [--out <dir>] ; writes private.pem + public.pem
(define (parse-keygen-opts rest)
(let loop ([args rest] [out "keys"])
(cond
Expand Down Expand Up @@ -242,7 +246,7 @@
(cond
[(null? args)
(values sub key pub product subject expiry machine out (reverse positional))]
[(and (null? sub) (member (car args) '("sign" "verify")))
[(and (not sub) (member (car args) '("sign" "verify")))
(loop (cdr args) (car args) key pub product subject expiry machine out positional)]
[(and (equal? (car args) "--key") (pair? (cdr args)))
(loop (cddr args) sub (cadr args) pub product subject expiry machine out positional)]
Expand Down Expand Up @@ -308,7 +312,7 @@
(init-project (if (null? rest)
"myapp"
(car rest)))]
["dev" (dev-server)]
["dev" (dev-app)]
["build" (build-command rest)]
["keygen" (keygen-command rest)]
["license" (license-command rest)]
Expand Down
Loading
Loading