Branch status resolves an open PR across the whole fork network first, so a
merged fork PR can never hide an open upstream PR for the same head. Only when
no target has an open PR does the branch's newest closed/merged PR come back,
as history.
The panel shows that history as a compact note and offers creating the next PR
below it, instead of either sticking on a terminal PR or going blank after a
merge. Terminal associations stay persisted for reload continuity but are never
treated as authority: they revalidate on the discovery cadence and on focus.
History is looked up only for the branch's own remote and name, and remembered
per repo+branch, so the extra lookup cannot exhaust the route's resolve budget.
The checks summary and merge-permission lookup are skipped for a closed or
merged PR, where neither is actionable.
Claude quota only worked when the user had signed into Anthropic through
OpenCode. Credentials are now discovered from Claude Code itself first: the
macOS Keychain entry, then the Linux/WSL credentials file (honouring
CLAUDE_CONFIG_DIR), then OpenCode auth.json, then CLAUDE_CODE_OAUTH_TOKEN.
All sources stay read-only and the OAuth token is never refreshed: Anthropic
allows one live refresh token per client_id, so refreshing here would sign the
user out of Claude Code. Credentials are re-read per request instead, and an
expired token reports that Claude Code needs a sign-in rather than a bare 401.
Usage is now read from the limits[] array, so model-scoped weekly limits work
again after Anthropic stopped populating seven_day_sonnet/seven_day_opus, and
new limit kinds no longer need a code change. Adds extra-usage spend and the
plan name, and holds the last good values through Anthropic's 429s with a
cooldown and an account-keyed cache.
The preview panel worked by proxying a dev server through OpenChamber's own
origin and rewriting the HTML that came back. Anything the rewriter did not
anticipate broke, and pages that refuse to be embedded never loaded at all.
This deletes the proxy (-1604 lines and its tests) and merges the preview and
browser panels into one surface backed by a real Chromium view.
What the panel is now
- A `<webview>` in its own session partition: logins and cookies persist, hot
reload works because nothing is rewritten, DevTools are one click away.
- Annotation: pick one element, drag a region, or draw freehand, write a note,
and it reaches chat with a screenshot of the visible page with the marks on it.
- Toolbar: hard reload, page zoom, device sizes, a light/dark switch that
applies to the page rather than the app, and cookie/cache clearing scoped to
the panel alone.
- Several pages at once, each tab showing the page's own favicon, and an address
bar that suggests pages already visited in this project.
- Dev servers are listed from what is actually listening on the machine, checked
against what a project announced, so a server is offered no matter how it was
started. One that is still starting is waited for instead of failing.
Remote dev servers
The desktop app binds a local port and pipes raw bytes to the OpenChamber host
over the existing authenticated connection, so the page keeps its own origin at
the root of its own host. The reachable set is exactly what discovery reports
and is re-checked per connection, so an authenticated client cannot dial
arbitrary local services on the host. Links and redirects to another loopback
port stay on the machine that served the page. A tunnel that cannot be opened is
reported; it is never replaced by the plain loopback URL, which would answer
from the user's own machine under a remote address.
Agent control
Browser actions are a separate `openchamber_web` tool: open, snapshot, click,
type, scroll, inspect computed styles, resize between mobile/tablet/desktop, and
capture a screenshot into `.openchamber/screenshots/` in the project. The
existing `openchamber` tool keeps sessions, worktrees and scheduled tasks. Each
has its own setting in the new Settings -> General -> OpenChamber Tools section,
and the plugin is not injected at all when both are off.
Capability belongs to the connected client, not to configuration: a client
declares on its event stream that it can drive a page, which only a Chromium
host does. Exactly one client performs each request — it claims the request
before acting, and the first claim wins — because deciding by whose result
arrives first would be too late for a click that already happened. No client
listening is answered immediately with an explanation rather than a timeout.
Runtime boundaries
Web tabs get a plain iframe that can display a page but not inspect one. The
VS Code extension no longer offers the surface at all, since nothing that makes
the panel worth having works there. Mobile is unaffected.
Native boundary
Camera, microphone, location and device-picker requests from panel pages are
denied — Electron grants them by default when no handler is set, and the panel
loads whatever address the user types. Page capture, appearance emulation and
storage clearing verify that their target belongs to the panel's own session
instead of trusting a web-contents id from the renderer.
Persisted state
Stored `preview` tabs migrate to `browser` (v13 -> v14). Context panel tab
limits are now per surface, so filling one surface no longer evicts another's
tabs. Address history is stored per project and per runtime.
Documentation
`preview.mdx` and `desktop-browser.mdx` rewritten across all locales, the agent
tool settings path corrected, new `DOCUMENTATION.md` for the browser-control
broker and the dev tunnel, and the `ui-api-decoupling` skill updated where it
still described the deleted proxy.
* fix(scheduled-tasks): claim schedule occurrences across server instances
Two OpenChamber servers sharing project config each armed timers and both
dispatched the same daily/weekly/cron/once slot (#2710). Claim the occurrence
in shared config under a cross-process write lock before creating a session.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* fix(scheduled-tasks): harden occurrence claim failure and lock ownership
Address PR review blockers: release running-slot bookkeeping when claim
throws, avoid silently dropping an armed occurrence after a due-slack sync,
verify lock-file ownership on release, and cover real on-disk lock behavior.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* fix(scheduled-tasks): always release running slot on state-write failures
Wrap runTask bookkeeping in finally so claim, manual-start, and completion
lock timeouts cannot stuck-run a task; drop the diskNext claim guard that
suppressed later occurrences; recover unparseable locks via mtime age.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* fix(scheduled-tasks): stop re-arming past nextRunAt and clear stuck running
Only schedule future nextRunAt values so once-task losers and claim-failed
paths cannot spin delay-0 retries. Clear past once nextRunAt on claim, and on
completion-write failure retry terminal status so manual runNow still returns
the session instead of a hard 500 with lastStatus stuck running.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* fix(scheduled-tasks): release write chain on lock acquire timeout
withProjectWriteLock left the in-process promise chain pending when
acquireProjectFileLock timed out, wedging every later project write and
stranding runTask before finally. Always release the chain; surface
persistError on run; record once claim failures in task state.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
---------
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Connection lifecycle events (probes, transport failures, resume decisions)
are mirrored into an in-memory trail that resets on every launch. A long
press on the connect-screen logo or the instances list opens a panel that
renders the trail with one-tap copy, so release builds can report the exact
probe sequence without a tethered debugger. Details reuse the already-masked
log payloads — no tokens or secrets are captured.
* fix: keep work-status panel reachable when all sections are hidden
When every section was unchecked in the Panel Sections dialog, the panel
went transparent and inert, making the settings gear icon unreachable.
The only recovery was knowing to toggle the panel off and on from the
header, which still rendered the same empty/inert state.
Changes:
- Panel stays interactive (not inert) when visible, even with zero
rendered sections. This matches how other panels in the app use inert
only for visually-collapsed (width/height = 0) states.
- Empty state shows 'No sections selected' with a link to reopen the
sections dialog, matching the centered text-muted-foreground pattern
used by the file tree, review panel, and home page empty states.
- Sections dialog gains a 'Show all' link (visible whenever any section
is hidden) and a warning when all sections are unchecked, matching the
keybinds settings 'Reset to defaults' pattern.
- Added i18n keys to all 10 locale files (English fallback).
Fixes#2804
* Round 1: fix interactive guard for fresh-mount; translate i18n keys
Address openchamber-bot review findings:
1. (blocker) Replace English fallback strings in all 10 non-English
locale files with real translations per locale-ui-patterns guidance.
2. (non-blocker) Restore the renderedSections > 0 guard for the
transient no-data-on-mount state so the panel doesn't flash a bare
bordered card. The interactive condition is now:
visible && (renderedSections > 0 || allSectionsHidden)
Empty-state rendering is gated on allSectionsHidden alone (not
renderedSections === 0) so it works correctly on fresh mount when
all sections were already hidden in persisted settings.
Validation:
tsc --noEmit: 0 errors
bun test work-status: 34 pass, 0 fail
* Round 2: use .every() guard, dedup chooseLabel key, add tests
1. Replace >= length check with areAllWorkStatusSectionsHidden() helper
that uses .every() — stale section ids left in persisted settings
from a future removal can no longer inflate the count.
2. Remove duplicate chooseLabel i18n key from all 11 locales — the
empty-state link now reuses the existing sections.open key.
3. Add 6 focused tests for areAllWorkStatusSectionsHidden covering
empty, null/undefined, partial, full, stale-id, and stale+full.
Validation:
tsc --noEmit: 0 errors
bun test work-status: 40 pass (6 new), 0 fail
Adds a work-status panel beside the transcript. Context fill, model and
cost, todos, running subagents and the permission requests blocking
them, branch and working-tree state, MCP servers, pinned messages and
context sources were scattered across the header, the composer and the
context panel — a blocked subagent was reported nowhere at all. The
panel reads them from live channels rather than persisted history, and
becomes an overlay where the chat is too narrow to seat a column.
It is on by default, including for existing installs. Because it now
carries these readouts, the desktop header and composer drop the ones it
duplicates: todo and changed-files chips, usage and MCP tabs. VS Code
and mobile keep theirs — neither hosts the panel.
Fixes MCP authorization, which was broken from the panel, invalidated by
a directory switch through a redirect URI that encoded the working
directory, and left the desktop app in the background because browsers
will not follow a custom-protocol link without a user gesture. The
settings page no longer asks the user to understand the MCP spec before
adding a server: one field takes the command or the link, with the kind
inferred and a visible override, and client-registration fields appear
only when a server actually asks for its own credentials.
Also: skills load from the panel instead of only when the composer's
slash autocomplete opens; the header button names the current instance
rather than falling through to the word "Instance" for relay hosts.
Three new optional UI settings keys, all migrated. No change to stored
MCP server configuration.
Review follow-up:
- Reject loop files whose frontmatter name exceeds MAX_TASK_NAME_LENGTH
(80): task names are clamped at storage time, so a raw name longer than
the limit could never match the stored task identity. The file is treated
as malformed (definition: null) instead of creating an unreachable
definition; MAX_TASK_NAME_LENGTH is now exported from project-config.js
and shared with loops.js.
- Surface loop-sourced tasks in the scheduled-tasks dialog: tasks carrying
loopFile show a 'Managed by loop file <path>' note, and the enable
toggle / edit / delete actions are disabled with an explanatory tooltip,
since the file remains authoritative and would revert any such change.
run-now stays available. New locale keys added to all 11 message files
(i18n parity test enforces exact key sets).
- ScheduledTask type gains an optional loopFile field (additive, unknown
to older clients).
The badge PR inverted showActivityDot to !== 'git', so editor/terminal/diff
picked up the blue activity dot whenever git had changes. Git already shows
a numeric badge; other surfaces should stay quiet. Also split the count
aria/tooltip strings into singular/plural keys.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Adds a per-session pending-question badge to sidebar rows, driven by the
live directory-store question state through a dedicated per-session
subscription channel so unrelated streaming never re-renders rows.
Collapsed parent rows roll up pending questions of hidden descendants
from their owning directory stores without bootstrapping them. Question
state is cloned on session delete/archive so badges clear when sessions
disappear. Adds the questionChangeCallbacks sync performance counter,
i18n keys for all locales, and unit tests for the subscription channel
and scope selection.
Fixes#2634
Replaces the plain activity dot on the context panel rail's Git button with
a numeric badge of the changed-files count from the git store status, so the
count is visible at a glance without opening the Git surface. Large counts
cap at 99+ to keep the pill within the 36px button. The badge is reflected
in the button's accessible label and the hover tooltip.
Fixes#2364
The spinner ran a CSS animation on every active row for the whole turn,
repainting a composited layer at frame rate. Rows now carry a static dot —
primary while running, info while unread — and the metadata slot on the right
shows how long the turn has been going, updating once per second in the dot's
colour. The counter is the motion the spinner used to provide, at 1 fps.
Collapsed groups, folders and projects take the dot only, since one counter
cannot speak for several running turns.
Elapsed time is measured client-side because SessionStatus carries no
timestamps, and starts are persisted so a reload resumes the same count. Two
rules keep that honest. Only a liveness stamp — refreshed while a session is
observed active, stamped as the page hides, and compared against the page's
navigation start so a slow bootstrap is not charged to the absence — and a 90s
adoption window may expire a record; a snapshot that cannot yet see a session
is not evidence its turn ended. And a busy event is never read as a turn
boundary, because OpenCode republishes busy at every step of the agent loop, so
after a reload one of those repeats normally beats the first status snapshot.
Idle and error events do end a turn, and retire the record with it.
Snapshot reconciliation walks the running turns and asks whether the snapshot
covers each one, rather than being handed everything it covers: only a live
start can settle, so the pass scales with timing work instead of with the
directory's session list, and allocates nothing per poll.
Also applied to the mobile sessions sheet and session switcher. The shared
duration ticker moves to hooks/ now that it has a second consumer.
- Add switch_context_surface shortcut (default Cmd/Ctrl + 1..9, 0 for the 10th
surface) that opens/closes/switches context panel rail surfaces by their
visible order, configurable and persisted in Settings -> Shortcuts.
- Show order-number badges on rail icons while the modifier is held >500ms;
dismiss on release, blur, or a number press until the next press-and-hold.
- Remove the legacy mod+2/3/4 (diff/terminal/git) and switch_tab_1..9 bindings
so numbered surface switching goes only through the new mechanism.
- Replace the help-dialog 'Switch Project' row with the surface-switch row and
update the shortcuts footer/header icons to the command icon.
A server without these routes does not answer 404 with JSON. The unmatched
/api path reaches the OpenCode proxy, and OpenCode serves its embedded web UI
for anything it does not recognise — HTML, status 200 — so a client newer than
its server parsed a web page as JSON and put "Unexpected token '<', "<!doctype"
in the panel, naming neither the cause nor the remedy.
The client now checks the content type before parsing. A non-JSON answer on 2xx
or 404 blocks with "this server is older than the app, update it and refresh".
A non-JSON 5xx keeps its own failure: a server that answered badly is not a
server missing the feature, and sending that user to upgrade chases the wrong
thing.
The "Critical" pill was painted in the status-error colour, so a stop marked
because it drives the change read as a severity reported against the code —
the one thing this feature never does. It is now "Key change", carries its
emphasis with weight and an outline rather than a status colour, and both tags
state their meaning in a tooltip. The panel links the guide from its header,
and the guide gained a section on what the tags mean and what they do not.
Also corrects two German strings that translated the noun "stop" as the verb.
Archived sessions had no way back to the active list: the only available
action was "Delete permanently". Add restore per session (sidebar context
menu, Archive page row) and in bulk (sidebar selection bar).
The OpenCode server cannot clear time.archived over HTTP — session.update
only applies the field for a finite number, so an omitted key is a no-op
and null is silently ignored (verified against opencode 1.18.12). Restore
therefore writes time.archived = 0: every client-side reader classifies
archive state by truthiness, so 0 reads as active in the UI, the event
reducer, and the OpenCode app/TUI.
The server's time_archived IS NULL list filter still excludes such rows,
so the global session cache no longer issues an archived:false request
for its active list. Full and per-directory loads now fetch once with
the inclusive flag and split client-side via splitGlobalSessionsByArchived,
which also halves per-directory refresh requests. Directory bootstrap
keeps the server filter because live child stores must not hold archived
sessions; a restored session re-enters its live store through the
authoritative session.updated event.
unarchiveSession/unarchiveSessions follow the archiveSession contract:
wait for server confirmation before reconciling stores, runtime-guard
every reconciliation, preserve partial batch results, and fail loudly
when the server keeps the session archived instead of toasting a
successful no-op.
Closes#2346
Reveal-in-file-manager was always offered whenever the server exposed
revealPath, including in a plain browser tab where there is no local
file manager to reveal into. Gate it behind a new isBrowserClientRuntime
check (web platform, no Electron shell) and relabel the save action to
"Download" for that case, since it triggers a browser-style file
download rather than an in-place save.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bind mod+l to append the current selection into the chat composer
(Cursor-style), and move session sidebar toggle to mod+alt+l so desktop
menus stay in sync. Closes#208.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
A guided explanation is only useful in a language the reader reads, so the
panel header gets a language picker alongside the model one, defaulting to
the interface language. Like the model, it is request state rather than a
setting: the language travels with the read and the generation, and the one
a walkthrough was written in is stored with it, so reopening a review
describes what is there instead of what a fresh one would be.
Only prose is translated. Hunk aliases resolve back to hunk ids and
icon/importance are validated against fixed English values, so a translated
one would be dropped by the normalizer — silently losing an anchor or a
style. Identifiers and paths stay as they appear in the code.
The language is part of the cache key, and a read now asks the cache for the
exact request it was given before falling back to the pointer. Without that
the panel answered a request to switch languages with the text it already
had, leaving the other language unused in the cache.
Alongside it:
- The answer budget is derived from the resolved model instead of a flat 24k.
That number was the same for a 64k-context model and for one that admits to
384k output tokens, and on the latter it was the only reason generation
failed: the model spent the whole allowance reasoning and returned nothing.
It is now min(96k, max(24k, a quarter of the context)) capped by the
catalog's output limit, decided once so the input reserve and the request
cannot drift apart.
- A read no longer offers Cancel. It is a few hundred milliseconds of git with
nothing to cancel, and the button flickered on every model or language
change. When the panel is showing a fallback, a banner names what is on
screen versus what was asked for — only once the read has settled.
- The header keeps one 32px control height and drops its labels below 680px
instead of squeezing them to two letters and an ellipsis.
Docs and module documentation updated in every locale.
Use the real status-warning tokens (--status-warning-bg did not exist, so
the card rendered without a fill), drop the decorative icons, and fold the
conflict count into the title as a single full-message key per locale.
The operation description now wraps instead of truncating, and both the
conflict and ready-to-continue states share the same action layout.
The tablet ran the phone layout with a half-finished iPad draft on top: two
custom sidebars, a leftover overflow menu, split Files/Changes header buttons,
and phone-width sheets stretched across a 13" screen. This brings it onto the
phone's navigation model and keeps only the differences a large screen earns.
- Sessions are a persistent resizable left sidebar; the overflow menu is gone
and its destinations moved into that sidebar's footer (connected instance,
settings, pending web update) and into the workspace drawer.
- The workspace (Changes / Files / Terminal / Notes / MCP) is the phone's
drawer everywhere: a resizable right sidebar where the screen can host one
(up to 900px) and the full-cover drawer otherwise, with its mounted panes —
an open diff, an edited file, an attached terminal — surviving rotation.
- Header dropdowns are anchored popovers: the recents switcher mirrors the
usage overlay on the left, and its trigger is sized to the title rather than
to the free width.
- App-level pages (settings, instances, update, an opened plan) render as
centered dialogs instead of covering the screen.
- Overlays center on the chat column through published insets, so the model
and directory pickers no longer sit off-centre; the directory picker also
stops overriding the shared width clamp.
- Wide chat layout applies to mobile surfaces, where a tablet chat column is
finally wide enough for the setting to mean anything.
The layout gate is a live size class rather than a device check, so Android
tablets and foldables are covered by the same code:
- `enabled` when the shortest viewport side is at le
sw600dp). The short side is what makes this a size question instead of a
device question — a phone reports ~360-430 whichev
unfolded book foldable ~600+, and folding shut drops back under it. iPads
also answer on identity, since iPadOS hands out od
- `roomyForPanels` when landscape and at least 1000px wide, which is what it
takes to host the sidebar, the panel and a readabl
foldables miss it in BOTH orientations — their long side is barely wider
than a tablet's short one — so they keep the portr
Every consumer re-decides instead of remembering wha
open sidebar closes if the device folds shut under it. iPad behaviour is
unchanged: its landscape widths all clear the panel
ones do not, exactly as the previous orientation check did.
Hardware keyboards are read natively. iOS reports them through GCKeyboard,
published to the web layer at document start and kep
disconnect and foregrounding; the layer stops inferring once that answers. A
single early publish was not enough — the connect no
already-attached keyboard fires before the page exists, and GameController can
populate late — so the state is re-published across
resume. With a keyboard attached the draft screen keeps its starter chips and
the composer never collapses; tablets skip the colla
Runtimes with no native answer fall back to inferring it from the keyboard
bridge, and only ever conclude "hardware" from silen
Also: sidebar rows no longer sit on a differently ti
footer is no longer clipped by an over-tall content box, the resize handles
moved above the panes' own overlays so they can actu
now-unreachable overflow menu, fullscreen terminal/MCP/notes surfaces and their
locale key are deleted.
Device behaviour is unverified — the tablet layout,
keyboard bridge and the foldable size class have not been exercised on
hardware, and the 600/1000 thresholds are derived fr
rather than measured on a foldable.
A diff is ordered by file path, which is almost never the order in which a
change makes sense. This adds a Walkthrough surface that reorders it: the model
groups related hunks into stops, explains what each group changes about
behavior, and orders the stops so each builds on the last. It explains and
orders; judging code stays with the existing Review action.
Reviews uncommitted work (all, staged, unstaged), a branch against its base, or
a pull request. Generation is always user-initiated — nothing runs on a timer,
on a file change, or as a side effect of opening a panel.
Invariants worth preserving:
- Hunk identity is derived on the server and only there. Ids are content
hashes, so an anchor that no longer resolves is proof the code it described
changed, and staleness needs no heuristics. The client matches ids to ids and
never recomputes them; two implementations would have to agree forever.
- The digest is never truncated. A diff that does not fit the model's context
is refused with an actionable reason, because a walkthrough written against
half a diff reads as confident and is wrong.
- Nothing disappears. Lockfiles and other generated output are excluded from
the model's input by name — never by size — and everything no stop covers is
listed at the end, so "have I seen all of it" stays answerable.
- Cost is explicit. Results are content-addressed, so returning the working
tree to an earlier state costs nothing; generation outlives its request, so a
refresh detaches the client rather than discarding paid-for work, and only an
explicit cancel stops it.
Supporting changes to shared modules:
- git: expose the existing getRangeDiff as GET /api/git
listUntrackedPaths and getUntrackedDiffs. The latter resolve the repository
once for a batch instead of per file, taking a panel
~340ms on an 80-file working tree.
- small-model: structured output across four wire forma
and abort signal, and an onOverflow policy so an oversized prompt fails
loudly instead of being silently clipped. A provider
remembered so the prompt-side fallback goes first next time.
- models.dev metadata: surface structured_output as tri
false blocks a model, a missing field does not, because the catalog omits it
for roughly half of all models.
Desktop and tablet only: VS Code serves Git through its
these routes, and the mobile shell does not consume the surface registry.
Docs: packages/docs walkthrough page in English and all eight locales.
Navigation model rebuilt around two full-width drawers and a minimal
header (sessions / title-switcher / usage ring / workspace):
- Left sessions drawer: cross-project tree with live status indicators,
swipe actions on sessions (rename/archive/delete) and on group headers
(project edit / two-step close, worktree delete), reorder-only edit
mode with collapsible project cards and draggable worktrees, app-level
footer (connected instance, settings, pending web update).
- Right workspace drawer: Changes / Files / Terminal / Notes / MCP as
pill tabs (inactive tabs icon-only); panes stay mounted once visited.
The full desktop file editor serves the Files tab; read/skill tool taps
in chat open the file there at the requested line.
- Header session switcher on title tap: 10 cross-project recents with
live busy/attention indicators and project · branch metadata; the
usage ring opens a metadata overlay with an explicit loading state.
- The overflow menu is gone on phones (its destinations moved into the
drawers); iPad keeps it until its dedicated layout pass.
Correctness and continuity:
- /auth/session answers bearer-first, so a stale WebView cookie can no
longer mask a revoked device token; cold launches classify failures
fast and land on an explicit connect screen.
- Authoritative session snapshots raise frozen ordering baselines and
stale live ranks — recents stay truthful after the app slept.
- Cold launches reopen the last active session per instance (persisted
pointer, confirmed against a sessions snapshot; a user-opened draft
clears it), with a logo hold instead of a draft flash.
Also: collapsed pill composer gains the stop control; chat tool rows
share one 36px rhythm; Task subtool rows truncate; larger bottom safe
area so the composer clears big-screen corner radii; Capacitor build
hides About/Update (store updates apply there); widgets link to the
sessions drawer with a list icon; MobileApp split into focused modules;
five mobile-surface detectors unified; translucent borders normalized to
70%; all new strings translated across the 10 locales.
iPad and foldable layouts are intentionally untouched - separate next version PR.
Add a dedicated desktop header menu for the active session while keeping recent-session switching available when the sidebar is closed. Match inline rename behavior with the sidebar and expose rename, copy ID, share, export, archive, and delete actions with localized feedback.
Automatically copy newly created share links, keep share/unshare state synchronized across live and global stores, and normalize stale upstream unshare responses so the UI immediately reflects successful unsharing.
Require Markdown exports to load every available message page before formatting the conversation. Abort incomplete root exports, retain explicit child-session skip warnings, and guard complete-history pagination against failures and cursor cycles.
Guard FilesView autosave until the selected file has finished loading,
refuse binary/PDF/office/archive text saves, and add a persisted global
autoSaveEnabled setting (default true) under Settings → General.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* feat(ui): add context surface registry and rail switcher
* feat(ui): move git and project notes into context surfaces, embed editor file tree
* feat(ui): replace right sidebar with context surfaces, per-surface panel widths
* refactor(ui): retire legacy main-tab overlays and right-sidebar state
* feat(ui): rail polish, right-docked file tree, terminal surface
* feat(ui): move terminal into context surface, per-surface tab closing, editor empty state
* feat(ui): tune default rail order and activity dot
* fix(ui): keep context panel controls anchored during width animations
* feat(ui): lazy-follow context panel resize with window-level drag tracking
* feat(ui): panel dividers, right-dock tree icon, muted outline folder icons
* feat(ui): restructure git view into changes-first surface with standalone PR surface
- Remove commit/update/pr tabs; git view is always changes + commit
- Promote pull request to its own rail surface with shared repo context
- Move update-branch and re-integrate flows into separate dialogs
- Add PR status chip and repo actions menu to the git header row
- Seed new PR-status entries from resolved sibling remotes to avoid
a false "checking status" state when the PR is already known
- History/graph dialog refresh button, fingerprint global identity icon,
muted outline folder icons follow-ups
* feat(ui): progressive-disclosure PR surface with live checks and pinned chat context
- Segment the PR surface into Overview / Checks / Comments pill tabs with
live badges; merge controls move to the status row
- Live checks segment: progress bar, per-run rows with workflow names,
elapsed timers, expandable failures, auto-refresh while pending
- PR comments and failed checks pin as chat-context drafts (like terminal
selections) instead of sending an immediate message; works on new-session
drafts too
- Shared prContext cache client+server, ETag conditional requests in the
octokit wrapper (304s bypass rate limits), extended checks aggregate
(inProgress/queued/startedAt)
- Resolve gh-CLI auth login for merge-permission checks
- Full-width description editor with matched control heights
* fix(ui): single source of truth for PR checks and status readers
- Derive the checks aggregate from the visible run list and sync it into
the PR-status store so bar, badges, header, and git-view chip agree
- Route PR body hydration through the shared context cache
- Git-view PR chip reads the freshest entry across remote keys
* fix(github): freshness stamps prevent stale cache responses from regressing PR state
- pr/status and pulls/context responses carry a server-side fetchedAt that
survives cache serves
- The status store rejects responses older than the held snapshot (only
clearing the loading flag), and the checks sync adopts the context's
stamp so stale status polls cannot flip fresher derived checks
- Regression test for the stale-response guard
* perf(github): repo-level pull-list cache collapses per-branch PR resolution
- One pulls.list per repo per state per 45s answers every branch (10
worktrees = 1 call, not 10 query fans); in-flight fetches coalesce
- A complete repo list makes a no-PR miss authoritative, skipping the
per-owner head queries AND the Search API fallback (the 30/min killer)
- force refresh bypasses the repo list cache; PR create/merge/ready
invalidate it
* perf(github): back off Search API misses per repo+branch
A branch without a PR re-searched on every poll; with >100 closed PRs the
list miss is never authoritative, so the search fallback still ran and
burned the 30/min search quota. Remember misses for 10 minutes; PR
creation clears remembered misses for the repo.
* fix(github): dedupe re-run check runs to the latest per (app, name)
listForRef returns the superseded completed run alongside its re-run;
GitHub's UI shows only the latest per name. Mirror that in both pr/status
and pulls/context so counts and run lists match github.com.
* fix(ui): address review findings on registry test, surface docs, and PR-context keys
- Rail-order test asserts against the registry itself (was stale after the
'pr' surface landed and failed)
- surfaces DOCUMENTATION.md describes actual behavior: has-content surfaces
hide until content exists; only multi-instance/terminal panes are
keep-alive, singleton surfaces remount and restore from stores
- PR-context cache keys are runtime-scoped JSON tuples; invalidation
compares the directory exactly instead of by string prefix (+ test)
* fix(ui): wrap long unbreakable tokens in check-run details
Annotation messages with long SHAs/URLs overflowed the panel; break-words
on annotation title/message/rawDetails and output summary/text, and the
expanded run body clips instead of widening the panel.
* fix(ui): busy state for context-attach buttons and honest attach labels
- 'Attach failed checks' / 'Attach all to chat' show a spinner and disable
while the context request runs (previously nothing happened for seconds)
- Action labels/tooltips reworded from send-to-agent to attach-to-chat
semantics across all locales
* fix(i18n): Ukrainian attach wording uses 'прикріпити' with proper cases
* fix(ui): runtime-scope PR-view remote caches, correct surfaces doc on preview
- Remote/remote-url caches in PullRequestView are keyed by runtime +
directory so a backend switch never serves another runtime's remotes
- surfaces DOCUMENTATION.md: preview is not keep-alive; preview tabs
remount on switch like singleton surfaces
* fix(ui): rail active color, clearer collapse icon, remove dead bottom-terminal dock
Design-review feedback on the context panel:
- Context rail: icons enlarged 16px -> 18px; the active surface is now
highlighted with the primary color only (no background, no scale
animation), replacing the previous scale-up effect that read as a
resize rather than a selected state.
- Files tree: the icon-only 'collapse all folders' toolbar button now
uses collapse-vertical instead of contract-up-down, which was easily
mistaken for a close button. The labelled 'Collapse all' dropdown item
in the session sidebar keeps its icon since text removes the ambiguity.
- Terminal: removed the leftover bottom-dock expand/close buttons that
rendered in the context-panel terminal but controlled a dock that no
longer exists (nothing toggles it anymore), so the expand button
appeared to do nothing and duplicated the panel-header fullscreen
control. Cleaned up the entire inert layer with it: four useUIStore
fields (isBottomTerminalOpen/Expanded, bottomTerminalHeight,
hasManuallyResizedBottomTerminal), five actions, their persistence,
the MainLayout resize listener that only served the dock height, the
dock-driven refit effect in TerminalView, and the
terminalView.bottomDock.* keys across all 10 locale dictionaries.
Validated: ui type-check and lint clean; messages parity test (2 pass)
and useUIStore contextPanel test (13 pass) green; icon sprite
regenerated via icons:generate.
* refactor: use PR visual state for git header icon
Derives the pull request icon color from a single visual state
Covers merged, closed, draft, blocked, and open PR states
Removes conditional class handling from the git header icon
Add a shared OpenChamber control service with two thin adapters — a native
`openchamber` tool injected into managed OpenCode, and new CLI commands — so
users can manage parallel sessions, worktrees, and scheduled tasks
conversationally through agents or from the terminal.
Control plane:
- New openchamber-control service owning a fixed action contract:
projects.list, models.list, session list/create/send/fork/status/messages,
and schedule list/create/run/delete/toggle. Session and worktree deletion
and project registration are deliberately not exposed.
- New openchamber-sessions module owning create/worktree/prompt orchestration,
Goal Mode dispatch, wait semantics (initial idle never counts as completion;
timeout and cancellation are failures), and explicit partial-failure results.
- Scheduled-task logic extracted into a service shared by routes, CLI, and the
agent tool.
Agent tool:
- Managed OpenCode gets a materialized plugin registering one typed tool with
a loopback-only callback, per-child ephemeral bearer (timing-safe, never
persisted or logged), and abort propagation into the service.
- The ~1.5k-token schema applies progressive disclosure: short descriptions,
server-side validation returning actionable usage errors, and intent
guardrails — created sessions/tasks are user-facing work (not age
self-delegation); worktree/goal/agent/variant/wait are omit-by-default;
dispatches produce no completion notification, and later result r
to session.messages, which now returns the authoritative sessionStatus.
- session.create without a user-named model picks from favorites/re
send/fork omit the selection and the service reuses the target session's
last user-message model, agent, and variant before falling back t
- An "Agent control tool" setting (default on, Save + Reload to apply)
disables plugin injection entirely.
CLI:
- New `openchamber session`, `schedule`, `projects`, and `models` commands
with automatic instance targeting, --wait/--timeout/--last-assist
worktree flags, and Goal Mode, preserving interactive, non-TTY, --quiet,
and --json contracts. The control HTTP timeout derives from the w
instead of the 4-second default.
UI:
- New built-in "Schedule a Task" starter (/schedule-task) running a
dialogue that defines a task and offers to create it via the tool after
explicit confirmation; Craft a Goal and Feature Planning gain the
handoff offer, and guided starters reserve the question tool for concrete
option choices. Localized in all 10 locales, migrated into custom
starter lists, hidden on VS Code.
- Sidebar shows CLI/agent-created sessions live via the control eve
- openchamber tool calls render with per-action titles and metadata.
Compare normalized attachment MIME types with the selected model's declared input modalities and show a non-blocking warning for incompatible files.
Recheck newly added attachments, restored drafts, async metadata, and existing files after model changes while avoiding warnings when capability metadata is unavailable. Summarize affected filenames and localize the warning across every supported locale.
Add focused modality compatibility coverage, document the composer behavior, and keep model metadata subscriptions stable to prevent startup render loops.
* preserve attachments in UI and materializer
* add support for non-image attachments
* preserve attachments in UI and materializer
* improve attachment rendering and state synchronization
* simplify attachment filtering and key handling
* prevent rendering tool attachments without URLs
* require `f.url` in `imageAttachments` to match gallery indices
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