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
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.
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
* 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.
* 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
* 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
* 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>
* 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>
* 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.
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.
* 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>