diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 80e968b..ac4712e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,6 +6,13 @@ on:
pull_request:
branches: [main]
+# A PR can receive many small stabilization commits. Only the newest SHA is
+# useful; cancel stale runs so they do not consume the cross-platform runners
+# or delay feedback for the current head.
+concurrency:
+ group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
jobs:
test:
runs-on: ${{ matrix.os }}
@@ -27,12 +34,41 @@ jobs:
shell: bash
run: raco pkg install --auto --no-docs --link "$PWD"
+ - name: Compile public entrypoints
+ run: raco make glaze/main.rkt glaze-cli/cli.rkt scripts/package-entry-smoke.rkt
+
- name: Run tests
run: raco test glaze-test/
- - name: Check formatting
- run: raco fmt --check glaze/ glaze-cli/ glaze-test/
- continue-on-error: true
+ source-package:
+ # Release hygiene: prove the filtered source archive is independently
+ # installable. This job intentionally does not link the checkout first.
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install Racket
+ uses: Bogdanp/setup-racket@v1.11
+ with:
+ version: '8.12'
+
+ - name: Create source package
+ shell: bash
+ run: |
+ cd ..
+ raco pkg create --source --format zip glaze
+ test -s glaze.zip
+
+ - name: Install source package archive
+ shell: bash
+ run: raco pkg install --auto --no-docs ../glaze.zip
+
+ - name: Verify installed facade and CLI
+ shell: bash
+ run: |
+ racket -e '(require glaze) (unless (procedure? run-app) (error "missing run-app"))'
+ raco glaze help
webview-e2e:
# Real-window WebView end-to-end on each OS: open -> load (title
@@ -112,20 +148,31 @@ jobs:
- name: Install installer toolchain (Windows)
if: runner.os == 'Windows'
+ shell: pwsh
run: |
- # WiX Toolset v4 (build falls back to zip if missing)
- dotnet tool install --global wix
- echo "$HOME/.dotnet/tools" >> $GITHUB_PATH
+ # Glaze supports WiX v4 syntax, but GitHub's latest global `wix`
+ # tool is now v7 and requires an additional OSMF license flow.
+ # NSIS is the other supported native Windows installer backend.
+ choco install nsis -y --no-progress
+ $nsis = "C:\Program Files (x86)\NSIS"
+ if (Test-Path $nsis) { $nsis | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append }
+
+ - name: Verify packaged entry executes
+ run: racket scripts/package-entry-smoke.rkt
- name: Scaffold and build a sample app
shell: bash
run: |
raco glaze init sampleapp
cd sampleapp
- # --sign - exercises the signing pipeline everywhere: macOS signs
- # the bundle ad-hoc (verifiable without a cert); Windows/Linux
- # degrade with a loud warning when no signing toolchain exists.
- raco glaze build --name sampleapp --version 0.0.1 --out dist --installer --sign -
+ # Exercise signing where CI can do it without secrets. On macOS,
+ # ad-hoc signing is verifiable; Windows signing correctly requires a
+ # real certificate and is covered by argument/tool failure behavior.
+ if [ "$RUNNER_OS" = "macOS" ]; then
+ raco glaze build --name sampleapp --version 0.0.1 --out dist --installer --sign -
+ else
+ raco glaze build --name sampleapp --version 0.0.1 --out dist --installer
+ fi
- name: Verify macOS bundle signature
if: runner.os == 'macOS'
run: |
@@ -143,3 +190,24 @@ jobs:
sampleapp/dist/*.msi
sampleapp/dist/*.dmg
sampleapp/dist/*.AppImage
+
+ package-racket-9-3-macos:
+ # Regression coverage for the macOS/Racket 9.3 launcher failure reported
+ # in issue #1. This job executes the final packaged binary, not merely the
+ # build command.
+ runs-on: macOS-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Install Racket
+ uses: Bogdanp/setup-racket@v1.11
+ with:
+ version: '9.3'
+
+ - name: Install Glaze package
+ shell: bash
+ run: raco pkg install --auto --no-docs --link "$PWD"
+
+ - name: Verify packaged entry executes
+ run: racket scripts/package-entry-smoke.rkt
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bea648a..ca4130c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
## [Unreleased]
+### Changed
+- `run-app` now enables a random API capability token by default; pass
+ `#:api-token #f` explicitly for an intentionally open local API.
+- Update checks are non-blocking from the application lifecycle and enforce a
+ five-second fetch timeout, 2xx status, and a 1 MiB manifest limit.
+- `raco glaze init` scaffolds the recommended `run-app` / `(require glaze)`
+ entry and refuses to overwrite non-empty project directories.
+
+### Fixed
+- Packaging now compiles the user's real entry module, preserving
+ `(module+ main ...)` execution instead of producing launchers that could
+ exit successfully without running the application.
+- Static-file serving rejects traversal outside `public/`, including resolved
+ symlinks, and Host validation correctly handles bracketed IPv6 loopback.
+- API handler exceptions are reported to the trusted error callback but no
+ longer leak arbitrary exception text in 500 responses.
+- Generated JavaScript API bindings safely escape route segments and no longer
+ use route parameter text as raw JavaScript identifiers.
+- Shutdown is idempotent, single-instance listeners are retained for process
+ lifetime with deterministic cross-process ports, and tray operations dispatch
+ from each tray handle rather than process-global fallback state.
+
+
## [0.6.0] - 2026-09-15
### Added
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index b18a565..b58c32e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,48 +1,75 @@
# Contributing to Glaze
+Glaze is a pre-1.0 cross-platform desktop framework. Prefer small changes that preserve application-facing APIs and keep platform details behind the public dispatchers.
+
## Development Setup
```bash
git clone https://github.com/turinglambdaai/glaze.git
cd glaze
-raco pkg install --auto --link "$PWD"
+raco pkg install --auto --no-docs --link "$PWD"
```
-(The repo root is one single Racket package — this installs the library,
-the `raco glaze` CLI, and the docs in one step. `"$PWD"` is needed because
-`raco pkg install` requires the source path to end in the package name.
-After pulling changes, refresh with `raco pkg update --link "$PWD"`.)
+The repository root is one installable Racket package using `collection 'multi`; the `glaze`, `glaze-cli`, `glaze-doc`, and `glaze-test` collections are installed together.
+
+## Before Opening a Pull Request
-## Running Tests
+Run the platform-independent suite and compile the public entrypoints:
```bash
+raco make glaze/main.rkt glaze-cli/cli.rkt scripts/package-entry-smoke.rkt
raco test glaze-test/
```
+When your change touches WebView or packaging behavior, also run the relevant verification script on the affected operating system. CI exercises native WebView behavior and package construction on Windows, macOS, and Linux, including a macOS/Racket 9.3 packaging regression test.
+
+## Architecture Rules
+
+Read [`docs/architecture.md`](docs/architecture.md) before moving modules or adding a new capability. In particular:
+
+- normal applications should prefer `(require glaze)`;
+- `glaze/main.rkt` is the compatibility-preserving application facade;
+- platform-specific modules belong behind `webview/main.rkt`, `tray/main.rkt`, or `sys/main.rkt`;
+- do not make platform backends depend on application-level orchestration;
+- shared protocols should not be duplicated independently in each backend;
+- avoid large directory migrations solely for aesthetics;
+- new public behavior should have a platform-independent contract test when possible.
+
## Code Style
-- Follow standard Racket conventions
-- Use `raco fmt` for formatting
-- Add tests for new features
-- Update Scribble documentation
+Follow the dominant Racket style already present in the repository. The optional [`fmt`](https://pkgs.racket-lang.org/package/fmt) package can be installed with:
+
+```bash
+raco pkg install fmt
+```
+
+Do not reformat unrelated files in a functional pull request. Formatting-only churn makes native and lifecycle changes harder to review.
+
+## Tests and Documentation
+
+A change is not complete when only the happy path works. Prefer small regression tests for:
+
+- public facade exports and argument validation;
+- lifecycle and cleanup behavior;
+- platform-independent protocol logic;
+- security boundaries such as path containment and localhost API access;
+- package artifacts that actually execute, not merely build successfully.
+
+Update Scribble/API documentation and user-facing examples when a public contract changes. Do not document features that are only planned.
## Pull Requests
-1. Fork the repository
-2. Create a feature branch
-3. Make your changes
-4. Run tests
-5. Submit a PR with a clear description
+Keep each PR focused enough to explain why every changed file is necessary. In the description include the problem, compatibility impact, tests run, and any platform behavior you could not verify locally.
-## Package Structure
+Security issues should follow [`SECURITY.md`](SECURITY.md) instead of being disclosed with exploit details in a public issue.
-The repo root is a single installable package; each top-level directory is a
-Racket collection:
+## Package Structure
| Directory | Purpose |
-|-----------|---------|
-| `glaze/` | Core implementation (collection `glaze`) |
+|---|---|
+| `glaze/` | Framework implementation and public facade |
| `glaze-cli/` | `raco glaze` commands |
| `glaze-doc/` | Scribble documentation |
-| `glaze-test/` | Tests |
-| `examples/` | Runnable examples (not compiled by setup) |
+| `glaze-test/` | Regression and contract tests |
+| `examples/` | Runnable examples |
+| `scripts/` | CI and verification scripts |
diff --git a/README.md b/README.md
index 6ff918a..b0977b2 100644
--- a/README.md
+++ b/README.md
@@ -1,431 +1,202 @@
# Glaze
-Build desktop apps with a [Racket](https://racket-lang.org/) backend and a web frontend. A [Tauri](https://tauri.app/)-like framework for Racket — write your app logic in Racket, build your UI with HTML/CSS/JS, and ship a desktop application.
+A Lisp-native framework for building modern desktop applications with Racket.
-[](https://github.com/turinglambdaai/glaze/actions/workflows/ci.yml)  [](LICENSE) [](CHANGELOG.md)
+Glaze lets you keep application logic in Racket, build the UI with normal web technologies, and connect that UI to native desktop capabilities such as WebView windows, system tray menus, clipboard access, notifications, dialogs, and application packaging.
-**English** · [中文](README.zh-CN.md)
-
-
-
-## Why Glaze?
-
-Racket's `racket/gui` works but is hard to style into a modern product-grade UI. Glaze takes a different approach: serve a local web app from Racket and display it in the system browser (Phase 1) or an embedded WebView (Phase 3).
+[](https://github.com/turinglambdaai/glaze/actions/workflows/ci.yml)  [](LICENSE)
-You get:
-
-- **Racket for logic** — the full power of Racket's macro system, contracts, pattern matching
-- **Web for UI** — Tailwind, Svelte, React, or any web framework
-- **JSON API bridge** — the page calls Racket with plain `fetch("/api/...")`
-
-### How it compares
+**English** · [中文](README.zh-CN.md)
-| | Glaze | Tauri | Electron | wails |
-|---|---|---|---|---|
-| Backend language | Racket | Rust | JS/Node | Go |
-| Native toolchain needed | **none** (pure FFI) | Rust + cargo | none | Go + WebView2 deps |
-| Binary size | tiny (Racket exe + assets) | small | 100 MB+ | small |
-| Frontend→backend | HTTP JSON routes (`fetch`) | `invoke()` IPC | Node APIs | bindings |
-| Works without webview (browser fallback) | **yes** | no | no | no |
-| Agent-friendly UI verification (`title`/`url`/screenshot) | **built-in** | via WebDriver | via CDP | limited |
-| WebView backends | WebView2 / WKWebView / WebKitGTK | same | bundled Chromium | WebView2/WKWebView |
+## Why Glaze
-All three webview backends pass the real-window CI e2e (open, load, capture, navigate, close, on-close). Remaining honest gaps: no typed IPC layer (plain JSON), Linux needs a desktop session or Xvfb.
+Racket already has `racket/gui`, but Glaze targets a different style of desktop application: web UI on top of a Racket runtime.
-## Platform status
+Glaze is not an Electron clone. It does not bundle Chromium or introduce a Node runtime. Its current model is closer to Tauri in spirit:
-| Capability | macOS | Windows | Linux |
-|---|---|---|---|
-| HTTP server + browser | ✅ | ✅ | ✅ |
-| System tray | ✅ | ✅ | ✅ (CI-verified) |
-| JSON API bridge | ✅ | ✅ | ✅ |
-| Native webview window | ✅ verified end-to-end | ✅ CI e2e (WebView2) | ✅ CI e2e (Xvfb + WebKitGTK) |
-| `webview-title` / `webview-url` | ✅ | ✅ | ✅ |
-| `webview-capture!` (screenshot) | ✅ | ✅ (PrintWindow + PowerShell PNG) | ✅ (gdk_pixbuf) |
-| `#:devtools?` | ✅ (inspectable, macOS 13+) | ✅ (`OpenDevToolsWindow`) | ✅ (WebKitGTK inspector) |
+```text
+Web UI
+ |
+ | HTTP / JSON / SSE
+ v
+Racket runtime
+ |
+ +-- WebView
+ +-- Tray
+ +-- System capabilities
+ +-- Packaging helpers
+ |
+Native OS APIs
+```
-Without a native backend, `run-app` / `open-window` automatically fall back to the system browser — the app still works everywhere.
+The framework currently uses the operating system WebView through Racket FFI:
-## Requirements
+- Windows: WebView2
+- macOS: WKWebView
+- Linux: WebKitGTK
-| Dependency | Purpose |
-|------------|---------|
-| [Racket](https://racket-lang.org/) | 7.0 or later (includes `raco`) |
+The frontend/backend bridge today is intentionally simple: local HTTP JSON routes for requests and Server-Sent Events for backend-to-frontend events. A larger RPC or plugin system is not part of the current public architecture.
## Quick Start
-### 1. Install
+Install the package:
```bash
raco pkg install --auto glaze
```
-A single Racket package: this installs the `glaze` library, the `raco glaze` CLI, and the documentation (browse it later with `raco docs`).
-
-### 2. Create a new project
-
-```bash
-raco glaze init myapp
-cd myapp
-```
-
-### 3. Run
-
-```bash
-racket main.rkt
-```
-
-A native window opens showing your app served from a local HTTP server; without a WebView backend it falls back to the system browser at `http://127.0.0.1:`.
-
-> Prefer installing straight from a GitHub checkout instead of the catalog?
-> ```bash
-> git clone https://github.com/turinglambdaai/glaze.git
-> cd glaze
-> raco pkg install --auto --link "$PWD"
-> ```
-> To work on Glaze itself, see [CONTRIBUTING.md](CONTRIBUTING.md).
-
-## CLI Commands
-
-```bash
-raco glaze init # Create a new Glaze project
-raco glaze dev # Start dev server with auto-open browser
-raco glaze build # Build a distributable (exe + bundled assets)
-raco glaze keygen # Create an RSA keypair for license signing
-raco glaze license # Sign or verify offline license files
-raco glaze help # Show help
-```
-
-### `build`
-
-Package a Glaze project into a platform distribution (`raco exe` + `raco distribute`) with the frontend assets bundled alongside the executable. On macOS the distribution is a proper `.app` bundle with your `--version` stamped into `Info.plist`.
+Or link a checkout for development:
```bash
-raco glaze build --name myapp # produces dist/myapp(.exe) + dist/lib + dist/public
-raco glaze build --name myapp --version 1.2.0 --installer # + msi / dmg / AppImage (zip/tar.gz fallback)
-```
-
-Options: `--name`, `--version`, `--icon <.ico/.icns>`, `--entry ` (default `main.rkt`), `--out ` (default `dist`), `--embed-dlls` (Windows: single-file exe), `--installer`.
-
-> The installer step probes for the native toolchain (WiX / NSIS on Windows, `create-dmg` / `hdiutil` on macOS, `appimagetool` / `linuxdeploy` on Linux) and **degrades gracefully** to a `.zip` / `.tar.gz` when it's absent, printing a warning naming what to install.
-
-### Code signing & notarization
-
-Unsigned apps get blocked by macOS Gatekeeper and Windows SmartScreen. `build` drives the platform signer for you:
-
-```bash
-# macOS — Developer ID identity, hardened runtime, notarize + staple:
-raco glaze build --name myapp \
- --sign "Developer ID Application: Acme Inc (TEAMID)" \
- --notarize acme-notary --installer
-
-# macOS — ad-hoc (no cert; for local testing / CI):
-raco glaze build --name myapp --sign -
-
-# Windows — signtool with a certificate thumbprint (RFC-3161 timestamped):
-raco glaze build --name myapp --sign 40HEXCHARS --installer
-```
-
-Details: `--sign` takes a codesign identity (macOS) or a SHA-1 thumbprint / subject name for `signtool` (Windows). Hardened runtime is applied automatically on macOS unless `--no-hardened-runtime` is passed (and is skipped for ad-hoc, where its library validation would reject the app's own framework). `--notarize ` submits the built dmg via `notarytool`, waits, and staples the ticket. `--entitlements `, `--timestamp-url ` round it out. Signing failures abort the build; a *missing toolchain* degrades with a loud warning.
-
-### Licensing (paid apps)
-
-`glaze/license` ships an offline license-key scheme with zero native dependencies — RSA-2048/SHA-256 signatures via the system `openssl` CLI, present on every platform:
-
-```bash
-# vendor side — once:
-raco glaze keygen --out keys # keys/private.pem + keys/public.pem
-# per customer (optionally expiry- and machine-bound):
-raco glaze license sign --key keys/private.pem --product "MyApp" \
- --subject "customer@example.com" --expiry 2027-12-31 --out app.license
-raco glaze license verify --pub keys/public.pem --product "MyApp" app.license
-```
-
-```racket
-(require glaze/license)
-
-(define r (validate-license "app.license" #:public-key "keys/public.pem" #:product "MyApp"))
-(unless (hash-ref r 'valid)
- (error 'myapp "license invalid: ~a" (hash-ref r 'reason))) ; expired / machine / signature ...
-
-;; machine binding: a stable per-machine digest of the OS machine id
-(issue-license ... #:machine-id (machine-id))
-```
-
-Failure reasons are stable tags (`missing-file`, `malformed`, `signature`, `product`, `expired`, `machine`, `openssl-unavailable`) suitable for UI messages. Honest scope: this defends against casual license sharing — a local attacker can always patch a binary; it is not tamper resistance.
-
-### Update integrity
-
-`check-update` passes through an optional `"sha256"` manifest field; verify a downloaded artifact before swapping it in:
-
-```racket
-(define info (check-update manifest-url #:current-version "1.0.0"))
-;; app downloads (hash-ref info 'url) ... then:
-(verify-file-sha256 artifact (hash-ref info 'sha256)) ; #t / #f (#f = cannot verify)
-```
-
-## Project Structure
-
-A new Glaze project looks like this:
-
-```
-myapp/
-├── main.rkt # Racket entry point
-└── public/
- └── index.html # Frontend
+git clone https://github.com/turinglambdaai/glaze.git
+cd glaze
+raco pkg install --auto --no-docs --link "$PWD"
```
-`main.rkt` starts a local HTTP server serving files from `public/` and opens the browser:
+A minimal application can use the single public facade:
```racket
#lang racket/base
-(require glaze)
-
-(define-values (port server)
- (start-dev-server #:public-dir "public"))
-
-(printf "Glaze app running at http://127.0.0.1:~a\n" port)
-(open-browser (format "http://127.0.0.1:~a" port))
+(require racket/runtime-path
+ glaze)
-(with-handlers ([exn:break?
- (lambda (e)
- (stop-server server)
- (printf "Server stopped.\n"))])
- (sync never-evt))
-```
-
-## Repository Structure
+(define-runtime-path public "public")
-One installable package at the repo root; each top-level directory is a Racket collection:
-
-```
-glaze/ # repo root = the `glaze` package (info.rkt)
-├── glaze/ # Library: server, API bridge, webview, tray, sys, build, app
-├── glaze-cli/ # CLI tool (raco glaze init / dev / build)
-├── glaze-doc/ # Documentation (Scribble)
-├── glaze-test/ # Test suite
-├── examples/ # Runnable examples
-└── scripts/ # CI helper scripts (webview e2e)
+(run-app #:public-dir public
+ #:title "Hello Glaze")
```
-## API
+Put an `index.html` file in `public/`, then run the Racket program. See [`examples/hello/`](examples/hello/) for the complete minimal example.
-### `run-app`
+The CLI can also scaffold a project:
-The one-call entry: picks a free port, starts the server (static + JSON API), opens the native webview window, and blocks until the window closes.
-
-```racket
-(run-app #:public-dir "public"
- #:api (list (GET "api/ping" ...)))
-;; webview path: window closed -> server stopped -> (values 'webview shutdown)
-;; browser fallback (no native backend): opens browser -> (values 'browser shutdown)
+```bash
+raco glaze init myapp
+cd myapp
+racket main.rkt
```
-### `start-server` / `start-dev-server`
+## Features
-Starts a local HTTP server serving static files with SPA fallback, plus optional JSON API routes. `start-dev-server` is a backward-compatible alias.
+Implemented today:
-```racket
-(start-server #:port 8080
- #:public-dir "public"
- #:api (list (GET "api/ping" (lambda (req) (hasheq 'pong #t)))))
-;; Returns (values port shutdown-proc); verifies the listener is accepting
-;; before returning.
-```
-
-### `stop-server`
+- native WebView windows with lifecycle, navigation, title/URL inspection, screenshots, window controls, and menu integration
+- local static-file server with SPA fallback
+- JSON API routes and generated browser client support
+- Server-Sent Events for backend-to-frontend events
+- system tray menus
+- clipboard, notifications, open/reveal helpers, and single-instance support
+- file dialogs, deep-link helpers, and autolaunch helpers
+- application packaging through `raco glaze build`
+- update and offline-license utilities
+- browser fallback when a native WebView is unavailable
-Stops the server.
+Glaze is implemented in Racket and uses FFI for native integrations; the core framework does not require a C compiler.
-```racket
-(stop-server shutdown-proc)
-```
+## Platform Support
-### `open-browser`
+The repository CI tests Racket 8.12 on Windows, macOS, and Linux. Native WebView end-to-end tests run on all three platforms; Linux uses Xvfb plus WebKitGTK in CI.
-Opens a URL in the system default browser (cross-platform: Windows, macOS, Linux).
+| Capability | Windows | macOS | Linux |
+|---|---|---|---|
+| Local server / JSON API / SSE | Yes | Yes | Yes |
+| Native WebView | WebView2 | WKWebView | WebKitGTK |
+| System tray | Yes | Yes | Yes |
+| System helpers | Yes | Yes | Yes |
+| Packaging pipeline | Yes | Yes | Yes |
-```racket
-(open-browser "http://127.0.0.1:8080")
-```
+Some native features depend on platform libraries or desktop-session availability. Unsupported native backends should fail clearly or use the framework's documented fallback behavior instead of requiring application code to import a platform implementation directly.
-## JavaScript Bridge
+## Architecture
-The frontend calls Racket with plain `fetch("/api/...")` — Glaze's answer to Tauri's `invoke()`. One code path works in the embedded WebView, in the system-browser fallback, and in dev (curl-able). Routes are ordinary values:
+The current repository already has a useful boundary: applications can depend on `(require glaze)`, while WebView, tray, and system modules dispatch to platform backends internally.
-```racket
+```text
+Application
+ |
+ v
(require glaze)
-
-(GET "api/ping" (lambda (req) (hasheq 'pong #t)))
-(POST "api/items/:id/bump" (lambda (req id) (hasheq 'id id 'bumped #t)))
-(POST "api/echo" (lambda (req)
- (define body (request-json-body req))
- (hasheq 'echo body)))
+Public facade: glaze/main.rkt
+ |
+ +-------------------------------+
+ | | |
+ v v v
+Runtime Capabilities Tooling
+app/server webview/main build/update
+api/events tray/main CLI
+ sys/main
+ | |
+ +-------+-------+
+ v
+Platform backends
+Windows / macOS / Linux / stub
```
-- Handlers take the request plus captured `:params`; return a jsexpr (auto-wrapped as JSON 200) or a full response.
-- `request-json-body` parses the JSON body — note Racket jsexpr parses JSON object keys as **symbols** (`(hash-ref body 'delta)`).
-- A handler that raises becomes a 500 JSON error, never a broken connection.
-- Unmatched requests fall through to static files (SPA `index.html` fallback).
+This PR-sized architecture is deliberately smaller than the long-term vision. The next goal is to make dependency direction and lifecycle contracts clearer without moving every implementation file.
-In the page:
+See [`docs/architecture.md`](docs/architecture.md) for the detailed boundary and dependency rules.
-```js
-const s = await fetch('/api/counter/bump',
- {method:'POST', headers:{'Content-Type':'application/json'},
- body: JSON.stringify({delta: 5})}).then(r => r.json());
-```
+## Packages and Collections
-### Typed routes, one declaration — `define-api-routes`
+The repository root is one installable Racket package using `collection 'multi`. The main top-level collections are:
-```racket
-(define-api-routes api
- [(POST "api/counter/bump")
- (bump [delta exact-nonnegative-integer? 1]) ; required, checked, or default
- (hasheq 'count (add1 delta))])
-```
-
-One clause defines a Racket procedure (`bump`), a route (bad input → a 400
-naming the parameter; handler errors → 500), and a JS client entry — the
-served `/glaze/api.js` exposes `glaze.api.counterBump({delta: 5})`, plus
-`glaze.call(method, path, body)` and `glaze.on(name, fn)`.
-
-### Backend → frontend push (SSE)
-
-```racket
-(define bus (make-event-bus))
-(start-server ... #:events bus)
-(bus-broadcast! bus 'count-changed (hasheq 'count 42)) ; from any thread
-```
+- `glaze/` — framework library and public facade
+- `glaze-cli/` — `raco glaze` commands
+- `glaze-doc/` — Scribble documentation
+- `glaze-test/` — test suite
+- `examples/` — runnable examples (excluded from package setup compilation)
+- `scripts/` — CI and verification scripts
-```js
-glaze.on('count-changed', s => render(s.count));
-```
-
-The page can also use `new EventSource('/glaze/events')` directly. Works in
-the browser fallback too — same origin, no extra port.
-
-### Security
-
-- Requests are only served for Host headers `127.0.0.1` / `localhost` /
- `[::1]` (DNS-rebinding guard; hostile origins get 403).
-- API handlers never crash the connection — parameter problems are 400
- JSON, handler exceptions are 500 JSON (and reach `run-app`'s
- `#:on-error` for crash reporting hooks).
-- Optional API token (`#:api-token`): guards API routes and the SSE stream
- (401 otherwise). The app window opens a one-time `?glaze-token=` bootstrap
- URL that exchanges the token for an `HttpOnly` cookie (api.js deliberately
- hands out nothing); programmatic clients send `X-Glaze-Token`.
- Honest scope: defense-in-depth against casual local callers — a process
- of the same user can still read the token from process memory.
-- Update checks: `run-app #:check-update #:current-version "1.0.0"`
- fetches `{"version","url","notes"}`, reports to stderr and broadcasts
- `update-available`. Self-replacement stays the app's decision.
+Inside `glaze/`, `webview/`, `tray/`, and `sys/` each contain a public dispatcher plus platform-specific backends. Applications should normally use `(require glaze)` instead of importing backend modules.
-See [`examples/counter/`](examples/counter/) for the complete working app.
-
-## System Integrations (`glaze/sys`)
-
-```racket
-(require glaze/sys)
-(clipboard-set! "hello") ; (clipboard-get)
-(notify! "Download finished" "report.pdf is ready")
-(open-path "/Users/me/report.pdf") ; default handler
-(reveal-path "/Users/me/report.pdf"); Finder/Explorer, selected
-(unless (single-instance? "com.me.app") (exit 0))
-```
+## Examples
-Desktop notifications work on all three platforms (osascript /
-notify-send / WinRT toast via PowerShell).
+Start with the small examples before the full showcase:
-Window controls (from `glaze/webview`): `webview-set-title!`,
-`webview-set-size!`, `webview-set-fullscreen!`.
+- [`examples/hello/`](examples/hello/) — minimal `run-app` application
+- [`examples/tray/`](examples/tray/) — system tray and menu actions
+- [`examples/events/`](examples/events/) — JSON request + SSE event push
+- [`examples/counter/`](examples/counter/) — fuller JS/Racket bridge example
+- [`examples/showcase/`](examples/showcase/) — integrated feature showcase
+- [`examples/agent-verify.rkt`](examples/agent-verify.rkt) — programmatic WebView verification
+- [`examples/webview-demo.rkt`](examples/webview-demo.rkt) — direct WebView lifecycle demo
-## System Tray
+## Project Status
-Glaze provides a cross-platform system tray so your app can live in the notification area / menu bar with a right-click (or left-click on macOS) menu. The backend is chosen by platform — pure Racket FFI, no native compilation required:
+Glaze is a pre-1.0 project (`0.7` in package metadata). It already contains working cross-platform implementations and CI coverage, but API boundaries are still being stabilized.
-- **Windows** — `Shell_NotifyIconW` via `ffi/unsafe`
-- **macOS** — `NSStatusItem` / `NSMenu` via `ffi/unsafe/objc`
-- **Linux** — `libayatana-appindicator` + `libgtk-3` via `ffi/unsafe`
+For new applications, prefer the `glaze` facade and documented APIs. Direct imports of files such as `webview-windows.rkt`, `tray-macos.rkt`, or `sys-linux.rkt` are implementation details and should not be treated as stable application APIs.
-If a platform's native libraries aren't available at runtime, the tray silently degrades to a no-op so the rest of the app keeps working.
+Backward compatibility is preferred during the 0.x stabilization work; large rewrites and unnecessary file moves are intentionally avoided.
-```racket
-(require glaze)
+## Roadmap
-(define t
- (make-tray #:icon #f
- #:tooltip "My Glaze App"
- #:menu (list (make-menu-item "Quit"
- #:action (lambda () (exit 0))))))
-(tray-set-tooltip! t "running")
-;; ...later
-(tray-close t)
-```
+See [`ROADMAP.md`](ROADMAP.md). The near-term focus is lifecycle, public API clarity, examples, tests, and documentation. IPC/event refinements and additional capabilities come later; a plugin SDK and hot reload are explicitly not part of the current stabilization pass.
-> **macOS note:** a pure menu-bar app (no Dock icon) requires building as an `.app` bundle with `LSUIElement` set — `raco glaze build` configures this for you.
+## Documentation
-## App Platform APIs
+- [`docs/architecture.md`](docs/architecture.md) — architecture and dependency rules
+- [`ROADMAP.md`](ROADMAP.md) — small staged roadmap
+- [`CONTRIBUTING.md`](CONTRIBUTING.md) — contributor workflow
+- `raco docs glaze` / the `glaze-doc` collection — API reference
-Beyond the server/webview core, Glaze ships the desktop-app odds and ends commercial apps need:
+## Development
-```racket
-(require glaze)
-
-;; ---- native file dialogs (NSOpenPanel / comdlg32 / zenity-kdialog) ----
-(define f (pick-file #:title "Open report" #:filters '(("Reports" "*.rep" "*.csv"))))
-(define dir (pick-folder #:title "Where?"))
-(define out (save-file-dialog #:title "Save as" #:default-name "out.rep"))
-;; #f = cancelled; check (dialog-supported?) for a graceful path.
-
-;; ---- menu bar (declarative, three platforms) ----
-(webview-set-menu! wv
- (list (make-menu "File"
- (list (make-menu-item "Open…" #:accel "CmdOrCtrl+O"
- #:action open-doc)
- menu-separator
- (make-menu-item "Quit" #:action (lambda () (exit 0)))))))
-;; macOS accelerators really fire; Windows/Linux show them (v1).
-
-;; ---- deep links (myapp://...) ----
-(ensure-url-scheme! "myapp") ; Windows registry / Linux xdg;
- ; macOS via build --url-scheme
-
-;; ---- launch at login ----
-(auto-launch-set! "MyApp" #t)
-(auto-launch-enabled? "MyApp")
-
-;; ---- multi-window ----
-(for ([w (all-webviews)]) (webview-focus! w))
-(wait-for-webviews) ; block until every window closes
+```bash
+raco pkg install --auto --no-docs --link "$PWD"
+raco make glaze/main.rkt glaze-cli/cli.rkt
+raco test glaze-test/
```
-## Examples
-
-| Example | What it shows |
-|---|---|
-| [`examples/showcase/`](examples/showcase/) | **Kitchen sink (start here)** — every capability in one window |
-| [`examples/hello/`](examples/hello/) | Minimal app — `run-app` in 8 lines |
-| [`examples/counter/`](examples/counter/) | JS↔Racket bridge — `fetch` calls Racket state |
-| [`examples/webview-demo.rkt`](examples/webview-demo.rkt) | Webview lifecycle: load, navigate, close, verification APIs |
-| [`examples/agent-verify.rkt`](examples/agent-verify.rkt) | Agent workflow: assert page state + screenshot with no human |
-| [`examples/tray-demo.rkt`](examples/tray-demo.rkt) | Cross-platform system tray with a working menu |
+CI additionally runs native WebView end-to-end tests and a packaging smoke build on Windows, macOS, and Linux.
-## Roadmap
+## Contributing
-- [x] **Phase 1** — Local HTTP server + system browser
-- [x] **Phase 2** — Frontend asset bundling, system tray, app packaging
-- [x] **Phase 3** — Native WebView embedding (WebView2 / WKWebView / WebKitGTK) — *done, verified by the 3-OS CI e2e*
+Contributions are welcome. Please keep changes small enough to review, preserve existing APIs where practical, add regression tests for behavior changes, and keep platform-specific code behind the dispatcher modules.
-> **Phase 3 done:** all three backends (macOS WKWebView, Windows WebView2, Linux
-> WebKitGTK) pass the real-window CI e2e — open, page load, `webview-title`/`url`
-> verification, `webview-capture!` screenshots, `webview-navigate`, close (programmatic
-> and OS chrome), and `#:on-close` callbacks; `#:devtools?` and resize-follow on all
-> three platforms. Pure Racket FFI throughout, no compiler. Remaining polish
-> (not a blocker): multi-window ergonomics.
+See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the repository workflow.
## License
-Licensed under the [MIT License](LICENSE).
+MIT — see [`LICENSE`](LICENSE).
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 318b128..07d7561 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -1,420 +1,213 @@
# Glaze
-用 [Racket](https://racket-lang.org/) 做后端、Web 技术做前端,构建桌面应用。一个 Racket 版的 [Tauri](https://tauri.app/) —— 用 Racket 写业务逻辑,用 HTML/CSS/JS 构建界面,打包为桌面应用。
+一个用 Racket 构建现代桌面应用的 Lisp-native 框架。
-[](https://github.com/turinglambdaai/glaze/actions/workflows/ci.yml)  [](LICENSE) [](CHANGELOG.md)
+Glaze 让应用逻辑继续留在 Racket 中,界面使用普通 Web 技术,并通过统一 API 接入原生 WebView、系统托盘、剪贴板、通知、文件对话框和应用打包等桌面能力。
-[English](README.md) · **中文**
-
-
-
-## 为什么选择 Glaze?
-
-Racket 自带的 `racket/gui` 可以用,但很难做出现代化的产品级 UI。Glaze 采用不同的思路:从 Racket 启动本地 Web 服务,用系统浏览器(Phase 1)或嵌入式 WebView(Phase 3)展示。
+[](https://github.com/turinglambdaai/glaze/actions/workflows/ci.yml)  [](LICENSE)
-你将获得:
-
-- **Racket 写逻辑** —— 完整的宏系统、contracts、模式匹配
-- **Web 写界面** —— Tailwind、Svelte、React 或任何 Web 框架
-- **JSON API 桥接** —— 页面用普通 `fetch("/api/...")` 调用 Racket
-
-### 横向对比
+[English](README.md) · **中文**
-| | Glaze | Tauri | Electron | wails |
-|---|---|---|---|---|
-| 后端语言 | Racket | Rust | JS/Node | Go |
-| 原生工具链 | **无需**(纯 FFI) | Rust + cargo | 无 | Go + WebView2 依赖 |
-| 二进制体积 | 极小 | 小 | 100 MB+ | 小 |
-| 前后端桥接 | HTTP JSON 路由(`fetch`) | `invoke()` IPC | Node API | 绑定层 |
-| 无 WebView 时浏览器兜底 | **支持** | 不支持 | 不支持 | 不支持 |
-| Agent 友好的 UI 验证(title/url/截图) | **内置** | 需 WebDriver | 需 CDP | 有限 |
-| WebView 后端 | WebView2 / WKWebView / WebKitGTK | 相同 | 自带 Chromium | WebView2/WKWebView |
+## 为什么是 Glaze
-三个平台的 WebView 后端均通过真窗口 CI e2e(open、加载、截图、导航、关闭、on-close)。剩余诚实差距:IPC 为纯 JSON 无类型层、Linux 需要桌面会话或 Xvfb。
+Racket 已经提供 `racket/gui`,Glaze 面向的是另一类桌面应用:**Web UI + Racket Runtime + 原生桌面能力**。
-## 平台支持状态
+Glaze 不是 Electron 的复制品。它不内置 Chromium,也不额外引入 Node Runtime。当前设计理念更接近 Tauri:
-| 能力 | macOS | Windows | Linux |
-|---|---|---|---|
-| HTTP 服务器 + 浏览器 | ✅ | ✅ | ✅ |
-| 系统托盘 | ✅ | ✅ | ✅(CI 验证) |
-| JSON API 桥接 | ✅ | ✅ | ✅ |
-| 原生 WebView 窗口 | ✅ 端到端验证 | ✅ CI e2e(WebView2) | ✅ CI e2e(Xvfb + WebKitGTK) |
-| `webview-title` / `webview-url` | ✅ | ✅ | ✅ |
-| `webview-capture!`(截图) | ✅ | ✅(PrintWindow + PowerShell 转 PNG) | ✅(gdk_pixbuf) |
-| `#:devtools?` | ✅(inspectable,macOS 13+) | ✅(`OpenDevToolsWindow`) | ✅(WebKitGTK inspector) |
+```text
+Web UI
+ |
+ | HTTP / JSON / SSE
+ v
+Racket Runtime
+ |
+ +-- WebView
+ +-- Tray
+ +-- System capabilities
+ +-- Packaging helpers
+ |
+Native OS APIs
+```
-原生后端不可用时,`run-app` / `open-window` 自动回退系统浏览器 —— 应用在所有平台都能跑。
+当前通过 Racket FFI 使用系统 WebView:
-## 环境要求
+- Windows:WebView2
+- macOS:WKWebView
+- Linux:WebKitGTK
-| 依赖 | 用途 |
-|------|------|
-| [Racket](https://racket-lang.org/) | 7.0 或更高版本(包含 `raco`) |
+当前前后端桥接有意保持简单:请求使用本地 HTTP JSON API,Racket 向前端推送事件使用 Server-Sent Events。完整 RPC 框架和插件系统还不是当前公共架构的一部分。
## 快速开始
-### 1. 安装
+安装:
```bash
raco pkg install --auto glaze
```
-单个 Racket 包:一次安装即包含 `glaze` 库、`raco glaze` CLI 和文档(之后可用 `raco docs` 浏览)。
-
-### 2. 创建新项目
+开发仓库可以直接 link:
```bash
-raco glaze init myapp
-cd myapp
+git clone https://github.com/turinglambdaai/glaze.git
+cd glaze
+raco pkg install --auto --no-docs --link "$PWD"
```
-### 4. 运行
-
-```bash
-racket main.rkt
-```
-
-会打开一个原生窗口展示你的应用(由本地 HTTP 服务器驱动);无 WebView 后端时自动回退系统浏览器,访问 `http://127.0.0.1:<端口>`。
-
-> 想直接从 GitHub 检出安装而不走包索引?
-> ```bash
-> git clone https://github.com/turinglambdaai/glaze.git
-> cd glaze
-> raco pkg install --auto --link "$PWD"
-> ```
-> 想参与 Glaze 开发,见 [CONTRIBUTING.md](CONTRIBUTING.md)。
-
-## CLI 命令
-
-```bash
-raco glaze init # 创建新的 Glaze 项目
-raco glaze dev # 启动开发服务器并自动打开浏览器
-raco glaze build # 构建可分发包(exe + 内置资源)
-raco glaze keygen # 生成用于许可证签名的 RSA 密钥对
-raco glaze license # 签发 / 校验离线许可证文件
-raco glaze help # 显示帮助
-```
-
-### `build`
-
-把 Glaze 项目打包为平台分发产物(`raco exe` + `raco distribute`),前端资源随可执行文件一起分发。macOS 产出标准 `.app` bundle,`--version` 会写入 `Info.plist`。
-
-```bash
-raco glaze build --name myapp # 产出 dist/myapp(.exe) + dist/lib + dist/public
-raco glaze build --name myapp --version 1.2.0 --installer # 额外产出 msi / dmg / AppImage(缺失工具链时回落为 zip/tar.gz)
-```
-
-选项:`--name`、`--version`、`--icon <.ico/.icns>`、`--entry `(默认 `main.rkt`)、`--out `(默认 `dist`)、`--embed-dlls`(Windows:单文件 exe)、`--installer`。
-
-> installer 步骤会探测本机的打包工具链(Windows 的 WiX / NSIS,macOS 的 `create-dmg` / `hdiutil`,Linux 的 `appimagetool` / `linuxdeploy`),**缺失时优雅降级**为 `.zip` / `.tar.gz` 并打印提示告知需要安装什么。
-
-### 代码签名与公证
-
-未签名的应用会被 macOS Gatekeeper 和 Windows SmartScreen 拦截。`build` 内置了平台签名器:
-
-```bash
-# macOS —— Developer ID 身份 + hardened runtime + 公证:
-raco glaze build --name myapp \
- --sign "Developer ID Application: Acme Inc (TEAMID)" \
- --notarize acme-notary --installer
-
-# macOS —— ad-hoc 签名(无证书,本地测试 / CI 用):
-raco glaze build --name myapp --sign -
-
-# Windows —— signtool 按证书 SHA-1 指纹签名(带 RFC-3161 时间戳):
-raco glaze build --name myapp --sign 40HEXCHARS --installer
-```
-
-说明:`--sign` 在 macOS 接受 codesign 身份,在 Windows 接受 `signtool` 的证书 SHA-1 指纹(40 位十六进制)或主题名。macOS 默认启用 hardened runtime(`--no-hardened-runtime` 可关;ad-hoc 身份下自动跳过——其 library validation 会拒绝应用自身的 framework)。`--notarize ` 会把构建出的 dmg 提交 `notarytool` 公证并钉上票据。`--entitlements `、`--timestamp-url ` 补齐其余场景。**签名失败会中止构建**;工具链缺失则响亮地降级并告警。
-
-### 许可证(收费应用)
-
-`glaze/license` 提供零原生依赖的离线许可证方案——RSA-2048/SHA-256 签名走系统 `openssl` CLI(三平台开箱即有):
-
-```bash
-# 开发者侧 —— 一次性:
-raco glaze keygen --out keys # keys/private.pem + keys/public.pem
-# 按客户签发(可选有效期与机器绑定):
-raco glaze license sign --key keys/private.pem --product "MyApp" \
- --subject "customer@example.com" --expiry 2027-12-31 --out app.license
-raco glaze license verify --pub keys/public.pem --product "MyApp" app.license
-```
-
-```racket
-(require glaze/license)
-
-(define r (validate-license "app.license" #:public-key "keys/public.pem" #:product "MyApp"))
-(unless (hash-ref r 'valid)
- (error 'myapp "许可证无效:~a" (hash-ref r 'reason))) ; expired / machine / signature ...
-
-;; 机器绑定:对系统机器标识做稳定摘要
-(issue-license ... #:machine-id (machine-id))
-```
-
-校验失败原因 (`reason`) 是稳定的标签(`missing-file`、`malformed`、`signature`、`product`、`expired`、`machine`、`openssl-unavailable`),可直接用于界面提示。诚实边界:这套方案防的是随手共享许可证——本地攻击者总能给二进制打补丁,它不是防篡改机制。
-
-### 更新包完整性
-
-`check-update` 会透传 manifest 里可选的 `"sha256"` 字段;下载完更新包后先校验再替换:
-
-```racket
-(define info (check-update manifest-url #:current-version "1.0.0"))
-;; 应用自行下载 (hash-ref info 'url) ... 然后:
-(verify-file-sha256 artifact (hash-ref info 'sha256)) ; #t / #f(#f = 无法校验)
-```
-
-## 项目结构
-
-一个新的 Glaze 项目结构如下:
-
-```
-myapp/
-├── main.rkt # Racket 入口
-└── public/
- └── index.html # 前端页面
-```
-
-`main.rkt` 启动本地 HTTP 服务器,从 `public/` 目录提供静态文件并打开浏览器:
+最小应用只需要统一公共入口:
```racket
#lang racket/base
-(require glaze)
+(require racket/runtime-path
+ glaze)
-(define-values (port server)
- (start-dev-server #:public-dir "public"))
+(define-runtime-path public "public")
-(printf "Glaze app running at http://127.0.0.1:~a\n" port)
-(open-browser (format "http://127.0.0.1:~a" port))
-
-(with-handlers ([exn:break?
- (lambda (e)
- (stop-server server)
- (printf "Server stopped.\n"))])
- (sync never-evt))
-```
-
-## 仓库结构
-
-仓库根目录即一个可安装的 Racket 包,每个顶层目录对应一个集合(collection):
-
-```
-glaze/ # 仓库根 = `glaze` 包(info.rkt)
-├── glaze/ # 核心库:服务器、API 桥、webview、托盘、系统集成、打包
-├── glaze-cli/ # CLI 工具(raco glaze init / dev / build)
-├── glaze-doc/ # 文档(Scribble)
-├── glaze-test/ # 测试套件
-├── examples/ # 可运行示例
-└── scripts/ # CI 辅助脚本(webview e2e)
+(run-app #:public-dir public
+ #:title "Hello Glaze")
```
-## API
+在 `public/` 中放置 `index.html` 后运行程序即可。完整最小示例见 [`examples/hello/`](examples/hello/)。
-### `run-app`
+也可以使用 CLI 创建项目:
-一键入口:自动挑空闲端口、启动服务器(静态 + JSON API)、打开原生 WebView 窗口、阻塞到窗口关闭。
-
-```racket
-(run-app #:public-dir "public"
- #:api (list (GET "api/ping" ...)))
-;; webview 路径:窗口关闭 -> 服务器停止 -> (values 'webview shutdown)
-;; 浏览器回退(无原生后端):打开浏览器 -> (values 'browser shutdown)
+```bash
+raco glaze init myapp
+cd myapp
+racket main.rkt
```
-### `start-server` / `start-dev-server`
+## 已实现能力
-启动本地 HTTP 服务器:静态文件 + SPA 回退 + 可选 JSON API 路由。`start-dev-server` 为兼容别名。
+当前仓库已经包含:
-```racket
-(start-server #:port 8080
- #:public-dir "public"
- #:api (list (GET "api/ping" (lambda (req) (hasheq 'pong #t)))))
-;; 返回 (values port shutdown-proc);返回前会确认端口已在监听
-```
-
-### `stop-server`
+- 原生 WebView 窗口:生命周期、导航、标题/URL 查询、截图、窗口控制和菜单
+- 本地静态文件服务器与 SPA fallback
+- JSON API 路由和自动生成的浏览器客户端
+- Racket → 前端的 SSE 事件推送
+- 系统托盘和菜单
+- 剪贴板、通知、打开/定位文件、单实例能力
+- 文件/目录对话框、Deep Link、开机自启动辅助能力
+- `raco glaze build` 应用打包
+- 更新检查和离线许可证工具
+- 原生 WebView 不可用时的系统浏览器 fallback
-停止服务器。
+Glaze 本身使用 Racket 实现,原生集成主要通过 FFI;核心框架不要求用户安装 C 编译器。
-```racket
-(stop-server shutdown-proc)
-```
+## 平台支持
-### `open-browser`
+仓库 CI 使用 Racket 8.12 在 Windows、macOS、Linux 上运行测试,并在三个平台执行真实 WebView 端到端验证。Linux CI 使用 Xvfb + WebKitGTK。
-用系统默认浏览器打开 URL(跨平台:Windows、macOS、Linux)。
+| 能力 | Windows | macOS | Linux |
+|---|---|---|---|
+| 本地 Server / JSON API / SSE | 支持 | 支持 | 支持 |
+| 原生 WebView | WebView2 | WKWebView | WebKitGTK |
+| 系统托盘 | 支持 | 支持 | 支持 |
+| 系统能力封装 | 支持 | 支持 | 支持 |
+| 打包流程 | 支持 | 支持 | 支持 |
-```racket
-(open-browser "http://127.0.0.1:8080")
-```
+部分原生能力依赖操作系统组件或桌面会话。应用层不应该直接 require 某个平台 backend;不支持的能力应通过公共 dispatcher 明确失败或使用框架提供的 fallback。
-## JavaScript 桥接
+## 架构
-前端用普通 `fetch("/api/...")` 调 Racket —— 这是 Glaze 对 Tauri `invoke()` 的回答。同一套代码在嵌入式 WebView、系统浏览器回退、dev 调试(可 curl)下都工作。路由是普通值:
+应用推荐只依赖 `(require glaze)`。WebView、Tray、Sys 模块在内部完成平台 backend 分发:
-```racket
+```text
+Application
+ |
+ v
(require glaze)
-
-(GET "api/ping" (lambda (req) (hasheq 'pong #t)))
-(POST "api/items/:id/bump" (lambda (req id) (hasheq 'id id 'bumped #t)))
-(POST "api/echo" (lambda (req)
- (define body (request-json-body req))
- (hasheq 'echo body)))
-```
-
-- Handler 收到 request 加捕获的 `:param`;返回 jsexpr(自动包装为 JSON 200)或完整 response
-- `request-json-body` 解析 JSON body —— 注意 Racket jsexpr 把 JSON 对象键解析为 **symbol**(`(hash-ref body 'delta)`)
-- handler 抛异常会变成 500 JSON 错误,不会断掉连接
-- 未匹配的请求回落到静态文件(SPA `index.html` 回退)
-
-页面侧:
-
-```js
-const s = await fetch('/api/counter/bump',
- {method:'POST', headers:{'Content-Type':'application/json'},
- body: JSON.stringify({delta: 5})}).then(r => r.json());
+Public facade: glaze/main.rkt
+ |
+ +-------------------------------+
+ | | |
+ v v v
+Runtime Capabilities Tooling
+app/server webview/main build/update
+api/events tray/main CLI
+ sys/main
+ | |
+ +-------+-------+
+ v
+Platform backends
+Windows / macOS / Linux / stub
```
-### 一处声明,三重产物 —— `define-api-routes`
+当前目标不是为了“架构漂亮”而一次性移动全部文件,而是先稳定依赖方向、生命周期和公共 API 合约。
-```racket
-(define-api-routes api
- [(POST "api/counter/bump")
- (bump [delta exact-nonnegative-integer? 1]) ; 必填+校验,或缺省
- (hasheq 'count (add1 delta))])
-```
-
-一个子句同时定义:Racket 过程(`bump`)、路由(坏输入 → 报参数名的 400;过程异常 → 500)、
-JS 客户端入口 —— `/glaze/api.js` 自动提供 `glaze.api.counterBump({delta: 5})`、
-`glaze.call(method, path, body)` 和 `glaze.on(name, fn)`。
-
-### 后端 → 前端推送(SSE)
-
-```racket
-(define bus (make-event-bus))
-(start-server ... #:events bus)
-(bus-broadcast! bus 'count-changed (hasheq 'count 42)) ; 任意线程
-```
+详细设计见 [`docs/architecture.md`](docs/architecture.md)。
-```js
-glaze.on('count-changed', s => render(s.count));
-```
+## 包与 Collection
-页面也可以直接 `new EventSource('/glaze/events')`。浏览器回退同样可用 —— 同源、无额外端口。
+仓库根目录是一个 `collection 'multi` 的可安装 Racket package:
-### 安全
+- `glaze/` —— 框架核心和公共 facade
+- `glaze-cli/` —— `raco glaze` 命令
+- `glaze-doc/` —— Scribble API 文档
+- `glaze-test/` —— 测试套件
+- `examples/` —— 可运行示例
+- `scripts/` —— CI 和验证脚本
-- 仅服务 Host 为 `127.0.0.1` / `localhost` / `[::1]` 的请求(DNS rebinding 防护,恶意源 403)。
-- API handler 永不断连接 —— 参数问题 400 JSON,过程异常 500 JSON(并送达 `run-app` 的
- `#:on-error`,接崩溃上报钩子)。
-- 可选 API token(`#:api-token`):保护 API 路由与 SSE 流(否则 401)。应用窗口打开一次性的
- `?glaze-token=` 引导 URL,把 token 换成 `HttpOnly` cookie(api.js 有意不发放任何凭据);
- 程序化客户端发 `X-Glaze-Token`。诚实边界:对随手本机调用者提高门槛 —— 同用户进程仍可从
- 进程内存读取 token。
-- 更新检查:`run-app #:check-update <清单url> #:current-version "1.0.0"` 拉取
- `{"version","url","notes"}`,stderr 提示并广播 `update-available`。自我替换由应用决策。
+`glaze/webview/`、`glaze/tray/`、`glaze/sys/` 内部包含公共 dispatcher 和平台实现。普通应用应优先 `(require glaze)`,而不是依赖 `webview-windows.rkt`、`tray-macos.rkt`、`sys-linux.rkt` 等实现文件。
-完整可运行的应用见 [`examples/counter/`](examples/counter/)。
+## 示例
-## 系统集成(`glaze/sys`)
+建议按以下顺序阅读:
-```racket
-(require glaze/sys)
-(clipboard-set! "hello") ; (clipboard-get)
-(notify! "下载完成" "report.pdf 已就绪")
-(open-path "/Users/me/report.pdf") ; 默认处理器打开
-(reveal-path "/Users/me/report.pdf"); Finder/资源管理器中定位
-(unless (single-instance? "com.me.app") (exit 0))
-```
+- [`examples/hello/`](examples/hello/) —— 最小 `run-app` 应用
+- [`examples/tray/`](examples/tray/) —— 系统托盘与菜单
+- [`examples/events/`](examples/events/) —— JSON 请求 + SSE 推送
+- [`examples/counter/`](examples/counter/) —— 更完整的 JS/Racket bridge
+- [`examples/showcase/`](examples/showcase/) —— 综合能力展示
+- [`examples/agent-verify.rkt`](examples/agent-verify.rkt) —— 程序化 WebView 验证
+- [`examples/webview-demo.rkt`](examples/webview-demo.rkt) —— 直接 WebView 生命周期示例
-桌面通知三平台可用(osascript / notify-send / WinRT toast 经 PowerShell)。
+## 项目状态
-窗口控制(`glaze/webview`):`webview-set-title!`、`webview-set-size!`、
-`webview-set-fullscreen!`。
+Glaze 当前仍是 pre-1.0 项目(package metadata 为 `0.7`)。跨平台实现、CI、打包链路已经存在,但公共 API 和生命周期仍处于稳定化阶段。
-## 系统托盘
+0.x 阶段优先保持兼容:不会仅仅为了未来目录更漂亮而大规模移动 backend,也不会随意删除已有 API。对于新应用,建议只使用文档化的公共入口。
-Glaze 提供跨平台的系统托盘,让你的应用驻留在通知区 / 菜单栏,带右键(macOS 为左键)菜单。后端按平台选择——纯 Racket FFI,无需编译任何原生代码:
+## 安全边界
-- **Windows** — 通过 `ffi/unsafe` 调 `Shell_NotifyIconW`
-- **macOS** — 通过 `ffi/unsafe/objc` 调 `NSStatusItem` / `NSMenu`
-- **Linux** — 通过 `ffi/unsafe` 调 `libayatana-appindicator` + `libgtk-3`
+Glaze 的本地 HTTP bridge、静态文件服务、打包/签名、更新与原生 FFI 都属于安全敏感边界。安全问题请参考 [`SECURITY.md`](SECURITY.md),不要在公开 issue 中直接发布利用细节或私钥等敏感信息。
-运行时若某平台的原生库不可用,托盘会静默降级为空操作,应用的其余部分照常运行。
+## Roadmap
-```racket
-(require glaze)
+见 [`ROADMAP.md`](ROADMAP.md)。近期重点是:
-(define t
- (make-tray #:icon #f
- #:tooltip "我的 Glaze 应用"
- #:menu (list (make-menu-item "退出"
- #:action (lambda () (exit 0))))))
-(tray-set-tooltip! t "运行中")
-;; ...稍后
-(tray-close t)
-```
+- 稳定 application lifecycle
+- 明确 public API
+- 完善跨平台测试与打包验证
+- 文档和示例
+- 收紧安全与错误处理边界
-> **macOS 注意**:纯菜单栏应用(不显示 Dock 图标)需要构建为 `.app` bundle 并设置 `LSUIElement`——`raco glaze build` 会为你配置好。
+IPC/event 模型的进一步演进、更多系统 capability 会放在后续阶段;插件 SDK、完整 hot reload 不属于当前稳定化工作的范围。
-## 应用平台 API
+## 文档
-除服务器/webview 核心外,Glaze 内置商业桌面应用所需的周边能力:
+- [`docs/architecture.md`](docs/architecture.md) —— 架构与依赖规则
+- [`ROADMAP.md`](ROADMAP.md) —— 分阶段路线图
+- [`CONTRIBUTING.md`](CONTRIBUTING.md) —— 贡献流程
+- [`SECURITY.md`](SECURITY.md) —— 安全报告流程
+- `raco docs glaze` / `glaze-doc` —— API 文档
-```racket
-(require glaze)
+## 开发
-;; ---- 原生文件对话框(NSOpenPanel / comdlg32 / zenity-kdialog)----
-(define f (pick-file #:title "打开报告" #:filters '(("报告" "*.rep" "*.csv"))))
-(define dir (pick-folder #:title "选择目录"))
-(define out (save-file-dialog #:title "另存为" #:default-name "out.rep"))
-;; #f = 用户取消;可先用 (dialog-supported?) 做优雅降级判断。
-
-;; ---- 菜单栏(声明式,三平台)----
-(webview-set-menu! wv
- (list (make-menu "文件"
- (list (make-menu-item "打开…" #:accel "CmdOrCtrl+O"
- #:action open-doc)
- menu-separator
- (make-menu-item "退出" #:action (lambda () (exit 0)))))))
-;; macOS 快捷键真实生效;Windows/Linux 目前仅展示(v1)。
-
-;; ---- 深度链接(myapp://…)----
-(ensure-url-scheme! "myapp") ; Windows 注册表 / Linux xdg;
- ; macOS 在构建时 --url-scheme 声明
-
-;; ---- 开机自启 ----
-(auto-launch-set! "MyApp" #t)
-(auto-launch-enabled? "MyApp")
-
-;; ---- 多窗口 ----
-(for ([w (all-webviews)]) (webview-focus! w))
-(wait-for-webviews) ; 阻塞直到所有窗口关闭
+```bash
+raco pkg install --auto --no-docs --link "$PWD"
+raco make glaze/main.rkt glaze-cli/cli.rkt
+raco test glaze-test/
```
-## 示例
-
-| 示例 | 展示内容 |
-|---|---|
-| [`examples/showcase/`](examples/showcase/) | **综合演示(推荐先看)** —— 全部能力一屏尽览 |
-| [`examples/hello/`](examples/hello/) | 最小应用 —— 8 行 `run-app` |
-| [`examples/counter/`](examples/counter/) | JS↔Racket 桥接 —— `fetch` 调用 Racket 状态 |
-| [`examples/webview-demo.rkt`](examples/webview-demo.rkt) | WebView 生命周期:加载、导航、关闭、验证 API |
-| [`examples/agent-verify.rkt`](examples/agent-verify.rkt) | Agent 工作流:无人值守断言页面状态 + 截图 |
-| [`examples/tray-demo.rkt`](examples/tray-demo.rkt) | 跨平台系统托盘 + 可用菜单 |
+CI 还会在 Windows、macOS 和 Linux 上运行原生 WebView e2e、最终打包产物执行验证和安装器构建,并验证过滤后的 Racket source package 可以独立安装。
-## 路线图
+## 贡献
-- [x] **Phase 1** — 本地 HTTP 服务器 + 系统浏览器
-- [x] **Phase 2** — 前端资源打包、系统托盘、应用打包
-- [x] **Phase 3** — 原生 WebView 嵌入(WebView2 / WKWebView / WebKitGTK)— *完成,三平台 CI e2e 验证*
+欢迎贡献。请优先提交范围清晰、能够单独审查的修改;在可行的情况下保持现有 API 兼容,并为行为修复增加 regression test。平台实现应继续位于公共 dispatcher 后面。
-> **Phase 3 完成:** 三个后端(macOS WKWebView、Windows WebView2、Linux WebKitGTK)均通过
-> 真窗口 CI e2e——open、页面加载、`webview-title`/`url` 验证、`webview-capture!` 截图、
-> `webview-navigate`、关闭(编程与系统按钮)、`#:on-close` 回调;三平台均已支持
-> `#:devtools?` 与窗口缩放跟随。全程纯 Racket FFI,无编译器。剩余打磨(非阻塞):多窗口体验。
+详细流程见 [`CONTRIBUTING.md`](CONTRIBUTING.md)。
-## 许可证
+## License
-基于 [MIT 许可证](LICENSE) 授权。
+MIT —— 见 [`LICENSE`](LICENSE)。
diff --git a/ROADMAP.md b/ROADMAP.md
new file mode 100644
index 0000000..1e15855
--- /dev/null
+++ b/ROADMAP.md
@@ -0,0 +1,51 @@
+# Glaze Roadmap
+
+Glaze is a pre-1.0 project. This roadmap is intentionally small: it describes the next architectural steps without promising a large plugin ecosystem or a full desktop platform rewrite.
+
+## v0.x — Stabilize
+
+The current priority is to make the framework predictable for application authors and maintainers.
+
+- stabilize application lifecycle semantics around `run-app`, window close, browser fallback, and shutdown
+- keep `(require glaze)` as the recommended application-facing facade
+- document which modules are public, internal, platform-specific, or experimental
+- tighten argument validation and error behavior where contracts are currently implicit
+- keep examples minimal, runnable, and aligned with recommended APIs
+- add regression tests around public API imports, platform-independent behavior, events, and lifecycle
+- keep Windows, macOS, and Linux backend contracts aligned
+- improve packaging and documentation without introducing avoidable breaking changes
+
+## Next
+
+Once the current lifecycle and API surface are better defined, the next layer of work can focus on communication and common desktop capabilities.
+
+- define a clearer JS/Racket message bridge on top of the existing HTTP/SSE model
+- make the event model more explicit and consistent across runtime and UI integration
+- add notification/storage capabilities behind the same public capability boundary
+- improve packaging metadata, signing workflows, and project configuration
+- continue consolidating generated/scaffolded applications around the public facade
+
+These changes should remain incremental. Existing HTTP JSON routes and SSE behavior should not be removed merely to introduce a new abstraction.
+
+## Later
+
+Possible longer-term work, after the core contracts are stable:
+
+- plugin SDK with explicit capability and version boundaries
+- development-time hot reload
+- richer project templates
+- ecosystem integrations for additional native capabilities
+- reusable capability packages such as filesystem, serial, CAN, or application-specific integrations
+
+These are directions, not commitments for the current release line.
+
+## Explicitly Not in the Current Stabilization Pass
+
+The current architecture work does not attempt to:
+
+- reproduce Electron feature-for-feature
+- bundle a custom browser runtime
+- introduce a large JavaScript build stack
+- rewrite all native backends
+- implement a complete RPC framework
+- implement a plugin system before the public API and lifecycle are stable
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..4a7544f
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,36 @@
+# Security Policy
+
+Glaze embeds native desktop capabilities behind a local HTTP bridge, so security reports are treated as correctness issues, not feature requests.
+
+## Supported versions
+
+Glaze is currently pre-1.0. Security fixes are applied to the latest development/release line. Older 0.x releases may require upgrading rather than receiving a backport.
+
+## Reporting a vulnerability
+
+Please do not publish exploit details, credentials, private keys, license-signing material, or sensitive reproduction data in a public issue.
+
+Prefer GitHub's private **Report a vulnerability** / Security Advisory flow for this repository when it is available. If private reporting is not available, open a minimal public issue stating that you have a security report and need a private contact channel; do not include exploit details in that issue.
+
+A useful report includes:
+
+- affected Glaze version or commit;
+- operating system and Racket version;
+- affected capability (server/API, WebView, tray, sys, packaging, update, license, etc.);
+- minimal reproduction steps;
+- expected and observed behavior;
+- impact and whether user interaction is required.
+
+## Security boundaries
+
+The project currently treats the following as security-sensitive boundaries:
+
+- the local HTTP API and SSE event stream are loopback-only and support capability-token protection;
+- Host and Origin checks are used to reduce localhost/DNS-rebinding and cross-origin abuse;
+- static files must remain contained inside the configured public directory;
+- native backends must not accept unvalidated application data directly into shells or command strings;
+- packaging/signing failures must fail closed rather than silently producing an artifact that claims to be signed;
+- update manifests and downloaded artifacts must be treated as untrusted input and verified before replacement;
+- offline licensing is a commercial policy mechanism, not a claim of tamper-proof DRM.
+
+Please report any case where implementation behavior violates these boundaries.
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 0000000..e083c29
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,259 @@
+# Glaze Architecture
+
+Glaze is a Racket-first desktop application framework that combines a web UI with a Racket runtime and native desktop capabilities.
+
+This document describes the architecture that exists today and the direction the project should preserve while it evolves. It is not a proposal to rewrite the repository into a new directory structure.
+
+## Goals
+
+- keep the application-facing API small and easy to discover
+- preserve a clear dependency direction from application code toward lower-level capabilities
+- isolate Windows, macOS, and Linux implementation details behind dispatch modules
+- keep native surface area small and explicit
+- make behavior testable without requiring callers to understand backend internals
+- preserve good Racket development ergonomics, including simple `require`, REPL use, macros, and ordinary modules
+- prefer incremental compatibility-preserving changes over architecture-driven rewrites
+
+## Non-goals
+
+The current stabilization work is not trying to provide:
+
+- Electron feature parity
+- a bundled browser runtime or custom renderer
+- a complete typed RPC framework
+- a large plugin ecosystem
+- hot reload as a framework-level subsystem
+- a large frontend toolchain
+- a rewrite of all native backends
+
+Those may be explored later when the current public contracts are stable enough to support them.
+
+## Current Layers
+
+The repository is one Racket package with multiple collections. The runtime architecture can be understood as the following layers:
+
+```text
+Application
+ |
+ v
+Public API / Facade
+ |
+ v
+Runtime and shared capabilities
+ |
+ v
+Platform dispatch
+ |
+ v
+Native backends
+```
+
+The mapping to existing code is:
+
+```text
+Application
+ |
+ v
+`glaze/main.rkt`
+Public facade exported by `(require glaze)`
+ |
+ +---------------------------+
+ | | |
+ v v v
+Runtime Capabilities Tooling
+`app.rkt` `webview/main` `build.rkt`
+`server.rkt` `tray/main` `update.rkt`
+`api.rkt` `sys/main` `glaze-cli/`
+`events.rkt` dialogs/etc.
+ | |
+ +------+-----+
+ v
+Platform-specific backends
+`webview/webview-{windows,macos,linux,stub}.rkt`
+`tray/tray-{windows,macos,linux,stub}.rkt`
+`sys/sys-{windows,macos,linux,stub}.rkt`
+```
+
+### Application
+
+Application code should normally depend on the `glaze` facade rather than individual implementation modules.
+
+Recommended:
+
+```racket
+(require glaze)
+```
+
+Direct imports such as `glaze/webview/webview-windows` or `glaze/tray/tray-macos` couple an application to implementation details and are not the recommended application-level API.
+
+### Public API / Facade
+
+`glaze/main.rkt` is the current facade. It re-exports the major framework surfaces so applications can use one `require` path.
+
+The facade is intentionally compatibility-oriented today: it exports a broad set of existing APIs instead of hiding them immediately. During 0.x stabilization, narrowing the facade should happen only with deprecation and migration planning.
+
+### Runtime
+
+The runtime composes capabilities into an application lifecycle:
+
+- `app.rkt` owns the high-level `run-app` flow
+- `server.rkt` serves static assets, JSON routes, and framework endpoints
+- `api.rkt` and `api-macros.rkt` define request/response routing
+- `events.rkt` provides backend-to-frontend event delivery over SSE
+
+`app.rkt` depends on the server, events, update support, and the public WebView dispatcher. This is an expected high-level dependency direction.
+
+### Capabilities
+
+Capability modules expose OS-facing functions without making application code select a backend:
+
+- `webview/main.rkt`
+- `tray/main.rkt`
+- `sys/main.rkt`
+- `dialogs.rkt`
+- `deeplink.rkt`
+- `autolaunch.rkt`
+- `browser.rkt`
+
+The WebView, tray, and sys modules already follow the same useful pattern: a platform-independent API dispatches lazily to the backend selected by `(system-type 'os)`.
+
+### Platform Backends
+
+Platform backend modules are implementation details. They use Racket FFI, Objective-C FFI, subprocesses, or operating-system APIs to satisfy the capability contract.
+
+These modules should not depend on `app.rkt` or other application-level orchestration modules. Backend modules may depend on small shared protocols or lower-level utilities needed to implement their contract.
+
+## Dependency Rules
+
+The project should evolve toward these rules without requiring a large file move:
+
+1. **Applications depend on the public facade.**
+ New examples and generated application templates should prefer `(require glaze)`.
+
+2. **The public facade may depend on runtime and capability modules.**
+ `glaze/main.rkt` is allowed to re-export stable application-facing functionality.
+
+3. **Runtime orchestration may depend on capabilities.**
+ For example, `run-app` may depend on `server.rkt`, `events.rkt`, and `webview/main.rkt`.
+
+4. **Capability dispatchers may depend on shared lower-level protocols, but not application orchestration.**
+ `webview/main.rkt` depending on the tray menu protocol is acceptable because the protocol is a shared data model used to build native menus. A dependency on `app.rkt` would reverse the intended direction.
+
+5. **Platform backends must not become application APIs.**
+ They should remain behind dispatcher modules and may change as native implementation details require.
+
+6. **Shared protocols belong below their consumers.**
+ If multiple capabilities need the same types or protocol definitions, they should live in a small lower-level module rather than one capability importing another capability's full implementation.
+
+7. **Avoid cycles.**
+ New code should not introduce cycles between runtime, capability dispatchers, and backend modules. If two modules need the same definition, extract only that shared definition rather than merging unrelated responsibilities.
+
+8. **Prefer facade and tests before file moves.**
+ When an internal/public boundary is unclear, first establish it in exports, documentation, tests, and comments. Move files only when the compatibility and maintenance benefit is clear.
+
+## Public vs Internal API
+
+The repository did not previously have a formal stability classification for every module. The following classification records the intended boundary for new development.
+
+### Public
+
+Preferred application-facing entry point:
+
+- `glaze` (`glaze/main.rkt`)
+
+The following module paths also expose useful APIs today and remain supported for compatibility, but application documentation should prefer the facade unless a focused import is useful:
+
+- `glaze/app`
+- `glaze/server`
+- `glaze/api`
+- `glaze/api-macros`
+- `glaze/events`
+- `glaze/webview/main`
+- `glaze/tray/main`
+- `glaze/sys/main`
+- `glaze/dialogs`
+- `glaze/deeplink`
+- `glaze/autolaunch`
+- `glaze/browser`
+- `glaze/build`
+- `glaze/update`
+- `glaze/license`
+
+This is a compatibility statement, not a promise that every exported binding already has a 1.0-stable contract.
+
+### Internal implementation
+
+Modules that implement framework mechanics but should not be imported by normal application code include platform backends and implementation-specific helper modules.
+
+Examples:
+
+- `glaze/webview/webview-windows`
+- `glaze/webview/webview-macos`
+- `glaze/webview/webview-linux`
+- `glaze/webview/webview-stub`
+- `glaze/tray/tray-windows`
+- `glaze/tray/tray-macos`
+- `glaze/tray/tray-linux`
+- `glaze/tray/tray-stub`
+- `glaze/sys/sys-windows`
+- `glaze/sys/sys-macos`
+- `glaze/sys/sys-linux`
+- `glaze/sys/sys-stub`
+
+Tests may import these modules when they explicitly test a backend contract. Applications should not.
+
+### Shared protocol
+
+`glaze/tray/tray-protocol.rkt` is currently a shared protocol/data-model module rather than a native backend. WebView menu support reuses its menu definitions. Although it is re-exported through the tray public API, its main architectural role is lower-level shared data.
+
+If menu definitions later become a broader application-wide concept, they can be promoted into a neutral shared module in a separate compatibility-focused change.
+
+### Platform-specific
+
+Any module named for an operating system is platform-specific by definition. Its implementation and FFI details are free to differ as long as the public dispatcher contract remains consistent.
+
+### Experimental
+
+Glaze is pre-1.0, so newly introduced APIs may be marked experimental in documentation before they are made part of the stable facade. Experimental status should be explicit; it should not be inferred merely because an API lives in a separate file.
+
+## Current Dependency Observations
+
+The current WebView, tray, and sys implementations already have a sound dispatch shape:
+
+- the public dispatcher selects the backend lazily
+- applications do not need to select an operating system implementation
+- unsupported native capabilities can report `#f` or use a stub/fallback behavior
+
+A small coupling exists where `webview/main.rkt` imports only `menu?` from `tray/tray-protocol.rkt`. This is not a cycle and does not pull in the tray backend, but it shows why shared protocol/types should remain lightweight.
+
+The repository therefore does not need a large platform-layer rewrite to make architectural progress. The higher-value near-term work is to stabilize lifecycle semantics, public contracts, tests, and documentation.
+
+## Testing Boundaries
+
+Tests should be divided conceptually into three groups:
+
+- **facade/API tests**: prove `(require glaze)` exposes the documented application surface
+- **platform-independent tests**: routing, argument validation, events, lifecycle helpers, parsing, and other logic that can run on all hosts
+- **backend/e2e tests**: validate the OS-specific implementation behind the dispatcher
+
+The existing CI already runs the test suite on Windows, macOS, and Linux and has a separate real-window WebView e2e job. That structure should be preserved.
+
+## Evolution Strategy
+
+Changes should normally follow this order:
+
+```text
+Bug / correctness
+ >
+API inconsistency
+ >
+dependency problem
+ >
+missing regression test
+ >
+documentation gap
+ >
+directory aesthetics
+```
+
+A future `private/` or `internal/` directory may be useful, but moving working FFI files solely for visual cleanliness is not a current priority. A documented boundary plus a tested facade gives the project most of the maintenance benefit with much lower compatibility risk.
diff --git a/docs/releasing.md b/docs/releasing.md
new file mode 100644
index 0000000..8a2d28d
--- /dev/null
+++ b/docs/releasing.md
@@ -0,0 +1,133 @@
+# Releasing Glaze
+
+This checklist keeps a Glaze release reproducible without putting signing credentials in the repository.
+
+Glaze is still pre-1.0, so releases should remain conservative: stabilize and verify existing public behavior before adding release-only features.
+
+## 1. Prepare the release commit
+
+Before tagging a release:
+
+1. update the root `info.rkt` version;
+2. update `CHANGELOG.md` with user-visible changes and compatibility notes;
+3. verify `README.md`, `README.zh-CN.md`, Scribble documentation, and examples describe behavior that actually exists;
+4. make sure no private keys, certificates, notary profiles, generated installers, or customer license files are committed;
+5. ensure the release commit is fully reviewed and CI is green.
+
+Racket version strings must follow the package manager's accepted version syntax; use the same value consistently in package metadata and release notes.
+
+## 2. Required CI gates
+
+The release commit should pass the repository CI without skipped failures:
+
+- compile public entrypoints on Windows, macOS, and Linux;
+- run the full `glaze-test/` suite on all three platforms;
+- run native WebView end-to-end tests on all three platforms;
+- execute a final packaged application, not only the build command;
+- build platform installers/distribution archives;
+- verify the macOS bundle signature used by CI;
+- run the dedicated macOS/Racket 9.3 packaging regression;
+- create a filtered Racket source package, install that archive from scratch, and verify `(require glaze)` plus `raco glaze`.
+
+A passing checkout build is not sufficient if the source archive or packaged executable fails independently.
+
+## 3. Build commercial distribution artifacts
+
+The CI artifacts are smoke-test artifacts. Production releases should be rebuilt with the publisher's real signing identity where the platform supports signing.
+
+### macOS
+
+Use a Developer ID Application identity and hardened runtime:
+
+```bash
+raco glaze build \
+ --name MyApp \
+ --version 1.2.3 \
+ --installer \
+ --sign "Developer ID Application: Example Corp (TEAMID)" \
+ --notarize my-notary-profile
+```
+
+Then verify the result independently:
+
+```bash
+codesign --verify --strict --verbose=2 dist/MyApp.app
+spctl --assess --type execute --verbose=2 dist/MyApp.app
+```
+
+When a DMG is shipped, verify the notarization/stapling status of the final artifact as well as the app bundle.
+
+### Windows
+
+Use a real code-signing certificate through `signtool` and an RFC-3161 timestamp server:
+
+```bash
+raco glaze build \
+ --name MyApp \
+ --version 1.2.3 \
+ --installer \
+ --sign
+```
+
+Verify both the executable and installer with the Windows signing tools before publication. Do not treat an unsigned fallback archive as equivalent to a signed commercial installer.
+
+### Linux
+
+Glaze can produce an AppImage when `appimagetool` is available, otherwise a portable archive fallback. Linux has no single universal code-signing mechanism in the current Glaze build API; distributors should use the signing/verification mechanism appropriate to their chosen channel.
+
+## 4. Create the Racket source package
+
+Racket packages are normally distributed as source. From the checkout parent directory:
+
+```bash
+raco pkg create --source --format zip glaze
+```
+
+Install the resulting archive in a clean Racket environment before publishing it:
+
+```bash
+raco pkg install --auto --no-docs glaze.zip
+racket -e '(require glaze)'
+raco glaze help
+```
+
+CI performs the equivalent source-package smoke test so repository-only files or local links cannot accidentally become hidden release dependencies.
+
+## 5. Security review
+
+Before publishing, re-check the boundaries documented in [`../SECURITY.md`](../SECURITY.md):
+
+- localhost API/SSE authentication and origin/host checks;
+- static-file containment;
+- command/subprocess argument construction;
+- update artifact verification;
+- signing/notarization failure behavior;
+- accidental logging or packaging of tokens, credentials, private keys, or customer data.
+
+Any unresolved vulnerability with material impact should block the release.
+
+## 6. Publish
+
+After the exact release commit passes all gates:
+
+1. create the release tag from that commit;
+2. publish release notes from `CHANGELOG.md`;
+3. attach only verified artifacts;
+4. update the Racket package catalog/source reference as appropriate;
+5. verify installation from the public release source on a clean machine or clean Racket installation;
+6. keep the previous known-good release available for rollback.
+
+Do not rebuild an artifact after tagging and publish it under the same version without documenting that the bits changed. A release version should identify one reproducible source state.
+
+## 7. Post-release smoke checks
+
+After publication, perform at least one clean install per supported desktop platform and verify:
+
+- application startup;
+- native WebView creation;
+- JSON API + SSE bridge;
+- shutdown/cleanup;
+- one native capability such as tray or clipboard;
+- the published package/installer launches without depending on the source checkout.
+
+Record any platform-specific release regression as an issue with the release version, OS version, Racket version, and reproduction steps.
diff --git a/examples/README.md b/examples/README.md
index c1995e5..3948d5b 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,20 +1,31 @@
-# Glaze Examples — 功能索引
+# Glaze Examples
-| 示例 | 一句话 | 展示的能力 |
+Start with the smallest examples. They use the public `(require glaze)` facade and show the recommended application-facing APIs without exposing platform backend modules.
+
+| Example | Purpose | Main capabilities |
|---|---|---|
-| [`showcase/`](showcase/) | **一屏看尽全部能力(推荐先看)** | 宏路由全形态(类型校验/400/:path/500)、SSE 事件流 + 后端错误回流(on-error)、系统功能(剪贴板/通知/Finder/窗口控制)、Agent 验证(title/url/capture + 截图回传)、API token(401 演示)、更新检查、托盘、单实例 |
-| [`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 实时打印、看门狗 |
-| [`agent-verify.rkt`](agent-verify.rkt) | 无人值守验证 | agent 工作流:轮询断言 + 截图 + 退出码 |
-| [`tray-demo.rkt`](tray-demo.rkt) | 跨平台托盘 | make-tray/菜单/tooltip 动态更新 |
+| [`hello/`](hello/) | Minimal desktop application | `run-app`, static assets, native WebView/browser fallback |
+| [`tray/`](tray/) | Minimal system tray application | `make-tray`, menu items, tray lifecycle |
+| [`events/`](events/) | Minimal JS/Racket communication | JSON request route + Server-Sent Events push |
+| [`counter/`](counter/) | Fuller bridge example | `define-api-routes`, generated client support, event bus, shared state |
+| [`showcase/`](showcase/) | Integrated feature showcase | API validation, events, system capabilities, WebView controls, tray, update checks |
+| [`webview-demo.rkt`](webview-demo.rkt) | Direct WebView lifecycle | open, navigate, inspect, capture, close |
+| [`agent-verify.rkt`](agent-verify.rkt) | Programmatic UI verification | polling assertions, title/URL inspection, screenshot, exit status |
+| [`tray-demo.rkt`](tray-demo.rkt) | Legacy single-file tray demo | tray menu and tooltip updates |
+
+## Quick Start
+
+```bash
+racket examples/hello/main.rkt
+racket examples/events/main.rkt
+racket examples/tray/main.rkt
+```
-## 快速开始
+Then move to the fuller examples:
```bash
-racket examples/showcase/main.rkt # 综合演示(单实例锁定)
-racket examples/counter/main.rkt # 桥接 + 事件
-racket examples/hello/main.rkt # 最小应用
+racket examples/counter/main.rkt
+racket examples/showcase/main.rkt
```
-所有示例均可 `raco glaze build` 打包为独立应用。
+The examples intentionally use public modules. Backend-specific modules under `glaze/webview/`, `glaze/tray/`, and `glaze/sys/` are implementation details unless you are working on Glaze itself.
diff --git a/examples/events/main.rkt b/examples/events/main.rkt
new file mode 100644
index 0000000..7941b56
--- /dev/null
+++ b/examples/events/main.rkt
@@ -0,0 +1,23 @@
+#lang racket/base
+
+;; Minimal request + event example using Glaze's existing HTTP/SSE bridge.
+;; Run: racket examples/events/main.rkt
+
+(require racket/runtime-path
+ glaze)
+
+(define-runtime-path public "public")
+(define bus (make-event-bus))
+
+(define-api-routes api
+ [(POST "api/ping")
+ (ping)
+ (begin
+ (bus-broadcast! bus 'pong (hasheq 'message "pong from Racket"))
+ (hasheq 'ok #t))])
+
+(module+ main
+ (run-app #:public-dir public
+ #:api api
+ #:events bus
+ #:title "Glaze Events"))
diff --git a/examples/events/public/index.html b/examples/events/public/index.html
new file mode 100644
index 0000000..a59d7be
--- /dev/null
+++ b/examples/events/public/index.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Glaze Events
+
+
+
+
Glaze Events
+
Click the button to call Racket over HTTP. Racket broadcasts the response back over Server-Sent Events.