Record the desktop work: the tray, macOS, and the release

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
2026-09-30 15:01:55 +05:30
parent 8137480877
commit 68a50d10b1

View File

@@ -2568,6 +2568,93 @@ raw string that is two literal characters and Postgres requires exactly one —
it would have failed the **whole** customer search at runtime, on a query no
in-memory test executes.
## The desktop app on two platforms, and three bugs found by launching it
All three were reported or found by *starting the app the way a person
starts one*, and all three had survived every test.
### "Open dashboard" in the tray did nothing reliable
`runtime.Show` was wrong three times over and the first is why it failed
rather than merely misbehaved. Wails implements `Show()` as a bare
`mainWindow.Show()` while `WindowShow()` wraps the identical work in
`runtime.LockOSThread`. Win32 window operations must run on the thread owning
the window's message pump, and the tray handler runs on the **systray's**
goroutine, which never is.
Two more, each sufficient alone: showing is not un-minimising (hidden and
minimised are different states), and Windows refuses the foreground to a
process that does not already hold it — so the window returned *behind*
whatever was being looked at. A tray click is by definition a moment when the
app is not in front, so that is every time, not an edge case. The
always-on-top flip is the ordinary way to ask, and it is why this runs in a
goroutine: a menu loop that sleeps is a tray that ignores the next click.
`OnSecondInstanceLaunch` had the same shape and is hit far more often —
double-clicking the desktop icon while the app is already running.
### The engine inherited whatever directory launched the app
Nothing ever set `cmd.Dir`, so the child took the parent's — and an app
started by double-clicking its bundle is handed `/`. On macOS the symptom was
`python: No module named behavision` forever, because the dev engine runs as
`-m behavision`, which resolves against the working directory.
**The same app launched from a terminal inside the repo worked perfectly**,
which is the shape of a bug that survives every test a developer runs.
`Config.EngineDir` (empty = install root) is set by both launchers, which had
identical code and the identical omission.
### macOS is a supported DEVELOPER target, not a product
Indian retail counters are Windows. A Mac product means an Apple Developer
account, notarisation, a second installer, and DPAPI having no macOS
equivalent — a permanent second platform for customers who do not have Macs.
What it *is* worth is demoing on the machine this is written on.
It cost one missing framework and then two crashes:
```
link Undefined symbols: _OBJC_CLASS_$_UTType
Wails' darwin frontend references it and does not link
UniformTypeIdentifiers. Fails at the LINK step after compiling
everything, so it reads like a broken toolchain.
systray.Run SIGTRAP in cgo — nativeLoop takes the macOS main
run loop and Wails already has it
RunWithExternalLoop "NSWindow should only be instantiated on the main
thread!" — still builds AppKit objects, and
OnStartup is not the main thread
```
So **there is no tray on macOS**, and the consequence is handled rather than
left: with no tray there is no way back from a hidden window and no way to
quit, so on macOS closing the window quits and stops the engine. Same rule the
tray's Quit follows — never leave it watching with no visible control.
**The window could not be maximised**, and that was an omission with a precise
consequence. Wails computes `zoomable` *inside* `if frontendOptions.Mac !=
nil`; the variable defaults to 0, and the native side then does
`if (!zoomable && resizable) [zoomButton setEnabled: NO]`. There was a
`Windows` options block and no `Mac` one — so the platform that was configured
behaved and the platform that was not looked broken.
### A macOS release costs nothing extra, and the reason is worth keeping
`release.sh` has never used PyInstaller. The Windows package is a **source
install**: a pure-Python wheel plus `behavision-setup`, which builds a venv on
the target machine, done that way because PyInstaller cannot cross-compile.
macOS therefore needs nothing new — same wheel, same setup tool, a natively
built `.app` instead of the `.exe`. `MAC=1 ./release.sh` opts in.
Not notarised, and that is stated in the notes rather than discovered: macOS
*blocks* an unsigned download rather than warning like SmartScreen, so a first
launch needs right-click → Open.
Verified by extracting the published zip to a clean directory: signature
intact through the round trip, the app runs, and it reports *"engine not
installed yet; run behavision-setup, then Start"* — the correct fresh-machine
state rather than a crash.
## Setting up on a new machine
1. Copy the `Behavision` folder **including `.env`** (gitignored, holds