project-switch v0.20
Webview polish and tighter webserver scoping
A day of sanding down the webview window and the tray-managed webserver.
The webview gained two conveniences. It now titles itself by the command that opened it on macOS — summon_or_open threads the command key through to a new --title flag, so a window opened from, say, a dashboard command reads “dashboard” instead of the generic constant. Windows deliberately ignores the title: the constant doubles as the handle that FindWindowW uses to find and reuse the single window, so renaming it would break window reuse. (A small follow-up fix renamed a shadowed local there so the title parameter wasn’t accidentally swallowed.) The webview also reloads on Cmd+R / Ctrl+R now, via an injected key handler — which neatly doubles as a retry button, since calling location.reload() on WKWebView’s or WebView2’s own error page re-requests the URL that failed.
Then external links. On macOS, WKWebView doesn’t route target=_blank anchor clicks or window.open calls through wry’s navigation handlers, so links in the page — like the URLs the assist terminal prints — simply did nothing. An injected LINK_SCRIPT now captures both, forwards external http(s) URLs to the host over IPC, and lets same-origin links navigate normally; the host opens them in the default browser.
On the webserver side, two scoping fixes. Stopping the server used to pkill -f on the command string, which would also catch unrelated assist/claude processes whose arguments happened to contain that substring. It now identifies the server by the port it listens on (lsof -ti tcp:3100) and waits, polling, until it’s actually gone. I also discovered the macOS server needed an interactive login shell (-ilc, not just -lc): version managers like fnm initialise in .zshrc, which a non-interactive shell never sources, so the server was resolving the wrong Node and sometimes couldn’t find assist on PATH at all. Finally, the hotkey service’s own process matching switched from pkill -f / pgrep -f to the exact-name -x form, so it stops matching unrelated command lines.