Shiki's bundled JS/TS/JSX/TSX grammars carry a `template-call` rule whose
triple-nested lookahead sends the Oniguruma WASM engine into exponential
backtracking on ordinary backtick templates. The WASM heap grows until the
renderer runs out of memory and the window goes black.
Strip `template-call` from those four grammars as they are loaded in the
Shiki worker. Plain backticks and simple tagged templates still highlight;
only the rare `ident<TypeArgs>` tagged-template form loses its specialized
type-argument coloring.
Add a 5s per-request budget on the worker client as a safety net for any
other pathological pattern. Matching is synchronous inside the worker, so
the only way to reclaim its heap is to terminate it from the main thread.
A timed-out request resolves `null` like any other failure, so the caller
keeps plain text and nothing is written to the result cache.
Closes#2587
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
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)