Drops the macOS vibrancy toggle, IPC, and related settings copy
Updates dialogs, popovers, tooltips, and dropdowns to use shared glass styles
Adds sticky header fade behavior to model picker and sidebar lists
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).
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
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.
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
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.
Cherry-picked from claude branch 7b3bec48 (fix: dedupe shared worktree ownership and title sessions immediately).
Only the worktree-dedupe half applies to main: partitionWorktreesByRegisteredProject and its callers (SessionSidebar, ElectronMiniChatApp, MobileApp, MobileSessionsSheet) plus tests and sidebar docs.
The session-title half (title sessions immediately) remains on the claude branch: the session-title runtime and its claude-code transcript translator imports do not exist on main and cannot build there until the Claude harness lands.
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.
* perf: optimize session loading and startup
* fix(chat): stabilize history prepend virtualization
* perf: unblock first session open from startup network contention
Opening the first session after app start waited seconds for its message
fetch. Three independent contributors, each measured via CDP network
capture and Chromium net-log against the packaged desktop app:
- The active-session watchdog fired an uncapped per-directory status poll
and child-session discovery burst at startup, and other subsystems
(git checks, global session pages, command/skill discovery) fanned out
alongside it, saturating the browser's ~6 HTTP/1.1 sockets per origin.
Add a shared background-network gate (concurrency 3) and route the
watchdog, poll-shaped git reads (also priority: low), global session
pages, command/skill loads, and the background update check through it.
- The packaged renderer is cross-origin to the loopback backend, so every
API call needs a CORS preflight; a few slow OpenCode-proxied requests
held the whole pool while preflights and interactive traffic queued
behind them. Lift Chromium's per-host connection cap for loopback via
ignore-connections-limit in the Electron shell.
- OpenCode initializes each directory lazily on its first request, so the
first click paid that cost interactively. Warm the last-used directory
and the three most recently opened projects right after OpenCode
readiness, sequentially and best-effort, overlapping UI startup.
Validation: new background-network tests, lifecycle warmup test, focused
store/sync tests, UI type-check and lint, dead-code report, node --check
plus electron type-check/lint, and CDP first-open measurements on the
packaged app (message fetch socket queue 5.4s -> 0.03s).
* fix(ui): keep interactive git reads out of background queue
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
- Drive the sticky-fade identity overlay's visibility synchronously from the scroller's top-scroll attribute (CSS :has), and seed its leading project synchronously, so it appears in the same frame the mask hides the real header instead of a React commit later (removes the one-frame blink)
- Match the overlay's vertical box to the real header rows (py-1 instead of a centered h-7) so the identity does not shift a few pixels on the sticky handoff
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.
Recent now shows active root sessions immediately, even if they are older than 48 hours
Archived sessions and subtasks still stay out of Recent
Added coverage and documentation for the new recency rules
Session lists now promote a conversation when it starts working and again when it settles, instead of reacting to every streaming timestamp update. This keeps ordering responsive without bringing back the sidebar churn removed by the recent performance work.
Apply the same user-visible order across Recent, project and worktree groups, session switchers, mobile navigation, widgets, the command palette, and the desktop tray. Preserve pinned priority, freeze timestamp fallback ordering, and keep child-session activity scoped to siblings under the same parent so it never moves the root conversation.
Seed reconnect snapshots without synthetic jumps, clear ephemeral ranks on deletion and runtime changes, and cover lifecycle transitions, mixed root/child trees, metadata-only updates, and project-group ordering with regression tests.
Improve OpenChamber responsiveness under large session workloads while fixing
cache, synchronization, and persistence correctness across runtimes, projects,
directories, and worktrees.
- prioritize selected and visible sessions during bootstrap and defer
non-critical enrichment work
- reduce redundant message loading, event processing, store publication, and
hidden sidebar work
- prevent stale session and message requests from overwriting newer
authoritative state
- preserve existing data when authoritative fetches fail instead of treating
failures as successful empty responses
- scope session materialization, messages, drafts, queues, todos, pins,
permissions, folders, tabs, Git state, and pull request data by runtime and
directory identity
- harden runtime switching, reconnect, cleanup, mutation reconciliation, and
persisted-state ordering
- preserve live subagent Task linkage when metadata arrives after an older
message request or while streaming parts are suspended
- coalesce overlapping tail refreshes without losing newer refresh demand
- improve cold-session loading by moving deferrable work out of the critical
bootstrap path
- isolate URL authentication, mobile credentials, native secrets, and other
runtime-owned state across endpoint changes
- bound long-lived caches and remove avoidable allocations from event and
rendering hot paths
- limit virtualization to archive collections where it improves rendering
without disrupting active sidebar layout
- stabilize session folders, pin ordering, expanded state, and persisted
sidebar behavior
- open skill files through the same secure editor and outside-workspace grant
flow used by file navigation, including worktree sessions
- expand regression coverage for stale completions, runtime collisions,
reconnect behavior, persistence races, authoritative empty results, and
subagent refresh ordering
- document the updated synchronization, cache ownership, performance, and
runtime-isolation invariants
Add a root-session action that creates a generated worktree from the session directory's current branch, transfers uncommitted changes, and moves the parent session plus its descendants through OpenCode's control-plane API.
Reuse existing project/worktree topology and quick-create behavior, keep the UI non-blocking, reconcile live and global session state across directories, and roll back partial moves and failed worktree creation safely.
Split worktree bootstrap readiness into directory-created, git-ready, and setup-ready phases across web and VS Code. Session moves wait for Git readiness while existing setup-aware flows continue waiting for full setup completion, and worktree removal is serialized with active bootstrap tasks.
Expose the move only for idle root sessions, show localized progress and explanatory tooltips in the sidebar, and keep pending/ready worktree metadata synchronized with authoritative session attachments to avoid stale setup indicators.
Add coverage for control-plane payloads, session-state migration, bootstrap phase ordering and compatibility, removal races, progress metadata, and fast-ready attachment races.
* 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>
New drafts now receive the active project ID from sidebar project actions.
Project-group draft creation also forwards the selected project.
Aligns draft opening behavior across project and session selection flows.
Delete the share opinion dialog and its sidebar trigger.
Remove the related toast and footer action wiring.
Clean up unused i18n entries in all supported languages.
Replace repeated project-by-session directory matching across sidebar hooks with a shared ownership index that resolves each unique directory once and exposes direct project and folder-scope buckets.
Gate destructive folder reconciliation on authoritative session data and topology readiness, preserve last-known worktrees after discovery failures, and retain nested-project, VS Code, active/archive dedupe, and Windows drive-root semantics.
Narrow cross-directory subscriptions to session and status slices so streaming deltas no longer trigger global aggregation. Reuse a cached session ID index for permission lineage checks instead of rebuilding it on every session switch.
On the reported 15-project, 67-worktree, 14,561-session shape, ownership indexing averages 3.81 ms versus roughly 450 ms for the cache-only hotfix.
Validation: 28 targeted tests, UI type-check, UI lint, and dead-code analysis.
* fix(ui,server): normalize Windows drive letter casing for consistent path resolution
Provider settings were failing to persist for specific projects on Windows
because path normalization was inconsistent across the codebase. Some
normalizePath functions uppercased the Windows drive letter (c:\ -> C:\)
and others did not, causing:
- directoryScoped cache misses (different keys for the same directory)
- broken model selection in the affected project
- lost conversation history (sessions could not match their project)
- false cache hits in resolveConfigDirectory on undefined inputs
This change extracts a single shared normalizePath utility and uses it
from the 5 client sites that were missing the drive letter normalization.
The server-side normalizePathForPersistence is updated to uppercase the
drive letter both before and after safeRealpathSync, so the persisted
path is consistent even when realpath returns a symlink/junction with
a lowercase drive letter on some Windows environments.
Fixes#2109
* test(ui,server): add coverage for Windows path normalization
Address review feedback on #2154:
- Add Windows-platform test for normalizePathForPersistence covering
drive letter uppercase on input and after realpath resolution
- Add dedicated test suite for the shared normalizePath utility
- Defensive fix: normalizePath now returns null for paths that consist
only of slashes (\\, ///), matching the documented contract
Refs #2154
* fix(server): scope drive casing normalization to Windows
---------
Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
Removes recent-project grouping from the sidebar project list
Keeps project ordering based on manual and default sort rules only
Reduces memoized session-derived state in the sidebar
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.
* perf(worktree): skip unchanged store updates and content-aware persist
- Add content-aware equality check before setState in all three discovery
loops (SessionSidebar, ElectronMiniChatApp, MobileApp). Compares
Map size and per-entry length + element references — avoids triggering
16+ subscriber re-renders when discovery finds the same worktrees.
- Add content-hash guard to persistWorktreeMap subscription with try-catch.
Avoids redundant localStorage writes when the Map reference changed but
the content is identical. Serialization errors are caught and skipped.
Contributes to #1990
* perf(worktree): extract shared worktreeMapsEqual, fix comparison, avoid double serialization
- Extract worktreeMapsEqual() into worktreeManager.ts as a shared utility
comparing worktree maps by path (not reference identity). This replaces
the inline reference-comparison logic in all three discovery loops
(SessionSidebar, ElectronMiniChatApp, MobileApp) that was ineffective
because readStableProjectWorktrees creates new object instances on
each call after cache expiry, making item !== value[i] always true.
- Pass pre-serialized JSON to persistWorktreeMap to avoid double
JSON.stringify on every persist. The subscriber already computes the
serialized string for the content-hash check; pass it through instead
of re-serializing inside persistWorktreeMap.
- Deduplicate 3 copies of the same comparison logic into the shared util.
* refactor(worktree): make worktreeMapsEqual generic over path-bearing type
The helper's equality contract is element-wise path comparison,
not anything specific to WorktreeMetadata. Generifying on
`T extends { path: string }` documents the contract at the type
level and keeps it reusable for any future map-of-arrays shape
that has a path field. Call sites stay compatible since
WorktreeMetadata has a required `path: string`.
No runtime change.
* refactor(worktree-store): clarify persist hash name and signature
Drop the optional preSerialized parameter from persistWorktreeMap —
its only caller (the subscriber) already builds the serialized
string for the content-compare, so the dual-path body is dead code.
persistWorktreeMap now takes the serialized string directly.
Rename lastPersistedWorktreeHash → lastPersistedWorktreeSerialized
(the variable holds the full JSON string, not a hash) and drop the
try/catch around JSON.stringify: it cannot realistically throw on
Map.entries() of WorktreeMetadata (no circular refs, no BigInt, no
custom toJSON). The try/catch around setItem stays — it can throw
on quota errors.
No behavior change in the success path.
* docs(worktree): trim repeated call-site comments
Replace the 5-line explanation block (copy-pasted in all three
discovery loops) with a one-liner that points at the worktreeMapsEqual
JSDoc. The '16+ subscribers' framing is also dropped — the helper
itself is general-purpose and the precise number was fuzzy.
* fix(worktree): compare branch in worktreeMapsEqual to avoid stale sidebar label
The helper compared entries by path only. An external git checkout
between discoveries changes branch (and the derived label /
headState) while path stays the same, so the helper returned true
and the store update was skipped — leaving a stale branch label in
the sidebar until the next worktree create/remove or project switch,
since there is no periodic worktree-list refresh.
Compare branch in the inner loop alongside path. Tighten the generic
constraint to T extends { path: string; branch: string } so the
contract is documented at the type level.
worktreeStatus is intentionally NOT compared: status transitions go
through setStoredWorktreeStatus, which writes a fresh Map reference
that the persist subscriber picks up directly. Adding worktreeStatus
to the contract would also force the sidebar to detect status changes
that the persist path already handles, and would couple this helper
to a field whose semantics differ from the discovery path.
Fixes the staleness concern raised by openchamber-bot in PR #1992.
* test(worktree): cover worktreeMapsEqual edge cases
Documents the helper's equality contract and guards against
regressions in the path+branch comparison. Eight cases:
- two empty maps
- identical entries (path and branch match in order)
- same path, different branch — the F1 regression case
- different paths at the same index
- per-project array length mismatch
- project-key count mismatch
- positional reorder (helper is order-sensitive)
- non-first-entry branch difference (subset detection)
All 10 tests in the file pass (2 existing + 8 new).
* ci: retrigger checks
* test(worktree): add benchmark for worktreeMapsEqual and persist path
Documents the actual cost of the PR #1992 optimizations on representative
sizes (1-1000 worktrees per project, 1-50 projects), so future contributors
can reproduce the numbers and detect regressions in the equality helper or
the persist subscriber.
Run with: `bun run packages/ui/src/lib/worktrees/worktreeManager.bench.ts`
Measured on V8 (one example run):
- worktreeMapsEqual early-exit (50×20 with first project differing):
412 ns/op vs 33,034 ns/op full sweep — ~80x speedup when any project
actually changed.
- F1 path+branch overhead vs path-only (10×50): +2.3 µs (+15.8%) on a
full sweep; on the early-exit path the F1 cost is irrelevant.
- Stringify dedup in persistWorktreeMap subscriber: 67% saved (552 µs
per persist on 10×50). This is the main absolute win of the PR.
- Content-compare guard: 19-29 ns/op, free relative to the stringify it
gates.
Bench file is standalone (import.meta.main guard) — does not run as part
of `bun test`, does not import React, does not touch localStorage.
---------
Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.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>