Commit Graph
13 Commits
Author SHA1 Message Date
jwcrystal 291d00de8b fix: remove shell-specific \&\& operators for non-POSIX shell compat (#870) (#888)
Replace shell `\&\&` and `||` operators in execCommand calls with
TypeScript control flow. This fixes re-integrate commits failing when
the user's default shell is Nushell, which does not support `\&\&`.

Changes:
- createTempWorktree: split `mkdir \&\& mktemp` into two calls
- ensureLocalBranch: use isOk() instead of `echo ok || echo missing`
- isCherryPickInProgress: use isOk() instead of `echo yes || echo no`

Closes #870
2026-04-11 23:43:57 +03:00
jwcrystal 6425161bef fix(sidebar): auto-expand parent node when navigating to subagent session (#893)
When a user navigates to a subagent (child) session, the parent session
node in the left sidebar was not automatically expanded, making the
child session invisible in the tree. This change adds a useEffect that
detects when the current session has a parentID and automatically adds
that parentID to expandedParents, ensuring the subagent session is
visible in the sidebar hierarchy.
2026-04-11 23:42:02 +03:00
jwcrystal 964c209ff6 fix(sync): remove stale-delta skip and add parts-gap recovery (#889)
The pipeline's stale-delta mechanism incorrectly marked all
message.part.delta events as stale when a message.part.updated
coalesced, regardless of queue position. This caused valid streaming
deltas to be silently dropped, resulting in blank or incomplete
assistant messages.

Additionally, when part events were dropped by the reducer (missing
parts array or partID not found), there was no recovery path — the
state stayed permanently out of sync until the next SSE reconnect
or manual refresh.

Also discovered: message.updated that successfully writes an assistant
message but has empty parts would render a blank bubble, with no
repair triggered since repair only ran on reducer return false.

Changes:
- Remove staleDeltas Set and deltaKey from event-pipeline.ts
- Coalesce still replaces same-key events, but deltas are never skipped
- Add enqueuePartsRepair + repairSessionParts to sync-context.tsx
  (5s cooldown, deduped, async SDK re-fetch)
- Trigger repair on reducer return false for part events
- Trigger repair on message.updated return true with empty parts
- Add sync debug.ts with gated diagnostic logging
- Add pipeline coalescing tests
2026-04-11 23:40:47 +03:00
jwcrystal 700138c9b5 fix(git): restore changes panel visibility and sidebar sync (#886)
* fix: restore git changes panel visibility and sidebar sync

Two independent fixes:

1. ChangesSection virtualizer returned empty rows due to useMemo caching
   getVirtualItems() with a stale stable reference. First render produced
   an empty array, and useMemo never recomputed because rowVirtualizer
   reference never changed. Removed the useMemo to call getVirtualItems()
   directly on every render. Also added a ResizeObserver to force
   remeasurement on visibility transitions (defensive).

2. RightSidebarTabs now keeps git status fresh while the sidebar is open
   via useRightSidebarGitSync hook (10s polling with ensureStatus).
   Replaces the GitPollingProvider removed in commit d9821716.

* fix(virtualizer): prevent React error #185 from render-phase getVirtualItems

Restoring useMemo for virtualRows with totalSize as an invalidation
dependency. Calling getVirtualItems() directly during render triggers
the virtualizer's maybeNotify() → onChange() → useReducer dispatch,
causing React error #185 (https://react.dev/errors/185 — cannot
update a component while rendering a different component).

The original useMemo([rowVirtualizer, shouldVirtualize]) was removed
because rowVirtualizer is a stable useState ref that never changes,
leaving the memo permanently stale after the first empty render.
Adding totalSize (from getTotalSize()) as a dependency solves this:
it changes whenever the virtualizer recalculates after measure/scroll,
ensuring getVirtualItems() returns fresh rows while staying wrapped
in useMemo.
2026-04-11 23:29:35 +03:00
jwcrystal b91a72c74b fix(chat): replace hover bridge with padding to unblock desktop interactions (#885)
* 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
2026-04-11 23:27:47 +03:00
jwcrystal 5ea2fed0d3 fix: hide empty archived section and folders (#872)
* fix: hide archived section and empty folders when no sessions remain

- Only push archived group in useSessionGrouping when there are archived
  sessions, preventing an empty archived section from rendering
- Hide empty folders in archived bucket via shouldKeepFolder check in
  SessionGroupSection (folders with no sessions and no content in
  children are filtered out)
- Always filter folders through shouldKeepFolder, not just during search

* perf: memoize archived folder filtering
2026-04-11 23:25:33 +03:00
jwcrystal 4fd1d68eb9 fix(chat): allow revert button hover on short user messages (#826) 2026-04-08 10:44:17 +03:00
jwcrystalandBohdan Triapitsyn 4cb918f6cb fix: implement loading timeout, SSE reconnect, and message retry (#857)
* 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>
2026-04-07 15:46:06 +03:00
jwcrystalandBohdan Triapitsyn e63450ae2c fix: resync session state after SSE reconnect to prevent stuck subagent UI (#817)
* 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>
2026-04-03 00:29:49 +03:00
jwcrystal aa071556bf fix(worktree): fix worktree detection and state reset when switching (#779)
* 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.
2026-04-01 18:33:49 +03:00
jwcrystalandBohdan Triapitsyn 58d7713581 fix(server): strip compression headers in generic OpenCode proxy (#795)
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-04-01 09:45:19 +03:00
jwcrystal fd31a8cc2d fix(desktop): auto-cleanup stale server processes on startup (#711)
When updating OpenChamber, stale openchamber-server processes from previous
versions can prevent the new version from starting. The app shows a loading
screen indefinitely with no error message.

This change adds a kill_stale_sidecar_processes() function that terminates
any existing openchamber-server processes before spawning a new one, ensuring
a clean startup every time.

- macOS/Linux: uses pkill -x for exact process name match
- Windows: uses taskkill /F /IM

Fixes startup issues after version updates.
2026-03-20 13:39:29 +02:00
jwcrystalandBohdan Triapitsyn bf61ccedc7 fix: external links in desktop app - context menu and open behavior (#716)
* 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>
2026-03-20 13:11:45 +02:00