Roll back aggressive chat reuse paths that froze message parts, tool state, and completed assistant bodies when sessions were inactive or backgrounded.
OpenCode v1.3.13 may omit cost and capabilities from provider model
config responses. The deriveModelMetadata function accessed these
fields without optional chaining, causing a TypeError that crashes
the chat interface.
* fix: implement loading timeout, SSE reconnect, and message retry
- Loading timeout: 30s timeout with retry/cancel buttons to prevent infinite loading
- SSE reconnect: Auto-reconnect up to 3 times with exponential backoff (1s, 2s, 4s)
- Message retry: Ensure critical messages reach webview with 5s timeout and 3 retries
This fix prevents the chat from getting stuck in a permanent loading state
and improves reliability of SSE connections and webview communication.
Fixes#851
* fix: harden vscode bridge retry flow
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
- Detect update package manager from install ownership across npm, pnpm, yarn, and bun
- Fix web self-update restart command so the server comes back cleanly after install
- Add clearer update logs with package manager choice, commands, and restart context
- Fix OpenChamber proxying to the correct OpenCode host for remote VPS setups
- Show a clear empty-state error when OpenCode is not reachable
- Harden session event routing for early or mismatched directory events
- Background polling pauses when tab is not visible
- Timer-based animations replaced with efficient frame-based updates
- Reduced CPU usage when OpenChamber is in background
- Unify send and session resumes around the latest chat tail
- Keep smooth follow active during assistant message growth
Remove staged chat rendering from the main scroll path
- Force notification summaries to avoid markdown formatting
- Normalize generated notification text before system display
- Cover plain-text notification formatting with tests
- Adjust package metadata and lockfiles across the workspace
- Refine chat sidebar and auto-scroll behavior in the UI
- Update PWA install handling alongside runtime package changes
- Keep older assistant messages in the active turn stable while new content streams
- Prevent existing tool rows from rerendering when new tool activity is appended
- Tighten message and tool memo comparisons to isolate render work to changed rows
- Keep assistant auto-scroll smooth during active responses without periodic snaps
- Restore live `/compact` rendering and streamed compaction summaries without refresh
- Preserve global event handling while routing session and message updates efficiently
- Show `/compact` command and streamed compaction summary live without refresh
- Handle versioned sync event types so message and part updates are processed
- Route session/message events via indexed lookup to avoid hot-path store scans
Replace live assistant-part status subscriptions with coarse session phase, retry, permission, and abort state so the composing row no longer rerenders on every text delta.
- Keep historical chat messages stable during streaming
- Isolate active stream state to the current message path
- Reduce chat container churn from sync message updates
Stop historical chat view from waking on live tail deltas by suspending detached part updates, narrowing scroll manager dependencies, and hardening sync-context HMR behavior during dev.
Split static history from live tail, stabilize chat data identities, and reduce scroll-shell churn so old viewport content stops waking on stream deltas. Also defer watcher startup and health polling so idle runtime work better matches actual usage.
- Cut broad render fanout across the app by replacing shared-store whole-object subscriptions with leaf selectors, memoizing hot chrome boundaries, and isolating disabled global providers from live session/message state. This keeps header controls, composer toolbars, side panels, and other non-hot UI surfaces from repainting on every assistant update or keystroke.
- Rework sidebar session ordering so recent, project groups, and worktree groups derive from one ordering source while avoiding streaming-time thrash. The sidebar now uses a stabilized session snapshot, preserves structural identity for unchanged rows, reads live row status/details per session, and applies a one-shot sort bump on idle->busy instead of continuously resorting during activity.
- Fix chat/input scroll instability by separating viewport-resize handling from message-growth handling, disabling conflicting native scroll anchoring, and stopping textarea autosize from collapsing on every growth keystroke. This removes the multiline typing jiggle during streaming and reduces unnecessary composer rerenders.
- Also gate voice context wiring behind voice-mode enablement and codify the learned render/scroll/order anti-patterns in AGENTS.md so future changes avoid the same classes of regressions.
- Add startup feature flag for plan mode from OPENCODE_EXPERIMENTAL* env vars.
- Stop recurring app health polling and keep a one-time startup check.
- Skip plan-mode synthetic parsing and plan tab/file work when flag is off.
- Prevent desktop chat input from inheriting mobile bottom safe-area gap
- Apply chat input safe-area classes only on mobile
- Scope standalone PWA safe-area and blur overlay CSS to mobile runtime
- Prevent header flicker to Untitled during session refresh
- Resolve header session data from global sessions first
- Pass session directory hints on more session-switch paths
* fix: resync session state after SSE reconnect to prevent stuck subagent UI
When a subagent completes while the page is in the background (common on
mobile PWA and desktop webview), the final SSE events are lost. The UI
then stays stuck on 'Waiting for subagent activity...' because:
- part.state.status never transitions to 'completed'
- session_status is never updated to 'idle'
- activeLatched remains true indefinitely
Fix:
- Add onReconnect callback to event pipeline, fired after SSE reconnect
- Add pageshow listener for bfcache restores (mobile PWA back-forward)
- On reconnect, re-fetch session list for directories with active sessions
- Pass explicit directory to useSessionActivity in ToolPart for subagents
to ensure the correct child store is queried
Closes#810
* fix(chat): resolve pending subagent task binding before metadata arrives
* fix: restore subagent activity and tool visibility after reconnect
- Resyncs session status and child session data after SSE reconnect
- Ensures child task tool messages are read from the correct directory
- Prevents stale assistant fallback from keeping sessions stuck as active
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
On Windows, SSE events from OpenCode arrive with native backslash
separators and system-cased drive letters (e.g. D:\Dev\...), while the
UI child store keys use forward slashes with lowercase drive letters
from VS Code's workspace folder (e.g. d:/Dev/...). This mismatch
caused the child store Map lookup to silently miss on every directory
event, preventing session data, messages, and streaming responses from
reaching the React UI.
Changes:
- sync-context.tsx: Normalize incoming SSE event directory paths by
converting backslashes to forward slashes and uppercasing Windows
drive letters before the child store lookup.
- useDirectoryStore.ts: Add drive letter uppercasing to
normalizeDirectoryPath, aligning it with normalizeCandidatePath in
client.ts and normalizeWorkspacePath in main.tsx.
Both normalizations are no-ops on macOS/Linux where paths already use
forward slashes and have no drive letters.
Closes#816
* feat(terminal): add resumable websocket transport
Unify terminal input and stream traffic on `/api/terminal/ws` with a v2 control-frame protocol and advertised transport capabilities.
Buffer recent PTY output on the server so rebinding clients can replay missed chunks after reconnects or startup races, while keeping SSE as a fallback stream path.
Update the web terminal client and store to negotiate the new transport, track tab lifecycle, and avoid reopening exited sessions when restoring tabs.
* fix(terminal): retry rehydrated websocket reconnects
* fix(terminal): keep reconnect retries silent
* fix(terminal): wire ws stream output and replay in runtime
* fix(web): enable ws proxy for /api in dev
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
- Add error icon and colored background/border for error messages
- Add break-all and max-w-full to prevent long error text overflow
- CSS: word-break and overflow-wrap for inline code in error messages
* fix(worktree): reset IntegrateCommitsSection state when switching worktrees
Three fixes for the re-integrate commits panel getting stuck:
1. Add `key={worktreeMetadata.path}` to IntegrateCommitsSection so React
fully remounts it when switching to a different worktree, clearing any
stale `ui` state (conflict, loading, ready) from the previous session.
2. Add `cancelled` flag to the conflict-restore effect so that an async
callback started for session A cannot overwrite session B's state after
the user switches sessions. Without this guard the stale callback could
restore the old session's conflict state on top of the new session's
computed-ready state.
3. Fix off-by-one in continueIntegrate: `moved` was returning
`remaining.length` (N-1, after shifting currentCommit out) instead of
`state.remainingCommits.length` (N), undercounting the commit that was
moved by `cherry-pick --continue`.
* fix(worktree): add git-based fallback detection when store metadata is missing
Root cause: the existing worktreeMetadata resolution relies entirely on
cached store state (worktreeMap + availableWorktrees). When the store
lookup fails—due to hydrateSessionWorktreeMetadata deleting entries on
API failure, availableWorktrees being stale, or worktrees created
externally via CLI—the "Re-integrate commits" section permanently shows
"Available in worktree mode." with no way to recover.
Fix: add useDetectedWorktreeMetadata hook that performs a lightweight
git probe (`git rev-parse --absolute-git-dir --abbrev-ref HEAD`) when
the store-based lookup returns undefined. If the current directory is a
secondary git worktree, a minimal WorktreeMetadata is synthesised with
the correct projectDirectory and branch, allowing IntegrateCommitsSection
and other worktree features to function regardless of store state.
The store-based lookup remains the primary fast path; the git probe only
runs as a fallback and caches its result per directory.
* fix(worktree): fix detection command and pass current branch from git status
Two bugs in the fallback worktree detection hook:
1. `git rev-parse --absolute-git-dir --abbrev-ref HEAD` combines two
independent rev-parse options whose combined output is unreliable –
the two-line assumption (`lines.length < 2`) caused silent null
returns, meaning the fallback never actually set worktreeMetadata.
Now uses only `git rev-parse --absolute-git-dir` (single-line,
deterministic output) for worktree detection.
2. The hook was called before `useGitStatus`, so no branch was
available. Move the call to after `const status = useGitStatus(...)`
and pass `status?.current` as `currentBranch`, eliminating the need
for a second git command and keeping the branch in sync with the
already-polled git status.
Also removes `detected` from the useEffect deps array – it was an
unnecessary dep that triggered a re-run on every detected state change.
* fix(worktree): use worktree toplevel path and reset stale metadata immediately
Two bugs in useDetectedWorktreeMetadata:
1. path was set from currentDirectory (the active sub-folder) instead of the
worktree root. git rev-parse --show-toplevel now provides the actual
worktree toplevel, so operations like `git worktree remove` receive a valid
root path regardless of which sub-directory is open.
2. When currentDirectory changed with no storeMetadata, the hook kept
returning the prior detected value until the async git probe finished.
Calling setDetected(undefined) before launching the async task eliminates
the stale-metadata window.
- Add JsonTreeViewer component with collapse/expand for nested objects and arrays
- Add rainbow colors by depth level using CSS color-mix() with syntax theme tokens
- Add virtualization support (@tanstack/react-virtual) for large JSON files (>200 nodes)
- Add JsonTreeView wrapper with Expand All / Collapse All toolbar
- Integrate into FilesView: tree/text toggle button, JSON file detection
- Integrate into ToolPart: auto-detect JSON tool outputs, render as tree
- Integrate into ToolOutputDialog: JSON tree view in expanded dialog
- Add jsonTreeUtils.ts: parse, flatten, path utilities
No new dependencies - uses existing @tanstack/react-virtual.
Colors derived from existing syntax.* theme tokens - works across all themes.
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
Tie macOS window transparency to the vibrancy setting so disabling vibrancy actually removes transparent backgrounds. Clarify settings copy that full transparency changes take effect for new windows or after restart.
* feat(chat): add arrow key navigation for thinking mode in model selector
Adds keyboard arrow key navigation for thinking mode selection within the model picker, enabling a fully keyboard-driven workflow for model and variant selection.
Changes:
- Model Selector: Added ←/→ arrow key handling to cycle thinking variants while picker is open
- Introduced transient state to track per-model variant selections during picker lifecycle
- Updated metadata display logic to show thinking mode only after adjustment with arrow keys
- Added contextual footer hints that appear only when highlighted model supports thinking variants
- Modified Enter key behavior to apply both selected model and pending thinking variant together
- Help Dialog: Added in-picker navigation shortcuts (↑↓ navigate, ←→ adjust thinking)
- Documentation: Updated CHANGELOG.md files with feature entry
Behavior:
- Navigate models with ↑↓, adjust thinking with ←→, press Enter to apply both
- Footer hints update dynamically based on highlighted model capabilities
- Thinking display persists only for models adjusted with arrow keys
- No-op for models without variants (clean, no feedback)
Testing:
- Type-check, lint, and build all pass
- Manual testing verified on dev server
- Add isHtmlFile() helper to detect .html/.htm files
- Add htmlViewMode state for preview/edit toggle
- Add localStorage persistence for HTML view mode preference
- Show PreviewToggleButton for HTML files
- Render HTML in sandboxed iframe with srcdoc
- Inject base tag for relative CSS/JS/image paths to work
- Sandbox permissions: allow-scripts, same-origin, forms
- Reset view mode when switching files