The tray controller called setToolTip and setContextMenu unconditionally on
every snapshot push. On Linux both are synchronous D-Bus calls into
plasmashell's StatusNotifierItem host. useTraySync debounced pushes at 120ms
(up to ~8/sec during token streaming), so each push made 2 blocking D-Bus
round-trips even when only dockBadgeCount changed or a token streamed into
an already-listed session — i.e., tooltip and menu content didn't change.
The native block has no JS-level log, is intermittent (depends on plasmashell
load), and eventually freezes the Electron main thread until crash.
setTitle and setImage were already deduped; setToolTip and setContextMenu
were the gap. Mirror the existing lastX pattern:
- tray.mjs: dedupe setToolTip (exact string compare via lastTooltip) and
setContextMenu (via lastMenuKey, a lightweight signature of menu-affecting
fields: sessions/approvals/usage). menuKey is a string concat, cheaper than
buildMenu itself, so skipping buildMenu when the key matches also saves
work. Cache-after-call ordering matches lastTitle/lastTooltip so a throw
forces a retry rather than skipping one. destroy() resets both new vars.
- useTraySync.ts: FLUSH_DEBOUNCE_MS 120 -> 500. Tray doesn't need sub-second
updates; approvals are rare discrete events that flush through the debounce
and the main app UI stays instant via SSE/stores.
The first change is the real fix; the second is a complement. Either alone
helps; together they eliminate the freeze under streaming.
* 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>
Session lists now promote a conversation when it starts working and again when it settles, instead of reacting to every streaming timestamp update. This keeps ordering responsive without bringing back the sidebar churn removed by the recent performance work.
Apply the same user-visible order across Recent, project and worktree groups, session switchers, mobile navigation, widgets, the command palette, and the desktop tray. Preserve pinned priority, freeze timestamp fallback ordering, and keep child-session activity scoped to siblings under the same parent so it never moves the root conversation.
Seed reconnect snapshots without synthetic jumps, clear ephemeral ranks on deletion and runtime changes, and cover lifecycle transitions, mixed root/child trees, metadata-only updates, and project-group ordering with regression tests.
Adds a General settings switch to show or hide the macOS menu bar item.
Persists the new desktopMacMenuBarEnabled setting and passes tray state into Electron.
Disables tray sync and native tray setup when the menu bar is turned off.
Improve OpenChamber responsiveness under large session workloads while fixing
cache, synchronization, and persistence correctness across runtimes, projects,
directories, and worktrees.
- prioritize selected and visible sessions during bootstrap and defer
non-critical enrichment work
- reduce redundant message loading, event processing, store publication, and
hidden sidebar work
- prevent stale session and message requests from overwriting newer
authoritative state
- preserve existing data when authoritative fetches fail instead of treating
failures as successful empty responses
- scope session materialization, messages, drafts, queues, todos, pins,
permissions, folders, tabs, Git state, and pull request data by runtime and
directory identity
- harden runtime switching, reconnect, cleanup, mutation reconciliation, and
persisted-state ordering
- preserve live subagent Task linkage when metadata arrives after an older
message request or while streaming parts are suspended
- coalesce overlapping tail refreshes without losing newer refresh demand
- improve cold-session loading by moving deferrable work out of the critical
bootstrap path
- isolate URL authentication, mobile credentials, native secrets, and other
runtime-owned state across endpoint changes
- bound long-lived caches and remove avoidable allocations from event and
rendering hot paths
- limit virtualization to archive collections where it improves rendering
without disrupting active sidebar layout
- stabilize session folders, pin ordering, expanded state, and persisted
sidebar behavior
- open skill files through the same secure editor and outside-workspace grant
flow used by file navigation, including worktree sessions
- expand regression coverage for stale completions, runtime collisions,
reconnect behavior, persistence races, authoritative empty results, and
subagent refresh ordering
- document the updated synchronization, cache ownership, performance, and
runtime-isolation invariants
Add Windows launch-at-login with background startup support and extend the
native tray integration to Windows.
Add a Windows-only setting to minimize or close the main window to the
system tray, persist it through desktop settings, and expose it in Settings
search and all locale dictionaries.
Keep tray state synchronized with live sessions on both macOS and Windows,
while preserving the existing macOS behavior.
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.
The tray showed the busy indicator for at most one session at a time.
Several gaps in how per-session status was sourced stacked up to that:
- Status was derived by iterating each sync child store's session list,
so a busy session missing from the list (created moments earlier from
another window, the tray, or the API while session.created raced or
the list got trimmed) was invisible even though the store's
session_status map already held its busy entry.
- The upstream /session/status endpoint is directory-scoped — querying
it without a directory only covers the server's own cwd, so there was
no authoritative cross-project snapshot to fall back on.
- Status events for directories without a child store were dropped by
the sync dispatcher, so sessions in unopened projects always rendered
idle.
Fix, layer by layer:
- Add a cross-project session-status store (sync/global-session-status)
fed two ways: the sync dispatcher now records status-bearing events
(session.status / session.idle / session.error) for ALL directories,
and the tray polls /session/status per visible-session directory to
seed initial state and reconcile missed events. Snapshots clear stale
entries both by directory key and by session id, so canonicalized
(realpath) directory mismatches can't strand a busy entry.
- Read live status straight from each child store's session_status map
instead of via its session list, and never let one store's idle entry
clobber another store's busy/retry for the same session.
- Resolve a session as active when either source (child stores or the
cross-project map) reports busy/retry, instead of letting the synced
store's idle shadow the fallback.
Verified end-to-end in the dev shell: two sessions running concurrently
in different projects — including a brand-new session in the open
project root, the exact case that failed — now both show busy in the
tray, and both return to idle when they finish.
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.