Terminal creation no longer waits for the Ghostty viewport to report its
size: it starts the PTY immediately with a container/font-derived
provisional size (falling back to 80x24), then resizes once the real
viewport dimensions are known, with a dedupe guard while sizing settles.
Starting the shell earlier means it can emit device/theme queries before
a browser terminal is attached to answer them, so the server now answers
primary device attribute queries itself (Fish blocks ~10s on this at
startup) and bun-pty buffers output emitted before a data subscriber
attaches. Also fixes a few WebSocket transport reconnect races surfaced
by session creation now overlapping renderer setup.
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.
* feat: add Windows ARM64 support with x64-baseline CLI workaround
Windows ARM64 native opencode.exe fails with a Bun FFI/TinyCC dlopen
error (anomalyco/opencode#19130). As a temporary workaround:
- Bundle x64-baseline OpenCode CLI on ARM64 instead of native ARM64
(prepare-opencode-cli.mjs, env-runtime.js)
- Disable OpenCode self-upgrade on ARM64 in server, VS Code, and UI
(upgrade-capability.js, opencode-upgrade-runtime.ts, useUIStore.ts,
OpenCodeCliSettings.tsx, search.ts, SettingsView.tsx, platform.ts)
- Add ARM64 Windows cross-compile builds to release and smoke workflows
(release.yml, release-desktop-smoke.yml)
- Refactor Windows latest.yml to use combine-electron-manifests pattern
matching macOS, since two arches now produce per-arch manifests
The CI ARM64 build itself is permanent; only the x64-baseline CLI
bundling and upgrade disablement are temporary and should be reverted
when the upstream issue is resolved.
* fix(desktop): select Windows updater by architecture
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
Seed omitted autoSaveEnabled from the hydrated client preference (including
legacy localStorage) instead of resetting everyone to enabled. Restore SVG
non-editable flags, treat clean draft saves as success, and throw again from
disposed content-cache owners while keeping runtime-switch cache invalidation.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Opening a terminal rebuilt the WASM terminal twice and rewrote the
persisted session snapshot on every streamed output chunk, so the cost
grew with each open terminal and could crash under load.
- Move PTY scrollback to a standalone buffers map keyed by directory and
tab id; output no longer touches sessions, so the tab strip, the
project-action monitor and the persist projection stay referentially
stable while chunks stream.
- Memoize partialize and add a dedup storage adapter that skips writes
when the persisted projection is unchanged.
- Reuse the idle terminal WebSocket across tab switches (15s grace)
instead of re-authenticating and replaying the snapshot on every attach.
- Key the viewport by directory + tab only so createSession no longer
tears down and rebuilds the Ghostty terminal.
- Reset the terminal in place on replay discontinuities instead of
bumping the renderer generation.
- Scan the chunk array from the end (O(1) per write) instead of findIndex.
Adds regression tests for the buffer map, socket reuse and viewport key,
and documents the store invariants.
Scope context-file open validation to the active project directory so
binary opens resolve against the correct workspace root.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
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>
Replace overflow-hidden with overflow-y-scroll on 5 content wrapper divs
so Settings sub-panels (Shortcuts, Plugins, etc.) have an always-visible
vertical scrollbar on both desktop and mobile.
Fixes#2475
Wraps the sync button in a focusable trigger when rebase sync is blocked
Preserves the existing sync button behavior and labels
Improves tooltip access for keyboard users
* 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
Give the active-pill tab strips the lifted, physical feel of a spring
capsule: the pill now travels with a slight overshoot instead of jumping,
leans toward a neighbouring tab on press, and sits on a raised surface with
a soft ambient shadow rather than a hard border.
Transitions are opt-in per switch so first measurement and container
resizes still snap into place, and reduced-motion disables them.
Also drop the icons from the git commit/update/pr switcher, along with the
now-unused iconOnlyActiveTab prop.
* 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>
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
On mobile web/PWA, useTouchTerminalInput is true, which gated the
autoFocus prop behind !useTouchTerminalInput, causing autoFocus to be
false and the hidden input overlay to never receive focus. The user
had to manually tap the terminal to trigger focus.
Remove the !useTouchTerminalInput condition so autoFocus works on
mobile the same way it does on desktop: when the terminal becomes
visible, focusTerminalInput() -> focusHiddenInput() is called,
positioning and focusing the hidden textarea/input portal that
captures virtual keyboard events.
Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
* Group settings navigation menu
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Revert "Group settings navigation menu"
This reverts commit 5983a4e82074b8dab1084af1cadd803ba28ea65d.
* Standardize settings layout feedback
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Fix settings save status timer typing
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Retain settings save status
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Report color mode save state
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Match Appearance settings to two-column layout
Rebuild Appearance into Color mode & Theme, Localization, and Density & type sections with responsive two-column grids, consistent section headers, page description, and green save status.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Migrate settings pages to SettingsPageLayout and SettingsSection
Replace ScrollableOverlay/max-w-3xl shells with the shared settings
layout primitives across entity and static settings pages, normalize
section headers, and add settings.page.behavior.description locales.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Unify remaining settings pages on shared section chrome
Wire Appearance, Projects, and Remote Instances through SettingsSection/SettingsPageLayout so every settings surface shares the same header, divider, and page shell treatment.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Align settings UI with OpenChamber design system kit
Apply kit geometry and control specs: 840px content width, 32/48 padding, fixed 260/280 sidebars, radius/spacing tokens, settings select height, stepper dimensions, and shared field/link typography.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Migrate OpenChamberVisualSettings to shared settings primitives
Replace ad-hoc radio/checkbox/chip/field layouts with SettingsSection
shared chrome for Appearance, Density, Navigation, Chat/behavior, and
Privacy while preserving handlers and data-settings-item anchors.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Migrate settings pages to shared field/checkbox/radio primitives
Unify Defaults, Notifications, Behavior, Git, Session Retention, Passkeys,
OpenCode CLI, Commands, About, Keyboard Shortcuts, and Desktop Network on
SettingsFieldRow / SettingsCheckboxRow / SettingsRadioGroup / SettingsChipGroup
for consistent grid, spacing, and DRY layout. Also remove the GitPage double
SettingsSection wrap around GitHubSettings.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Unify remaining settings pages onto shared field primitives
Migrate Agents, Snippets, Skills, Usage, MCP identity rows and selects to
SettingsFieldRow/CheckboxRow/ChipGroup and SETTINGS_SELECT_*; align page
titles; light-touch Voice/Tunnel/Providers/Plugins without rewriting
complex OAuth, permissions, or tunnel flows.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Polish settings section dividers and transport helper text
Slightly stronger section borders for clearer group separation, and keep
message-stream transport description under the chip control.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Fix settings grid alignment, control heights, and Chat section titles
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Tighten settings grid: flat Chat 2x2 and full-width stacked selects
Message options use a flat two-column grid so row headers share a baseline.
Stacked selects fill their column; field-row selects keep a fixed sm:w-56 width.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Align mixed settings controls to shared FieldRow/CheckboxRow grid
Separate FieldRows from CheckboxRows with SettingsInset, move enum
radios into ControlGroups, and convert misplaced StackedFields to
full-width FieldRows so left edges no longer clash.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Translate Behavior response-style preset labels for es and pl
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Group settings nav into categories and improve icons/order
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Fix settings layout: fewer hrs, shared control widths, chat transport section
Remove SettingsInset top borders, align App install/Density controls to full cluster width, give Message Stream Transport its own Chat section, and fold Sessions Small Model into the first section to cut extra dividers.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Align Voice and MCP settings labels with shared heading classes
Swap form field labels to SETTINGS_FIELD_LABEL_CLASS and use
SettingsGroupTitle for MCP control-group headings (manual auth fallback,
request headers).
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Differentiate settings heading levels by context
Page titles are larger and quieter than section titles; group and field
labels use dedicated shared classes so hierarchy is consistent across
settings surfaces without ad-hoc typography mixes.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Use shared settings title classes in SettingsView home
Wire home and unavailable headings through the shared L1/L2 class
constants so they stay aligned with SettingsPageLayout.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Keep settings number steppers from stretching +/- buttons
Density & type NumberInputs no longer flex-grow across the row, and
NumberInput locks minus/plus to fixed width so the plus side cannot
inflate when the control is placed in a full-width cluster.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Place spacing and input-bar offset on their own row
Density & type now lays out as font families, then font sizes, then
Spacing Density / Input Bar Offset on the row below.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Regroup settings nav and improve Voice layout
Drop Advanced/Usage/Git nav categories: Voice and About under
Interface, Usage under OpenCode, Git under Workspace. Voice provider
chips and STT model cards use shared settings primitives with roomier
spacing and a two-column model grid.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
* Space out chat feature groups in visual settings
* Polish settings: save-state wiring, container-query layouts, unified dropdown triggers
- Wire shared save indicator into Behavior page saves and git identity CRUD
- Convert settings layout primitives and page rows from viewport to container
queries so narrow panes stack instead of clipping
- Unify custom dropdown triggers (model/agent/provider pickers) and remaining
page selects on the settings control size
- Bump global radius scale by 1px; align variant input with select size
- Migrate stray raw controls (MCP OAuth checkbox, worktree remove button,
git identity dialog rows, skills dialog labels) onto shared primitives
- Make settings nav items span full sidebar width; trim nav to 240px
* Add General settings page, regroup nav, cap control widths, promote chat feature headers
* Mobile settings nav: plain background and touch-sized rows
* Hide secondary settings descriptions behind clickable info hints
* Move quota credentials to Usage, navigation settings to General, rename External Tunnel
* Quiet settings save indicator: silent success, delayed spinner, visible errors
* Rewrite settings-ui-patterns skill around shared primitives and refactored conventions
* Remove settings starter page; open last visited page, defaulting to General
* Settings polish: spacing, control sizes, trigger widths, readable model names, device dates
* Centralize dropdown trigger chrome, settings nav polish, mobile-only input bar offset
* Fix global line-height regression, auto-hide first-section divider, shortcut row spacing
* Global line-height 1.45, align scheduled tasks header button with select
* Hide editor toolbar and About in VS Code, animate chat render preview outside desktop dialog
* Rebuild agent tool permissions on source-of-truth model
Edit the agent's own permission map verbatim (inherit vs explicit actions, pattern rules only for pattern-capable keys), save permission-only, drop the server-side non-wildcard re-merge that resurrected deleted rules, and surface session-granted rules as read-only.
* Agents model parameters polish: row spacing, variant dropdown, unified widths, dash for unset numbers
---------
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
Replace the legacy terminal flow with a shared authenticated WebSocket
runtime used across web, desktop, relay, and mobile surfaces.
- introduce the v3 terminal protocol with scoped attachments, snapshots,
ordered output, bounded replay history, reconnects, and explicit lifecycle
- harden PTY creation, restart, resize, close, force-kill, idle cleanup,
shell selection, login mode, environment sanitization, and appearance sync
- add runtime-aware terminal APIs with relay authentication and Electron parity
- add a fullscreen mobile terminal workspace with touch scrolling,
long-press selection, safe-area controls, quick keys, and Ctrl/Alt input
- add terminal selection attachments, preview detection, project actions,
shell settings, and localized UI
- harden Ghostty rendering, resize recovery, Unicode handling, block
characters, line height, and stale-row behavior
- remove the obsolete terminal SSE path and update reverse-proxy guidance
- expand terminal runtime, transport, input, selection, and store coverage
- avoid duplicate web builds when preparing mobile assets in root CI builds
The distillation moved from PlanView into setSessionGoal — the shared
choke point for every goal source (composer, fork dialog, plans) — and
into the scheduled-tasks goal creation server-side. Any objective over
5000 chars is distilled into completion criteria by the small model; on a
transient failure a head+tail excerpt keeps the intent (top) and the
acceptance criteria (bottom) with a trim marker, plus a toast. The
working agent always received the full prompt in chat — only the auditor
is bound by the limit.
Plan-goal objectives are capped at 5000 chars for the auditor while the
working agent reads the full plan from its file. Plans over the limit are
now distilled by the small model into completion criteria (end goals +
per-phase verification, no implementation steps), prefixed with a header
pointing back at the plan file so every continuation re-anchors on the
live source of truth. If distillation fails (transient small-model
hiccup), a head+tail excerpt keeps the plan's intent (top) and acceptance
criteria (bottom) with a trim marker between — sacrificing the
implementation middle the agent reads from the file anyway — and a toast
tells the user the objective is degraded.
Arm the target button in the composer and the next prompt becomes a goal:
the server keeps the session working toward it (idle tick -> small-model
audit -> continuation) until the objective is verifiably complete, blocked,
or out of budget — even with the UI closed.
Server (packages/web/server/lib/session-goal):
- event-driven loop on the global SSE hub; goal state lives in
session.metadata.openchamber.goal (merge-safe patches, stale-write guard
by goal id), so it survives restarts and syncs to every client for free
- the small-model audit (objective + last assistant turn only, language
pinned to the objective) is the sole termination authority; blocked needs
3 consecutive verdicts, audit outages tolerate one unaudited continuation
then stop the goal as resumable-blocked
- hard stops: optional token budget, auto-continuation cap (Resume grants a
fresh allowance), turn errors; user abort pauses the goal instead of
blocking it, and resuming over an aborted tail nudges immediately
- token accounting as a snapshot of the latest turn (input + cache.read +
output), goal-relative via a creation baseline and segmented across
compactions; a compaction summary skips the audit and continues
- continuations reuse the session's own provider/model/agent/variant
UI:
- three-mode target button (arm / disarm / manage dialog), informational
goal strip with inline pause/resume and an Evaluating indicator, sidebar
state glyph, objective length counter (2000-char server clamp),
read-only completed goals
- goal entry points: composer (sessions and drafts), start-new-session-
from-answer dialog, plan implement dialog (plan content becomes the
objective), scheduled tasks (Run as goal + budget)
- Settings -> Chat -> Goal: feature toggle + default token budget with
three-layer parity (web server, client persistence, VS Code bridge);
VS Code renders goal state but hides the entry points (the loop runs in
the web server only)
Notifications: per-turn "ready" notifications are suppressed while a goal
is active; settling sends one final notification (desktop, web-push, APNs
generic titles with the session name as body) honoring the completion
toggle. Error/question/permission notifications are untouched.
Docs: user guide (session-goals) in all 9 locales + sidebar entry,
scheduled-tasks cross-reference, server module DOCUMENTATION.md.
* feat(settings): add editor font size setting for chat input and code editor
Adds an 'Editor font size' control in Settings > Appearance that sets an
absolute px font size for the chat input textarea and the in-app
CodeMirror editor. Mirrors the existing terminalFontSize lifecycle.
- New store field editorFontSize (default 13, clamp 9-32, step 1) in
useUIStore with narrow selectors at each consumer.
- Persistence wired through appearanceAutoSave, desktop + runtime API
types, and persistence.ts read/normalize.
- Settings UI row (NumberInput) with reset to 13, VisibleSetting union
entry, OpenChamberPage registration, and search index entry
appearance.editor-font-size.
- Applied as a post-zoom absolute override on the chat input textarea
and on the CodeMirror theme's content rule, leaving gutter/line-number
chrome at its existing hardcoded sizes (matches terminal scope).
- All 10 locales translated (en, es, fr, ja, ko, pl, pt-BR, uk, zh-CN,
zh-TW); no English placeholders in non-English dictionaries.
Refs #1325
* fix(codemirror): use unitless lineHeight so it scales with editor font size
The & rule in the CodeMirror theme set lineHeight to 1.5rem (~24px),
which does not scale when editorFontSize is increased (e.g., 28-32px).
This causes overlapping lines at larger font sizes.
Change to unitless 1.5, which scales proportionally with whatever fontSize
resolves to (dynamic prop or --text-code fallback). Matches browser best
practice for proportional leading.
Review comment: https://github.com/openchamber/openchamber/pull/2065
---------
Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
Fixes GitHub PR/issue context endpoints returning 404 when working
from a fork because they resolved the repo from origin remote only.
- Pass sourceRepo: status?.repo ?? null to all prContext() calls in
PullRequestSection.tsx (5 call sites)
- Pass sourceRepo: args.pr.sourceRepo ?? null / args.issue.sourceRepo
?? null to NewWorktreeDialog.tsx (3 call sites: prContext, issueGet,
issueComments)
- Add status?.repo to dependency arrays to prevent stale closures
The prStatus endpoint already resolves the correct repo through the
fork network; this change wires it through to the downstream API calls.
Closes#2090
Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
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.
Reworks how devices connect to an OpenChamber server, end to end.
Pairing v2:
- One-time pairing links/QR codes (openchamber://connect?v=2) carrying a set of transport candidates (LAN/tunnel/relay) and a single-use secret redeemed server-side; no tokens embedded in links
- Add-a-device dialog written for first-time users: intent-based transport choice (Anywhere / Home network only / This computer only) with plain-language descriptions, transparent fallback checkboxes, server-authoritative LAN detection, high-res QR dialog
- Private relay folded into pairing as a transport candidate with a demand-driven lifecycle (enables when a relay device is paired, disables when none remain)
Multi-transport devices:
- A saved device holds all its transports and one token; mobile re-probes on connect, resume, and network change and hot-switches LAN<->relay seamlessly (no re-pairing, no remount, session preserved)
- Desktop can import relay pairing links, switch to relay hosts through the E2EE tunnel, and restore a relay default host after relaunch
Device management:
- Device list (web + desktop) shows live per-device connectivity with the active transport (Connected - Local network / Relay) and platform badges (iOS/Android/macOS/Windows/Linux)
- One physical device = one record: stable per-install dedupe keys across pairing and password re-login; typed pairing label names the device, paired devices name the connection by the issuing server hostname
- Trusted desktop-local client manages all devices (list, revoke, clear revoked); relay host reaps dead client sockets after 3 missed keepalives
Android:
- LAN transport unblocked (cleartext + mixed content, mirroring iOS ATS exceptions); resume re-probe retries through network flux and silently auto-reconnects from a disconnected state
Adds a Last turn scope to DiffView that renders OpenCode snapshot diffs from the latest user message summary without re-fetching git contents. The view hides Review in that mode and carries the selected diff scope through main and context-panel navigation.
Connects latest-turn changed-file chips in chat to the snapshot diff view on desktop and mobile, while keeping older turn chips static/read-only to avoid misleading affordances and extra subscriptions. Updates localized labels and empty states plus changelog.
Validation: bun run type-check (packages/ui); bun run lint (packages/ui).
Uses hunk contents to find the first modified line instead of the hunk start
Handles added, removed, and binary-only patches more accurately
Adds tests for patch parsing edge cases
Complete rebuild of voice input on a server-authoritative streaming
architecture, replacing the legacy Web Speech / whole-blob / WASM engines
and the dead voice-agent layer (~4k lines removed).
Speech-to-text (dictation):
- Client streams 16 kHz mono PCM16 chunks over /api/dictation/ws with
seq/ack ordering; buffered audio is retained and replayed on reconnect
- Server transcribes and streams live partial transcripts back;
segments auto-commit every ~15s with silence suppression and adaptive
finalization timeouts
- Local provider (default, zero config): sherpa-onnx models in a forked
worker process — auto-download with progress, staged extraction with
verification, corrupt-model auto-recovery, idle shutdown after 5 min
- Model catalog with settings picker (accuracy/speed ratings, sizes,
download/delete): Parakeet TDT v2 (English) and v3 (25 European
languages, auto-detected), Whisper base and tiny (multilingual, light)
- OpenAI-compatible provider for any Whisper endpoint
- Composer overlay with live transcript, volume meter, timer, and
cancel / insert / insert-and-send actions; failed transcriptions keep
their audio for retry or accepting the partial text as-is
- Configurable keyboard shortcut (default mod+alt+v) toggles dictation;
Enter confirms and Escape cancels while recording
- Overlay is pixel-aligned with the composer (measured footer height,
matching paddings/typography/gaps) — no layout shift when toggling
Text-to-speech:
- Local Kokoro provider (English, 11 voices) synthesized in the same
worker via /api/dictation/tts/speak, managed by the shared model
pipeline; sentence-pipelined playback keeps time-to-first-audio at
~1 sentence regardless of message length, and stop cancels in-flight
synthesis
- Sanitizer keeps inline-code content (strips backticks only), reads
interword slashes aloud, and removes only absolute file paths
Settings:
- Voice page unified: a single read-aloud toggle owns all playback
options (the confusing "Enable Voice Mode" is gone); a new "Enable
voice input" toggle (default on, persisted to settings.json) hides
the composer mic entirely when disabled
Mobile and transport:
- iOS/Android microphone permissions added (dictation was previously
impossible on mobile)
- Fixed Android WebSocket upgrades: the Capacitor WebView origin
(https://localhost) was missing from the packaged-client allowlist,
403-ing every WS connection — root cause of the old mobile SSE lock,
which is now removed for all transports
Security and conventions:
- All HTTP routes sit behind the global /api auth gate; the WS upgrade
explicitly validates the UI session and origin, with oc_url_token
narrowly allowlisted and covered by tests; the dictation socket mints
a fresh URL token before connecting
- Routes register before the generic OpenCode proxy; the client goes
through runtimeFetch/getRuntimeUrlResolver, and runtime switches
reset the dictation socket
- VS Code deliberately reports dictation as unavailable (no server
process in that runtime)
CI: workflow Node bumped 20 -> 22 to match the repo engines and fix
better-sqlite3 installs broken by node-gyp@latest on Node 20.
New dependency: sherpa-onnx-node (prebuilt N-API; macOS/Linux x64+arm64,
Windows x64 — Windows-on-ARM falls back to the OpenAI-compatible provider)
- Migrate sidebar session groups, git changes panel, virtualized code
blocks, and JSON tree viewer from virtua to @tanstack/react-virtual;
virtua remains only inside the Pierre diff viewer integration
- Sidebar: preserve scroll position when virtualization enables
mid-session (enable only once the ancestor scroll element is resolved,
seed initial offset from its live scrollTop, render plain rows for the
single pre-paint frame); disable native scroll anchoring on the
sessions scroller; keep row spacing identical between plain and
virtualized modes; absolute row positioning so variable-height rows
cannot drift past the container
- Chat: expand tool/thinking blocks downward by only adjusting scroll
for rows growing above the viewport; raise the desktop history-load
lead to 1.5 viewports so prepends land above the visible area
- Git changes: compute the prefetch window from the first visible row,
skipping overscan rows above the viewport
- Sidebar rows: make the whole highlighted row area clickable, guarded
against double-firing from interactive children
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.
Removes missing expanded folders from persisted Files state
Prevents repeated 404 noise for stale file tree paths
Avoids startup SDK race when restoring sessions
Makes Commit & Sync fetch and pull before push when needed
Prevents stale git status from showing already up to date
Adds regression coverage for git status cache invalidation
In the changes/diff view, an unwrapped diff's intrinsic width leaked up the
flex chain: the flex-1 column holding the scroll area lacked min-width:0, so its
min-content (the widest line) stretched it — and every nested w-full element,
including the .pierre-diff-wrapper (overflow-x-auto) and the file header — grew
to the content width. That pushed the header's action controls past the narrow
viewport and left overflow-x-auto with nothing to scroll.
Add min-w-0 to the diff layout's flex items so the chain stays at viewport
width: long lines now scroll horizontally inside the diff, and the file header
controls stay visible.
The oc_url_token has a ~50s effective lifetime and was only fetched once at
preview mount, so HTML/image/PDF previews cycled to 'authentication required'
when it expired and nothing forced a re-render with a fresh token.
Add a consumer-gated proactive refresh in runtime-auth: while at least one
url-token consumer is active, a single scheduler mints a fresh token just
before the skew window and swaps it in atomically (the previous token stays
valid until the new one lands — no empty-token window for other consumers).
acquire/release manage the consumer count; subscribe fires only on a real
token replacement.
FilesView consumes this via a shared useAssetAuthRefresh hook (replacing three
near-duplicate effects) and remounts the iframe/img only when the token
actually changes, not on a blind interval.
* perf(right-sidebar): gate live effects, memoize lookups, always-mount tabs
Performance fixes for the right sidebar (git/files/context tabs).
== Correctness / leak fixes (P0)
* RightSidebar: drop dead useEffect that re-nulled refs the resize
handler already nulled; collapse the redundant width/minWidth/maxWidth
triple into width + the existing --oc-right-sidebar-width variable.
* useUIStore: clamp setRightSidebarWidth to [MIN, MAX]; simplify
setRightSidebarOpen (22 lines -> 12).
* RightSidebarTabs: useRightSidebarGitSync now takes the right tab and
main tab and only polls when the right git tab is the visible consumer
and the browser is online + visible. Replaces a global poll that
fired for the lifetime of the open sidebar.
* GitView: commit-files fetch refactored to cancelled + Promise.all
(was a per-hash loop that could setState after unmount); getRemoteUrl
and refreshRemotes gated on cancelled/mountedRef; new module-scoped
mountedRef guards setIsSettingIdentity from firing after unmount.
* GitView + useGitmojiList: extract gitmoji fetch/cache into a hook
with module-level inflight promise + subscribers Set; stale-while-
revalidate from localStorage; ensureLoaded() for call-site-initiated
hydration; cancelled flag on setIsLoading to avoid the React
setState-after-unmount race.
* ProjectNotesTodoPanel: 400 ms notes debounce now cancels on blur
(was double-saving); persistProjectData chained per project through
a module-level Map<projectId, Promise> so a fast todo toggle racing
the debounced save no longer hits the server in parallel; resize
auto-adjust guards against same-value pings.
== Render fanout (P1)
* RightSidebarTabs: all three tab content components are now always
mounted with the hidden attribute. State and cache survive tab
switches. When activeMainTab === 'git' (or 'context') the matching
right tab is filtered out of the tab strip and a redirect effect
snaps any persisted-but-now-hidden right tab to 'files'. onSelect is
now a type-guarded handler instead of `as RightTab`.
* GitView: 13 separate useGitStore action selectors collapsed into one
useShallow block (one re-evaluation per store change instead of 13).
* GitView: new isGitViewActive flag (true when this instance is the
visible consumer) gates the 7 live effects — load identities, fetch
remote URL, refresh remotes, ensureAll, sessionEvents.onGitRefreshHint,
worktree bootstrap poll, default-identity auto-apply. Hidden
GitView instances no longer run these.
* GitView: gitViewSnapshots module-level Map is now backed by an
LRU wrapper (cap 20) so per-directory draft snapshots cannot leak
across hundreds of project switches. Removed the dead `unique.set`
dedup in changeEntries — GitStatus.files is already unique by path.
* SidebarFilesTree: statusByPath Map<path, FileStatus> and
badgeByDir Map<dirPath, { modified, added }> are precomputed once
per gitStatus change. Tree render is O(1) per node instead of O(N)
per node via the previous per-row find/scan. badgeByDir walks each
file's path segments and increments counters for every ancestor
dir, so total cost is O(N + total_dirs_in_files) per gitStatus
change.
* SidebarFilesTree: FileRow wrapped in React.memo with a custom
comparator. Context-menu open state moved INTO FileRow as local
state — opening a menu in one row no longer re-renders siblings.
* SidebarFilesTree: loadDirectory accepts an isCancelled predicate;
the batch-load effect for expandedPaths passes a stable predicate
so per-dir fetches stop touching state once the effect tears down.
* SidebarFilesTree: module-level fileTreeCacheByRoot Map (LRU,
cap 8 roots) hydrates childrenByDir / loadErrorsByDir /
loadedDirsRef on mount or root change. Mirror effects write state
back to the cache. Survives close-and-reopen of the right sidebar;
populated entries are dropped on unmount only when they had no
data.
== Result
Net diff: 6 files modified, 1 new (useGitmojiList.ts), 682 insertions,
283 deletions. Existing test suite baseline preserved (537 pass / 58
fail / 1 error) — no new regressions. The 58 pre-existing failures are
in unrelated chat/streaming tests and were verified via git stash on
the same branch.
Architecture assumptions, verified by manual review:
- P1.1's redirect effect snaps rightSidebarTab to 'files' whenever
activeMainTab === 'git', so the right and main GitView instances
are mutually exclusive — isGitViewActive cannot be true for both.
- The 7 gated effects plus the useRightSidebar GitSync poll cover all
cases where git state should advance: visible consumer fetches; the
poll keeps the store warm when only the right git tab is visible.
- The aborted loadDirectory predicate is sufficient because
inFlightDirsRef and loadedDirsRef dedup at the call site before
any network IO is initiated.
* fix(sidebar): always clean up inFlightDirsRef regardless of cancellation
* refactor(sidebar): deduplicate RIGHT_SIDEBAR_MIN/MAX_WIDTH constants, export from useUIStore
* docs: split right sidebar perf plan into standalone file, clean up merged master status from chat plan
* fix(git): gate GitView effects by instance visibility
---------
Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
Reuse the file-editor Shiki extension for the PlanView and SkillsPage editors,
gated to non-markdown files. Markdown sources keep the lezer highlighter (its
markdown-aware styling is better for editing and there's no Shiki view to match).
- PlanView: code files opened through it get Shiki colors; plan .md stays lezer.
- SkillsPage: code supporting files get Shiki colors; SKILL.md stays lezer.
Bring the CodeMirror file editor up to the same rich highlighting as the Shiki
file view, so toggling edit <-> view is visually consistent. lezer collapses far
more tokens than TextMate (import/from/const are all "keyword"), so a theme
remap can't reach parity — instead, project real Shiki tokens onto decorations.
- Worker: add highlightTokens — tokenize with an arbitrary registered TextMate
theme and return per-line styled runs with offsets. The theme object ships to
the worker once per name; later calls send only the name.
- New shikiHighlight CodeMirror extension: a StateField of mark decorations
built from worker tokens. Re-tokenizes on a short idle (off the keystroke
path) and maps decorations through edits so colors persist while typing.
- flexokiTheme: add { syntaxColors: false } to keep only the editor UI theme,
so the lezer highlighter doesn't compete with the Shiki decorations.
- FilesView: enable Shiki highlighting (same language resolver as the file view
→ identical language) and drop lezer token colors when it's active. lezer
language stays on for indentation/folding/brackets.
The Prism syntaxTheme prop is no longer read after code highlighting moved to
the Shiki worker. Remove the now-dead prop threading and its source.
- Drop syntaxTheme from interfaces, destructures, prop passes, and React.memo
comparators across ChatMessage, MessageBody, ProgressiveGroup, ToolPart,
TurnActivity, ToolOutputDialog, and ChatInput.
- Drop the unused _syntaxTheme param from renderWebSearchOutput.
- Remove the dead generateSyntaxTheme usages (ChatMessage memo, PlanView
unassigned memo) and delete the now-unimported syntaxThemeGenerator module.
* fix: pass effective workspace directory in Files API requests
The web Files API used useDirectoryStore.currentDirectory as the
workspace root, but the FilesView's effective directory comes from
useEffectiveDirectory() which can differ (e.g. worktree sessions).
When they diverged the server rejected file reads with 'Path is
outside of active workspace'.
Add directory override to FileReadOptions so callers can pass the
effective directory per-call. The FilesView now passes its root
(from useEffectiveDirectory) through readFile, statFile, image/PDF
URLs, and the desktop image fallback. The server receives the
correct workspace root via x-opencode-directory header or directory
query parameter.
Fixes#1456
* fix: cover files workspace directory regressions
* fix: sync directory store on draft session and forward cache options
The content cache wrapper in RuntimeAPIProvider was dropping the
options parameter (including the per-call directory override) when
making internal statFile and readFreshFile calls during cache
validation and misses. This caused the underlying web API to fall
back to getDirectory() which reads useDirectoryStore.currentDirectory.
Additionally, openNewSessionDraft, setNewSessionDraftTarget, and
overrideNewSessionDraftTarget updated the draft's directory without
ever syncing useDirectoryStore. Since the web API's getDirectory()
reads from that store, it returned the stale previous-project
directory during draft sessions, causing 'Path is outside of active
workspace' errors when opening files.
Forward options through all internal calls in the content cache
wrapper, and sync useDirectoryStore via setDirectory() whenever the
draft session directory changes.
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>