036 · Windows
Blitz on Windows: the tray serves the desktop page (spec_windows_036)
| Status | Implemented (2026-10-07); checked on macOS and Linux and cross-built for Windows. Running it on Windows is still to do (WIN-40, BL-VER-03). |
| Source | apps/tray (main.go, page.go, icons_windows.go, lock_windows.go), apps/tray/internal/tray (browser.go, ico.go, serve.go, detach_windows.go), pkg/pagehost (host.go), pkg/pageserver, pkg/fileview, pkg/loginitem/run_windows.go, the service’s Shutdown (proto/blitz/v1/workspace.proto), the page’s apps/desktop/web/src/desktop.ts, release/BUILD.bazel |
| Tests | pkg/pageserver/*_test.go, pkg/fileview, pkg/pagehost/host_test.go, apps/tray/internal/tray/{browser,ico,serve,service}_test.go, apps/service/internal/{server/info,daemon/daemon}_test.go (Shutdown), apps/desktop/web/src/desktop.test.ts, release/contents_test.sh |
Until v0.7.0 Windows had no desktop app (spec_desktop_024 DSK-05), but the page needs no native shell. It asks its own origin for the API, and every one of the app’s bindings has a stand-in in a browser. On Windows the tray becomes the launcher. blitz-tray.exe is one small pure-Go program that runs the service, serves the page on a loopback port, and opens it in a window of its own. There’s no installer and no web view to ship. Since v0.7.0 the Electron app gives Windows a real app (spec_electron_038); its window uses this tray’s host (blitz-tray --serve), and the tray’s page stays.
- WIN-01
blitz-tray.exe(spec_service_021 SVC-53) builds for Windows: pure Go (fyne.io/systray’s Win32 tray), a GUI program (-H=windowsgui, no console), cross-built on any machine (//apps/tray:blitz-tray_windows_amd64). - WIN-02 Its icons are the bolt in colour, because Windows doesn’t tint tray icons: amber while the service runs, grey while it’s stopped, each edged dark so it shows on light and dark taskbars (
icons/windows-*.svg). The PNGs are wrapped as.icowhen it starts (tray.ICO: one PNG-compressed image, which Windows Vista and later read). - WIN-03 One tray at a time: it locks
~/.blitz/run/tray.lockwithLockFileEx(flock elsewhere). The programs it starts (blitzd, Edge) run detached: their own process group, and since v0.7.0 a console that’s never shown (CREATE_NO_WINDOW), not none (DETACHED_PROCESS): without a console, every console program the service ran (git, shells, language servers) got a window of its own, flashing open and shut every few seconds.blitzstarting the service does the same. - WIN-04 The service has no login item on Windows (
loginitem.Installis unsupported). The tray starts it: when the tray starts and finds no service, it runsblitzd.exefrom beside itself orPATH. - WIN-05 Start Blitz at login, a menu item with a tick, toggles the tray’s start-at-login entry: the value
Blitz(the quoted path toblitz-tray.exe) underHKCU\Software\Microsoft\Windows\CurrentVersion\Run.blitz-tray --installwrites it and starts the tray;--uninstallremoves it. - WIN-06 Stop the service asks the service to stop (
WorkspaceService.Shutdown, WIN-30), since Windows has no SIGTERM to send. It falls back to the signal where there is one. Every OS does it in this order. - WIN-07 Open the logs opens the folder in File Explorer.
- WIN-10 Open Blitz serves the page (embedded in the tray;
pkg/pageserver) the first time, on127.0.0.1and a free port, and opens a new one-time link to it. If Edge is installed (msedge.exeunder%ProgramFiles(x86)%,%ProgramFiles%or%LOCALAPPDATA%), the link opens there as an app (--app=…), a window with no tabs or address bar. Otherwise it opens in the default browser (rundll32 url.dll,FileProtocolHandler). - WIN-11 The page server lets in only whoever opened a link.
GET /open?t=<ticket>takes a ticket once, within a minute of its making, and sets a cookie:blitz_<port>, a 32-byte secret, HttpOnly and SameSite=Strict. It then redirects (303) to/, so neither the address bar nor the history keeps a working link. A used or expired link redirects a browser that already has the cookie, and refuses one that doesn’t. - WIN-12 Every request needs the cookie and a
Hostthat is the server’s own127.0.0.1:<port>(no DNS rebinding). When a request names anOrigin, it must be the page’s own. Cookies ignore ports, so this also shuts out pages served from other loopback ports. Everything else is refused (403). Responses forbid framing (X-Frame-Options: DENY) and send no referrer;index.htmlis never cached. - WIN-13
/blitz.v1.*goes to the service’s socket through the desktop app’s own proxy (pageserver.Proxy, moved fromapps/desktop): it streams, adds keep-alives and returns a 503 Connect error when the service is down. The cookie andOriginare not forwarded. The service itself still never listens on a port (spec_service_021 SVC-01): only the tray’s server does, and only on loopback, behind the cookie. - WIN-14
/host/is the tray answering what the desktop app’s bindings would (pagehost.Host, shared since v0.7.0 with the Electron app’sblitz-tray --serve, spec_service_021 SVC-53a). The page sendsPOST /host/<Method>with the arguments as a JSON array, and gets the result as JSON, or{"error": …}with a 500.GET /host/infolists the methods:Version,ServiceStatus,InstallService(on Windows: start at login, and start the service now),SetTray,StopService,RestartService,ProgramExists,OpenFolder,OpenDocument,RevealPath,FileManager(“File Explorer”) andLicense; since v0.7.0 alsoSandboxStatusandFixSandbox,GetPrefsandSavePrefs(the window’s preferences in~/.blitz/desktop.json, as the desktop app keeps them), andCLIStatus,InstallCLIandReinstallCLI: on Windows theblitz.exebeside the host goes on PATH by its folder, not a link (links need an administrator or Developer Mode): Install puts the folder first on the user’s PATH (HKEY_CURRENT_USER\Environment, no administrator needed) and tells Windows (WM_SETTINGCHANGE), so terminals opened from then on find it; the status says where a terminal findsblitz.exe(the system’s PATH, then the user’s) and whether it’s this one; Reinstall puts this folder first again and takes other copies of Blitz’s folders off the user’s PATH, those still there and those moved or deleted (cli_winpath.go, its registry calls replaced in its tests;method: "path", which Settings words for). The desktop app’s bindings for these call the same code (pkg/pagehost), so every host answers alike. Folders and documents open throughpkg/fileview, shared with the desktop app, which opens only folders, viewable documents, and paths shown in their folder, never a program. - WIN-15 Before it renders, the page asks
/host/info(detectHost), but not inside the app or an editor. For each binding it uses the app’s window if it’s there (the Electron window since v0.7.0; the Wails app before), else the host if it has the method (canCall), else the browser’s stand-in. So in the tray’s page, Show in File Explorer, Open in viewer, the logs’ Open folder, the service’s controls and the licenses work. Choosing a workspace remains a prompt for a path, and the app-only features (Export as PDF, the CLI’s installation, the sandbox’s status) stay hidden. Since v0.7.0 every binding is routed this way, not only some (the Electron app’s window first, then the host, spec_electron_038 EL-40), so the tray’s page keeps its preferences in~/.blitz/desktop.jsonthrough the host, as the app does: the first time, preferences the browser kept move there (EL-41).
- WIN-30
WorkspaceService.Shutdownstops the service as SIGTERM does. It answers, then turns in progress get their few seconds and the socket is removed. A server built without a way to stop (server.WithShutdown; tests) answersFAILED_PRECONDITION. - WIN-31 On Windows the socket’s permission bits are not set (Chmod only sets read-only there). The socket is as private as
~/.blitz/run, whose access control list comes from the user’s profile.
- WIN-35 The Windows archive (
blitz_<version>_windows_amd64.zip, spec_release_025 REL-12) also holdsblitz-tray.exe. Run it; Start Blitz at login keeps it.release/contents_test.shchecks the zip. - WIN-36 CI builds the zip with the other archives on Linux and macOS, and the reproducible job compares them. The page in the tray is built for the machine building it, so it must also come out the same on both.
- WIN-40 Running on Windows has yet to be checked by hand (Manual verification): the icon, starting the service, the Edge window and a turn in it, Stop and Restart, Start at login across a sign-out, a request without the cookie refused, and that
~/.blitz/runis only the user’s (icacls). - WIN-41 What Windows lacks in the engine is unchanged (BL-VER-03): no OS sandbox, no process guard, no skill scripts; shell tools need Git Bash or WSL.
- WIN-42 A native folder picker (the page asks for a path), notifications from the tray, and a Windows Arm64 build.