Commit Graph
83 Commits
Author SHA1 Message Date
Bohdan Triapitsyn a12be061e3 feat: add OpenCode update and in-app Browser features 2026-05-14 14:45:04 +03:00
Isaac Sanchez-HawkinsandIsaac Sanchez bac655ddb4 fix(ui): preserve runtime worktree source (#1259)
Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
2026-05-14 02:06:21 +03:00
chutasticandBohdan Triapitsyn 9347f97a98 fix(ui): persist selection-store state across reloads (#1229)
* fix(ui): persist selection-store state across reloads

* fix(ui): resolve selection store persistence build

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-05-13 15:06:20 +03:00
Jake b4cbd7f0d6 Fix: archived session bulk delete for VS Code extension (#1256)
* fix/archived sessiongroup bulk delete

* Remove in line comment

Signed-off-by: Jake <101855602+jjdubski@users.noreply.github.com>

* small fix for lint

* indentation fix, restore to original

---------

Signed-off-by: Jake <101855602+jjdubski@users.noreply.github.com>
2026-05-13 11:12:30 +03:00
Isaac Sanchez-HawkinsandIsaac Sanchez 2b514e4cb9 test(input): restore FileReader mock (#1200)
Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
2026-05-12 11:11:34 +03:00
Isaac Sanchez-HawkinsandIsaac Sanchez f7c5144c62 fix(sync): settle failed attachment reads (#1193)
Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
2026-05-12 11:09:31 +03:00
Isaac Sanchez-HawkinsandIsaac Sanchez e50a484633 fix(sync): drop orphan session parts (#1183)
* fix(sync): drop orphan session parts

* fix(sync): guard missing part cache

---------

Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
2026-05-12 11:05:37 +03:00
Isaac Sanchez-HawkinsandIsaac Sanchez 109aecf3de fix(sync): advance sidebar recency after streams (#1232)
Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
2026-05-12 11:01:20 +03:00
Isaac Sanchez-HawkinsandIsaac Sanchez 65c54411de fix(sync): fetch sessions in active directory (#1231)
Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
2026-05-12 11:01:10 +03:00
Isaac Sanchez-HawkinsandIsaac Sanchez eb5b1de9b7 test(sync): shorten websocket fallback test (#1211)
* test(sync): shorten websocket fallback test

* test(sync): avoid duplicate fallback cleanup

---------

Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
2026-05-12 10:57:27 +03:00
Isaac Sanchez-HawkinsandIsaac Sanchez 9715b6c23c fix(sync): preserve part update ordering (#1167)
* fix(sync): preserve part update ordering

* test(sync): wait for pipeline delivery

---------

Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
2026-05-08 23:14:07 +03:00
14c0bfe0bc fix(sync): skip duplicate status events (#1152)
* fix(sync): skip duplicate status events

* test(sync): cover duplicate idle statuses

---------

Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-05-08 15:56:18 +03:00
Isaac Sanchez-HawkinsandIsaac Sanchez c1c21fe340 fix(sync): track only trailing assistant streaming (#1151)
* fix(sync): track only trailing assistant streaming

* test(sync): cover streaming completion paths

---------

Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
2026-05-08 15:47:56 +03:00
811aa50312 fix(input): ignore stale attachment reads (#1150)
* fix(input): ignore stale attachment reads

* fix(input): centralize attachment replacement

* fix(input): route queued attachment restores through store action

---------

Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-05-08 15:47:46 +03:00
Isaac Sanchez-HawkinsandIsaac Sanchez 47fc6a4606 fix(sync): compare retry status metadata (#1141)
* fix(sync): compare retry status metadata

* fix(sync): compare status fields directly

---------

Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
2026-05-08 15:12:08 +03:00
Isaac Sanchez-HawkinsandIsaac Sanchez a3a82166f1 fix(sync): update request arrays immutably (#1139)
* fix(sync): update request arrays immutably

* test(sync): cover rejected question updates

---------

Co-authored-by: Isaac Sanchez <isanchez-hawkins@arize.com>
2026-05-08 15:07:40 +03:00
Bohdan Triapitsyn c827d6b8df fix: restore messages after redo
Refetches session messages before applying redo
Aligns undo/redo navigation with OpenCode behavior
Keeps restored messages visible after unrevert
2026-05-08 14:53:48 +03:00
Bohdan Triapitsyn e1ff21bc0a feat: add Electron Mini Chat windows (#1161)
Add dedicated Electron Mini Chat windows for focused chat sessions without the full desktop shell. Mini Chat can open existing sessions or draft sessions, supports pinning above other windows, transfers sessions or drafts back to the main window, and deduplicates existing-session windows.

Expose Mini Chat entry points from the main header, session sidebar, command palette, and `mod+alt+n`. Add a dedicated Vite entry and React runtime so the compact surface can stay isolated from full-app chrome while still sharing chat, sync, theme, locale, model, agent, and worktree behavior.

Keep Mini Chat behavior scoped to the compact surface:
- limit assistant/user message actions to the appropriate Mini Chat set
- hide workspace changed-files UI in Mini Chat
- keep draft worktree selection and streaming directory state in sync
- mark sessions viewed while they are open in Mini Chat
- support Mini Chat-specific keyboard shortcuts for input focus, model selection, thinking variant cycling, favorite model cycling, and opening new Mini Chat drafts

Harden Electron integration by gating Mini Chat controls on desktop IPC availability, restricting pin/unpin IPC to Mini Chat windows, and only closing Mini Chat after the main window handoff succeeds.
2026-05-08 12:22:59 +03:00
Bohdan Triapitsyn e892346c6b refactor: stabilize live chat sync materialization (#1132)
Canonicalize session message/part materialization across load, prefetch, reconnect, and recovery paths so OpenChamber restores session snapshots through one consistent merge flow.

Preserve live assistant streaming text when stale or delayed snapshots arrive, while still replacing optimistic user parts with confirmed server snapshots to avoid duplicated user messages.

Narrow recovery triggers to explicit incomplete snapshot signals instead of broad session-event fallbacks, reducing unnecessary session refetches during active streaming.

Keep turn windowing aligned with parented assistant replies and add regression coverage for materialization gaps, stale snapshot protection, optimistic user replacement, reconnect recovery, and turn grouping.
2026-05-07 18:57:44 +03:00
Bohdan Triapitsyn ff830d3812 fix: protect live session sync caches 2026-05-07 12:30:30 +03:00
Bohdan Triapitsyn de9aa05bae perf: reduce sync recovery message fetch size
Limits reconnect and repair message fetches to recent messages
Reduces repeated bandwidth for large tool outputs
Keeps normal session loading unchanged
2026-05-06 19:58:28 +03:00
2b0e1ef42e fix(sync): preserve pending questions across session switch and directory eviction (closes #918) (#1103)
* fix(sync): preserve pending questions across session switch and directory eviction

Closes #918, completes the gap left by #909.

The 'agent question disappears after switching session / coming back
later' bug had two root causes that #909 only partially addressed:

1. Directory-eviction TTL (20 min) silently dropped child stores that
   held pending questions/permissions. The discard wasn't gated on
   in-flight blocking-request state, so any 'question.asked' event
   that arrived during the eviction-then-rehydrate window was routed
   to a non-existent store and silently lost.

2. PR #909 re-fetches listPendingQuestions/Permissions only on SSE
   reconnect. Switching sessions within the same socket — including
   navigating back to a directory whose child store was rebuilt after
   eviction — left the UI relying on store state that may have missed
   events that fired while a different session was active.

Three edits, in src/sync:

- eviction.ts / types.ts / child-store.ts: add hasPendingBlockingRequests
  to EvictPlan + DisposeCheck and never evict a directory whose store
  carries a non-empty state.question or state.permission record.
- sync-context.tsx: extract resyncBlockingRequestsForDirectory from the
  reconnect path and call it on currentSessionId changes (debounced
  250ms), reusing PR #909's signature-based merge so concurrent SSE
  updates aren't clobbered.
- __tests__/eviction.test.ts, __tests__/session-switch-resync.test.ts:
  new unit coverage for the eviction guard and resync semantics
  (deduped fetch per switch, in-flight SSE preservation, stale entry
  cleanup, unknown-session filtering).

* fix(sync): refresh store before blocking request resync

---------

Co-authored-by: Alexander Busse <alex@ableph.net>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-05-05 19:39:35 +03:00
David SazandBohdan Triapitsyn 5475ef2db3 Feat/add ide opened file to chat context (#1106)
* feat(chat): add active editor file context and related functionality

* feat(chat): improve active editor file context handling and update translations

* feat(i18n): standardize quotation marks in file attachment messages

* update Korean translation for image removal action in file attachment

* feat(chat): refine active editor file handling and optimize broadcast logic

* fix(chat): stabilize VS Code editor context chips

---------

Signed-off-by: David Saz <david.saz.g@gmail.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-05-05 01:24:03 +03:00
Bohdan Triapitsyn 5614012acb fix(ui): keep streaming deltas through pipeline 2026-05-03 13:42:50 +03:00
Bohdan Triapitsyn f810a3316c fix(ui): prevent streaming text flicker and first-chunk loss 2026-05-03 02:32:05 +03:00
pasta-paulandBohdan Triapitsyn 1991736ebf fix(server): prevent streaming hang during long agent sessions (#1088)
* fix(server): increase WS buffer/replay limits and add backpressure warning

During long-running agent sessions (e.g. ultrawork loops with many tool
calls), the browser WebSocket client can briefly fall behind the server.
When the outbound buffer exceeds the limit, the server force-disconnects
with close code 1013, and the small replay buffer (512 events) is
insufficient to recover all missed events — leaving the UI permanently
stalled.

Changes:
- Raise MESSAGE_STREAM_WS_MAX_BUFFERED_BYTES from 4 MB to 16 MB to
  tolerate larger bursts without disconnecting
- Add MESSAGE_STREAM_WS_BACKPRESSURE_WARN_BYTES (12 MB) threshold that
  sends a one-shot "backpressure" frame to the client before the hard
  disconnect, giving it a chance to shed low-priority updates
- Raise MESSAGE_STREAM_GLOBAL_REPLAY_LIMIT from 512 to 2048 so more
  events survive brief reconnection gaps
- Add tests for the backpressure warning behavior (emit, dedup, reset)

* fix(ui): batch event flushes under backpressure

---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-05-01 13:25:19 +03:00
jwcrystalandBohdan Triapitsyn 03c9065c90 fix: preserve per-session scroll position on session switch (#1083)
* fix: restore scroll position when switching chat sessions

When switching between chat sessions, scroll position now restores to
where the user left off instead of always jumping to the bottom.

- Save pixel-level scrollPosition (scrollTop/scrollHeight/clientHeight)
  in viewport store on every scroll event
- Add restoreSavedScrollPosition to timeline controller for ratio-based
  restoration (handles content size changes between visits)
- Suppress intermediate scroll events during session transition with an
  explicit flag, cleared deterministically after restore completes
- Cancel in-flight animations/follow-loops on session switch
- Preserve scrollPosition when session-ui-store rebuilds SessionMemoryState

* fix: keep streaming sessions pinned on restore

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-05-01 12:39:04 +03:00
bd9a91335c feat(preview): embedded dev-server preview pane + dev shutdown controls (#1062)
* feat: embedded preview proxy for local dev servers

Add a same-origin server proxy under /api/preview/proxy/:id and
matching UI surfaces so local dev servers (Vite, Next, etc.) can be
embedded inside OpenChamber.

Server (packages/web/server):
- New lib/preview/proxy-runtime.js: cookie-gated HTTP+WebSocket proxy
  to loopback hosts only, with TTL'd targets and SSRF allowlist.
- index.js wires the runtime alongside terminal/event-stream.

UI (packages/ui):
- ContextPanel preview tab with iframe, reload, and open-in-browser.
- Inline html code-block preview in MarkdownRenderer.
- Terminal auto-detects loopback URLs and offers to open them.
- i18n keys across en, es, pt-BR, uk, zh-CN.

* perf(preview): cache proxy targets across PreviewPane remounts

Module-scoped Map keyed by upstream URL so tab switches and component
remounts within the same page session reuse the existing proxy
registration instead of POSTing a fresh target each time.

In-memory only by design: the server holds the target map in memory
and the auth cookie is HttpOnly + scoped to the proxy id, so a stale
persisted entry would 404 after a server restart. Entries are evicted
on registration error and on a 30s safety margin before TTL expiry.

* feat(preview): surface dev-server-down state with retry overlay

Iframes don't expose HTTP status to the parent, so when the proxy
returns a 502 (upstream dev server is offline) the iframe just renders
the raw JSON error body. Probe the proxy URL out-of-band with HEAD
(falling back to GET on 404/405) and replace the iframe with a
friendly 'Dev server is not responding' overlay + retry button when
the upstream is unreachable.

Re-probes on reload, on URL change, and on proxy re-registration.

* feat(preview): strip frame-busting response headers

Many dev servers (Next.js, others) send X-Frame-Options: SAMEORIGIN
and/or a CSP with frame-ancestors that block embedding inside the
OpenChamber iframe. The proxy is same-origin and already
authenticated per-target, so embedding is otherwise safe.

- Drop X-Frame-Options outright on proxied responses.
- Surgically remove only the frame-ancestors directive from
  Content-Security-Policy and Content-Security-Policy-Report-Only,
  preserving every other directive. Drops the header entirely if no
  directives remain.
- Verified end-to-end: upstream sending both headers comes through
  with X-Frame-Options removed, CSP retaining default-src/script-src
  but no frame-ancestors, and unrelated headers untouched.

* docs(preview): design for remote-host relay agent

Design-only doc for the next phase of the embedded preview feature:
when OpenChamber runs remotely (cloud/shared/tunnel) and the user's
dev server runs on their local machine. Covers architecture (local
agent + outbound control WebSocket + server dispatch), pairing flow,
wire protocol, security model, failure modes, open questions, and
implementation milestones. No code changes.

* feat(preview): auto-open preview pane for loopback URLs in chat

Detect http(s) loopback URLs in incoming assistant messages and open the
preview pane automatically, deduped per (session, url) pair so re-renders
or repeated mentions do not steal focus. Add an inline Preview button
next to loopback links in chat markdown as a manual fallback when the
auto-open was dismissed or the URL appeared in an older message.

- url.ts: isLoopbackHttpUrl / extractLoopbackUrls helpers
- ChatContainer: module-level dedupe Set + effect on active session tail
- MarkdownRendererImpl: optional onPreviewLoopback in main renderer only
  (SimpleMarkdownRenderer for tool diffs is intentionally untouched)
- Reuses existing terminalView.preview.open i18n keys

* feat: preview enhancements, dev shutdown, and reliability fixes

Add preview start/stop UI in ContextPanel/Header, improve URL detection (Python HTTP server logs, trailing punctuation, IPv6 loopback), fix proxy path filtering to avoid disrupting non-preview WebSockets. Add dev-only /api/system/dev-shutdown endpoint and Header button to terminate local dev processes and orphaned preview servers. Improve terminal cleanup with process group killing, event pipeline reconnect backoff. Update file read APIs with optional flag and cache control. Add /api/system/free-port endpoint, detectDevServer.ts utility, and preview/shutdown i18n strings for 5 languages.

* fix: harden preview support

* fix: keep terminal toolbar interactive

* fix: keep expanded terminal below header

* fix: keep preview iframe under proxy path

* fix: respect project action preview urls

* fix: rewrite preview asset urls

* feat: capture preview console logs

* feat: annotate preview elements

* feat: attach preview annotation screenshots

* fix: improve proxied preview hmr

* feat: refine preview action UX

* fix: address preview review feedback

* fix: show auto-discover preview wait state

---------

Co-authored-by: William Biggers <will@Williams-MacBook-Pro.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-30 00:03:38 +03:00
jwcrystal 9424cff02c fix: reconnect SSE immediately on OS wake-from-sleep (#1066)
* fix: reconnect SSE immediately on OS wake-from-sleep

When the desktop app resumes from OS sleep, TCP connections are dead
but timers were paused during sleep so the heartbeat watchdog doesn't
fire until ~30s after wake.

Add Electron powerMonitor.resume → renderer notification → event-pipeline
immediate abort, cutting reconnection delay from ~30s to ~0ms.

Changes:
- electron/main.mjs: import powerMonitor, emit openchamber:system-resume
  to all renderer windows on OS resume
- ui/sync/event-pipeline.ts: listen for openchamber:system-resume, set
  attemptAbortReason and abort the active SSE/WS attempt to trigger
  immediate reconnection with retryDelayMs=0 and lastEventId preservation

* fix: reconnect SSE immediately on OS wake-from-sleep

When the desktop app resumes from OS sleep, TCP connections are dead
but timers were paused during sleep so the heartbeat watchdog doesn't
fire until ~30s after wake.

Add Electron powerMonitor.resume → renderer notification → event-pipeline
immediate abort, cutting reconnection delay from ~30s to ~0ms.

Changes:
- electron/main.mjs: import powerMonitor, emit openchamber:system-resume
  to all renderer windows on OS resume
- ui/sync/event-pipeline.ts: listen for openchamber:system-resume via
  globalThis.window, set attemptAbortReason and abort the active SSE/WS
  attempt to trigger immediate reconnection with retryDelayMs=0 and
  lastEventId preservation
- Test: event-pipeline-resume.test.js verifies abort → reconnect flow
2026-04-29 12:19:31 +03:00
Bohdan Triapitsyn 958ffe063e fix: stabilize older message loading 2026-04-28 16:26:35 +03:00
Jinwoo An (안진우)andBohdan Triapitsyn 6470d9d205 fix: preserve lastEventId in SSE path and add proxy heartbeat (#1041)
* fix: preserve lastEventId in SSE path and add proxy heartbeat

- Extract event.id from SSE stream events in event-pipeline.ts so that
  reconnects carry the correct Last-Event-ID header for gapless replay.
- Emit :heartbeat comment every 20s in the direct SSE proxy to keep
  the UI heartbeat watchdog from aborting idle connections.

* fix: handle SSE metadata through SDK callback

* Guard SSE proxy heartbeats

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-27 14:16:44 +03:00
Islam NoflandBohdan Triapitsyn 8fdc1e7e55 fix: resolve sidebar stale state after worktree, folder, project, and session mutations (#1038)
* fix: update sidebar store after worktree creation

* fix: update sidebar store after worktree removal

* fix: update sidebar store with correct key in removeProjectWorktree

- Fixed key mismatch in sidebar store updates for worktree removal
- Removed worktrees now properly disappear from sidebar
- Aligned remove path with create path key resolution

* fix: resolve sidebar stale state after folder, worktree, project, and session mutations

* fix: now builds

* Fix worktree sidebar store key

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-27 13:42:52 +03:00
Dave OteroandBohdan Triapitsyn b62faadd15 Improve and unify the model picker across desktop and mobile (#1037)
* feat: improve agent and quick model picker behavior

* fix: keep the active model highlighted in the quick picker

* feat: streamline mobile model selection

Open the full mobile model picker directly and remove the intermediate controls drawer so mobile model changes follow the same core selection flow as desktop.

Add inline thinking-mode chips that show each model's remembered or default variant, apply model and variant together on tap, and fall back to a dedicated overflow panel for larger variant sets. Also keep favorites and recents searchable on mobile and fix clearing remembered default variants so the picker stays consistent across sessions.

* fix: polish desktop model picker interactions

Stabilize desktop model picker behavior by keeping keyboard and hover selection in sync, preventing hover-driven closes, and making the footer hints visually stable.

Also make quick-picker thinking mode changes apply consistently when switching plan/build or agent mode inside the picker, clamp left/right variant cycling at the ends, and keep thinking feedback visible even when the selected variant cannot move further.

* fix: condense mobile model picker rows

Tighten the mobile model picker to use a more compact, consistent row layout across favorites, recents, and provider sections while keeping context length and capability icons easy to scan.

Also preserve inline thinking-mode selection, improve metadata spacing, and keep the mobile controls readable without reintroducing the heavier drawer-based flow.

* fix: include all primary-like agents in picker cycling

Keep desktop Tab cycling and mobile tap cycling aligned with the rest of the selection UI by including agents marked as all or left unset, not just strict primary agents.

* fix: preserve remembered agent variants in picker flows

* Fix model picker variant restore

* Polish favorite model drag handle

* Fix Korean model picker locale

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-27 13:28:51 +03:00
septonbarth 2bc7b9b178 fix(ui): preserve slash command message ids (#1031) 2026-04-27 12:09:56 +03:00
Bohdan Triapitsyn 177631f123 fix: improve VS Code header context and settings visibility
Show language selection in VS Code settings
Display context usage in the VS Code chat header
Keep VS Code header usage controls visible in expanded layouts
2026-04-26 20:20:23 +03:00
Islam NoflandBohdan Triapitsyn 4523e9c486 perf: reduce re-renders, fix mobile keyboard handling, add chunk load recovery, and improve PATH management (#1028)
* fix: exclude file content from reverted prompt text

Revert and fork now restore only the user's original prompt, not server-injected file content
Uses existing isSyntheticPart helper for type-safe filtering

* fix: keep scrollbar visible when hovering over thumb

* fix: prevent ESC abort from triggering when terminal is focused

* fix: pass directory to permission/question reply calls so approvals actually resolve

* fix: default model selection not responding after Base UI migration

* fix: prevent modal content from shifting and clipping footer buttons

* fix: improve session switching performance and add sub-agent export with prompt collapse

Defer viewport anchor saving to eliminate ~800ms UI freeze when switching sessions
Add export dialog to include sub-agent tasks recursively in markdown export
Add collapse chevron button for expanded user prompts in sticky header

* fix: resolve sidebar scroll and TDZ crash in session sidebar

* perf: reduce CPU overhead and re-renders across chat, layout, and settings

* fix: position collapse button at top of message and prevent ESC abort in terminal

* fix: position collapse button at top and add padding only when expanded

* refactor: extract shared PATH utilities and mobile keyboard hook

* refactor: import shared path-utils in electron, use module-level style constants

- Electron now imports pathLooksUserConfigured/mergePathValues from
  shared path-utils.js instead of inline duplication
- ToolPart collapsedCustomStyle moved from useMemo([]) to module const

* fix: resolve remaining merge conflicts and type errors

- Remove duplicate variable declarations in SessionNodeItem
- Remove orphaned export callback body from conflict resolution
- Fix HelpDialog description -> descriptionKey (i18n rename)

* fix: resolve type-check and lint errors in session-actions.test.ts

- Added missing bun:test type declarations (beforeEach, mock, mock.module)
- Removed unused State import
- Replaced 'as any' casts with proper OpencodeClient and ChildStoreManager types
- Added eslint-disable for unused _ parameter in mock function

* fix PR 1028 export and PATH edge cases

* fix startup retry exhaustion state

* remove opencode package lock change

* fix sub-session rename cancellation

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-26 16:24:07 +03:00
Bohdan Triapitsyn d40041fccd Fix startup connection and chunk recovery 2026-04-25 16:56:37 +03:00
jwcrystalandBohdan Triapitsyn 5c7f5aa4f7 fix(sync): resync the active session after reconnect transitions (#1011)
* fix(sync): resync the active session after reconnect transitions

Include the viewed session in reconnect recovery and trigger a targeted resync on transport switches so the active chat catches up after missed live events.

Constraint: Keep the fix in the sync layer instead of adding ChatContainer-only recovery

Rejected: Widen reconnect heuristics for every cached session | broader recovery scope than needed

Confidence: high

Scope-risk: narrow

* Avoid no-op reconnect resync store writes

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-24 12:39:58 +03:00
Bohdan Triapitsyn 9d9af7e263 Improve event stream resilience 2026-04-24 12:30:11 +03:00
a9830cfcf1 perf: drastically improve cold-start, bundle size, and streaming performance (#1000)
* perf: drastically improve cold-start, bundle size, and streaming performance

Cold-start optimizations:
- main.tsx: Remove blocking await on prefs I/O — render immediately with
  defaults, hydrate persisted settings asynchronously. Cuts 50-200ms from
  time-to-first-paint.
- bootstrap.ts: Split directory bootstrap into 3 phases:
  * Phase 1 (blocking): path, config, provider, session status — minimum
    data needed to render UI. Mark status complete after this phase.
    path.get and session.status must both succeed; they have no fallback.
  * Phase 2 (deferred): agents, commands, mcp, lsp, vcs, questions,
    permissions — fetched after first paint without blocking.
  * Phase 3 (lazy): session messages — loaded without blocking init.
- App.tsx: Keep identical provider tree before/after init to prevent
  full subtree remount when isInitialized flips. FireworksProvider and
  VoiceProvider are lightweight shells; overlays deferred until init.

Bundle-size optimizations:
- App.tsx + MainLayout.tsx + VSCodeLayout.tsx: Code-split heavy views
  (SettingsView, GitView, DiffView, TerminalView, FilesView, PlanView,
  OnboardingScreen, SettingsWindow, MultiRunWindow) with React.lazy.
  Views load on demand when user switches panels.
- vite.config.ts: Lower chunkSizeWarningLimit from 1200KB to 500KB.

Streaming render optimizations:
- streaming.ts: Throttle streaming store writes ~60Hz → ~1Hz. Busy-session
  only scan (Set, O(1)).
- MessageList.tsx: Lower virtualization threshold 40 → 15.
- ChatMessage.tsx: React.memo with areRenderRelevantMessagesEqual.
- MarkdownRenderer.tsx: React.memo with explicit prop comparators.

* fix: address Greptile review feedback on bootstrap and provider tree

- bootstrap.ts: Tighten Phase 1 error guard. path.get and session.status
  must both succeed; they have no global fallback.
- bootstrap.ts: Replace dead .catch() on Promise.allSettled() with .then()
  that inspects individual results for errors.
- App.tsx: Keep identical provider tree before/after init to prevent full
  subtree remount when isInitialized flips.

* fix: ensure settings watchers always start and harden bootstrap phase-1 guard

- main.tsx: Start appearance/model/typography watchers unconditionally
  after initializing appearance preferences. Previously they only started
  inside the secondary settings Promise.all().then(), so a transient I/O
  failure would silently leave auto-save disabled for the entire session.

- VSCodeLayout.tsx: Move React.lazy SettingsView const after all imports
  to satisfy ESLint import/first rule.

- bootstrap.ts: Replace fragile hardcoded array indices in Phase 1 guard
  with destructuring so reordering the Promise.allSettled array won't
  silently break the critical-failure check.

---------

Co-authored-by: Shyamalan Kannan <yabuku@Shyamalans-MacBook-Pro.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-23 13:54:15 +03:00
Shyamalan KannanandShyamalan Kannan ecb22e19c3 perf: lazy-load heavy dependencies (MarkdownRenderer + CodeMirror languages) (#997)
* perf: drastically improve cold-start, bundle size, and streaming performance

Cold-start optimizations:
- main.tsx: Remove blocking await on prefs I/O — render immediately with
  defaults, hydrate persisted settings asynchronously. Cuts 50-200ms from
  time-to-first-paint.
- bootstrap.ts: Split directory bootstrap into 3 phases:
  * Phase 1 (blocking): path, config, provider, session status — minimum
    data needed to render UI. Mark status complete after this phase.
    path.get and session.status must both succeed; they have no fallback.
  * Phase 2 (deferred): agents, commands, mcp, lsp, vcs, questions,
    permissions — fetched after first paint without blocking.
  * Phase 3 (lazy): session messages — loaded without blocking init.
- App.tsx: Keep identical provider tree before/after init to prevent
  full subtree remount when isInitialized flips. FireworksProvider and
  VoiceProvider are lightweight shells; overlays deferred until init.

Bundle-size optimizations:
- App.tsx + MainLayout.tsx + VSCodeLayout.tsx: Code-split heavy views
  (SettingsView, GitView, DiffView, TerminalView, FilesView, PlanView,
  OnboardingScreen, SettingsWindow, MultiRunWindow) with React.lazy.
  Views load on demand when user switches panels.
- vite.config.ts: Lower chunkSizeWarningLimit from 1200KB to 500KB.

Streaming render optimizations:
- streaming.ts: Throttle streaming store writes ~60Hz → ~1Hz. Busy-session
  only scan (Set, O(1)).
- MessageList.tsx: Lower virtualization threshold 40 → 15.
- ChatMessage.tsx: React.memo with areRenderRelevantMessagesEqual.
- MarkdownRenderer.tsx: React.memo with explicit prop comparators.

* fix: address Greptile review feedback on bootstrap and provider tree

- bootstrap.ts: Tighten Phase 1 error guard. path.get and session.status
  must both succeed; they have no global fallback.
- bootstrap.ts: Replace dead .catch() on Promise.allSettled() with .then()
  that inspects individual results for errors.
- App.tsx: Keep identical provider tree before/after init to prevent full
  subtree remount when isInitialized flips.

* perf: lazy-load heavy dependencies (MarkdownRenderer + CodeMirror languages)

MarkdownRenderer dynamic import:
- Move heavy implementation (marked, react-markdown, beautiful-mermaid,
  react-syntax-highlighter, ~1500 lines) to MarkdownRendererImpl.tsx
- Replace MarkdownRenderer.tsx with thin lazy wrapper using React.lazy
- All 11 existing imports work unchanged — no consumer code modified
- Full markdown stack loads on first render of markdown content

CodeMirror language lazy loading:
- languageByExtension.ts: remove static imports for 10+ less-common
  language packages (@codemirror/lang-go, lang-rust, lang-sql, etc.)
- Keep only 6 most common languages static: javascript, json, css, html,
  markdown, python, shell
- Less common languages return null from languageByExtension, causing
  callers to fall back to loadLanguageByExtension which dynamically
  loads from @codemirror/language-data
- Reduces initial bundle by ~200KB+ of language parsers

---------

Co-authored-by: Shyamalan Kannan <yabuku@Shyamalans-MacBook-Pro.local>
2026-04-23 12:31:42 +03:00
Bohdan Triapitsyn f24e6de21b fix: improve event stream reconnect reliability
Recover stalled event streams without dropping the session
Wait briefly for reconnection before showing connection lost errors
Persist Electron server logs for easier disconnect debugging
2026-04-22 21:03:02 +03:00
jwcrystal 17dd526731 fix: eliminate parent-child session desync across reconnect and navigation (#985)
* fix(pipeline): distinguish transport switch from real disconnect

WS_FALLBACK errors (e.g. ready timeout → SSE fallback) are transport
switches, not disconnections. No events are lost because lastEventId is
preserved across the switch.

Previously, every WS timeout triggered onDisconnect → onReconnect with a
full resyncDirectoryAfterReconnect, which:
- Missed idle parent sessions in candidate selection (root cause 1)
- Could overwrite in-flight SSE state with stale fetch data (root cause 4)
- Caused isConnected to flash false→true

Now: WS_FALLBACK fires onTransportSwitch (sets isConnected only).
Real disconnections (heartbeat timeout, network error) still fire the
full onDisconnect → onReconnect → resync cycle.

* fix(sync): relationship-aware reconnect with merge-not-replace

Two changes to resyncDirectoryAfterReconnect:

1. Candidate selection now also includes parent sessions of any child
   sessions in the directory. Previously, if a child completed during
   the disconnect gap (busy→idle), neither child nor parent was selected
   because both appeared idle. The parent's task tool part would remain
   permanently stale.

2. Parent resync merges parts instead of replacing. Previously, the
   resync deleted parts for messages not in the fetch snapshot, which
   could erase parts delivered by SSE events that arrived between the
   fetch and the setState. Now only parts for messages in the snapshot
   are overwritten; everything else is preserved.

* fix(sync): demand-load child session messages on access

Bootstrap only loads session metadata — messages are populated
exclusively by SSE events. When a user navigates to an old session
that spawned subagents, child session messages were never in the store.

Add useEnsureSessionMessages hook that detects this gap (session exists
in state.session but state.message[sessionID] is absent) and triggers a
background API fetch to load messages and parts.

ToolPart already calls useSessionMessageRecords(taskSessionId) which
returns empty when not loaded. Now it also calls useEnsureSessionMessages
to populate the store on first access.

* fix(sync): unmount-safe parent resync when child session goes idle

When a child session transitions to idle (completes), the sync layer
now schedules a targeted parts repair for the parent session's task
tool part. Previously this only happened when the ToolPart component
was mounted and had observed the child being active (taskChildSeenActive).

This covers:
- User navigated away while child was running
- App restarted with active subagent sessions
- SSE reconnect where child completed during disconnect

Uses the existing repairSessionParts mechanism with its 5s cooldown
to avoid redundant fetches.

* fix: type-check fixes for sync-layer parent resync

Fix TypeScript errors in Fix 5 implementation:
- Convert currentSessionId from null to undefined for resolveFallbackTaskSessionId
- Add default empty string for dir parameter in getScopedSdkClient
- Use explicit sessionID parameter for scopedClient.session.messages

All type-checks now pass.

* fix(sync): address PR review feedback on deduplication

- Use enqueuePartsRepair for session.idle parent resync instead of
direct repairSessionParts call. enqueuePartsRepair already has a 5s
cooldown to prevent redundant parallel API calls when multiple child
sessions go idle concurrently.

- Move useEnsureSessionMessages loading guard from component-scoped
React.useRef to a module-level Set keyed by directory:sessionID.
Prevents parallel fetches when multiple ToolPart instances mount
for the same child session.

* fix(sync): add missing semicolon on useEnsureSessionMessages call

Address Greptile P2 review comment on ToolPart.tsx:1931.
2026-04-22 20:10:21 +03:00
Bohdan Triapitsyn 5ec7f46105 fix: surface disconnect reason, switch health probe to /global/health (#978)
- event-pipeline: onDisconnect now carries a reason tag (ws_closed:code=N, ws_error_frame, ws_closed_before_ready, sse_error)
- useConfigStore: store lastDisconnectReason, clear on reconnect
- send guards: embed reason in Connection lost toast so we can tell which path tripped
- lifecycle: isOpenCodeProcessHealthy hits /global/health (healthy flag) with 5s timeout instead of /session with 2s, avoids false restarts under stream load
2026-04-22 00:33:21 +03:00
jwcrystal 247dc0481e fix: allow git checkout with uncommitted files (#945) 2026-04-21 23:03:27 +03:00
jwcrystalandBohdan Triapitsyn d4a4f43a83 feat: show file change summary bar (#950)
* feat: add FileChangeSummary component for multi-file diff preview in ToolPart

Provides an aggregated diff card for apply_patch and multi-edit tools,
showing per-file stats with click-to-expand diff view.

* feat: add PendingChangesBar above chat input with collapse/expand and file opening

- Collapsed/expanded toggle with aggregate +N -N stats (green/red)
- Relative path display, chat-column alignment with ChatInput
- Click file to open in diff viewer (web/desktop) or editor (VS Code)
- Support edit/multiedit/apply_patch/write tool metadata extraction
- Add PendingChangesBar to main chat view (ChatContainer)

* feat: dual-mode ChangedFilesBar — Git diff state vs latest AI turn

Git mode: reads git status from useGitStore (status.files + diffStats),
auto-clears on commit/restore. Shows 'N files changed in workspace'.

Non-Git mode: latest assistant turn only (no accumulation), clears on new
user message or manual dismiss. Shows 'AI updated N files in the last reply'.

Both modes: dismiss button with signature-based tracking.
Add pendingChangesBarDismissed state to session-ui-store, cleared on sendMessage.

* fix: address code review issues in ChangedFilesBar

- Gate non-git mode on streaming state to prevent flicker during AI turns
- Return null when isGitRepo is unknown (loading state)
- Add group/row class for reject button visibility in FileChangeSummary
- Use per-session Map for dismiss tracking to prevent cross-session leaks
- Include additions/deletions in dismiss signature for re-edit detection
- Extract shared parsePatchStats/parseCount to fileChangeHelpers

* chore: revert local .opencode/package-lock.json changes from PR

* fix: per-part fallback guard and git-only reject button

- Fix extractChangedFiles to use per-part files.length snapshot instead
  of global guard, preventing file entries from being skipped when
  earlier parts already contributed files
- Hide reject button in FileChangeSummary when not in a git repo,
  preventing silent revert failures

* refactor: remove FileChangeSummary — dead code redundant with ToolPart

FileChangeSummary duplicated diff rendering that ToolPart already
provides (PatchDiff, per-file stats, DiffViewToggle). The only unique
feature was a git revert button, which conflicts with the design
principle of not having accept/reject on file change previews.

Moved parsePatchStats/parseCount back into PendingChangesBar (sole
consumer) and deleted the shared helper module.

* chore: remove stray Tester.txt

* fix: deduplicate Fallback 4 'Diff' placeholder via seen set

* chore: update non-Git mode copy to neutral 'changed in the last reply'

* fix(chat): unify changes row with tasks

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-21 22:34:06 +03:00
Bohdan Triapitsyn 34a8cb3f3b fix: don't silently swallow bootstrap SDK errors
sdk.path.get() / sdk.project.list() / sdk.app.agents() etc. return
{ data, error, response } without throwing on non-2xx. The x.data! /
x.data ?? [] pattern turned HTTP 5xx warmup responses into empty state,
and retry() never saw an error to retry.

Wrap everything through unwrap(): preserve the HTTP status on the
thrown Error so retry()'s transient detection (5xx) fires. Fixes the
class of bugs where a slow opencode sidecar leaves agents/commands/
sessions looking empty after startup.
2026-04-20 22:28:41 +03:00
Bohdan Triapitsyn f1b84e3291 fix: always signal connected state on first successful connect
markConnected skipped onReconnect when hasConnected was false and there
was no prior disconnect. Consumer state (isConnected) starts at false and
stayed false — the send button threw "Connection lost" until the HTTP
health check in initializeApp happened to race a setState through.

Now onReconnect fires on every successful connect; hasConnected is no
longer needed and gets removed.
2026-04-20 22:28:18 +03:00
Bohdan Triapitsyn 895edaf557 fix: preserve sessions when bootstrap races with event stream
Two fixes for intermittent "sessions missing at app launch":

- loadSessions: if session.list comes back empty but the store already
  has sessions populated via WS events, don't clobber. OpenCode can
  answer HTTP with [] while the WS snapshot is still arriving.
- loadSessions: wrap SDK errors preserving HTTP status so retry()'s
  transient detection (5xx) actually fires. String(errorObject) was
  erasing the status and retry gave up after one attempt.
- retry: broaden transient 5xx window from just 502/503 to any 5xx.
  OpenCode can return 500/504 while warming up (session store reading
  from disk), and those deserve the same retry treatment.
- Log bootstrap empty-session retries for future diagnosis.
2026-04-20 21:42:16 +03:00
Bohdan Triapitsyn c2eeadd9a3 fix: unstick isConnected when first connect follows a disconnect
markConnected skipped onReconnect on the very first successful connect
(hasConnected=false branch), but an earlier failed attempt could have
already fired onDisconnect -> isConnected=false. Consumer state stayed
stuck at false forever. Fire onReconnect in that case too.

Repro: launch app while opencode sidecar is still warming up so the
first WS attempt fails. Second attempt succeeds but isConnected stays
false; user sees "Connection lost" toast on every send.
2026-04-20 21:42:04 +03:00