* feat: implement LaTeX math rendering via KaTeX
- Add katex, remark-math, and rehype-katex dependencies
- Integrate KaTeX CSS with light/dark theme color overrides
- Wire remark-math and rehype-katex plugins into MarkdownRenderer
- Support inline ($...$) and display ($$...$$) math delimiters
- Add plan and task documentation
* fix(katex): display inline error messages for invalid LaTeX
- Configure rehypeKatex with throwOnError: false to render errors inline
- Use destructive color var for error text
- Update feature documentation with verification results
* chore: remove latex PR artifacts and hardcoded color fallback
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* feat: add session-worktree contract types and canonicalizeWorktreeState API
- Add SessionWorktreeAttachment type and worktree metadata fields (worktreeRoot,
worktreeStatus, headState, worktreeSource) to session/worktree types
- Add GitAPI.validateWorktreeDirectory() and canonicalizeWorktreeState() methods
with full HTTP delegation chain (gitApiHttp → routes.js → service.js)
- Add canonicalizeWorktreeState() implementation that resolves worktreeRoot,
headState (branch/detached/unborn), attentionReason (merge/rebase/etc), and
worktreeStatus (ready/missing/invalid/not-a-repo) for a given directory
- Add validateWorktreeDirectory() to check whether a cwd is inside a worktreeRoot
- Add session-worktree-contract.ts: pure functions for resolving session worktree
state, formatting badges, and building repair actions
- Add session-worktree-store.ts: authoritative Zustand store for session-to-worktree
attachments, replacing session-ui-store as the source of truth for worktree binding
- Add unit tests for contract functions and store operations
* feat: canonicalize worktree metadata producers
- worktreeManager.listProjectWorktrees: derive headState (branch/detached/unborn)
from worktree list entry instead of relying on external state, and populate
all Phase 1 canonical fields (worktreeRoot, worktreeStatus, worktreeSource)
for each discovered worktree entry
- worktreeManager.createWorktree: include all Phase 1 canonical fields
(worktreeRoot, worktreeStatus, headState, worktreeSource) in returned metadata
- useDetectedWorktreeRoot: populate fallback canonical fields so that
sessions without store-based metadata still have worktreeRoot/worktreeStatus/
headState/worktreeSource when resolved through the fallback path
* feat: route sessions through authoritative worktree attachments
- session-ui-store: import session-worktree-store as the authoritative source
for session↔worktree attachment state
- setWorktreeMetadata: mirror all writes to session-worktree-store so that
session-worktree-store.attachments is always the authoritative record;
local worktreeMetadata map is kept for backward-compatible reads
- Add session-ui-store.test.js with unit tests covering: valid cwd routing,
degraded fallback, created-for-session attachments, legacy upgrade recovery,
missing/not-a-repo status handling
* feat: clarify session worktree targets
- session-worktree-contract: extend buildSessionTargetOptions to accept
pendingBootstrapDirectory and mark pending worktrees with pending=true;
extend SessionTargetOption to include optional pending flag
- ChatInput: replace manual worktree branch options construction with
buildSessionTargetOptions; add ⏳ prefix for pending bootstrap worktrees
- Add test for pending bootstrap worktree distinction
* feat: show worktree-backed session state
- Header: read worktree attachment from authoritative session-worktree-store
and render needs-attention/degraded/missing badge with alert icon next to
current session info when session has degraded/missing/invalid state
- GitView: show 'Worktree features are unavailable' message when session has
missing worktree status and open-without-worktree-features repair action
* feat: enforce safe mutations for attached worktrees
- session-worktree-contract: add getMutationBlockingReasons helper that returns
blocking reasons (missing/invalid/attention state) for high-risk mutations
- GitView: gate handleCheckoutBranch, handleCreateBranch, and handleRenameBranch
with getMutationBlockingReasons; block with explicit toast message when
worktree is missing, invalid, or has an in-progress git operation
- session-worktree-contract.test: add 7 tests covering mutation blocking for
missing/invalid/attention states (merge/rebase/cherry-pick)
* feat: implement session worktree isolation
This adds a shared session↔worktree contract that makes session switching
worktree-backed. Sessions attached to different worktrees keep stable branch
context without shared-directory auto-checkout.
Commits:
- feat: add session-worktree contract types and canonicalizeWorktreeState API
- feat: canonicalize worktree metadata producers
- feat: route sessions through authoritative worktree attachments
- feat: clarify session worktree targets
- feat: show worktree-backed session state
- feat: enforce safe mutations for attached worktrees
* feat: make authoritative attachment first-priority source for session directory resolution
Phase A: resolveSessionDirectory, getDirectoryForSession, hooks read
authoritative attachment before falling back to worktreeMetadata.
Phase B: createSession canonicalizes and writes attachment on creation;
setCurrentSession recovers legacy/missing attachments via async
canonicalization.
* feat: make authoritative attachment the primary branch source in Header/GitView
Phase C: Header branch label and GitView project root now read from
authoritative SessionWorktreeAttachment first, falling back to live git
and legacy sources only when attachment is absent, degraded, or legacy.
Adds getAttachmentBranchLabel() helper with 7 tests.
* feat: add runtime parity for validateWorktreeDirectory and canonicalizeWorktreeState
Phase D: Web runtime API, VS Code bridge, and VS Code gitService now
expose validateWorktreeDirectory and canonicalizeWorktreeState, matching
the server-side implementations. All three runtimes (web, desktop, VS Code)
can now delegate worktree canonicalization without HTTP fallback.
* feat: add dirty-tree blocking to mutation safety gates
getMutationBlockingReasons now accepts an optional gitStatus param
and blocks branch mutations when the tree has uncommitted changes.
GitView passes live status to all three blocking call sites.
5 new tests covering dirty, clean, null, combined, and no-file-count cases.
* refactor: revert branch label to live-git-first, remove getAttachmentBranchLabel
Live git is the correct source for branch labels in all scenarios:
dedicated worktree sessions have identical live/attachment branches,
and shared-directory sessions must show the real current branch.
Attachment remains authoritative for worktreeRoot, cwd, degraded/
missing/repair status, and mutation blocking.
* chore: remove session worktree isolation plan doc
* refactor: simplify session worktree isolation implementation
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* fix(task): prevent subagent silent failures in session resolution and polling lifecycle
Two failure points fixed:
1. Fallback session resolution window too narrow (3s):
- resolveFallbackTaskSessionId now accepts hasRetried boolean
- First attempt uses 3s window (avoids binding wrong sessions)
- Subsequent attempts widen to 8s (handles late-appearing child sessions)
- Uses useState + useEffect instead of side effects in Zustand selector
2. Polling stops before child results are captured:
- When child session goes idle before parent sees it active, polling
would stop without fetching results
- Added final-fetch-before-stop: a one-shot delayed fetch that runs
after the settle grace period, ensuring child results are captured
- Uses taskFinalFetchDoneRef to guarantee exactly one final fetch
- Preserves existing happy path (active child → normal settle timer)
* fix(task): serialize final fetch after polling stops
Move the subagent final-fetch into a dedicated effect that runs only after
polling has stopped, avoiding races between polling writes and final-fetch
writes to the child sync store.
Also retry safely on final-fetch failure by reopening polling instead of
marking the final fetch as done before the request succeeds.
* feat(task): distinguish child session errors from normal idle
When a subagent terminates with an error, abort, timeout, or failure,
the parent session could not tell it apart from a normal completion.
Changes:
- event-reducer.ts: session.error now stores { type: 'error' } instead of
{ type: 'idle' }, so consumers can distinguish failed from completed sessions
- useSessionActivity.ts: add 'error' phase to SessionActivityPhase and
isError flag to SessionActivityResult; error phase is non-active (like idle)
but distinguishable via isError
- ToolPart.tsx: pass childSessionError to TaskToolSummary and show
'Subagent session ended with an error.' instead of the generic
'No subagent session id on task metadata.' when the child errored
Other consumers of session_status that only check 'busy'/'idle' are
unaffected — 'error' falls through to existing idle-like behavior.
* Revert "feat(task): distinguish child session errors from normal idle"
This reverts commit b3cc749bde16ed8fc55e4f304dcc455484335fba.
* fix(task): delay fallback retry window widening
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* fix: show question content in ToolPart instead of 'Awaiting response...' after refresh
Previously, when the question tool was pending/running or completed
without parseable output, the ToolPart fell through to a generic
'Awaiting response...' message. After a page refresh or app restart,
this made questions appear empty even though the tool state still
contained the question input data.
Now the ToolPart reads question text, headers, and options from the
tool state's input field, ensuring question content persists across
refreshes regardless of QuestionCard store availability.
Fixes#879
* fix: restore QuestionCard after refresh and pause working status during active questions
Two fixes for question tool UX:
1. ChatContainer: sessionIsWorking now returns false when there are
active questions (same as it already did for permissions). This
prevents the status row from showing 'Asking question...' and
instead shows the QuestionCard.
2. sync-context: resyncDirectoryAfterReconnect now re-fetches
pending questions via listPendingQuestions(). Previously only
sessions and messages were re-fetched on SSE reconnect, so
questions asked during disconnection were lost, causing
QuestionCard to disappear after page refresh.
Refs #879
* fix: hide assistant working status while questions are pending
The assistant status hook only special-cased pending permissions, so
question tools still surfaced 'Asking question...' after refresh even
when the UI was already waiting on a QuestionCard response.
Treat pending questions like other blocking requests by clearing the
working indicator until the user answers.
Refs #879
* fix: merge question/permission stores instead of full replace on bootstrap and reconnect
The root cause of QuestionCard disappearing after refresh was a race
condition between SSE events and HTTP bootstrap. Bootstrap and reconnect
both did full replacement of state.question, wiping SSE-delivered data
that arrived between the HTTP call initiation and response arrival.
Changes:
- bootstrap.ts: question and permission stores now use merge semantics.
Only sessions present in the API response are overwritten. Sessions
absent from the response are left untouched (they may hold SSE data).
- sync-context.tsx: reconnect question resync uses the same merge pattern.
No longer clears question entries for sessions not in the API response.
- bootstrap.ts: sdk.question.list() now passes directory parameter to
scope the query correctly.
This ensures SSE-delivered question data survives the bootstrap window,
while still allowing the API response to be authoritative for sessions
it covers.
Refs #879
* fix: prune stale pending requests after reconnect
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
Add reflect-metadata bootstrap so desktop sidecar no longer crashes on startup.
Update SDK v1.4 compatibility for model variant and diff payload handling.
Unify write/edit/apply patch expanded previews and hide write success output noise.
OpenCode 1.4.0 moved `UserMessage.variant` from the top-level to
`UserMessage.model.variant`. OpenChamber still read the legacy location,
so variant selection silently stopped being restored on sessions served
by OpenCode 1.4.0+.
Read `model.variant` first and fall back to the legacy top-level field
so both pre-1.4 and 1.4+ OpenCode servers keep working.
* fix(chat): replace hover bridge with padding to unblock desktop interactions
PR #826 removed pointer-events-none from the hover bridge div to fix
short user message revert button hover, but the transparent 44px
full-width bridge blocked all interactions (clicks, text selection)
with messages below it on Tauri desktop where the rendering difference
made the overlay consume pointer events across the entire message area.
Replacing the bridge div approach with pb-12 padding on the message
bubble container (max-w-[85%]) when sticky inline hover is active.
This extends the hover hit area downward within the bubble's own
padding, maintaining continuous hover path from bubble to action
buttons without any overlay that could block sibling elements.
The padding approach works because:
- The 48px bottom padding extends max-w-[85%]'s hover area to cover
the gap between bubble bottom and the absolute-positioned buttons
- Hovering the padding counts as hovering group/user-shell (parent)
- No absolute-positioned invisible layer means nothing blocks the
next message's interactive content
* fix(chat): reduce hover padding from pb-12 to pb-5 for tighter spacing
Roll back aggressive chat reuse paths that froze message parts, tool state, and completed assistant bodies when sessions were inactive or backgrounded.
- 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
- 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
- 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>
- 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
- 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>
* 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 HTML escaping for user messages in Markdown mode
- Fix agent mention link rendering order issue
- Plain mode remains unchanged (uses React elements)
Tested:
- User messages: <div> now displays as <div>
- Agent mentions render correctly as clickable links in Markdown mode
Co-authored-by: 郭皓楠 <guohaonan@MacBook-Neo.local>
* fix: added desktop app background throttling
* perf: add streaming debug metrics panel
- Show streaming performance metrics in the debug panel
- Auto-enable stream profiling while the panel is open
- Add JSON export for sharing UI and VS Code metrics
* perf: batch streaming updates more aggressively
- Buffer message deltas and metadata updates to cut render churn
- Skip no-op part updates before they touch the message store
- Fix the desktop debug panel shortcut binding
* perf: split streaming event handling and coalesce deltas
- Move streaming content events onto a dedicated fast path
- Defer non-critical stream side effects off the hot path
- Merge repeated message delta events before they reach the UI
* perf: isolate streaming rows from chat rerenders
- Memoize chat rows against render-relevant message changes only
- Read live assistant text directly from store to narrow streaming updates
- Split the active streaming entry from the stable message list path
* perf: streamline chat streaming and SSE proxying
- Reduce chat rerenders around the active streaming path
- Simplify server SSE forwarding to avoid duplicate proxy work
* fix: preserve the first streaming text chunk
- Show the initial text chunk immediately before batched deltas arrive
- Bypass batching for the first text or reasoning part update
- Keep later streaming updates buffered for performance
* perf: align streaming/render hot paths with opencode parity
* perf: harden turn/cache stability and stale delta suppression
* fix: stabilize chat rendering and disable timeline interactions
- Disabled timeline dialog access from shortcuts, commands, and chat input
- Reduced chat render churn by simplifying message list and turn staging behavior
- Improved session-switch stability to prevent update-depth crashes
* perf: track static message rerenders during streaming
* perf: reduce sorted-mode activity rerender fanout
* perf: reduce chat rerender fanout and add active-turn metrics
- Reduced sorted-mode rerender coupling by tightening turn context propagation
- Added a metric for static rerenders outside the active turn during streaming
- Exposed new chat render counters in the debug panel for parity tracking
* fix: keep sorted activity mounted while stream grows
* fix: stabilize session and history scroll rendering
* refactor: decouple server routes from index
* refactor: extract fs module from server index
* refactor: move opencode route ownership into module
* refactor: extract notification route registration
* refactor: extract opencode and notification runtimes from index
* refactor: extract settings runtime and complete server modularization pass
* refactor: modularize server config, skills, icons, and tunnel routes
* refactor: extract server modules from monolithic index.js
Split proxy, routes, runtime helpers, and notification emitter
into dedicated modules under packages/web/server/lib/.
* refactor: replace session/message stores with SSE-driven sync layer
Delete ~9200 lines of old architecture (useEventStream, messageStore,
sessionStore, useSessionStore, questionStore, useTodoStore, client SSE).
New sync layer: event pipeline with coalescing + 16ms flush, pure event
reducer, per-directory child stores with LRU eviction, cursor pagination,
optimistic updates, deferred timeline staging, text throttle.
Migrate all UI consumers to sync hooks (useSessionMessages,
useSessionMessageRecords, useSessionStatus, useSessionPermissions, etc).
Strip session-ui-store to UI-only state, delegate SDK ops to
session-actions with abort-if-busy, optimistic store updates, and
response merging for revert/fork/archive/delete.
Add notification-store for SSE-driven session attention tracking,
cross-directory GlobalSessionStatusStore for sidebar indicators,
client-side diff snapshot sanitization to prevent memory bloat,
and revert message filtering via useVisibleSessionMessages.
* feat: notification store, session actions, activity detection
Add notification-store.ts for SSE-driven attention tracking.
Add sanitize.ts to strip diff snapshot memory bloat.
Add session-actions.ts with optimistic revert/fork/archive/delete.
Improve useSessionActivity with incomplete-message fallback.
Delete useServerSessionStatus polling hook.
* fix: add directory param to all SDK calls, fix command/shell/abort routing
All SDK calls in session-actions.ts now pass directory parameter —
required by OpenCode server to scope session operations. Without it,
abort, commands, revert, fork, and other operations returned 500.
Add routeMessage() in session-ui-store for shell mode (session.shell),
slash commands (session.command), and normal prompts. Command lookup
checks both sync child store and useCommandsStore. Handle /compact
locally via session.summarize().
Implement getContextUsage() to restore header context usage display —
reads token counts from last assistant message in sync store.
* refactor: replace custom API proxy with http-proxy-middleware
Remove ~280 lines of custom proxy code: forwardSseRequest,
forwardGenericApiRequest, collectRequestBodyBuffer, header
manipulation, hop-by-hop filtering, SSE block buffering.
Replace with single createProxyMiddleware() call that handles
SSE streaming, large bodies, and timeouts out of the box.
Dynamic router for OpenCode port changes after restarts.
Auth headers injected via proxyReq hook.
Keep: readiness gate, Windows session merge, API prefix detection.
* perf: targeted event draft cloning to fix streaming render cascade
Event handler was eagerly cloning all state slices on every event,
breaking Zustand selector referential equality. During streaming
(~60 events/sec), this caused every subscriber to re-render regardless
of which slice actually changed.
Now only clones fields the specific event type mutates. Also extracts
StatusRowContainer to isolate high-frequency useAssistantStatus
subscription, removes dead messageStreamStatesMap subscription from
ChatContainer, and narrows useAssistantStatus to only track last
assistant message parts.
MessageList renders: 1972 → 296 per streaming session (-85%).
* fix: null safety for sync state slices
Add defensive ?? {} guards on permission, question, session_status,
and message record access. Prevents crashes when child store state
is partially initialized during bootstrap.
* perf: dedup inflight SDK calls, extract concurrency util, delay PR tracking
Extract mapWithConcurrency to shared lib/concurrency.ts. Add in-flight
dedup for loadProviders/loadAgents to prevent concurrent duplicate SDK
calls. Delay initial PR background tracking by 5s to reduce startup
CPU burst.
* fix: header session lookup across all child stores
Session title and context panel click failed when session belonged to
a different directory than the current child store. Fall back to
getAllSyncSessions() to search all initialized stores.
* chore: bump @opencode-ai/sdk to 1.3.5
* docs: add sync event handling guide
* Optimize session prefetch and improve delete/archive UX
- Add settlement delay to session prefetch to avoid race conditions on
rapid session switches
- Reduce git diff prefetch and session cache limits for better performance
- Implement optimistic UI updates for session delete/archive operations
with proper rollback on failure
- Wire session prefetch hook into SessionSidebar with sync integration
* Add file content cache and sync optimizations
- Wrap FilesAPI with in-memory LRU cache for file content with dual
constraints (entry count and byte size)
- Optimize chat timeline scroll restoration using useLayoutEffect
- Preserve React references in message and part arrays to prevent
unnecessary re-renders when prepending history
- Add session prefetch TTL cache to prevent redundant fetches
- Integrate session prefetch cache clearing with eviction flow
* Improve session sidebar error handling and add diff prefetch filtering
Load active and archived sessions independently using Promise.allSettled
to prevent one failure from blocking the other. Add retry logic to session
API calls and skip large files during diff prefetch to improve performance.
* Replace sendMessage with optimisticSend wrapper
Introduces optimistic UI updates for normal chat messages to provide
instant feedback. Messages appear immediately in the UI while the API
call executes in the background, with automatic rollback on errors.
* perf: split stores, proper optimistic send, fix revert/directory bugs
- split session-ui-store into voice/input/selection/viewport stores
to reduce subscriber re-evaluation during streaming
- wire optimisticSend through useSync shadow Map infrastructure
matching OpenCode's pattern (no heuristic part detection)
- port OpenCode Identifier.ascending ID format for correct sorting
- pass messageID to promptAsync to prevent duplicate messages
- fix worktree directory not propagating to session actions
(dynamic dir() via opencodeClient.getDirectory)
- fix setCurrentSession accepting directoryHint for new sessions
- fix revert not hiding messages (session limit was 5, bumped to match loaded count)
- fix revert optimistic message removal from store
- fix load-more flicker (useLayoutEffect scroll compensation)
- add prefetch TTL cache, file content LRU cache
- add session prefetch for adjacent sessions
- add instant archive/delete (optimistic before SDK call)
- migrate legacy window.__zustand_session_store__ to session-ui-store
- add retry + independent error handling for archived sessions
- add AGENTS.md performance rules
* perf: startup optimization — dedup, caching, light git status, diff rendering gates
- defer diff prefetch to git tab open, reduce concurrency 4→2, skip >500 changed lines
- cap project git checks concurrency (2), directory status probe (3)
- dedup provider/agent loading, github auth, worktree list (in-flight + TTL caches)
- delay PR tracking 5s, cache 403 search failures per-repo
- coalesce settings PUT (200ms debounce), cache settings GET (2s TTL)
- cache canonical directory resolution (60s TTL)
- persist missing directory status to localStorage (10min TTL)
- light/heavy git status: polling skips numstat+line counting+rev-list
- large diff rendering gate (>500 lines → "render anyway" button)
- tokenization degradation for >500KB files in Pierre
- parallelize main.tsx pre-render awaits
- batch sidebar file tree expanded paths restoration (3 at a time)
- remove bare useConfigStore() subscription in AgentsPage
- sync worktree sandboxes to OpenCode SQLite DB
- fix RightSidebarTabs ternary → explicit tab matching
- defensive guards on sync state (session_status, permission, question, message)
* fix: add defensive guards on remaining sync state field accesses
guard session_status, permission, message, todo, part, config with ?? {}
in useDirectorySync selectors, session-cache, and bootstrap
* fix: add missing directory dep to useCallback in use-sync.ts
* fix: preserve diffStats when light-mode polling overwrites status
* perf: optimize startup git status polling and diff rendering
Preserves diff stats when lightweight polling updates repository status
Reduces startup overhead with smarter git polling and store updates
Adds detailed optimization and migration docs for next performance steps
* fix: keep chat diff stats stable during git status updates
Prevents lightweight git polling from dropping diff statistics
Keeps MessageList diff indicators consistent while status refreshes
Improves reliability of git-aware chat rendering
* fix: user animation replay, queued message variant, startup provider loading
- consume animation ID after first play to prevent re-animation
on neighbor assistant message completion
- capture send config (model/agent/variant) at queue time matching
OpenCode's FollowupDraft pattern instead of re-resolving at send time
- replace one-shot startup recovery effect with polling interval
that retries every 2s until providers and agents load
- fix optimistic bridge to avoid re-render loop (stable ref wrappers)
* chore: update tauri to 2.10.3 and all plugins to latest
- tauri 2.9.4 → 2.10.3
- tauri-build 2.5.3 → 2.5.6
- tauri-plugin-dialog 2.4.2 → 2.6.0
- tauri-plugin-log 2.7.1 → 2.8.0
- tauri-plugin-shell 2.3.3 → 2.3.5
- tauri-plugin-updater 2 (floating) → 2.10.0 (pinned)
- @tauri-apps/api ^2.9.0 → ^2.10.1
- wry 0.53.5 → 0.54.4 (transitive)
* refactor: decouple web server index orchestration runtimes
* fix: align VS Code runtime behavior with web and reduce draft view CPU load
- Queue VS Code bridge and SSE startup requests until API readiness to avoid false bootstrap failures
- Make agent manager actions directory-aware and remove real worktrees with safer partial-failure handling
- Replace heavy logo animation path with a lightweight pulse to cut draft-session CPU usage
* fix: restore auto-selected file sending in chat input
- Send server-selected files as proper file URLs in the message payload
- Include server-backed attachments in submit flow instead of dropping them
- Restore queued-message attachments through the refactored input store
* fix: restore session model selection consistently on session switch
- Restore agent, model, and variant from the latest loaded user message for each session
- Wait for session messages before applying restored selections to avoid stale or missing state
- Remove legacy session-choice inference paths that caused overlap and instability
* fix: restore permission replies and auto-accept across sessions
- Scope permission and question replies to the target session directory so answers take effect reliably
- Make permission auto-accept immediately handle pending requests and react to new permission prompts
- Keep parent-session handling working for child-session requests through the shared response path
* feat: add reusable fuzzy branch fuzzy-search helper and dialog integration (#798)
* feat: add reusable fuzzy branch search for worktrees
* chore: drop planning docs from feature branch
* feat: make worktree branch refresh manual
* feat: add configurable session retention action
* refactor: centralize global session state in ui store
* fix: cancel debounced permission push after reply
* docs: clarify global and directory session store architecture
* docs: refine agent development rules and session activity guidance
- Clarify agent code of conduct and durable development patterns
- Add explicit shared-store rerender and live-state guidance
- Narrow session activity fallback to avoid stale working state
* chore: updated .gitignore
---------
Co-authored-by: Iuliia Ivashko <yulia.ivashko@gmail.com>
* fix: improve session sidebar tooltip and truncation behavior
- Keep new-draft tooltip anchored to its trigger button
- Fix minimal-mode worktree/group header text truncation
- Tune minimal-mode right padding to reduce early label clipping
* fix: render reasoning through markdown pipeline
- Use Streamdown rendering for reasoning in live chat mode
- Remove italic styling from reasoning text
- Render expanded reasoning content with MarkdownRenderer
* chore: remove legacy electron dependencies
- Removed unused Electron packages from root and UI manifests
- Deleted obsolete Electron context menu type declaration
- Regenerated lockfile after dependency cleanup
* fix: handle non-repository folders in git status API
- Prevent 500 errors when status is requested outside a valid Git repo
- Improve repository detection using `git rev-parse --git-dir`
- Reduce noisy server logs for expected non-repo status checks
* fix unloaded session chat layout flicker
* fix: reduce noisy TTS status polling
Cache and dedupe TTS status requests, and only check provider availability when the related voice features are enabled so disabled voice setups stay quiet.
* perf: throttle background PR git status refreshes
* fix: improve VS Code Explorer file drop mentions in chat
- Add Explorer context action to insert selected files as @mentions.
- Handle Explorer drag-and-drop to prefill @file mentions instead of attachments.
- Prevent duplicate plain-path text when dropping multiple files.
* fix: deduplicate recent sessions in VS Code sidebar
- Hide sessions from main list when already shown in recent
- Apply dedup only in VS Code runtime
- Keep session search behavior unchanged
* feat: add true HMR dev flow for VS Code extension
- Load VS Code webview from Vite dev server with React refresh preamble
- Add `vscode:dev` runner that starts watchers and opens Extension Development Host
- Update VS Code dev docs and scripts to use the new HMR startup flow
* feat: polish VS Code session sidebar and attachment UX
- Add resizable sessions sidebar in VS Code layout
- Tighten session list spacing and hover behavior in VS Code
- Remove bulk file/image attach success toasts while keeping error toasts
## Summary
- **Instant worktree creation from chat draft**: selecting "+ New worktree" in the draft branch selector immediately creates a session draft and bootstraps the worktree in the background — no modal interruption
- **Redesigned multi-run launcher**: compact 2-column grid layout in a right-sized dialog with scroll shadow, sticky footer, tooltips replacing verbose descriptions, and project icons in the selector
- **Branch selector aligned across surfaces**: multi-run and agent manager branch pickers now use the shared git store and match NewWorktreeDialog behavior (same default resolution cascade, no synthetic HEAD option, all branches shown)
- **Opaque model multi-select dropdown**: fixes text bleed-through on translucent backgrounds by compositing `--surface-elevated` over `--surface-background`
- **"+ New" inline button in sidebar worktree headers** for faster worktree creation
## Why
Worktree creation was behind modal flow that interrupted the user's train of thought. The draft-first approach lets users start typing immediately while the worktree bootstraps. The multi-run launcher had an oversized form layout with redundant explanations, and its branch picker behaved differently from the main worktree dialog - causing confusion about which branches were available and what the default was.
* fix: make textarea focus highlight render inside
Apply inset focus ring to shared textarea component
Prevent focus border from appearing clipped near container edges
* fix: build desktop sidecar with target-matched architecture
Map Tauri target triples to Bun compile targets
Pass explicit Bun compile target for sidecar builds
Prevent x86_64 releases from shipping arm64 sidecar binaries
* fix: allow Windows git custom binary paths
Enable safe use of resolved custom git executable paths
Prevent git status failures when path contains restricted characters
Keep default behavior unchanged for plain git invocations
* fix: allow toggling diff line wrap on mobile
Stops forcing wrapped lines in mobile diff view
Line-wrap button now reflects and applies user preference
* fix: align VS Code managed server env with shell settings
Import login-shell environment variables before starting managed OpenCode
Apply Windows and Unix shell snapshot resolution for parity
Improve proxy-dependent provider connectivity in VS Code extension
* fix: respect user scope when adding MCP servers
Prevent user-scope MCP entries from being written to project config
Keep project writes only for explicit project scope
* fix: show linked GitHub issues and PRs as user message attachments
Preserve synthetic issue/PR context parts during message filtering.
Convert synthetic GitHub context JSON into attachment-style user parts.
Open issue/PR attachment links via shared external URL helper.
* fix: restore and polish project notes in sessions sidebar
Restored the Notes button in the left sessions sidebar header
Improved notes panel layout with wider dialog, larger notes area, and project name in the header
Refined todo rows with inline expand/collapse text and stable action/checkbox alignment
* fix: hide sidebar footer actions in VS Code runtime
Remove Settings, About, and Shortcuts buttons from the sessions sidebar footer in VS Code
Keep update button behavior unchanged across runtimes
* fix: normalize Windows paths for VS Code session loading
Canonicalize drive-letter casing in session path normalization
Align VS Code workspace path persistence with the same Windows path format
Normalize client directory context before API calls to keep session filtering consistent
* fix: open linked GitHub attachments with shared URL helper
Use runtime-aware external URL opening for issue/PR attachment links.
Keep GitHub attachment labels readable without altering normal file name rendering.
* fix: keep user MCP config writes out of project files
Respect user scope when selecting config write target
Prevent MCP user entries from being written to project opencode.json
* fix: prevent project menu from overlapping new session button
Align project menu positioning for non-git and git project rows
Avoid kebab-menu and plus-button overlap in sessions sidebar
* fix: allow native context menu on links in chat messages
The desktop app was blocking the context menu on all elements except
specific allowlisted ones (terminal, input, textarea, etc.). This
prevented users from right-clicking on HTTP links in chat messages
to access the 'Open Link' option.
Added 'a' (anchor) tag to the allowlist to restore native context
menu functionality for links.
Fixes#708
* fix: use tauri.shell.open for external links in desktop app
- Add global window.open override to init_script that routes HTTP/HTTPS
URLs through tauri.shell.open() instead of window.open()
- Add openExternalUrl utility that prefers tauri.shell.open with window.open fallback
- Add openExternalUrl to MarkdownRenderer for link safety.onLinkCheck
- Replace window.open with openExternalUrl in ProvidersPage for OAuth URLs
Fixes#708
* fix: unify external link opening across desktop and UI
Added a shared URL opener that only allows http/https links.
Replaced duplicated Tauri/window link-open logic in key UI sections.
Removed fragile desktop window.open override and markdown external-link modal behavior.
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* fix: preserve unsent prompt when adding editor context in VS Code
* fix: append Add to chat selections as markdown blocks with stable spacing
Convert selected assistant content to markdown before appending
Wrap each Add to chat selection in an `md` fenced block
Preserve multiline composer formatting across repeated appends
* fix: normalize persisted Windows paths to prevent identity mismatches
* fix: hide Windows subprocess console popups across server tasks
Hide OpenCode startup and shell command child windows in the web server
Apply windowsHide to cloudflared and skills-catalog git subprocesses
Cover remaining git service exec paths that could surface console windows
* fix: restore chat auto re-pin when reaching bottom
Re-pin now triggers when scrolling back into the bottom zone, not only via the button.
Upward user scroll intent still unpins immediately and is not overridden by re-pin.
Unified bottom/re-pin threshold logic to reduce sensitivity mismatches.
* fix: restore chat scroll release on mobile during streaming
Restores pinned-scroll release on touch scroll up so mobile users can leave auto-follow while streaming.
Improves re-pin behavior near bottom to avoid sticky or inconsistent pin states.
Includes related chat UI and dependency updates in the same change set.
* fix: hide daemon startup probe consoles on Windows
* fix: prevent pinned scroll tug-of-war during streaming
* fix: prefer git.exe to avoid Windows diff popup flashes
* fix: prefer git.exe discovery in Windows git flows
* fix: avoid where probes in Windows git resolution
* fix: avoid update-check subprocess flashes on Windows
* fix: normalize read file path labels
* feat: add OpenChamber defaults and improve theme ports
Add new OpenChamber light and dark themes
Regenerate imported themes with stronger surface mapping
Set OpenChamber themes as the default top options
* fix: stabilize chat pin and unpin behavior during streaming
Restores reliable unpin on upward wheel and touch gestures while auto-follow is active.
Prevents immediate re-pin while the user is actively scrolling upward near the bottom.
Keeps smooth follow-to-bottom behavior while reducing scroll tug-of-war.
* fix: suppress Windows command popups in VSCode runtime processes
Hide spawned git and server process windows in VS Code runtime
Extend hidden-window handling to server port cleanup and reveal commands
Keep behavior unchanged on non-Windows platforms
packages/ui/src/components/chat/message/TextSelectionMenu.tsx
- What: Wrap selectedText in ```md fenced block before appending to composer.
- Why: Selections were appended as raw inline text with no visual distinction.
packages/ui/src/components/chat/ChatInput.tsx
- What: Replace trimEnd()/trim() + single-space join with raw prev + \n\n separator in append branch.
- Why: Existing composer newlines were being destroyed and multiple appends ran together.
packages/ui/src/components/session/ProjectNotesTodoPanel.tsx
- What: Wrap todoText in ```md fenced block before appending to composer.
- Why: Todo text sent to current session should have the same fenced formatting.
Reduce layout thrashing during virtual keyboard open/close on mobile by
batching resize and visualViewport event handlers to once per animation
frame. No visual or behavioral changes — desktop is unaffected.
- MainLayout: rAF-batch visualViewport resize/scroll and window resize
listeners (was firing 10-20x per frame during keyboard animation)
- Header: rAF-batch ResizeObserver and resize/orientationchange callbacks
- ChatContainer: rAF-batch ResizeObserver and resize fallback callbacks
- useDeviceInfo: 150ms debounce on resize listener (device type does not
change during keyboard transitions)