Files
openchamber/packages/electron
18b58bdb6b feat(desktop): Linux AppImage releases and desktop feature parity (#2398)
* feat(electron): add Linux AppImage releases

* ci: cache Linux OpenCode CLI artifacts

* fix(ci): await Linux release inventory check

* fix(electron): add frameless window controls on Linux desktop

Linux AppImages were created without native WM decorations and without
in-app controls, leaving users unable to close the window with a mouse.

Treat Linux like Windows: frameless BrowserWindow plus the existing
WindowsWindowControls header buttons and app-menu entry. macOS keeps
hidden title bar with traffic lights unchanged.

Shared usesFramelessElectronChrome() helper drives main window, mini
chat, header insets, and titlebar controls.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* Linux desktop feature parity: Open in, background start, tray, multi-window (#2392)

* feat(electron): Linux parity for Open in, background start, and tray

Enable Linux desktop feature parity with macOS/Windows: open projects in
the default file manager and discovered apps, XDG autostart with
--background launches, system tray (including minimize-to-tray), and
tray sync from the renderer.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* fix(electron): allow packaged UI protocol navigations on Linux

Prevent openchamber-ui:// navigations from being handed to
shell.openExternal, which fails on Linux and blocked desktop UI flows.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* fix(ui): surface Linux tray settings in settings search

Include isLinux in settings search runtime context so minimize-to-tray
is discoverable on Linux desktop, matching Windows search behavior.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* fix(electron): stop Linux AppImage Waiting-for-OpenCode flicker

Sync updated boot-outcome init scripts to all BrowserWindows after
desktop_hosts_set, and prefer state.initScript on dom-ready so chooser
reloads inject local/ok instead of a stale not-configured outcome.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* fix(desktop): restore Linux AppImage updater feed and error UX (#2396)

Treat missing latest-linux*.yml (404) as no update available instead of a
hard failure, and stop swallowing updater capability/download errors in the
desktop bridge so About/sidebar can show actionable messages.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

* docs: credit Linux AppImage contributors in changelog

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>

---------

Co-authored-by: jibanez-staticduo <staticduo@gmail.com>
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-07-24 09:59:32 +03:00
..

OpenChamber Desktop

Electron desktop runtime for OpenChamber on macOS, Windows, and Linux.

This package owns the native shell: windows, menus, deep links, native notifications, auto-updates, host switching, SSH connections, tunnel helpers, and packaged desktop builds. The web UI and OpenChamber server logic still live in packages/web and shared React UI lives in packages/ui.

How It Runs

Desktop starts the OpenChamber web server in the same Electron main process. There is no separate sidecar subprocess for the OpenChamber server.

main.mjs imports @openchamber/web/server/index.js and calls startWebUiServer(). The Electron window then loads the UI from the local server in development, or from packaged resources/web-dist assets in packaged builds.

The preload bridge exposes desktop-only APIs to the web UI through window.__OPENCHAMBER_DESKTOP__. Privileged commands are checked in main.mjs, not only in the UI.

Main Files

File Purpose
main.mjs Electron main process, app lifecycle, windows, menus, deep links, native IPC handlers, updates, local server startup
preload.mjs Safe bridge from the rendered UI to Electron IPC
ssh-manager.mjs SSH host import, connection lifecycle, tunnel/port forwarding helpers
scripts/electron-dev.mjs Desktop dev launcher with Vite HMR support
scripts/build-web-assets.mjs Builds packages/web and stages UI assets into resources/web-dist
scripts/prepare-opencode-cli.mjs Downloads and stages the pinned OpenCode CLI into resources/opencode-cli
scripts/bundle-main.mjs Bundles Electron main code into dist-bundle/main.mjs for packaging
scripts/rebuild-native.mjs Rebuilds native modules against the Electron runtime
scripts/package.mjs Runs electron-builder, with unsigned Windows builds when signing env is missing
resources/ Packaged web assets, icons, and macOS entitlements

Development

From the repo root:

bun install
bun run electron:dev

bun run electron:dev starts the web dev server with HMR, then launches Electron against packages/electron/main.mjs.

The Electron workspace package trusts Electron's install script so bun install downloads the platform runtime in fresh checkouts and worktrees.

Useful variants:

bun run electron:dev:bundled
bun run type-check:electron
bun run lint:electron

electron:dev:bundled builds and uses packaged web assets instead of the HMR server. Use it when testing behavior closer to a packaged app.

Packaging

From the repo root:

bun run electron:build

That runs, in order:

  1. build:web-assets to build the web UI and copy it into packages/electron/resources/web-dist.
  2. prepare:opencode-cli to download/cache the pinned OpenCode CLI and copy it into packages/electron/resources/opencode-cli.
  3. bundle:main to create packages/electron/dist-bundle/main.mjs.
  4. rebuild:native to rebuild native modules for Electron.
  5. package.mjs to run electron-builder.

Build output goes to packages/electron/dist.

macOS builds produce dmg and zip artifacts. Windows builds produce an NSIS installer. Linux builds produce an AppImage for the native x64 or arm64 host.

Platform Notes

macOS packaging needs Xcode/build tools for notarized builds and icon asset compilation.

Windows packaging needs NSIS support through electron-builder. If no Windows signing env is set, package.mjs disables code signing and builds an unsigned installer.

Linux AppImages must be built natively. Set OPENCHAMBER_TARGET_ARCH=x64 or OPENCHAMBER_TARGET_ARCH=arm64 when packaging; the build rejects a target that does not match the Linux host. The same target selects the bundled OpenCode CLI, native Electron rebuild, and Electron Builder architecture. Linux identity is stable across architectures: executable openchamber, desktop file openchamber.desktop, icon openchamber, and StartupWMClass=openchamber.

After packaging, run bun run --cwd packages/electron verify:linux-appimage. The verifier extracts the final AppImage and checks its ELF architecture, desktop identity, Electron executable, pinned OpenCode CLI version and architecture, and all packaged native .node modules.

Running a packaged Linux AppImage requires FUSE (libfuse.so.2, typically libfuse2 / libfuse2t64 on Debian/Ubuntu). Without FUSE, start with APPIMAGE_EXTRACT_AND_RUN=1. Keep the AppImage on a writable path so in-app updates can replace it.

Linux updates are supported only when the packaged app is running from a writable AppImage. Update checks, downloads, and installation report an actionable error when APPIMAGE is missing, invalid, or read-only; a missing release feed (latest-linux.yml 404 before the first Linux publish) is treated as “no update available”. macOS and Windows updater behavior is unchanged. Release builds keep latest-linux.yml (x64) and latest-linux-arm64.yml separate and validate each manifest against its AppImage before upload. Linux AppImages download full updates (no .blockmap differential channel yet).

Updater End-to-End Fixture

A loopback-only updater fixture is available for contributor QA of N-to-N+1 AppImage replacement and restart behavior. It is test infrastructure, not a user-configurable update source. See scripts/updater-e2e-fixture.md for the controlled test procedure. Unit tests cover feed selection, check failures, no-update results, and fixture generation; actual AppImage replacement and restart remains a manual native N-to-N+1 release boundary because it requires executing two packaged versions on each supported architecture.

The package supports macOS, Windows, and Linux desktop features. Linux AppImage builds include in-app window controls, auto-update, system tray, and launch-at-login (XDG autostart). Some native discovery helpers are platform-specific. For example, app icon fetching and app filtering currently only work on macOS, while opening files in installed apps and installed-app discovery work on macOS, Windows, and Linux.

The macOS menu bar item is enabled by default and can be disabled in General settings. The setting applies after restart; while disabled, Desktop does not create the native tray controller or start the renderer subscriptions, polling, quota refresh, or IPC updates that feed it.

Bundled OpenCode CLI

Packaged Desktop builds include the official OpenCode CLI that matches the pinned @opencode-ai/sdk version in the root package.json. prepare:opencode-cli downloads the platform-specific release artifact, caches it under packages/electron/.cache/opencode-cli, stages opencode or opencode.exe into resources/opencode-cli, and verifies opencode --version before packaging. Re-running the step is fast when the staged binary already matches the pinned version.

Managed local Desktop startup prefers OpenCode binaries in this order:

  1. settings.opencodeBinary.
  2. Environment overrides: OPENCODE_BINARY, OPENCODE_PATH, OPENCHAMBER_OPENCODE_PATH, or OPENCHAMBER_OPENCODE_BIN.
  3. The bundled Desktop CLI in process.resourcesPath/opencode-cli.
  4. System installs discovered from PATH.
  5. Known npm/Bun/Homebrew/Scoop/Chocolatey and other standard install locations.
  6. Platform discovery through where opencode on Windows or a login shell on macOS/Linux.

Use an explicit override when testing a different OpenCode CLI build or when a user needs to point Desktop at a custom binary. The configured path must point to the standalone CLI, not the OpenCode Desktop app executable.

Common Env Vars

Variable Use
OPENCHAMBER_ELECTRON_DEV=1 Marks the runtime as desktop development mode
OPENCHAMBER_ELECTRON_USE_BUNDLED_UI=1 Uses staged web assets instead of the HMR dev server
OPENCHAMBER_SKIP_LOCAL_SERVER=1 Skips the in-process local OpenChamber server and uses the configured default remote instance; Desktop imports this from the user's login-shell environment, and packaged/bundled UI remains available for connection recovery
OPENCHAMBER_HMR_UI_PORT Preferred Vite UI port for desktop dev, default 5173
OPENCHAMBER_HMR_API_PORT Preferred API port for desktop dev, default 3901
OPENCHAMBER_RUNTIME=desktop Set by Electron before starting the web server
OPENCHAMBER_OPENCODE_CLI_VERSION Optional packaging override for the bundled OpenCode CLI version; defaults to the pinned root @opencode-ai/sdk version
OPENCHAMBER_TARGET_ARCH Explicit desktop package architecture (x64 or arm64); Linux requires it to match the native host
OPENCHAMBER_DESKTOP_NOTIFY=true Enables desktop notification flow in the web server
OPENCHAMBER_SKIP_API_COMPRESSION=true Defaulted by Desktop to reduce local CPU overhead
OPENCODE_HOST / OPENCODE_PORT / OPENCODE_SKIP_START Connect Desktop to an external OpenCode server instead of starting one locally

Native Features Owned Here

  • Floating Mini Chat windows.
  • Multiple native windows.
  • Native notifications.
  • One-click open/reveal/open-in-app actions.
  • Desktop host switcher and deep-link imports.
  • Local and remote instance handling.
  • SSH host import, connections, logs, and port forwarding.
  • SSH uses OpenSSH ControlMaster on macOS/Linux. Windows uses independent hidden OpenSSH processes for setup commands and each long-lived forward because Win32 OpenSSH does not support ControlMaster reliably.
  • Tunnel lifecycle integration through the web server runtime.
  • Auto-update checks, downloads, and restart/apply flow.

IPC Pattern

Renderer code should call the desktop bridge exposed by preload.mjs. Do not import Electron from shared UI code.

Add new native capabilities in this order:

  1. Add or update the preload.mjs bridge only if a new renderer-facing shape is needed.
  2. Add the real command handling in main.mjs under openchamber:invoke.
  3. Gate privileged commands in main process logic so remote pages cannot access local filesystem or shell capabilities.
  4. Keep shared UI runtime contracts in packages/ui and server/runtime APIs in packages/web when the behavior is not inherently native.

Logs And Data

Electron uses electron-log. In development, console logs are also visible in the terminal. In packaged apps, logs are written through the platform log path for the OpenChamber app name.

Development builds use a separate user data directory named OpenChamber Dev, so dev state does not overwrite normal packaged app state.

Things To Be Careful With

  • Keep desktop-specific code in this package. Do not move OpenCode feature backend logic into Electron.
  • Use hidden Windows process launches for background helpers. Avoid visible console flashes.
  • Keep @openchamber/web, bun-pty, node-pty, and native modules external in bundle-main.mjs; bundling them can break Electron startup.
  • Rebuild native modules after dependency or Electron version changes.
  • Test both HMR dev mode and bundled UI mode when changing startup, preload, routing, or packaged asset behavior.

Quick Checks

bun run type-check:electron
bun run lint:electron
bun run electron:dev:bundled

For full repo validation before shipping:

bun run type-check
bun run lint