Desktop apps
The live protocol does not change. Go still renders elements, patches props
over SSE, and receives component events over POST. app is a host for that
model: the same http.Handler listens on loopback, and the operating system’s
webview (WKWebView, WebView2, WebKitGTK) is the client.
There is no JavaScript binding layer and no generated TypeScript. Native
capabilities are Go function calls, usually from a live.On handler or a
menu callback.
app.Run(app.Options{ Title: "Board", Width: 1100, Height: 800, Handler: mux, // the mux a browser build would ListenAndServe Menu: app.DefaultMenu(), Identifier: "com.example.board",})The example board is that handler with a flag:
go run -tags desktop ./examples/todo -desktopWithout -tags desktop, Run returns an error that names the tag. Listen
does not need it: it is ordinary HTTP, and that is what the tests exercise.
The nested module
Section titled “The nested module”import "github.com/bmartel/alacris-go/app"It has its own go.mod. The root module still depends on templ and nothing
else; CGO and the webview library live only here. A project that never opens a
window never sees them.
Releases tag app/vX.Y.Z at the same commit as vX.Y.Z. go get that version,
not v0.0.0. A replace in this repository’s go.mod is ignored when the
module is a dependency.
go get github.com/bmartel/alacris-gogo get github.com/bmartel/alacris-go/appCookie on loopback
Section titled “Cookie on loopback”The host is still plain HTTP: EventSource only speaks HTTP family URLs, so a
custom alacris:// scheme would break live.js. Set
CookieSecure: live.SecureNever on the live server you pass to that handler.
SecureAuto omits Secure on non-TLS already; naming it keeps a later TLS
experiment from dropping the cookie.
The live cookie still authorises the page. Do not drop it because this is a desktop build.
Host token
Section titled “Host token”Open / Run bind 127.0.0.1 (never 0.0.0.0) and reject a Host header
that is not that address. They also issue a random host token: the webview
navigates to a one-shot query parameter, the gate swaps it for an HttpOnly
cookie and redirects, and every later request (including EventSource) must
present that cookie. Another process on the machine that can open a TCP
connection to the port cannot create a live session.
Listen does not install the gate. It is the testable half of the host.
Native APIs are Go
Section titled “Native APIs are Go”live.On(srv, "export", func(c *live.Ctx, d struct{}) error { path, err := app.SaveFile(c.Context(), app.FileFilter{Name: "JSON", Ext: ".json"}) if err != nil { return err } return os.WriteFile(path, payload, 0o644)})| Surface | API |
|---|---|
| Window | Run, Options, NewWindow. SetTitle, SetSize, SetPosition, Center, Minimize, Maximize, Fullscreen, Show, Hide, Focus, SetAlwaysOnTop, SetDecorations, SetBadge, Close. |
| Menus | Menu, MenuItem, DefaultMenu, EditMenu. Do is a Go callback. Roles (RoleQuit, RoleCopy, …) map to the first responder. Installed on macOS, Windows, and Linux. |
| Tray | Options.Tray with a Menu. |
| Dialogs | OpenFile, SaveFile, SaveAs, Message, Confirm. ErrCanceled means the user dismissed the dialog. |
| Files | The Go standard library. There is no JS fs plugin. DataDir / CacheDir are identifier-scoped. |
| Clipboard | WriteClipboard, ReadClipboard. |
| Notifications | Notify. |
| Open / reveal | OpenURL, RevealInFileManager. |
| Shortcuts | Window.RegisterShortcut (global; macOS and Windows). |
| Single instance | Options.SingleInstance plus Identifier. Second launch returns ErrAlreadyRunning and forwards args. |
| Deep links | Options.DeepLinkScheme and OnDeepLink. The bundler writes CFBundleURLTypes / .desktop MimeType. |
| Updates | Updater.Check / Apply / ApplyAndRelaunch. ed25519 over the artifact bytes. Relaunch execs the new file; on Windows a locked exe is parked as .new and swapped then. |
Do not add window.__TAURI__ or a Wails-style invoke. A component that needs
the filesystem emits an event; Go does the rest.
alacris.app.json is bundle metadata (name, identifier, icon, optional
deepLinkScheme). Window size and live wiring stay in Go.
alacris-go app init -name Board -id com.example.boardgo get github.com/bmartel/alacris-go@latest github.com/bmartel/alacris-go/app@latestgo generate ./...alacris-go app devalacris-go app build -o distalacris-go app infoinit writes a desktop-first project: app.Run, a templ page, web/components.js,
and the JSON file. It is not a Vite app.
dev is go run -tags desktop, and it rebuilds when .go, .templ, or .js
change. Pass -watch=false for a single run.
build compiles with that tag, wraps the binary (.app / .desktop+nfpm.yaml
/ .exe+winres.json), writes icons from a PNG, and runs codesign,
hdiutil, nfpm, or go-winres when those tools are on PATH. Set
bundle.macOS.signingIdentity to codesign. Notarize with
ALACRIS_NOTARY_PROFILE pointing at a notarytool keychain profile.
Cross-compiling CGO (Linux → macOS) is not supported; build on the target OS
or a CI matrix.
Auto-update
Section titled “Auto-update”The endpoint serves a JSON manifest. The signature is ed25519 over the artifact bytes, base64-encoded, same idea as Tauri’s updater so a GitHub Release can host one file:
{ "version": "0.2.0", "notes": "…", "platforms": { "darwin-arm64": { "url": "https://github.com/org/app/releases/download/v0.2.0/Board_darwin-arm64.tar.gz", "signature": "<base64>" } }}The map key is GOOS-GOARCH (darwin-arm64, windows-amd64, linux-amd64).
Embed the public key in the app; keep the private key off the machine that
builds nothing.
upd, err := app.Updater{ Endpoint: "https://example/latest.json", PublicKey: pub, Current: version,}.Check(ctx)if upd != nil { _ = updater.ApplyAndRelaunch(ctx, upd)}Apply replaces the executable (on Windows, writes app.exe.new when the
running file is locked). ApplyAndRelaunch then execs the new file. Run
calls FinishPendingUpdate so a parked .new is applied if a previous
relaunch did not finish.
Build tags and CGO
Section titled “Build tags and CGO”Default go test ./... | Does not enter app/. Root stays free of webkit. |
cd app && go test ./... | Host, dialogs, updater, host token. No display. |
-tags desktop | Links the OS webview. Needs CGO, Xcode CLT / WebView2 / libwebkit2gtk. |