Commit Graph
11 Commits
Author SHA1 Message Date
pablogonzalez 8aaef3dec1 fix(desktop): dedupe Linux tray D-Bus calls to stop main-thread freeze (#2459)
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.
2026-07-27 11:51:22 +03:00
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
Bohdan Triapitsyn 89f7c37d60 fix: make session ordering follow activity lifecycle
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.
2026-07-23 15:45:53 +03:00
Bohdan Triapitsyn 1c7cdf68e3 feat: add a macOS menu bar toggle for desktop
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.
2026-07-22 10:08:58 +03:00
Bohdan Triapitsyn 85400459e9 perf: overhaul session loading, caching, and runtime isolation (#2360)
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
2026-07-21 20:52:20 +03:00
achcyano 4296efb64d feat(electron): add Windows startup and system tray support (#2112)
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.
2026-07-10 12:40:48 +03:00
Bohdan Triapitsyn 84d7303346 feat(desktop): add macOS dock badge for chats with unseen activity
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.
2026-06-28 01:07:52 +03:00
Bohdan Triapitsyn 89aa389e6c fix(tray): show live activity for every session, not just one
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.
2026-06-10 14:01:10 +03:00
Bohdan Triapitsyn b27e8efb5f feat(desktop): per-session metadata and status icons in the tray menu
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.
2026-06-10 01:58:11 +03:00
Bohdan Triapitsyn ae46060e1d feat(desktop): add provider usage submenu to the macOS tray
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.
2026-06-10 00:50:20 +03:00
Bohdan Triapitsyn 9cf79a8890 feat(desktop): macOS menu bar tray with live session state and mini-chat UX
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.
2026-06-10 00:20:23 +03:00