ADR-0002: Desktop shell: pywebview and pystray, PyInstaller, per-user installers¶
- Status: accepted
- Date: 2026-11-05
Context¶
relaybay 0.6 adds a desktop program: a tray icon, the board in a window, the server in the same process, start at login, installers for Windows, macOS and Linux and in-app updates (design 0016). The board is server-rendered HTMX (ADR-0001) and must stay the same page in the browser and in the program. The Claude Code hooks start a forwarder on every hook event, so its path must be stable and its start-up fast. The project has one maintainer and no code-signing certificates.
Options¶
- Window and tray: (a) pywebview (the system web view: WebView2, WKWebView, WebKitGTK) plus pystray for the tray; (b) Qt through PySide6 (QtWebEngine and QSystemTrayIcon); (c) Electron or Tauri around the existing server.
- Freezing: (a) PyInstaller onedir; (b) Briefcase; (c) PyOxidizer or Nuitka.
- Windows installer: (a) Inno Setup, per user without admin rights; (b) MSIX; (c) WiX MSI.
Decision¶
1a, 2a, 3a.
- pywebview + pystray reuse the web engine the OS already has: a small download, security updates come with the OS, and the HTMX board runs unchanged. Qt bundles Chromium (about 200 MB) and a second UI toolkit; Electron or Tauri bring a Node or Rust toolchain into a Python project. pywebview has no tray API, so pystray (LGPL-3.0, shipped as separate module files in the onedir build) draws the icon.
- PyInstaller onedir has maintained hooks for uvicorn, pywebview, pythonnet and pystray, builds
three executables over one shared folder (
relaybay,relaybay-desktop,relaybay-hook) and never unpacks to a temp folder at each start, which keeps the hook forwarder's start-up in tens of milliseconds and its path stable. Briefcase wants its own project layout and packaging model; PyOxidizer and Nuitka have fewer ready hooks for this stack and were not taken further. - Inno Setup per user (
PrivilegesRequired=lowest,%LOCALAPPDATA%\Programs\relaybay) installs without admin rights and needs no certificate. MSIX needs a trusted signing certificate; WiX is heavier for the same result. - macOS ships an ad-hoc signed
.appin a.dmg(arm64), Linux a.debinto/opt/relaybay. AppImage is deferred: its mount path changes on every run, which breaks the hook paths written into Claude Code's settings.
Consequences¶
- Without certificates the OS warns on first start (SmartScreen, Gatekeeper); the install guide shows how to get past it, and macOS and Linux builds are marked beta.
- The Linux build has no board window yet (the frozen interpreter cannot load the system's PyGObject); the tray opens the board in the browser.
- The window depends on the system web view: WebView2 on Windows (present on current Windows 10 and 11), WebKitGTK on Linux.
relaybay setupfrom the installed program writes hooks that start the bundledrelaybay-hookby its full path; it refuses while the program runs from a place that goes away (a mounted disk image, Gatekeeper's translocation, the temp or Downloads folder).- Updates download the next release's installer and check it against
SHA256SUMS.txtof the same release: integrity, not authenticity, since nothing is signed.