The Ctrl/Cmd+digit shortcut for switching context surfaces fired even
while the user was typing in an editable target (input, textarea,
select, or contenteditable element), hijacking the keystroke. Guard the
switchSurfaceDigit branch in useKeyboardShortcuts with the existing
isEditableEventTarget helper, matching how other shortcuts in the same
hook already bail out of editable targets.
Closes#2503
The Recent list's metadata slot only opened for activity duration, a
goal glyph, or a branch marker, so plain rows fell back to hover-only
or touch-only timestamps. Extend the slot guard to also open for
renderContext === 'recent' and render the compact timestamp inline,
mirroring the existing touch-only branch.
Closes#2560
Merge main and resolve conflicts in RemoteInstancesPage.tsx (re-applied
the width bumps by hand at their current locations) and
KeyboardShortcutsSettings.tsx (dropped the PR's hunk entirely: main
replaced that fixed-width input with ShortcutRecordingDialog, which has
no equivalent clipping at 200% interface font size).
Closes#2320
The Small Model override chosen in Settings never reached the managed
OpenCode process config, so OpenCode's own title/summary generation
kept using its fallback chain instead of the user's explicit choice
and sessions stayed untitled.
Merge main into this branch to pick up the includeWeb/includeMemory
flags added to prepareManagedOpenCodeEnv, and re-apply the Small
Model injection on top of that current env shape in
getManagedOpenCodeEnv (server/index.js).
Closes#2497
On macOS trackpads, a light touch on a file/folder row can start a
native HTML5 drag after ~4px of movement, and Chromium then swallows
the resulting click. Track the drag start position and, on dragend,
treat a micro-drag (dropEffect 'none' and under 8px of travel) as the
click the gesture was meant to be.
Reconciled with main's OS file drag-drop upload handlers on the same
rows: kept onDrop/onDragOver/hasExternalFiles for external file
uploads, and wired the new click-recovery logic alongside it on both
the file tree row and the search-results row. Dropped the row's
cursor-grab/active:cursor-grabbing classes, since these rows are not
meant to read as draggable to the user.
Closes#2368
Merge main and reduce the change to the defect that reproduces: the server's
synchronous login-shell probes (env snapshot and command -v for opencode,
node, bun) ran with no timeout, so a slow or interactive rc file held startup
until it returned — on macOS that is what made a brew-installed opencode look
undetected from a Dock launch. Every probe now carries the same 5s bound the
Electron shell probe already uses and falls through on overrun; the known
install locations already include both Homebrew prefixes.
The non-login command -v fast path and the reproduction script are dropped:
a plain sh inherits the same PATH the resolver has already walked.
Closes#1720
Merge main and reshape the follow opt-out to the gestures the timeline was
missing: a middle-button press starts the platform autoscroll pan (the only
scroll gesture on wheel-less mice and tablets with a pointer), Shift+Space
scrolls up from the keyboard, and an upward wheel over a nested scroller that
still has room above stays with that scroller instead of releasing the chat.
The grace re-pin timer and scroll-direction tracking are dropped: returning to
within the end band already re-arms follow, and the mode machine is built
without timers on purpose. Pause/Break never move the viewport and are not
gestures.
Closes#1640
The list's animated maintainScrollAtEnd is single-flight and re-pins only
within a tenth of the viewport, so in a narrow viewport (VS Code sidebar) each
revealed block — several screens tall there — left the reader a second behind
and multiple screens above the live edge. The timeline hook now follows growth
itself: glide when within a viewport of the end, otherwise jump to one screen
above the end and glide the rest. Measured at 420x640: time spent >40px behind
dropped from 45% to 11%, max distance from 1682px to 382px.
Claude-Session: https://claude.ai/code/session_017TK5JAYDfT3Fotc23UEg98
Chat sessions without a project were dropped from the mobile session tree
because no registered project owned their directory. The sheet now partitions
sessions like the desktop sidebar and shows Chats as a collapsible section above
the project tree, with the same rows, swipe actions and paging; search results
label them "Chats" instead of the raw directory name.
Claude-Session: https://claude.ai/code/session_017TK5JAYDfT3Fotc23UEg98
With "Follow new content while streaming" disabled, sending from mid-history
no longer anchors the new message at the top of the viewport; the reader stays
where they were and the scroll-to-bottom pill leads to the new turn. Sending
from the live edge still parks the message as before. The setting's info text
now describes this in every locale.
Claude-Session: https://claude.ai/code/session_017TK5JAYDfT3Fotc23UEg98
Chromium scrolls overflow:hidden ancestors when a textarea caret moves out
of view (PageUp/PageDown in the prompt box, long prompts), shifting the whole
app up and hiding the title bar with no way to scroll back. Snap html/body/#root
back to zero on any root scroll event in the web, desktop, VS Code and mini-chat
apps.
Claude-Session: https://claude.ai/code/session_017TK5JAYDfT3Fotc23UEg98
The VS Code app mounts VSCodeLayout, which exposes sessions, chat, and settings but not the shared GitView or its branch selector. The remote-tracking checkout path added for that selector therefore had no user-reachable caller in the extension.
Restore the existing VS Code checkout implementation and remove the VS Code changelog claim. The web runtime keeps the fix because its Git view exposes the branch selector.
CodeMirror collapses a CRLF pair into one line break, so the document is
shorter than the string it was given. The composer derived the caret from
the JS string length, which put it past the end of the document and made
dispatch throw `RangeError: Selection points outside of document`.
Because the exception fires before the transaction applies, the document
never updates, the un-normalized text stays in React state, and the draft
persists as-is: every later visit to the session restores it and crashes
again, with no way out from the UI.
Derive the caret from the change set instead, in the controlled writeback
and in the imperative insert/replace handles.
fixes#3013
# Conflicts:
# CHANGELOG.md
# packages/vscode/CHANGELOG.md
Since opencode 1.18.x, `POST /global/upgrade` requires a `target` semver in
the body. OpenChamber sent an empty object, so every "Update OpenCode" click
came back 400. The rejection arrives as `{name, data:{message}}`, which has
no `error` field, so the user was left with the bare status text: "Bad
Request".
Resolve the target from the latest release — the same lookup the upgrade
prompt already uses to decide there is anything to offer — and fail with an
explicit code when it cannot be resolved, rather than sending a body opencode
is guaranteed to reject. Read the upstream rejection message so a refused
upgrade explains itself.
The VS Code extension carries its own copy of this flow and had the same two
defects; both are fixed there.
fixes#3121
Canonical answer to 'is this reachable in VS Code': three webview hosts,
VSCodeLayout's three views, a 33-row surface table (mounted / partial /
not mounted with the mount chain or cut-off point), the dead-bridge
handler list, and which fs routes stay live. Reviews, changelog entries,
and parity claims consult this map; whoever mounts or unmounts a surface
updates it in the same change.
"Restart to Update" answered the renderer with null before the install was
attempted, so a rejected install only reached main.log and the button looked
dead. The apply-update path now keeps the IPC call open until the app quits or
autoUpdater reports the failure, rolls the quit/install flags back when the app
stays up, and the update dialog shows the real reason with a translated hint for
a rejected code signature.
Also settle the download promise on downloadUpdate() itself: an already cached
payload emits no 'update-downloaded', which left that promise pending with its
listeners attached on every retry.
Post-merge hardening of the session-to-worktree move (#2998), driven by
review findings on the follow-up pass:
- an ambiguous transport failure (relay abort, timeout) on the
change-carrying move no longer force-deletes the fresh worktree that
may hold the user's only copy of their changes; both intent kinds
surface honest guidance and refresh both directories
- assertSdkSuccess re-tags ambiguous transport errors when wrapping SDK
failures, so ambiguity classification survives the wrapper on every
path, matching the prompt-send precedent
- session liveness checks scan all child stores plus the global status
index, and report unknown (not idle) when no store covers the session
— an evicted background directory can no longer make a busy session
look movable
- incomplete-rollback errors carry the changes-may-be-in-destination
guidance instead of swallowing it
- move-message assembly shared across the three call sites; tests now
exercise the real ambiguity classifier (extracted to
send-failure-classification.ts) instead of a hand-mirrored mock
- i18n fallout from the merge train: Turkish gains the 21 worktree-move
keys, all 12 locales get the hedged ambiguous-failure toast; owning
DOCUMENTATION.md files record the new contracts