Follow-ups promised on merge, plus review findings on the batch itself:
- chat: task-tool output now respects the 512KiB render cap; quick-open
icon is visible at rest on coarse pointers and reachable by keyboard
(row keydown no longer swallows inner-button Enter/Space); composer
inline-code decoration drops the metric-shifting padding; a btw fork
send carries only the boundary instruction, never the promotion notice
- sync: cascade revert/unrevert aborts busy descendants, busy state is
read from every child store at the moment of use; rule 9 documents
redo clearing all descendant revert markers
- electron: renderer recovery keeps memory-eviction (a valid
render-process-gone reason) and both windows share one
attachRendererRecovery helper
- vscode: process registry is a thin re-export of the web module
(provider-env-aliases precedent) with ordered register/unregister
writes and an awaited close
- server/cli: managed-process registry takes injectable deps (fixes the
unreaped-orphans ReferenceError), corrupt settings errors name the
file, getWorktrees test restores console.warn
- tests: module-mock harnesses removed (AgentsSidebar, SettingsView
mobile focus — behaviors stay live but uncovered, accepted trade),
QuestionMarkdown asserts rendered DOM
- i18n: German gains the debug-panel request keys, Japanese/German drop
removed worktree keys, Ukrainian unit spacing fixed
- changelog: Copilot AI Credits entries (main + VS Code)
Saved Project knowledge plans opened as an empty editor whenever the
viewer could not resolve the owning project from the current directory:
managed chats (openchamber:chats is not a registered project), worktrees
outside the repo path, and plan tabs restored after a reload. Titles
still rendered because the list reads the manifest through the correct
owner.
- Thread the owner explicitly (savedProjectPlan = { projectRef, planId })
from the panel, mobile surfaces, and persisted context tabs; PlanView
no longer guesses the project.
- An unrecognized directory resolves to no owner instead of borrowing
the active project's knowledge.
- Serialize plan writes per document (planSaveQueue) so close/switch
within the autosave debounce no longer drops the last edits, saves
cannot land out of order, and a recovered save clears the error banner.
- Send saved-plan contents inline in Improve/Implement prompts (they
have no file path); disable those actions for managed-chat plans,
which have no project directory to create a session in.
- Drop persisted plan tabs that carry an id without an owner rather than
reopening them against a guessed project.
Review catch: `promoteBtwSession` removes the btw metadata, but the
boundary instruction is persisted on every message the session sent while
it was a side conversation, and there is no API to delete a message part
after the fact. A promoted session therefore keeps reading "no sub-agents,
do not touch the workspace" out of its own history, in a session that is
no longer a side conversation.
Promotion cannot delete those lines, so it answers them instead:
`withoutBtwSessionMarker` now leaves `openchamber.btwPromoted`, and the
composer sends `BTW_PROMOTION_NOTICE` with every message in a session
carrying it — stating that the session is now the main thread and the
usual tool, sub-agent and workspace permissions are in force.
It rides with every send for the same reason the boundary does: the
instructions it revokes are re-read on every turn, so a one-shot notice
would lose its position relative to them as the conversation grows.
`btwPromoted` is additive and optional. A session that never went through
`/btw` never has it, and one promoted before this change simply keeps the
old behavior.
Two related fixes to what a btw session inherits and shows.
**Fork point.** `/btw` is typically typed *while* the main thread is
working — that is the moment a side question comes up. Forking at HEAD
then clones a turn that is still streaming, so the fork inherits a
truncated assistant message and the user instruction that provoked it as
the newest, most salient thing in its context. Fork at the parent's last
completed assistant turn instead, read from the sync store with no extra
round-trip. A parent with no completed turn yet keeps the previous
fork-at-HEAD behavior.
**Boundary.** `filterBtwTailMessages` shows every inherited message when
the boundary is `null`, and the boundary is `null` whenever the
newest-cloned read comes back empty. That is correct for a fork of an
empty parent, but it also turns an empty read into "nothing was
inherited" for a fork that demonstrably did inherit history — the panel
then opens with the parent's whole transcript in it. Having picked a
fork point proves the parent had turns, so fall back to that id instead
of `null`. The fork's own messages are created later and still sort
after it, so its tail stays complete either way.
`/btw` forks the session, so the model receives the parent's whole
conversation — including whatever plan was in flight when the user typed
the command. Nothing tells it that this history is context rather than
its own task, so the fork frequently carries on with the parent's work
instead of answering the side question, which is the opposite of what
`/btw` is for.
Send a boundary instruction as a synthetic part with every message in a
btw session: with the first question in `startBtwSession`, and with each
later send while the panel is expanded and the composer is talking to the
fork.
The wording is deliberately position-independent — it names the history
inherited from the parent thread rather than "everything before this
boundary". The instruction rides along with each send instead of being
pinned once at fork time, so a positional phrasing would be re-anchored
every turn and would end up telling the model to disregard the btw
session's own earlier turns.
The part is synthetic, so it is filtered out of the rendered transcript
whenever the message also carries user text — which is always the case
here. No visual change.
Removes the transient aborted banner from the composer status area
Simplifies status row rendering to focus on working state and pending changes
Cleans up unused abort-status localization strings
Command Code's official API has no usage endpoints; the old usage source
was the unofficial studio API reached through a now-archived plugin, so
the tile could only ever fail for officially configured users. Removed
across server, shared UI, and the VS Code extension; the provider logo
fallback stays — it serves the model picker, not usage.
Add to input leaves the desktop menu (mod+L owns it; mobile keeps the
button) and the New session action is gone from both variants along with
its handler and dead locale keys.
Every window shares one server settings document, and every PUT returns the
merged whole, so one window's activeProjectId write was adopted by the
other on its next unrelated settings save — its sidebar then auto-selected
a session in that project and wrote the pointer back, converging both
windows onto one session. settings-synced now carries an adoptWorkspace
flag: only bootstrap-grade syncs (startup, runtime switch) may adopt the
shared workspace pointers; reconcile responses keep the window's own active
project while it exists. Notification clicks and session deep links also
stopped broadcasting the session switch to every window.
Every response already funnels through runtimeFetch, so a classifier there
spots 401s, confirms them against /auth/session (a proxied provider 401
must not read as a logout), and flips a small auth-session store. The web
and hosted surfaces show a frosted banner under the header whose Log in
button hands off to the session gate's existing unlock flow; sends are
paused while expired, the session-load error screen explains the auth case
and retries itself after login, and returning to a long-idle window
revalidates once via visibility/focus. Native mobile feeds the same signal
into its connection re-probe instead of showing the banner; VS Code is
exempt.
A trailing configure button on the rail — outside the sortable list and the
digit shortcuts — opens a dialog that toggles each surface. The choice is
stored as the hidden set so newly added surfaces appear for everyone, and
the rail and the mod+alt+digit switcher share the same visibility filter, so
badges and shortcuts always agree. Hidden surfaces keep their data and stay
reachable from the command palette.
mod+alt+arrows step through this window's session-open history (or between
neighbouring tabs when session tabs are on), mod+k r renames the current
session inline, and mod+k a toggles permission auto-accept. Pending
permission cards respond to alt+enter / alt+shift+enter / alt+backspace with
the keys printed on the buttons. The commit message box commits on
mod+enter, alt+arrows step the diff review between changed files, and the
command palette gains search-only commands for rare actions so the initial
list stays short.
Single chords stay for everyday actions; open/go actions move to two-step
mod+k sequences; held mod+digit switches header session tabs and held
mod+alt+digit switches context panel surfaces. Rare actions leave the
shortcut schema for the command palette, every remaining action ships with
a default binding, and stored overrides from the old layout reset once.
Key matching now follows the physical key on non-Latin layouts and for
Option-modified digits on macOS, including in the recording dialog.
The schema/config/bindings/registry/dispatcher module, useKeybind hooks,
recording dialog, reworked shortcuts settings page, help dialog, and the
localized action labels — re-based onto current main rather than merged
(the branch predates 440+ commits including the session-tabs shortcuts).
Review fixes applied on top of the original:
- close_session_tab (alt+w) joins the schema with labels in every locale;
it shipped on main after the PR's base and would otherwise silently die.
- switch_context_surface's special-case in conflict resolution is now a
declared prefixStyle config property instead of a magic id string.
- Duplicate handler registration warns in dev builds.
- The risky-browser-shortcut warning inspects every chord and covers
mod+q/d/h/j/o/u plus mod+shift+w/q.
- The dispatcher remembers which target armed a two-chord prefix so the
window-level completion handler can distinguish a deliberate sequence
from typing in an editable field (guard lands with the dispatch hook).
- Schema tests: unique normalized default bindings enforced, and the
flat-file-era override format proven to keep resolving.
When the caller passes its own AbortSignal and AbortSignal.any is
unavailable, the timeout was silently dropped — disabling the fix on
exactly the bootstrap reads it targets, since those carry a cancellation
signal. Compose the two signals manually through an AbortController in
that case; listeners detach when the request settles.
- Markdown DOM cache: key includes a content-length fingerprint so an
edited or reverted part re-materializing under the same id cannot
restore stale DOM, memoization uses scalar identities instead of the
part object (store reducers recreate part objects on unrelated updates,
which re-ran the async render pipeline for identical content), and a
probe with a mismatched locale/directory no longer destroys the entry
it failed to claim.
- Sidebar bootstrap: the layout-level sync owner only knows known
directories, so expanded projects bootstrapped serialized at background
priority. The visible collection now publishes a second, expansion-
aware demand owner, restoring concurrent hydration for expanded
projects and worktree groups.
- Settings: local changes still sitting in the debounce buffer are not
yet tracked as mutations, so a settings GET racing the debounce window
briefly reverted them; reconciled results now reapply the pending
buffer.
Main replaced the chat timeline scroll engine while this branch was in
flight, which obsoletes two of its subareas and reshapes a third:
- Chat timeline: main's LegendList-based MessageList/ChatContainer win;
the activation-overscan staircase targeted the removed tanstack path
(LegendList provides adaptive rendering natively) and is dropped along
with its test.
- Scroll shadows: main's hook-based masks stay (the virtualized list owns
its scroll element — there is no wrapper to hand the styling to); the
viewport-wrapper ScrollShadow rewrite, its index.css replacement, its
test, and the call-site viewportClassName adaptations are reverted to
main. The chat OverlayScrollbar keeps this branch's disableHorizontal.
- OverlayScrollbar: the direct-DOM rewrite lands, but binding now follows
the live container node instead of binding once per ref object — the
chat scroller remounts on every session switch, and a bind-once
contract left the scrollbar attached to a dead element.
- Markdown renderer: the detached-DOM cache and warm-block fast path
merge with main's block-commit reveal (enter cascade), streaming code
highlighting, and gutter reservation; the per-block reconcile keeps
both the decoration-refresh path and the reveal cascade.
Skip mutation history when no settings operation is in flight and merge updates that share the same operation visibility window. This keeps stale-response reconciliation unchanged while preventing history from growing with repeated debounced updates.
Add a repeated-update regression covering an older pending load and latest-value reconciliation.
Tests: bun test src/lib/persistence.test.ts
Keep settings mutation revisions and in-flight operations behind a private tracker so stale GET and PUT responses cannot overwrite newer local settings.
Remove redundant runtime identity and numeric operation IDs. Use operation object identity while preserving runtime reset and mutation retention behavior.
Tests: bun test src/lib/persistence.test.ts
Add a small external-link icon next to the tool display name in the
collapsed tool card header (Write/Edit/MultiEdit/ApplyPatch), so the
target file can be opened in the side panel (web/desktop) or editor
(VS Code) without expanding the card.
On web/desktop (no runtime.editor), the icon falls back to
useUIStore.openContextFile{AtLine} + mobileActions.openFiles(), matching
the existing openEntryFile pattern. The existing handleMainClick only
opens the file when runtime.editor is available, so this icon is the
first way to open a file from the tool header in the browser.
Path resolution reuses getPrimaryToolPath + toAbsoluteFilePath; the diff
tools also resolve the first changed line and primary diff via the
existing getFirstChangedLineFromMetadata / getPrimaryDiffFromMetadata
helpers. The icon stops click propagation so the card-toggle-on-click
behavior is preserved.
Adds the chat.toolPart.openFile i18n key across all 11 locales.
New Streaming section on the Chat settings page with a checkbox that
controls whether the viewport follows new content while a response
streams. Default stays on. With it off, the anchored user message still
parks at the top on send, but no glide or end-follow correction runs and
the list's maintain-scroll-at-end stays disabled; the scroll-to-bottom
pill and session open keep scrolling explicitly.
Persisted through desktop settings like the other chat toggles (auto-save
diff, authoritative apply, sanitize), registered in settings search, and
localized in every locale.
The 'Always show editor toolbar' preference is gone — the docked
toolbar under the file tabs is now the only mode, and the floating
hover toolbar branch in the files editor (with its open-state and
outside-click machinery) is deleted. The stored preference is dropped
by a store migration and removed from desktop settings persistence,
settings search and every locale.
Navigation section order now reads: file editor keymap, auto-save,
terminal shell + login shell, Terminal Quick Keys, and the Session
tabs group last.