Move session export into the sidebar menu
Add desktop save-and-reveal flow for exported markdown
Show empty-state UI instead of a loading skeleton for empty sessions
Add a download button in the chat view that exports the current session's
messages as a formatted Markdown file. User and assistant turns are
separated by horizontal rules. The filename is derived from the session
title and today's date (e.g. fix-login-bug-2026-04-17.md).
Add "Copy Relative Path" menu item below "Copy Path" in the file tree
context menu. Uses the existing getDisplayPath helper to compute a path
relative to the project root, which is more useful when sharing file
references in AI chat or commit messages.
Show the empty chat logo when a session has no messages
Keep skeletons only for real session hydration
Avoid misleading loading UI after reverting the last message
Move go-to-line UI into the editor action bar for a unified layout
Keep toolbar height stable with compact inline line input and Go action
Align file action icons to a single always-enabled visual style
Option+G now matches correctly on macOS keyboard layouts
Go-to-line shortcut no longer gets blocked by editor textbox guards
Prevents accidental symbol insertion when triggering go-to-line
Add a Go to Line dialog to the file editor that accepts a line number
and scrolls the CodeMirror editor to that position. Triggered via:
- Keyboard shortcut: Alt+G (macOS/Windows/Linux)
- Toolbar button next to the existing Find in file button
* 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>
Introduce a dedicated Quick Open dialog, wire it to Cmd+P and the macOS app menu, and show file-type icons in quick-open results so file navigation matches the rest of the app.
* 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>
* feat: keep desktop app running in background when closing last window
Closing last window hides it instead of quitting — sidecar keeps running
Cmd+Q now shows confirmation dialog warning about stopping background processes
Clicking dock icon reopens hidden window or creates a new one
* docs: add scheduled tasks impl plan
* feat: add scheduled tasks runtime, api, and ui
* feat: conditionally confirm desktop quit on risks
* chore: remove scheduled tasks plan doc
* feat: add scheduled tasks runtime and management UI
Add server-side scheduled task runtime with project-backed config persistence
Add task scheduling UI and API integration for creating and editing schedules
Add tests for runtime scheduling behavior and project config validation
* feat: add locale display preferences for scheduled tasks
Add Appearance settings for time format and week start with settings.json persistence
Apply preferences in scheduled task editor for time display and weekday ordering
Rename Thinking level control and disable it when model variants are unavailable
* feat: improve scheduled tasks editor and sidebar action order
Reorder session sidebar header actions to separate creation and management tools
Polish scheduled tasks dialog layout and controls for clearer editing flow
* feat: polish scheduled task editor usability
Improve scheduled task dialog layout for clearer scheduling controls
Refine time and weekday inputs for more intuitive task configuration
Update editor labels and control states for better model variant guidance
* feat: add prompt autocomplete and command-aware scheduled runs
Add @ and / autocomplete support to task, multi-run, and agent manager prompt fields
Fix agent mention selection so subagents can be inserted from @ suggestions
Run scheduled prompts as commands when they match slash commands, with message fallback
* 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(sync): deduplicate overlapping delta after coalesced part.updated
When message.part.updated coalesces in the event pipeline and a
message.part.delta for the same part arrives in the same flush window,
the reducer appends the delta verbatim to the already-complete field
value, producing duplicated text in tool output and assistant messages.
Add targeted overlap reconciliation: only when a part.updated replaces
an existing part with overlapping string content, mark the next delta
for that field as dedupe-eligible. Normal streaming deltas remain
untouched (pure append).
Covers four cases:
- Full overlap: delta already present -> no-op
- Partial overlap: only non-overlapping suffix appended
- No overlap: unchanged append behavior
- Legitimate repeated output (ha + ha -> haha): preserved
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
Prefer graceful process termination before taskkill fallback
Force ConPTY for Windows terminal sessions to avoid console flashes
Keep git and server process operations running without visible command windows
* feat: implement desktop boot outcome architecture
- Add structured DesktopBootOutcome with target/status fields
- Implement boot outcome computation and validation
- Add desktop hosts configuration management (Tauri + TypeScript)
- Add desktop hosts probing with timeout and retry logic
- Support local/remote host classification and health checks
This provides the foundational infrastructure for desktop onboarding
flow to determine whether to show local setup, remote connection,
or recovery screens based on OpenCode availability and remote host
reachability.
* feat: add desktop onboarding UI components
Add comprehensive onboarding flow for desktop app:
- ChooserScreen: First-launch local/remote selection
- LocalSetupScreen: CLI installation guidance and manual detection
- RecoveryScreen: Recovery mode with routing to local/remote
- RemoteConnectionForm: Remote host connection with validation
- DesktopConnectionRecovery: Recovery variants and routing logic
- ConnectionSettingsPage: Manage remote connections
Components handle:
- Local vs remote choice persistence
- Recovery scenarios (unreachable, wrong-service, missing)
- Manual CLI detection (replaced auto-polling)
- Back navigation and state preservation
* feat: integrate desktop onboarding with app shell
- Update App.tsx to handle onboarding routing and recovery
- Add onboarding mode switching (first-launch/local-setup/recovery)
- Integrate desktop hosts in SettingsView
- Update DesktopHostSwitcher with recovery routing
- Add desktop shell utilities for onboarding detection
- Update web manifest for desktop app metadata
Completes the desktop onboarding feature integration,
allowing users to choose local or remote OpenCode on
first launch and recover from connection failures.
* fix: hide back button in remote connection form for first-launch chooser
In first-launch chooser mode, the back button is redundant since users
can simply click the "Local Install" tab. The back button is still shown
in recovery mode where there's no tab interface.
Changes:
- Add showBackButton prop to RemoteConnectionForm (default: true)
- Set showBackButton={false} in ChooserScreen remote tab
- Keep showBackButton={true} in RecoveryScreen for navigation
* refactor: remove Connection Settings page and simplify recovery UI
Remove the Connection Settings page as it was redundant:
- Local server is single-instance (no need to "choose")
- Remote servers are one-time setup (first-launch chooser)
- SSH Instances remain for multi-instance management
Changes:
- Remove ConnectionSettingsPage component and directory
- Remove 'connection' from Settings metadata
- Remove "Open Settings" button from recovery screens
- Remove desktopBootBypassToSettings state and logic
- Update recovery config to use 'local' icon instead of 'settings'
- Update tests to reflect removed showOpenSettings field
This simplifies the UX by focusing on:
- First-launch chooser for initial local/remote decision
- Remote Instances (SSH) for managing multiple remote machines
- No persistent "server management" needed for typical desktop usage
* fix: remove unused enableCliPolling prop and clean up TypeScript errors
Remove the obsolete enableCliPolling prop that was used for auto-
polling CLI detection. We replaced this with manual "Check and Continue"
button in a previous commit, so this prop is no longer needed.
Changes:
- Remove enableCliPolling from OnboardingScreen props and usage
- Remove enableCliPolling from App.tsx calls
- Remove unused 'connection' case from getSettingsNavIcon()
- Remove unused RiGlobalLine import
This resolves all TypeScript compilation errors reported by Copilot.
* fix: remove unused onChooseLocal prop and CLI_MISSING_ERROR_REGEX
These were left over from the refactoring:
- onChooseLocal in RecoveryScreen was defined but never used
- CLI_MISSING_ERROR_REGEX in App.tsx was leftover from removed enableCliPolling code
* fix: remove unused variables and fix React Hook dependency warnings
Remove unused memoized components and variables that were causing
lint errors in packages/ui:
- MainLayout.tsx: Remove unused MemoHeader, MemoChatView, MemoPlanView,
MemoGitView, MemoDiffView, MemoTerminalView, MemoFilesView,
MemoRightSidebarTabs, DesktopLeftSidebar, and DesktopRightPanel
- useGitHubPrStatusStore.ts: Remove unused prVisualPriority function
- useChatScrollManager.ts: Add missing markProgrammaticScroll dependency
to React.useEffect hook
These fixes resolve the CI lint failures in PR 850.
* chore: remove local claude settings from repo
* refactor(desktop): drop vibrancy code from onboarding PR
---------
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: 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
* perf(sync): per-directory event queues to eliminate cross-session HoL blocking
The SSE event pipeline previously used a single global queue and a single
flush timer shared across all directories. Under concurrent multi-session
workloads, a busy directory's delta storm would block other directories'
status and state events from reaching the UI until the next flush tick,
producing the "multi-session latency" symptom users report.
Split the queue into one DirectoryQueue per directory, each with its own
coalesce map, stale-delta set, and flush timer. Directories flush
independently so a busy directory can no longer starve a quiet one. Coalesce
keys are now scoped to a single directory's queue, so the directory prefix
is removed from the key strings.
Cross-directory behavior only; same-directory multi-session behavior is
unchanged (React 18 auto-batching still collapses a single directory's
flush into one render).
* perf(sync): coalesce consecutive message.part.delta events per flush window
Within a 16ms flush window, consecutive delta events for the same
(messageID, partID, field) tuple are string-concatenated into a single
accumulated delta rather than being queued individually.
This directly addresses same-project multi-session workloads — most
notably parent sessions with subagent tasks (child sessions share the
same directory queue). Both parties stream deltas concurrently, which
previously multiplied raw event count proportionally to the number of
active sessions. Coalescing can reduce queue depth by 10-100x during
active streaming.
Safety: verified against event-reducer.ts — the delta handler is a pure
string append (existingValue + props.delta) with no per-event side
effects (no time.updated, no notifications, no diff calculations). The
merged result is semantically identical to applying each delta separately.
The staleDeltas skip mechanism is unaffected: accumulated delta payloads
retain their type and identifiers, so message.part.updated supersession
still works correctly.
* test(sync): cover per-directory queues and delta coalescing
Extend event-pipeline.test.js with behavioural coverage for both
optimizations landed in 98d013a and 258acf0:
P1 (per-directory queues)
- Delivers events from two directories without loss
- Keeps distinct sessionIDs in the same directory as independent coalesce
slots (session.status is not overwritten across sessions)
- Collapses repeated session.status for the same session down to latest
Option C (delta coalescing)
- Accumulates consecutive deltas for the same (messageID, partID, field)
into a single dispatched event with concatenated content
- Does not merge deltas across different fields on the same part
- Does not merge deltas across different parts on the same message
- Does not merge deltas across different directories (per-dir queues)
- Skips accumulated deltas when message.part.updated is coalesced onto
an earlier update, proving staleDeltas still works with C
- Leaves non-delta coalescing (session.status replace semantics) intact
All 13 tests pass under bun:test.
Also adds event-pipeline.bench.js, a runnable synthetic benchmark that
reports delta reduction and byte integrity across 8 workload scenarios
from "single session, 500 tokens" up to "10 projects × 5 sessions ×
1000 tokens". Run with:
bun packages/ui/src/sync/__tests__/event-pipeline.bench.js
Current numbers on this machine: 99.5% - 99.9% delta event reduction
with full byte-level integrity (concatenated delta bytes always equal
the input total).
* fix(sync): remove staleDeltas — it silently drops delta events
---------
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>
- Fixes header drag behavior in Desktop when connected to remote hosts
- Routes all window-drag entry points through a shared safe desktop helper
- Keeps local desktop drag behavior unchanged while avoiding remote IPC failures
Validate and normalize custom TTS server URLs before proxying requests.
Gate server TTS availability by provider mode to avoid OpenAI/custom misrouting.
Keep browser and message TTS hooks aligned with the new provider-specific checks.
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.
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
- Add Kiro to OPEN_IN_APPS list with id 'kiro' and appName 'Kiro'
- This enables users to open files and projects directly in Kiro IDE
via the Open in menu, similar to VS Code, Cursor, and Windsurf
* 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