Four sync tests had been failing for a while (CI doesn't run them, so
nobody noticed). All four asserted behavior that was deliberately
changed by earlier refactors — the production code is correct:
- Three event-pipeline tests still expected message.part.updated events
to coalesce in the queue. That coalescing was removed in #1167 to
preserve part update ordering (the new contract is covered by
event-pipeline.test.ts). Updated the delta-ordering and no-coalescing
expectations, and switched the routes-before-queueing test to
session.status, which is still a coalescible type, so it keeps
proving that coalescing happens on the resolved directory.
- One session-ui-store test expected shell sends to run inside an
opencodeClient.withDirectory scope. Since #1228 the session directory
travels as an explicit request param on shellSession; the test now
asserts that contract directly.
All 165 sync tests pass.
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.
Adds item-level search inside Settings so users can find concrete settings like provider auth, agent mode, terminal font size, tunnel options, notification events, and similar controls instead of only filtering top-level pages.
Groups search results by Settings page and shows localized labels plus optional descriptions where useful.
Supports keyboard navigation with Arrow Up/Down, Enter, and Escape, matching the existing autocomplete interaction style.
Opens the correct Settings page or split-page draft state before scrolling to the matching control.
Highlights the matched setting with a subtle token-based background so users can see where they landed without an aggressive outline.
Adds explicit data-settings-item anchors across Settings pages and a centralized search registry with runtime/mobile availability guards.
Updates Settings UI skill guidance so future Settings changes keep search registry entries, anchors, localization, and availability guards in sync.
Added a mobile MCP overlay so MCP tools can be opened and managed from the mobile UI without relying on desktop-only dropdown behavior.
Improved mobile session panel touch handling so tapping the status/session area opens the right panel reliably on phones and tablets.
Cleaned up mobile usage provider metadata by removing duplicate rows, hiding unset providers, and showing provider logos consistently.
Added eager loading for provider logos used in mobile usage views to avoid delayed or missing icons when the panel opens.
Refined the mobile update and about flows in OpenChamber settings so release/update information is easier to read on small screens.
Adjusted related layout, header, VS Code layout, command palette, and settings text/localization details needed for the mobile polish.
Desktop app windows should follow native conventions where clickable controls
use the arrow cursor, not the web's pointer/hand. Under the desktop runtime
only (web in a browser is unchanged), neutralize pointer cursors on buttons,
role=button, links, summary/label, the cursor-pointer utility (and its
hover/group-hover variants), and markdown file links. Resize, grab, text and
not-allowed cursors are left intact.
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).
Rework the desktop layout so the left sidebar spans the full window
height and reads as a distinct column, separate from the header.
- Persistent top-left controls (sidebar toggle + project actions) as a
single overlay that no longer migrates between header and sidebar, so
it stays put while panels animate. Drag regions carve out only under
the buttons so the strip stays draggable and the buttons clickable.
- Header sits in the content column (bg-background); the divider lives
on the chat content (border-t) so it doesn't run between header and
right sidebar. Right sidebar gets a top border + small content padding.
- Drop the rounded chat card and corner masks for flat 1px borders.
- Right sidebar, context panel and git/files views inherit the
background color instead of bg-sidebar.
- Remove hover background from Changes/Staged rows.
- Mini-chat header: borderless, bg-background.
OpenChamber could show stale providers in Settings after users changed opencode.json because provider lists were restored from the persisted config-store cache. OpenCode itself was already correct, but the UI could keep showing old providers across app or machine restarts.
Stop persisting provider lists/defaults, strip old persisted provider snapshots during hydration, and invalidate provider cache before startup/config reload fetches. Provider config changes now refresh all known project directories immediately with capped concurrency, so switching projects later uses fresh cached data instead of triggering a refetch on navigation.
Also validate the selected provider/model after every provider reload, fall back to a live valid model when the old one was removed, and preserve a still-valid current model variant. Add regression coverage for hydration stripping, multi-directory provider refresh, and variant preservation.
Agent settings now correctly save an emptied system prompt instead of silently keeping the old text.
After saving agent, command, or skill settings, OpenChamber refreshes the updated configuration instead of showing stale values from a short-lived cache.
Reload actions now surface refresh failures consistently while avoiding noisy background errors.
Prevents permission changes from being overwritten by other agent fields
Writes built-in and custom agent permissions to the correct config source
Refreshes agent state after permission saves
Updates live session state after share and unshare actions
Preserves session directory metadata while applying share changes
Adds regression coverage for unshare and sanitized share responses
Open isolated Multi-Run sessions while worktree setup continues in the background
Apply the faster flow to VS Code Agent Manager isolated runs
Document the new Multi-Run and Agent Manager behavior
Compute review transfer state once per chat render
Hide transfer actions when linked review sessions are inactive
Remove session-list scans from individual message rows
Introduce a desktop/web-only /handoff-review flow that generates a handoff from the active implementation session, creates or reuses a separate review session in the same directory, and links the pair through hidden OpenChamber session metadata.
Add review flow orchestration, metadata helpers, magic prompts, localized command/action labels, session metadata create/update support, and assistant message transfer actions for sending reviewer feedback back to the implementer or implementation responses back to the reviewer.
Review sessions are ordinary sessions, not child sessions. The flow avoids exposing session IDs or routing metadata to agents, hides review controls on mobile and VS Code, hides unrelated assistant actions inside review sessions, cleans up stale metadata where possible, and uses the optimistic send path so cross-session sends scroll like normal composer messages.
Add a directory-created fast path for worktree creation so session and send flows can continue once the target directory exists while Git attachment and bootstrap finish in the background.
Track bootstrap status explicitly in shared UI contracts, including pending, ready, and failed states. Background watchers now surface failures and timeouts, update stored worktree metadata, and keep web and VS Code runtime behavior in parity.
Move GitHub issue/PR worktree sessions and assistant-answer fork sessions onto the unified send path so provider, model, agent, and variant selections are preserved. The assistant-answer fork dialog can optionally create a worktree outside VS Code.
Make worktree deletion dialogs close after linked-session cleanup while removing the worktree in the background, and clean up failed fast-create artifacts safely without recursively deleting user or agent-written files.
Validation: bun test packages/ui/src/lib/worktrees/worktreeBootstrap.test.ts packages/ui/src/lib/worktrees/worktreeManager.test.ts; bun run type-check; bun run lint.
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
Keeps optimistic prompt state in the session directory
Routes live assistant part updates using upstream event payloads
Adds regressions for startup session switch races
Adds safe PR review automation for pull requests and /oc-review comments
Supports fork PR review without checking out or executing untrusted code
Defines a dedicated read-only PR review agent
Add shared time formatting helpers that apply the Appearance time format preference consistently. Update visible time labels across chat, quota usage, scheduled tasks, tunnels, context details, git views, PR metadata, and passkey settings to use the selected 12-hour, 24-hour, or automatic format. Leave date-only and non-UI formatting untouched so unrelated behavior does not change.
The preview proxy now rewrites imports inside inline module scripts, which covers the Vite React preamble emitted into HTML. This keeps /@react-refresh behind the same proxy path and preserves preview/url auth tokens for iframe subresources. Added coverage so future proxy rewrites do not regress Vite React preview startup.
Moved file preview mode toggle next to Show Dotfiles
Saved checkbox changes through settings config
Applied preview mode changes to open editor tabs immediately
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
Preserves session directory metadata across live updates
Keeps desktop and mobile session lists in project groups
Adds regression coverage for session grouping
Electron updater now uses Electron release metadata only
Removed legacy Tauri package and migration workflow
Replaced Tauri shim usage with the desktop bridge
Lift both starter groups into one DndContext so a trash drop-zone is reachable from either group's drag. On mobile the trash replaces the + button while dragging (color-only over feedback, no resize); dropping a chip on it removes that starter. Hide the hover-x on mobile since there is no hover. Desktop keeps the hover-x + add button unchanged.
Make the new mobile layout the default when no preference is stored, and
relabel the legacy 'default' option as Old (localized as Previous/older where
it reads better) while keeping its stored value for backwards compatibility.
Use a shorter comment placeholder on mobile (drop the Cmd+Enter hint that has
no touch equivalent) so it no longer clips in the auto-sized textarea.
Add a remove (x) button to the review comments pill, matching the annotation
and dev-server-log pills, and stop the mobile touch-target min-height/width
from inflating the pill close buttons.
Center PWA dialogs again by dropping the stale iOS offset that pushed a
flex-centered (position: relative) dialog down by half the viewport.
Seat the mobile overlay panel flush to the bottom (drop the float margin),
drop its bottom border in favor of a scroll shadow on the content, and raise
it above sheets (z-60) so an overlay opened over a surface sheet renders on
top instead of behind it.
Defer mounting heavy sheet content until the enter slide finishes (revealed on
the slide's transitionend, with a timer fallback) so the animation no longer
competes with a large content render. Shorten the slide to a quick rise from a
small offset, and seat the sheet a few px below the top safe area.
Revert the 100dvh sizing back to inset-0/100% — it did not fix the bottom inset
and reintroduced intermittent top safe-area dimming.
Drop the safe-area-bottom padding inside the sheet and size the surface from
100dvh (instead of fixed inset:0 / 100%, which on iOS 26 stops above the bottom
safe area), and avoid keeping the sheet on a compositing layer when settled.
Tightens the bottom gap so the sheets sit close to the bottom edge like the
chat composer.
Use a dark scrim for MobileOverlayPanel instead of a translucent background so
it dims the rest of the screen like the sessions sheet.
Rework MobileSurfaceShell to mirror that structure: the scrim is the fixed
parent's background with a quick fade (keeping it on a compositing layer iOS
Safari clips to the viewport), and the sheet is a flex child sized to leave the
top safe area dimmed — dropping the old top shadow that pooled at the corners.
Mount Files/Changes/Settings only while open so each recomputes its
safe-area layout fresh, fixing intermittent top-inset dimming, and drop the
dim behind the header overflow menu to avoid a scrim-to-scrim flicker on
select.
Nest child sessions (by parentID) under their parent in the mobile sessions
list, with a chevron in the row's left gutter to expand/collapse them
recursively. Top-level pagination counts only parent sessions; children whose
parent isn't in the same bucket stay top-level so nothing is hidden.
Expansion state lives in an in-memory store that survives closing and
reopening the sheet but resets on a full page reload.
Restructure the changes list so the staged/changed file list scrolls inside
ChangesPanel while the commit message and actions stay pinned to the bottom,
matching the desktop git sidebar instead of letting the commit box scroll out
of view with the list.
Remove the per-row agent color dot (and its now-unused helpers), keeping only
the status indicator. Add a "+" button to the panel header that opens a new
session draft targeting the filtered project (or the most recently active
session's project under "All", falling back to the default target), and
restyle the close button to match it.
Show the context token usage next to the percentage (e.g. 5.1% 20.4K/400.0K)
at a larger, consistent size, and compute the percentage with a fraction
instead of the store's integer-rounded value.
Add a two-step archive affordance to each mobile session row, mirroring the
project remove confirmation: tapping the archive icon arms an inline confirm
(with cancel), and confirming archives the session. Only one row can be in
the confirming state at a time.
Also relabel the project remove confirmation from "Delete" to "Close"
across all locales, since removing a project closes it from the app rather
than deleting anything.
Replace the up/down arrows in the mobile project reorder list (drag already
covers reordering) with an edit button that opens a dedicated project editor
surface: rename, pick icon and color, and discover a favicon (no upload).
The editor lists the project's worktrees with drag-to-reorder (persisted per
project, like project order) and a delete button that opens a mobile
confirmation built on the shared worktree primitives — archiving attached
sessions and optionally removing the local/remote branch.