Show a count of chats (root sessions) with unseen activity on the macOS
dock icon. The count is computed in the existing tray snapshot (full
cross-project list, not the capped tray view; a subtask's unseen rolls up
to its root only when subtask notifications are enabled) and pushed to the
main process over the existing desktop_tray_update IPC, which calls
app.setBadgeCount (0 clears it). The badge clears as sessions are marked
seen on window focus.
Add a Dock badge toggle in Appearance settings (default on, persisted,
darwin desktop only), localized across all dictionaries, with a matching
settings-search entry whose availability mirrors the render guard exactly.
OpenChamber spawns the OpenCode server as an external child binary (detached
on Unix), so a hard crash, SIGKILL, or Ctrl+C of the host before graceful
teardown could leave it running. Orphaned servers then accumulate and contend
on the shared SQLite DB, causing severe startup slowdowns.
Add a per-process registry plus a startup reaper, mirroring the pattern
OpenCode's own CLI daemon uses for its detached server:
- One file per spawned process at
~/.config/openchamber/managed-opencode/<pid>.json. Per-process files avoid
the read-modify-write clobber race between concurrent runtimes/windows that a
single shared file would suffer.
- On spawn, record the child (pid, owner pid, port, binary, host runtime).
- On graceful close/restart, delete the record.
- On startup, reap only our own, verified, genuinely-orphaned processes:
recorded by us AND still a live `opencode serve` on the recorded port AND
whose spawner is provably gone (reparented to pid 1, or recorded owner dead).
It never touches a process a live instance is using, the user's standalone
server, the official desktop app, or the TUI.
Wire it into every runtime that spawns the server:
- web/desktop via the OpenCode lifecycle (register on spawn, unregister on
close/restart, reap at startup). The restart-for-config-change flow inherits
this automatically through the same kill/spawn paths.
- VS Code carries a parity implementation (it does not bundle the web package)
that reads/writes the same registry directory and uses the same algorithm.
- Tag the actual host runtime (desktop/web/ssh-remote/vscode) for observability.
Also tighten teardown so the registry stays accurate and orphans die promptly
instead of only on the next start:
- The web server now also handles SIGHUP and SIGUSR2 (terminal close and the
nodemon restart used by dev:server:watch / dev:web:hmr).
- Electron now installs SIGINT/SIGTERM/SIGHUP handlers that run the same
background teardown as a normal quit, covering Ctrl+C on electron:dev.
External OpenCode servers (OPENCODE_SKIP_START) are intentionally excluded: we
never manage or kill processes we did not spawn.
The global event-stream WebSocket opened before a valid oc_url_token was
minted, so the upgrade failed auth ("no valid credentials available") in
packaged builds with a UI password. The resulting reconnect storm churned
the sync store and made session status flicker busy<->idle. Await the URL
auth token before connecting (a WS upgrade can't send a bearer header like
SSE does) and drop a rejected token on pre-ready close so the next attempt
re-mints a fresh one.
Also harden /session/status reconciliation: the watchdog poll is now
monotonic (only confirms/raises active status, never blindly lowers a
busy/retry session to idle on a transient or misscoped snapshot). Idle is
applied only by the authoritative reconnect/escalation resync, which trusts
the live server snapshot as the source of truth. Add a Help -> Toggle
Developer Tools menu item so production builds can open the console.
Packaged desktop showed no sessions in 1.12.4. Root cause: the sanitized
session-list proxy path added in #1538 forwarded the renderer's
"authorization" header (the OpenChamber UI client token) to the managed
OpenCode upstream alongside the managed "Authorization" credential.
OpenCode does not recognize UI client tokens, so every session-list
request answered 401 — only in the packaged app, because only its
renderer (openchamber-ui:// origin) attaches a bearer token; dev web and
dev Electron run same-origin without one. The legacy http-proxy path
overwrote the header correctly, which is why everything except session
lists kept working.
Proxy fix:
- proxy-headers: filter the client "authorization" header out of
forwarded request headers; the OpenCode upstream must only ever see
its own managed credentials. Covered by tests.
Desktop cwd:
- electron: launch the managed OpenCode CLI from the user home instead
of app userData, matching upstream desktop behavior. userData-as-cwd
made OpenCode treat the app-data folder as a separate empty workspace.
Home directory poisoning loop:
- directoryPersistence: stop replaying localStorage homeDirectory
through synchronizeHomeDirectory on boot/auth resync. The persisted
value is only a boot-time cache; replaying it re-wrote stale values
(e.g. a project path) into desktop settings on every start, overriding
the authoritative /api/fs/home resolution.
- persistence: never overwrite an injected window.__OPENCHAMBER_HOME__
with a persisted value.
- useDirectoryStore: host switches happen in place (no reload), so
re-resolve home from the new runtime's /api/fs/home on endpoint
change instead of keeping the previous host's value.
- opencode client: only short-circuit to the injected desktop home when
the active runtime is local; remote runtimes ask /api/fs/home.
Settings hygiene:
- persistSettings: log field names only — change payloads can carry
credentials (UI password, client tokens, tunnel tokens) that must not
reach the log file; drop step-by-step log chatter.
- validateProjectEntries: only stat project paths when the incoming
update actually touches the projects list, not on every settings save.
- remove the write-only approvedDirectories setting everywhere and add
a migration that strips the stale key from persisted settings.
Tests:
- usePluginsStore.test: register an own runtime-fetch module mock so the
suite is independent of process-global mock.module leakage from other
files, and restore globalThis.fetch after the suite.
- persistence.test: clean up the window global created for the suite.
Start managed OpenCode from the app data directory instead of the home folder
Prevent unnecessary Desktop, Documents, Downloads, and Music access prompts
Add coverage for configured OpenCode working directory
Enrich each session row in the macOS tray menu and refine its layout.
- Add a "project · branch" sublabel to every session row, resolved from the
session directory: project-root sessions map to their project + live/cached
git branch; worktree sessions map back to their parent project and use the
worktree's branch. Branch resolution falls back live VCS → git store → worktree
metadata, with normalized directory keys.
- Replace the inline text status glyph with a native left-aligned status icon
(vertically centred across the title + sublabel). Idle rows use a transparent
placeholder so every row shares the same gutter and both text lines align.
- Status icons use the app icon set: pulse (busy), check (unread), error-warning
(error), loop-right (retry); all rendered as tinted template images.
- Drop the unread count "(n)" from the row label — the check icon already
signals it and the number wasn't self-explanatory.
- Show the first 8 sessions inline; the rest stay in the overflow submenu.
- Subscribe the tray to the projects, worktree and git stores so subtitles stay
current.
Surface rate-limit usage in the tray, mirroring the header/mobile usage view.
- New "Usage (Used/Remaining)" submenu groups enabled providers with their
window limits (e.g. 5-Hour, Weekly Limit, Credits) and per-window values,
reusing the quota store and the same formatting helpers as the rest of the UI.
- Honors the "configured to show" rule: only providers the user enabled for the
dropdown and that report as configured are shown; when none qualify the submenu
is omitted entirely.
- Tray-side data: build usage groups in useTraySync, push on quota-store changes,
do one initial fetch for enabled providers on launch, and refresh on a
desktop-only interval that respects the user's auto-refresh setting (no change
to web behavior).
- Rows are read-only (greyed) info items; provider flush, windows indented.
- Show the first 8 sessions inline and move the rest into the overflow submenu.
Add an always-visible macOS status bar (tray) item that surfaces OpenChamber's
live state and acts as a quick launcher, plus a series of related desktop UX
fixes around mini-chat, window routing, notifications and shortcuts.
Tray (new):
- Monochrome template cube glyph that adapts to the menu bar light/dark.
- Icon-driven activity indicator: a smooth, eased, infinite "breathing" fill
while sessions are busy; a static filled cube when finished sessions are left
unread; a plain outline when idle. Text counters next to the icon only for
actionable states (pending approvals, errors).
- Menu lists active sessions (status glyph, branch, unread count) with overflow
rolled into a submenu; pending permission/question approvals with inline
Allow once / Allow always / Deny; quick actions (New Session, New Mini Chat,
Show OpenChamber, Quit). Header shows the active instance name
("Local OpenChamber" or the remote host label) for multi-window clarity.
- Session list sourced from the global (cross-project) sessions store, sorted by
last-updated, independent of which directories are currently open; live
status/unread/branch merged in from directory sync stores where available.
Sub-session (multi-run) activity rolls up to the parent row.
- Event-driven updates (global store + directory stores + notifications +
registry) with a short debounce; polling kept only as a slow safety net.
Tray/window routing:
- Opening a session from the tray targets the surface the user was last on: if a
mini-chat is active it switches that existing window to the session in place
(no new window); otherwise the main window (revealed without a reload).
- app.activate (dock click) restores the last-focused/minimized window instead
of spawning a new main window; only creates one when nothing is left.
- "Open in main window" and tray session-open now create the main window when
none exists, queuing the session as a pending deep-link so it opens once the
fresh renderer is ready.
Mini chat:
- New Mini Chat is now a customizable shortcut, exposed in Settings > Shortcuts,
in the File menu (hint only, renderer owns the binding), and in the tray.
- Themed splash backdrop on window open to remove the white flash / flicker;
dismissed once content is ready, leaving the content's single cube logo.
- Mini-chat can switch sessions in place via openchamber:open-session.
Notifications:
- The active/selected session only counts as "seen" when the window is focused,
so turns completing while the app is backgrounded raise an unread marker;
refocusing the window clears it.
Add native macOS vibrancy behind the left sidebar (the only translucent
surface; header/chat/right sidebar stay opaque), plus a setting to turn it off.
- Window created with vibrancy applied after first show (avoids the cold-launch
no-composite quirk); minimize/restore suppress the frost during the genie
animation. Renderer frosts the sidebar via --sidebar-vibrancy-overlay once
data-oc-vibrancy[-ready] are set; project-actions pill matches when open.
- data-oc-vibrancy-ready defaults are set in cssGenerator (DOM guaranteed),
not the preload (document-start race left the sidebar un-frosted on launch).
- prefers-reduced-transparency falls back to solid surfaces.
- Appearance settings (macOS desktop only): a checkbox to enable/disable
vibrancy, persisted to settings.json and applied via a Save & restart button
(vibrancy is a window-creation option, so it needs a relaunch).
Replace the macOS-derived icon.ico with a Windows-tailored design:
isometric cube filling the canvas edge-to-edge on a transparent
background, solid-gray faces, and a black hexagon outline framing
the silhouette. Add icon-win.svg as the source for regeneration.
Load native Windows app icons for open-in menu
Open Explorer and Terminal to the selected project directory
Resolve Windows Terminal icon from installed app assets
Exit the desktop app without waiting on background cleanup
Kill managed OpenCode by process group with a port fallback
Make OpenCode shutdown reuse the active shutdown promise
Electron updater now uses Electron release metadata only
Removed legacy Tauri package and migration workflow
Replaced Tauri shim usage with the desktop bridge
Add a packaged-client runtime boundary so the shared UI can talk to local,
desktop, remote, and VS Code runtimes through the right transport instead of
assuming one same-origin web server.
Centralize OpenChamber-owned API access behind RuntimeAPIs, runtimeFetch, and
runtime URL helpers, while keeping official OpenCode traffic on the SDK path.
Support runtime switching, remote host selection, desktop client credentials,
and headless connection links for pairing packaged clients with remote
OpenChamber servers.
Harden the new auth model by moving long-lived client tokens out of browser
URLs, introducing short-lived scoped URL tokens for browser-owned transports,
restricting URL-token access to explicit readable/realtime routes, and making
client-token management session-scoped or self-scoped as appropriate.
Update browser-owned assets and preview proxy flows to work with the split
runtime model, including authenticated project icons, preview token propagation,
CSP-safe preview bridge injection, and preview proxy auth that survives
short-lived URL-token expiry.
Tighten Electron security boundaries for packaged clients by gating privileged
preload state to trusted origins and requiring explicit confirmation before
connect deep-links import or switch remote runtimes.
Also refresh agent guidance and project skills so future runtime/API, auth,
preview, UI, CLI, settings, locale, and drag-to-reorder work follows the new
architecture.
* fix: make upstream sync actions target the selected remote
Ensure fetch and pull actually honor upstream selection so fork maintenance works from the Git sidebar, and surface upstream branch status alongside the primary origin-tracking indicators.
* feat: add Windows Electron desktop foundation
* fix(electron): stabilize Windows desktop packaging
* fix(electron): stabilize Windows desktop chrome
Use native Windows titlebar behavior with an Alt-accessible hidden menu, and harden Windows dev command launching so the desktop app follows platform conventions.
* fix(electron): stabilize Windows dev startup
* fix(electron): clarify desktop artifact names
* fix(electron): harden Windows desktop release and launch
* fix(electron): address Windows release review
* fix(electron): point updater and release links to org repo
* Fix Windows settings persistence fallback
* Fix Windows Electron dev startup
* Add Windows Electron window controls
* Fix Windows Electron install and opencode launch
* fix: resolve git status for repositories without upstream
Fixes repository detection stuck on Checking repository
Handles git status when no upstream is configured
Adds regression coverage for git status loading
* Add Windows app menu button
* fix: preserve file editor line endings
* ci: add desktop release smoke workflow
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
Add launch-at-startup support across the Electron desktop app and the web CLI.
Electron now supports macOS launch-at-login through the native login item API. Login launches start OpenChamber in the background without opening a window, while Dock activation, deep links, and second-instance launches still open or focus the normal app window. The desktop Settings UI now exposes a localized launch-at-login toggle in Desktop Network Access.
The web CLI now includes `openchamber startup status|enable|disable`, backed by native user services:
- macOS: launchd LaunchAgent
- Linux: systemd --user service
- Windows: Task Scheduler
Startup services run `openchamber serve --foreground` so the OS service manager owns process lifetime and restarts. Foreground service updates now defer restarts to the service manager instead of spawning duplicate CLI restarts.
Startup services snapshot useful environment variables by default so provider tokens, PATH, SSH agent settings, and OpenCode configuration survive login/reboot starts. The snapshot avoids shell/session-only state, uses systemd-compatible env quoting on Linux, and avoids unused env artifacts on macOS.
Also adds localized docs for startup services and environment variables.