Files
OpenResearch/scripts
Myles AndersonandClaude Opus 5.5 c95be22a35 OR-326 Add the Linux desktop app as a self-updating AppImage (#445)
* Open the macOS app's dashboard in its own window

The OpenResearch.app now hosts the dashboard in a native window (tao + wry
WKWebView) instead of handing it to the user's browser. It adds a standard
menu bar, save panels for downloads, a native window.confirm panel, and a
Cmd+Q that flushes workspace state and shuts the server down cleanly.
Pop-ups open in the system browser (http/https/mailto only).

The app now prefers port 4792 so the window's localStorage survives
relaunches. The Dock-click tab-focus script, its Apple-events entitlement,
and the SSE client counter it relied on are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Open the dashboard in its own window on Windows

Adds the Windows desktop app. A small GUI-subsystem OpenResearch.exe
(windows/launcher) starts the orx.exe beside it as `orx app` in a hidden
console, so orx and the git, shell, and agent processes it runs share one
invisible console instead of each flashing a window.

`orx app` shares the macOS window code in src/commands/app.rs: WebView2
window, pop-ups to the system browser, save panel, page-load reveal, and
port 4792. On Windows, closing the window quits, a second launch focuses
the running window (named mutex + event), and the taskbar groups the
window with the Start menu shortcut. An update restart relaunches as the
app on the same port. Quit on both platforms now goes through
up::request_shutdown instead of a self-sent SIGTERM.

An Inno Setup script builds a per-user OpenResearch-Setup.exe with a Start
menu entry and a WebView2 bootstrap. CI builds it as an artifact, and
release-windows-app.yml attaches it to releases once WINDOWS_APP_ENABLED
is set. The icon is embedded via embed-resource.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix the Windows build and address review of the Windows app

- The focus listener captured its bare HANDLE (edition 2021 disjoint
  capture) instead of the Send wrapper, which failed to compile on Windows.
- An orx.exe away from the CLI installer's prefix, like the app's, is now
  Portable even when the installer's receipt exists, so it can update itself.
- Single instance creates its event before the mutex; the launcher hands its
  foreground rights to orx.exe.
- Focus requests and server readiness are ignored while quitting, and focus
  waits for the first load. The save dialog is owned by the window.
- Telemetry counts an app start only after the instance claim.
- The installer reports a failed WebView2 bootstrap and shows progress.
- Icon embedding now fails the build if no resource compiler is found.
- CI format-checks the launcher; the release job drops an unpinned action.
- Docs: maintainer notes for the release gate, two known gaps, and wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Tighten the Windows app after a second review

- Keep the macOS save panel free-floating: only Windows gets the window as
  its owner, since a parent makes rfd show a sheet on macOS.
- Treat the WebView2 bootstrap as failed unless the runtime is then present.
- Move the UTF-16 helper out of the single-instance module, and bind the
  kernel object names before the mutex call that GetLastError follows.
- Docs and a dead_code reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add the Linux desktop app as a self-updating AppImage

A `desktop` cargo feature builds the windowed app on Linux (glibc, WebKitGTK),
leaving the static musl CLI builds untouched. build.rs sets cfg(desktop_app)
for macOS, Windows, and Linux with the feature, replacing the macOS-or-Windows
gates.

The AppImage's AppRun starts `orx app`, sharing the Windows entry point:
the webview is built into the GTK window's box (X11 and Wayland), closing
quits, a Unix socket in XDG_RUNTIME_DIR keeps one instance and focuses it,
and the login shell's PATH is adopted as on macOS.

scripts/build-linux-appimage.sh bundles WebKitGTK with pinned linuxdeploy,
its GTK plugin, and appimagetool, copying WebKit's helper processes and
rewriting libwebkit2gtk's /usr paths to ././ so they resolve inside the
image. CI builds x86_64 and aarch64 AppImages on Ubuntu 22.04 and
smoke-tests each under Xvfb; release-linux-app.yml attaches them with
linux-app.json once LINUX_APP_ENABLED is set.

The AppImage updates itself (InstallChannel::AppImage, updates/linux_app.rs):
a sha256-checked download renamed over the file, production builds only,
and Restart execs the new AppImage on the same port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix the Linux app's downloads, bundled WebKit, and host environment

From review of the Linux desktop app:

- Downloads froze the app: rfd's GTK backend runs its dialog on a thread
  of its own, which waits forever on the main context tao holds. The save
  dialog is now a gtk::FileChooserDialog run on the main thread.
- The bundled WebKit helpers found their libraries only beside themselves,
  so they loaded the host's WebKit or none. They now get an RPATH to the
  image's usr/lib, and linuxdeploy no longer makes stray copies of them.
- The GTK hook's variables reached every host program orx opens. AppRun
  now saves the session's values and orx hands them back to xdg-open, the
  folder picker, error dialogs, and agents.
- The smoke test now runs after WebKitGTK is removed from the runner and
  checks that each WebKit helper runs, and loads libwebkit2gtk, from the
  image; cleanup kills the app's whole process group.
- Startup failures now reach stderr and a zenity or kdialog dialog.
- The tools and the AppImage runtime are pinned by sha256, and only the
  release job that publishes can write to the release.
- Smaller fixes: the shell probe runs after the single-instance claim and
  falls back to /bin/sh; APPDIR is canonicalized and must not be empty or
  root; relative project paths resolve against home inside the image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep the session's GTK settings across Linux app restarts and terminals

Restore the host GTK variables before an update relaunches the AppImage, so
the new AppRun doesn't save the old image's values as the session's, and in
the dashboard's terminals. Share one APPDIR containment check between the
update channel, relative project paths, and the folder picker, which now keeps
a terminal `orx up`'s working directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Relaunch the AppImage only from inside it, and tidy after review

Pick the relaunch target with the same APPDIR check as the update channel, so
an `orx app` started from the app's terminal restarts itself. Hand the shell
probe the session's GTK settings, harden the smoke test's helper checks and
cleanup, and bring the docs up to date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Run the AppImage detection test only on Unix

Its mount paths aren't absolute on Windows, which has no AppImage anyway.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Import the AppImage test's helper inside the Unix-only test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Show the app window even when the dashboard never finishes loading

On a UTM VM the Linux app ran with no window: it stays hidden until the page
finishes loading, and that never happened. Show it 15 seconds after the
server is up regardless, say so on stderr, and have the smoke test fail if
that fallback fires or no window appears.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Give the Linux app its name and icon in the dock, and keep GIO off host modules

GNOME shows an unmatched window as a gear named after its class, so name the
program OpenResearch and have each launch install a desktop entry and icon
pointing at the AppImage (TryExec hides it once the file is gone).

Bundle GLib's TLS module and set GIO_MODULE_DIR to the image's, so the bundled
GLib stops loading the host's modules, built against a newer one. A second
launch now shows the window even before the page loads, so a stuck instance
can't swallow it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Tidy the Linux app after the fourth review

Write the desktop entry only from the AppImage's own orx (shared with the
update relaunch), keep a session's GIO_EXTRA_MODULES off the bundled GLib,
mark a Dock-reopened macOS window as shown so the load fallback can't reopen
it, close a race in the smoke test, and note the Fedora and openSUSE TLS gap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Show a stalled app window, and close the app before the installer runs

If the dashboard never finishes loading, show the window 15 seconds after the
server is up and say so on stderr; a relaunch (or a Dock click on macOS) also
shows it before the page loads. The installer and uninstaller now check the
app's single-instance mutex and ask the user to close it, rather than
replacing files under a running app and skipping its shutdown path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep a replaced download's original until it lands, and don't offer a launch without WebView2

Move the file a download replaces aside and restore it if the download fails
or is cancelled (WebView2's flyout can cancel), rather than deleting it up
front. The installer no longer offers to start OpenResearch when the WebView2
Runtime is still missing, since the window could not open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:19:33 -07:00
..