From 68a50d10b129c20d5414f00393158522ebff6407 Mon Sep 17 00:00:00 2001 From: Suriyakumarvijayanayagam Date: Wed, 30 Sep 2026 15:01:55 +0530 Subject: [PATCH] Record the desktop work: the tray, macOS, and the release Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj --- CLAUDE.md | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 2bce8b9..759b523 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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