17af1f3369bb527915519bb12bf726ea6d473710
126
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bfaf62e222 |
fix(sidebar): add project sort modes (#2067)
* fix(sidebar): add project sort modes * fix(sidebar): preserve manual sort order * fix(sidebar): move sort state before render --------- Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com> |
||
|
|
a1aae30e66 |
Share project edit form; add per-project default model (#2015)
* feat(chat): migrate history list to @tanstack/react-virtual with deterministic mobile history loading - Replace virtua with @tanstack/react-virtual for chat history on all surfaces: bottom anchoring (anchorTo: end), key-stable prepend preservation, and native iOS touch/momentum deferral live in the core - Patch virtual-core to clamp the render range to real scroll bounds during transient adjustments (OpenCode upstream parity) - Rows render in normal flow inside a translated wrapper so sticky user headers keep working; measurement snapshots cached per session - Pre-write container height in scrollToFn so the browser cannot clamp anchor corrections to the stale height; hold the prepend anchor for up to 180 frames on mobile while fresh rows settle (cancelled by user input; desktop relies on core anchoring alone) - Adaptive row-size estimate from per-session measured averages; disable reveal fade-in for virtualized history rows - Mobile loads older history only through an explicit localized top button: no scroll-position trigger and no post-mount background prepend, so every insert happens from a resting state; a quiet-window hold defers any stray prepend commit while a touch gesture is active - Desktop/VS Code keep the seamless scroll-up trigger and progressive background prepend * Share project edit form between settings and sidebar dialog Extract ProjectIdentityFields and useProjectIdentityForm so the projects settings page and sidebar Edit dialog share the same layout and behavior. Rename the project menu action from Rename to Edit, and add per-project default model selection for new chats with persistence and draft-session resolution ahead of global defaults. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * Unify project edit UI with shared ProjectIdentityEditor shell Wrap header, fields, and inline Save changes button in one editor component used identically by settings projects page and sidebar dialog. Remove dialog-specific footer, title, and padding so both surfaces render the same layout. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * Include Actions and Worktree sections in project Edit dialog Extract ProjectSettingsPanel with the full settings=projects content (identity, actions, worktree) and render it from both the settings page and sidebar Edit dialog. Keep the dialog open after identity save so users can configure actions and worktrees without reopening. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * Narrow project Edit dialog to modal-appropriate width Use max-w-2xl instead of max-w-4xl so the popup does not inherit the full settings page width. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * Unify project settings subsections and auto-save all fields - Add shared ProjectSettingsSubsection with consistent titles and dividers - Auto-save identity, actions, and worktree setup commands (debounced) - Remove Save changes and Save Actions buttons - Split worktree into Worktree and Existing worktrees subsections - Align controls to shared max width across all subsections Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * Harden project settings auto-save error handling - Only update worktree setup snapshot after successful save; toast on failure - Toast when actions auto-save is blocked by validation for >1s Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * Show toast when project identity auto-save fails Wrap onSave in try/catch and surface settings.projects.page.toast.saveFailed so rejected parent callbacks are not silently swallowed. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> * Fix clearing project default model from settings Send null instead of undefined when no default model is selected so updateProjectMeta enters the defaultModel branch and deletes the field. Apply consistently in prepareSaveData, ProjectsPage, and SessionSidebar. Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com> |
||
|
|
086982c1b3 |
feat: add temporary sidebar share opinion prompt
Adds a dialog with call booking and survey actions for user feedback Shows a one-time sidebar toast prompting users to share what’s useful or missing Adds localized copy for the new feedback entry points |
||
|
|
1505274f94 |
perf(stores): defer safeStorage writes off the interaction path (#1941)
* perf(stores): defer safeStorage writes off the interaction path Session switches funnel every persisted store slice through safeStorage.setItem, and doing those large JSON.stringify writes synchronously blocked the main thread for over a second. Add a write-behind buffer that: - Defers each setItem/removeItem to a later task via setTimeout(0) so the click-to-paint path is not blocked. - Coalesces repeated writes to the same key into a single backing flush. - Serves pending values from memory so read-after-write stays consistent within the deferral window. - Flushes synchronously on pagehide/beforeunload/visibilitychange/freeze so deferred state survives tab close, reload, and the mobile freeze lifecycle. Adds a test covering write deferral, coalescing, and pending read serving. * fix(stores): defer persisted JSON serialization * fix(stores): defer direct safeStorage writes --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com> |
||
|
|
a25fc4c25a |
fix(sync): reflect share status from global store after cancel (#1709)
* fix(sync): reflect share status from global store after cancel Fix #1551: unshareSession() called updateLiveSession() which silently fails when the child store doesn't exist. The sidebar rendered from the child store first, showing stale share data. Now overlays the global session's share field at merge points. * fix(sync): extract shared mergeLiveSessionWithGlobalSession helper Extracted the share-field overlay into a single shared helper in useGlobalSessionsStore.ts. All 3 merge sites now use the helper instead of duplicating the overlay logic. * test(sync): add unit tests for mergeLiveSessionWithGlobalSession helper --------- Co-authored-by: Leonid Skorobogatyy <bash@opencode.itc.local> |
||
|
|
59ecd86b4b |
perf: isolate chat streaming renders and reduce sidebar render cost (#1672)
Reworks the chat and session-sidebar render paths to cut render cascades, memory
churn, and UI jank on large sessions and big session trees. Behavior is preserved;
the changes are about *when* and *how much* the UI re-renders.
## Chat streaming
- Freeze the streaming message's parts in the bulk turn projection during streaming,
and re-inject live parts only in an isolated tail leaf, so a ~60/sec delta stream
no longer re-runs the whole-session projection or re-renders unrelated rows.
session with referential reuse of unchanged turns.
- Memoize message rows with field-aware comparators instead of reference equality.
- Replace the manual child-session polling in the task tool with the live SSE
stream + a one-shot load, removing a fetch/settle state machine.
## History loading & scroll
- Load an initial page fast, then prepend one older page in the background so the
scroll container has headroom and "load older on scroll-up" fires before the user
hits the absolute top.
- Compensate scroll synchronously (in a layout effect, before paint) for prepends —
including background prepends that don't originate from a user scroll — so the
viewport stays stable instead of judder-correcting on the next frame.
## Markdown rendering
- Render markdown synchronously *styled* on first paint (paragraphs, lists, code
cards, tables, inline code) instead of raw escaped text; the async pass then only
upgrades syntax-highlight colors. Eliminates the flash of full-width raw text.
- Load KaTeX CSS eagerly with the main bundle instead of inside the lazy markdown
chunk, avoiding a late stylesheet injection on first render.
## Sidebar
- Hoist per-row recursive tree walks out of row comparators into per-group
precomputed sets/keys; batch live-session lookups into a single map; add a
group-level memo boundary.
- Isolate rename drafts so per-keystroke typing doesn't repaint the row tree.
## Sync layer
- Add a staleness guard so a slow message fetch can't repopulate a session the user
navigated away from.
- Throw on fetch failure for authoritative loaders so a transient blip can't read as
an empty server response.
## Cleanup
- Remove dead code (unused hooks, params, duplicated inline types) surfaced while
reworking the above.
## Known issue
- A rare, purely cosmetic first-paint width flash can still appear on large sessions;
it has no behavioral or data impact and is tracked for a follow-up runtime trace.
|
||
|
|
d909c43979 |
refactor(sidebar): remove dead directoryStatus probe artifact (#1660)
Commit
|
||
|
|
c73ab9cbd5 |
feat(vscode): startup parity + workspace-grouped session list (#1658)
* perf(vscode): gate API readiness and coalesce duplicate startup reads Bring the VS Code bridge runtime to parity with the web startup optimizations (PR #1650), which were web/desktop-only. waitForApiUrl now hands out the OpenCode API URL only once the manager reports 'connected', instead of as soon as getApiUrl() exposes server.url. The URL is available the moment the process is spawned — before waitForReady confirms it can serve and during a workspace-switch restart (stale port) — so URL-presence alone let the bridge forward to a not-yet-ready OpenCode and surface 502s. Gating on connected status mirrors the web proxy's isOpenCodeReady hold. Also fail fast on 'error' status so a missing CLI doesn't burn the full 30s timeout. Coalesce concurrent identical GET reads (config/path/agents/agent/ project/command) at the bridge proxy so the single OpenCode process serves them once. On cold start the webview's sync bootstrap and config store fire these reads in parallel with no shared dedup; this is the extension-host analog of the runtimeFetch coalescer. Shared reads carry no AbortController so one caller's abort can't strand the others, and the entry clears as soon as it settles (never serves stale). * perf(vscode): fade the startup splash once mounted + connected, not on live fetch The webview's initial-loading overlay held until a successful live /api/config/providers AND /api/agent fetch completed. After the cache hydration work those live reads are the slowest cold-start tail — the UI underneath already paints pickers and the sidebar from cache and refreshes in the background — so gating the splash on them kept it spinning long after the app was usable. Fade the overlay as soon as the UI is mounted and OpenCode is connected. Per-widget loaders convey any remaining background refresh, matching how web/desktop (which have no such splash) already behave. Removes the now -obsolete bootstrapProvidersReady/AgentsReady/Failed tracking and recordBootstrapFetch. Connection error/disconnected splash messages are unchanged. * fix(vscode): include captured OpenCode output in spawn-timeout error When the managed OpenCode server fails to emit its 'listening' line within the start timeout, the error discarded everything the process printed to stdout/stderr — so the status report showed a bare 'Timeout waiting for server to start' with no clue whether the process hung, crashed silently, or printed a config/auth error. The exit path already includes the output; the timeout path now does too (or notes that nothing was printed). * feat(vscode): workspace-grouped session list with working folders, pinning, and archived toggle Replace the flat multi-workspace session list with the grouped project view, using each open VS Code workspace folder as a header (no per-worktree subgroups). This restores native folder and pin support, which the flat list silently dropped, and fixes the clipped left padding on session rows. - Group sessions strictly by open workspace; funnel all non-archived sessions into the workspace's group so they no longer fall into the archived bucket. - Keep the project/group/folder + buttons but make them open a draft in the correct workspace and navigate to chat; hide the project actions (...) menu, which isn't relevant in VS Code. - Force the minimal single-line row layout (the second metadata row is redundant under workspace headers) and drop the per-row tooltip. - Add a show/hide archived toggle next to the archive-all control, since the VS Code header has no display-mode menu. - Size the hover action reveal so the timestamp clears the row buttons. |
||
|
|
560e24cbc2 |
fix: prevent worktree modal from disappearing after opening (#1585)
The worktree dialog state was local React state in SessionSidebar, which gets destroyed when the component unmounts (mobile drawer close, VSCode view switch). This caused the modal to briefly appear then disappear. Three changes: - Move newWorktreeDialogOpen from local state to useUIStore so it survives component unmount - Guard useProjectSessionSelection layout effect to skip when the worktree dialog is open (prevents auto-session-selection from closing the sidebar) - Remove setSessionSwitcherOpen(false) from worktree button handler on mobile (prevents drawer close that unmounts the sidebar) Fixes #1414 |
||
|
|
c281937406 |
refactor(recent): replace active-now tracking with recent session window
Replace persisted 'active now' tracking with 48-hour recency window Remove Zustand ActiveNowStore and localStorage persistence Simplify session sidebar data flow |
||
|
|
7b33805ea0 |
feat(vscode): support multi-root workspaces (#1493)
Add proper VS Code multi-root workspace support. New sessions now start in the workspace folder the user chooses instead of always using the first folder. The VS Code sidebar now shows one shared flat session list for the currently opened workspace folders, keeps that list synced when folders are added or removed, and excludes sessions from worktrees unless that worktree is opened as a workspace folder. Also keep the OpenCode server process independent from a specific workspace folder so changing the selected folder does not restart or interrupt existing sessions. |
||
|
|
e0113c637d |
feat: support fast worktree-backed session flows
Add a directory-created fast path for worktree creation so session and send flows can continue once the target directory exists while Git attachment and bootstrap finish in the background. Track bootstrap status explicitly in shared UI contracts, including pending, ready, and failed states. Background watchers now surface failures and timeouts, update stored worktree metadata, and keep web and VS Code runtime behavior in parity. Move GitHub issue/PR worktree sessions and assistant-answer fork sessions onto the unified send path so provider, model, agent, and variant selections are preserved. The assistant-answer fork dialog can optionally create a worktree outside VS Code. Make worktree deletion dialogs close after linked-session cleanup while removing the worktree in the background, and clean up failed fast-create artifacts safely without recursively deleting user or agent-written files. Validation: bun test packages/ui/src/lib/worktrees/worktreeBootstrap.test.ts packages/ui/src/lib/worktrees/worktreeManager.test.ts; bun run type-check; bun run lint. |
||
|
|
48b0a4f292 |
fix: preserve chat folder assignments on reload
Prevents folder cleanup before sessions finish loading Keeps saved chat folder memberships after desktop reload |
||
|
|
837a47b6a3 |
fix: keep session titles and lists in sync
Update sidebar titles from session events Load all session pages instead of partial lists Refresh sessions only for newly added project directories |
||
|
|
570ae9dbc8 |
fix: keep worktree sessions in the right group
Prevents stale worktree lists from overwriting newly created worktrees Uses the created worktree path when selecting linked worktree sessions |
||
|
|
a0b22c9960 |
fix: show recent sessions in project groups
Keeps Recent as an extra shortcut list Restores sessions under their project and worktree groups |
||
|
|
8e25bc4cce |
fix: keep new sessions grouped by project
Preserves session directory metadata across live updates Keeps desktop and mobile session lists in project groups Adds regression coverage for session grouping |
||
|
|
2f3d5107e8 |
feat: improve session sidebar archive and pagination controls
Add a toggle for displaying archived sessions in the sidebar menu Load more sessions in smaller increments Reset expanded session counts when collapsing groups or projects |
||
|
|
32ab46c567 | feat: refine VS Code session UI | ||
|
|
c444415cb2 |
feat: brought back mobile new session sidebar action
Adds a mobile-only new session button to the sidebar header Opens a draft session from the sidebar Keeps desktop sidebar actions unchanged |
||
|
|
f8beffce32 |
perf: reduce startup request fanout
Avoid repeated sidebar session and worktree refreshes Limit concurrent GitHub PR status checks Reuse cached worktree root resolution |
||
|
|
471ae69b1a |
refactor(ui): full-width header with framed chat shell
Header now spans the full window width above the [sidebar | chat | right-sidebar] row instead of nesting inside the central column. The chat area becomes a self-contained framed window with its own border and rounded corners on all four sides, and sidebars sit flush against the header sharing its bg-sidebar so the seam is invisible. Removed the duplicated shell controls the old layout needed to fake header-height inside sidebars: portal host on RightSidebar, paddingTop reservation, top drag overlay, duplicated layout-left / chat-new buttons in SidebarHeader, the showDesktopSidebarChrome block in SessionSidebar, and the conditional traffic-lights inset on the desktop header. Mac WCO inset now lives only on the header. Moved the new-session action into the SessionSwitcher dropdown as its first item, removed the standalone chat-new button from the header, relocated scheduled-tasks into the left action group of the sidebar header, and bumped ContextPanel tab strip to h-10 to balance the more prominent header. |
||
|
|
9ab90d4f1a |
fix(sidebar): key parent-expansion per render context (#1302)
The same session can appear in two render contexts at once — most visibly under "Recent" and under its project's root group. Expansion state was keyed by bare session id, so toggling the chevron in one context flipped it in the other; the user clicked one row and a duplicate elsewhere expanded/collapsed in sync. Switch the expansion set to composite keys of the form `<renderContext>:<active|archived>:<sessionId>`. SessionNodeItem already computes this exact shape for its menu instance key (menuInstanceKey); reuse it as expansionKey so the two render-time instances of one session keep independent expand state. Updated: - isExpanded reads `expandedParents.has(expansionKey)`. - Chevron click / Enter / Space pass `expansionKey` to toggleParent instead of session.id. - The memo-equality check compares the same composite key on both sides so it correctly re-renders when only one context's state changes. - toggleParent's parameter is renamed `expansionKey` to reflect the new shape; the implementation is unchanged. - The auto-expand-parent-on-subagent-navigation effect doesn't know which context the user will look at the parent in, so it fans the parentID out to all four (project|recent) × (active|archived) combinations. Storage is bumped to `oc.sessions.expandedParents.v2`. Existing v1 data (bare session ids) is one-shot migrated by fanning each id across all four contexts and rewritten under the v2 key; the v1 key is then removed so a downgrade-then-reupgrade doesn't re-migrate. Co-authored-by: vhqtvn <8930337+vhqtvn@users.noreply.github.com> |
||
|
|
b6149614bd |
feat: add session switcher dropdown in header
Open recent sessions from chat headers Support session switching in mini chat Share pinned and active session state |
||
|
|
14357257ae |
perf(ui): migrate icons to SVG sprite system
Replace @remixicon/react with a shared Icon component that renders via <use href> references to a single hidden SVG sprite. This reduces DOM node count by replacing inline SVGs with lightweight references. - Create Icon component with sprite injection (packages/ui/src/components/icon/) - Migrate all 164 files from @remixicon/react to Icon component - Auto-generate sprite data from remixicon bundle (scripts/generate-icon-sprite.mjs) - Add bun run icons:generate to package.json - Move @remixicon/react to devDependencies - Add icon usage instructions to theme-system skill |
||
|
|
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. |
||
|
|
fc2f4746e5 |
fix: avoid iPad PWA traffic light overlap
Adds tablet standalone PWA detection Reuses macOS traffic-light header spacing Protects session sidebar and multirun headers |
||
|
|
6595322f1a |
fix: show tablet actions without hover
Makes hover-only controls visible on tablets Preserves existing mobile and desktop layouts Covers chat, session sidebar, file, tab, and picker actions |
||
|
|
43f98e0344 | feat(ui): add recent section toggle | ||
|
|
21253d7fc2 |
feat: fork-aware issue/PR listing & OpenCode startup loading indicator (#1061)
* Add design spec: OpenCode readiness loading indicator * Add implementation plan: OpenCode readiness loading indicator * feat: add useOpenCodeReadiness hook * feat: add i18n keys for common.loading * feat: add loading state to ModelSelector * feat: add loading state to AgentSelector * feat: add loading state to ModelControls chat selectors * update package-lock * feat(github): add shared fork detection utility * feat(github): make issue listing fork-aware * feat(github): make PR listing fork-aware * feat(types): add sourceRepo to issue/PR summary types * feat(ui): add source badges to GitHub integration dialog * feat(ui): add source badges to issue/PR picker dialogs * feat(github): pass headRemote in PR creation for fork support * feat(ui): add source→target label in PR tab for fork workflows * fix(github): allow PR section on base branch when upstream remote exists * fix(github): show PR section on any branch including main for fork→upstream PRs * fix(github): allow PullRequestSection to render on base branch when upstream remote exists * feat(github): auto-detect upstream repo for fork→upstream PR creation - Add GET /api/github/repo/upstream endpoint to discover fork's upstream - Fix PullRequestSection canShow to allow PR creation on base branch when repo is a fork - Add virtual upstream target in remote dropdown (no explicit upstream remote needed) - Add targetRepo parameter to /api/github/pr/create for direct upstream targeting - Add repoUpstream() API client method and GitHubRepoUpstreamResult type * feat(github): auto-detect upstream repo for fork→upstream PR creation - Add GET /api/github/repo/upstream endpoint to discover fork's upstream - Fix PullRequestSection canShow to allow PR creation on base branch when repo is a fork - Add virtual upstream target in remote dropdown (no explicit upstream remote needed) - Add targetRepo parameter to /api/github/pr/create for direct upstream targeting - Add repoUpstream() API client method and GitHubRepoUpstreamResult type * fix: complete fork→upstream PR workflow - Server: return defaultBranch from /api/github/repo/upstream endpoint - Server: fix cross-repo head ref construction (compare repos, not remote names) - Server: filterActiveRemoteBranches checks all remotes, not just origin - UI: set targetBaseBranch to upstream's default branch when using detected upstream - UI: include all remote branches in base branch dropdown when using detected upstream - UI: skip base===head check for cross-repo PRs (same branch name on different repos is valid) - Types: add defaultBranch to GitHubRepoUpstreamResult * chore: delete superpowers folder * feat: add (local)/(remote) labels to PR branch display and adapt Repository button to selected remote * feat: Repository button adapts to selected remote (upstream vs origin) * fix: complete fork→upstream PR feature gaps Server: - Extend /api/github/repo/upstream to return defaultBranchSha and remoteName - Reuse headRepo result instead of redundant resolveGitHubRepoFromDirectory call - Return clear error when headRepo is null (invalid GitHub URL) UI: - Add upstream's default branch to availableBaseBranches when using detected upstream - Use upstream's default branch SHA in git log for generate description (fixes 'No commits found in range main...main') - Show qualified names (owner/repo · branch) in base branch dropdown when using detected upstream Types: - Add defaultBranchSha and remoteName to GitHubRepoUpstreamResult * fix: move detectedUpstream state before availableBaseBranches to fix temporal dead zone * fix: fetch upstream branches from GitHub API for base branch dropdown - Add GET /api/github/repo/branches endpoint to fetch branches via Octokit - Add repoBranches() to GitHub API client and interface - Fetch upstream branches on detection and store in upstreamBranches state - Include upstreamBranches in availableBaseBranches when using detected upstream - Re-add availableBaseBranches memo and auto-correction effect that were lost - Remove unnecessary qualified names from dropdown (upstream is already selected) * fix: restore prStatusKey and statusEntry declarations lost during refactor * fix: cleanly re-apply all fork→upstream PR UI changes Restored PullRequestSection.tsx from clean base and re-applied: - Expand detectedUpstream type with defaultBranch, defaultBranchSha, remoteName - Add upstreamBranches state and fetch on upstream detection - Include upstream branches in availableBaseBranches when using detected upstream - Use upstream default branch SHA in generate description (fixes 'No commits found') - Adapt Repository button URL to selected remote - Add (local)/(remote)/(upstream) labels to branch display * fix: move detectedUpstream/upstreamBranches before availableBaseBranches to fix TDZ * style: add pill badge styling to upstream repo source labels * fix: don't cache error PR status responses, allow force-bypass of server cache * fix: resolve PR status cache bugs, stale directory fallback, and upstream re-detection * fix: keep collapse button visible when scrolling long user messages - Collapse button now sticks to top of scrollable user message content instead of scrolling away * fix: checkbox focus ring blends into sidebar background * fix: polish fork PR follow-ups * fix: remove user message collapse artifact * fix: tighten fork PR internals * fix: check all remotes for fork PR status * fix: recover sidebar PR status misses --------- Signed-off-by: Islam Nofl <islamnofl.official@gmail.com> Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com> |
||
|
|
6186688e17 |
fix: polish pinned session indicators
Pinned recent sessions stay at top when already recent Default session rows align activity, pin, and chevron indicators Pinned icon no longer crowds session titles |
||
|
|
c83986cd94 |
feat: improve project directory picker
Adds command-palette style project browsing Keeps native Finder picker as optional desktop action Supports disabled existing projects and create-and-add paths |
||
|
|
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> |
||
|
|
7205d7a34f |
fix: skip stale session directories (issue #1010)
Filter active sessions to known project and worktree directories Stop probing session directories with filesystem listing |
||
|
|
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> |
||
|
|
7d7285655d |
Add i18n foundation and translations (#1027)
* feat: add i18n foundation * feat: localize sessions sidebar * Localize multirun/scheduled tasks and fix dialog dropdown interactions * localize git sidebar surface and add zh-CN keys * feat(ui): localize context panel, diff/plan views, and context sidebar content * fix(config): resolve user config home via fs/home before embedded home * localize header/chat UI and complete model/worktree panel strings * localize worktree + github issue/pr dialog flows * localize settings sections and split settings i18n dictionaries * localize additional settings sections and sidebars * localize more settings pages and dialogs * fix settings select trigger localization * localize tunnel settings ui surface * localize additional settings sections * localize keyboard shortcuts labels in settings * localize terminal and utility dialogs surfaces * feat(i18n): localize remaining UI strings * Add Ukrainian locale * Add Spanish locale * Add Brazilian Portuguese locale * Polish locale translations |
||
|
|
125094d371 | fix: сохранил pinned sessions после reload (#943) | ||
|
|
08258908c2 |
feat: bulk select sessions in sidebar
Select toggle in header enters selection mode. Click toggles, shift-click ranges, parent auto-includes subtasks, Cmd/Ctrl+A selects visible, Esc exits, Cmd/Ctrl+Backspace deletes. Bulk bar: move to folder (existing or new inline), archive/delete via existing confirm + "Never ask" pref. Cross-scope forbidden — new scope clears previous. Also: web sidebar header folds toggle into action row (no separate chrome row, same position on toggle); sidebar min width bumped 250→280 so icons fit; minimal-mode hover reveal padding trimmed. |
||
|
|
285c3bcaae |
Migrate desktop shell from Tauri to Electron (#964)
* feat(electron): scaffold Electron desktop package
Main + preload + ssh manager, packaging scripts, icons, root build/lint/type-check wiring.
* feat(ui): add Electron runtime detection and desktopNative facade
isElectronShell via window.__OPENCHAMBER_ELECTRON__, isDesktopShell now covers both. desktopNative wraps window/title/theme calls so UI avoids direct Tauri imports. revealDesktopPath added.
* refactor(ui): route window/title/theme/export through desktopNative
SessionSidebar, MultiRunLauncher, useWindowTitle, ThemeSystemContext, exportSession drop direct @tauri-apps imports.
* refactor(ui): treat all desktop shells uniformly
device.ts switches Tauri-only checks to isDesktopShell. Header OpenInApp button uses actionDirectory so it falls back to the active project path.
* fix(ui): menu Copy clipboard fallback and softer sidebar tint
useMenuActions falls back to Clipboard API for the native Copy action when the page doesn't intercept. cssGenerator lowers sidebar strong/soft alpha so the tinted surface reads gentler.
* chore(electron): mirror Tauri build/type-check script shape
build script becomes no-op so root 'bun run build' skips packaging. Syntax validation (node --check) moves into type-check. electron:build root script still runs full sidecar+bundle+electron-builder.
* fix(electron): sync app identity, preload path, boot outcome, dev entry
Read version from packages/electron/package.json so 'electron ./main.mjs' dev entry reports the app version instead of Electron's. Bump electron package to 1.9.6 for workspace parity.
Resolve preload via app.getAppPath() in prod (bundle lives in dist-bundle while preload.mjs ships at app root).
Compute and inject __OPENCHAMBER_DESKTOP_BOOT_OUTCOME__ in main + preload so the loading gate dismisses (mirrors Tauri Rust injection).
Dev entry uses ./main.mjs to bypass the stale dist-bundle so source edits apply.
* refactor(open-in-app): split directory and file flows
Header button now opens the project/worktree directory only — drop activeFilePath prop and its Header prop passthrough. FilesView editor dropdown opens the active file only via new openDesktopFileInApp.
Electron main.mjs mirrors Tauri's open-chain logic: buildOpenProjectSpecs (finder/terminal direct, vscode-like via CLI -n, JetBrains via open -na --args) and buildOpenFileSpecs (finder -R reveal, terminal opens parent dir, editors via CLI or open -a). runSpecChain falls through specs until one exits 0.
* fix(files-view): keep floating toolbar mounted while its dropdowns are open
Portalled Base UI menu popups render outside floatingToolbarRef. The document mousedown listener and onMouseLeave collapsed the toolbar as soon as the popup appeared, unmounting the DropdownMenu root and swallowing clicks on its items. Track open dropdowns via onOpenChange and skip the collapse while count > 0; also ignore mousedowns that land inside a dropdown-menu-content/item.
* feat(electron): add quit confirmation with risk poller
Mirrors Tauri's macOS-only behavior: poll /api/openchamber/scheduled-tasks/status and /api/openchamber/tunnel/status every 5s. If active tunnel or running/enabled scheduled tasks are detected, Cmd+Q / dock Quit / menu Quit shows a native warning dialog listing reasons; otherwise quit proceeds silently.
performConfirmedQuit persists window state, kills sidecar, shuts down SSH, and fires a 1500ms unref'd safety timeout that calls app.exit(0) if the normal quit sequence stalls.
* feat(notifications): fix payload parsing, restore-on-click, session deep-link
Normalize input so both sidecar stdout path (flat) and UI IPC path ({ payload: {...} }) work; previous destructuring missed requireHidden (camelCase) and the payload wrapper so notifications showed with empty body.
Click handler restores the window if minimized, shows it if hidden, and focuses. When the notification payload carries sessionId, emit openchamber:open-session which the App listener routes to setCurrentSession — matches the PWA service-worker deep-link behavior. macOS notifications now also use sound 'Glass' for parity with Tauri.
* chore(electron): bump to Electron 41 + latest updater/context-menu
electron ^38.2.0 -> ^41.2.1
electron-updater ^6.6.2 -> ^6.8.3
electron-context-menu ^4.0.4 -> ^4.1.2
Dev boot verified: main process starts, preload exposes globals, API server + quit risk poller + autoUpdater all initialize without errors.
* fix: keep todo row alignment stable when expanding text
Keep checkbox and action buttons vertically centered in collapsed todo rows
Prevent first todo line from shifting when expanding to multiple lines
* fix: make commit highlights visible and input behavior reliable
Switch commit message field to native textarea for predictable auto-resize
Fix AI highlights append flow so inserted text is applied consistently
Make chat scroll-to-bottom control fully circular
* style: increase chat bubble corner radius consistency
Use larger radius for user chat message bubbles
Match chat input container radius to user message styling
* feat(electron): adopt OpenCode playbook improvements
mac: hardenedRuntime + entitlements.mac.plist + notarize + dmg.sign for Apple notarization parity.
single-instance lock + openchamber:// protocol with session/project/host routing (host switch done fully in main via activateMainWindow).
setAppUserModelId for Win toast identity; proxy-bypass-list switch; chdir(homedir) for Finder-launch cwd safety.
shell env probe (\$SHELL -il -> -l) merged into sidecar spawn; PATH deduped.
electron-log with 5MB rotation + 7-day cleanup; autoUpdater.logger wired; startup info log.
webContents zoom locked to 1 (zoom-changed + did-finish-load).
UI: openchamber:open-project -> useDirectoryStore.setDirectory.
* fix(electron): make bootOutcome mutable across re-navigation + project deep-link
host deep-link used to land on chooser because contextBridge exposed bootOutcome as read-only; initScript re-assignment became a silent no-op. drop preload's contextBridge for bootOutcome, inject it via main-world initScript, and move injection from did-finish-load to dom-ready so it lands before React mounts.
project deep-link updated currentDirectory only; activeProjectId stayed stale so the sidebar didn't highlight the new project. switch to projectsStore.setActiveProject (or addProject for new paths) which updates both.
add log.info around deep-link dispatch + host switch for diagnostics.
* fix(electron): desktop_hosts_set IPC args + persist initialHostChoiceCompleted + re-eval bootOutcome
UI calls invoke('desktop_hosts_set', { input: {...} }) but main was reading args.config — every onboarding 'i've completed installation' / host-dialog save wrote nothing, so desktopDefaultHostId stayed null and the chooser screen looped forever.
also:
- writeDesktopHostsConfig now persists desktopInitialHostChoiceCompleted so the tauri-compat flag survives writes.
- readDesktopHostsConfig returns initialHostChoiceCompleted so the UI-side config mirror is complete.
- after writing hosts, recompute state.bootOutcome + state.initScript; a subsequent window.location.reload() picks up target=local/status=ok via dom-ready injection without needing a full app restart.
- app.setName('OpenChamber') early (pre log.initialize) so electron-log logs land in ~/Library/Logs/OpenChamber/ instead of the package-derived '@openchamber/electron' path.
* chore(electron): rename appId to dev.openchamber.desktop
ai.opencode.* is the OpenCode team's reverse-DNS namespace; OpenChamber should not squat there. now that we're on Electron, drop the tauri-era inherited identifier and claim our own under openchamber.dev.
user-facing productName stays "OpenChamber". tauri identifier left as-is — legacy shell on the way out.
* feat(ci): add electron build+notarize+publish jobs to release workflow
three new jobs in release.yml, running in parallel with tauri:
- build-desktop-electron-macos: matrix(arm64, x86_64) on macos-26; installs Developer ID via keychain, runs build:sidecar + bundle:main + electron-builder --mac --arch <> --publish=never (with APPLE_ID / APPLE_APP_SPECIFIC_PASSWORD / APPLE_TEAM_ID env mapped from existing secrets). verifies hardened runtime, stapled notary ticket, required entitlements. uploads DMG/ZIP/blockmaps to the release and emits per-arch latest-mac.yml as a GH artifact.
- combine-electron-manifests: downloads latest-yml-*-apple-darwin artifacts, runs the existing finalize-latest-yml.mjs to merge per-arch files entries into a single latest-mac.yml, uploads combined yml to the release.
- finalize-release: now also waits on the two new jobs before flipping the draft release to published.
also: explicit artifactName in electron-builder config so arm64 and x64 dmg/zip never collide.
electron-updater in main.mjs (setFeedURL btriapitsyn/openchamber) fetches this latest-mac.yml on desktop_check_for_updates; downloadUpdate / quitAndInstall wire through our existing IPC handlers unchanged.
* docs: future-agent brief for tauri -> electron auto-update cutover
self-contained plan for the one-shot migration release that carries existing tauri installs into the electron shell via tauri's updater. written so a fresh agent with no branch context can execute it.
covers: the trick (repackage signed electron .app as a tauri tarball, minisign with existing TAURI_SIGNING_PRIVATE_KEY), workflow surgery on release.yml, rollback plan, validation steps against a real tauri install, and edge cases (CFBundleIdentifier change, notification perms re-prompt, deep-link re-registration).
* docs: soften framing of cutover playbook (no user-shaming)
* chore: mark electron as primary desktop shell; tune dmg installer window
AGENTS.md: explicit note that new desktop work lands in packages/electron/, packages/desktop/ (tauri) is maintenance-only until the cutover described in docs/TAURI_TO_ELECTRON_CUTOVER.md. updated runtime/entry-points/build-commands sections accordingly.
electron/package.json build.dmg: cleaner title ("OpenChamber 1.9.6" without -arch suffix), 660x400 window matching the tauri layout users are used to, icon size 128, explicit app/Applications positions.
* refactor(web): drop bun-specific runtime deps from server
- 11 test files migrated bun:test -> vitest; API (describe/it/expect) is drop-in; all 73 tests pass under vitest run.
- bun:sqlite -> better-sqlite3 in git/service.js::syncSandboxesToOpenCodeDb. api shift is db.query().get()/run() -> db.prepare().get()/run().
- add "test": "vitest run" script in packages/web.
no production code used Bun.* APIs; server is Express-on-Node already. this commit removes the remaining bun-runtime shape so the server module can be imported and booted inside an electron main process.
* feat(electron): boot web server in-process, drop sidecar subprocess
the electron main process now imports @openchamber/web/server/index.js as a workspace dependency and calls startWebUiServer({...}) directly. the returned handle exposes getPort() / stop() and the notification emitter takes an onDesktopNotification callback, so we no longer spawn a bun-compiled sidecar binary and no longer parse stdout for the one-line notify protocol.
- packages/electron/package.json: +@openchamber/web (workspace:*); extraResources drops 'sidecar'; build:sidecar script renamed to build:web-assets (kept the vite build step, dropped the bun compile step).
- packages/electron/main.mjs: remove spawn/kill-stale-sidecar/sidecar path resolver/stdout-prefix parser; rewrite spawnLocalServer to probe a free port (stored | DEFAULT_DESKTOP_PORT | OS-assigned) then import server and await startWebUiServer; killSidecar calls handle.stop({ exitProcess: false }); hoist user shell env (PATH, etc.) onto process.env once so opencode / git / rg children still inherit the expected runtime environment.
- packages/web/server/lib/notifications/emitter-runtime.js: accept an onDesktopNotification callback (late-bindable via setOnDesktopNotification). when set, notifications are dispatched through the callback instead of process.stdout; tauri path still uses stdout when no callback is bound.
- packages/web/server/index.js: main() wires options.onDesktopNotification to notificationEmitterRuntime.setOnDesktopNotification.
- release.yml + AGENTS.md updated for the new script name + runtime shape.
payoff: -300ms cold start on mac, single process in activity monitor, no stdio IPC, no bun binary in the packaged app. tauri sidecar path is untouched.
* build(electron): rebuild native deps explicitly, bump electron-builder
the previous build failed because electron-builder 24.13.3 tried to run \`bun rebuild\` on native deps (better-sqlite3, node-pty) and bun has no rebuild subcommand; it also couldn't find prebuild-install because bun hoists under node_modules/.bun/<pkg>@<ver>/ and never populates node_modules/.bin for transitive deps.
fix:
- bump electron-builder devDep to ^26, whose packageManager detection understands bun workspace layouts.
- add @electron/rebuild devDep + scripts/rebuild-native.mjs. the script rebuilds better-sqlite3 / node-pty / bun-pty against the installed electron version before electron-builder is invoked.
- set build.npmRebuild=false so electron-builder no longer attempts its own broken PM-based rebuild.
- package script: build:web-assets -> bundle:main -> rebuild:native -> electron-builder.
verified: CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron:build produces signed-ad-hoc dmg/zip/blockmap/latest-mac.yml; artifacts land under packages/electron/dist as expected. cold-start from Applications should work (native bindings now match electron 41 node ABI).
* fix(electron): externalize web server + native deps from main bundle
the ESM bundle was statically inlining @openchamber/web transitively, which pulled in bun-pty/src/terminal.ts with its top-level \`import { dlopen } from "bun:ffi"\`. node's ESM loader parses every static import when the bundle loads, so the bun:ffi scheme crashed the packaged app at startup with ERR_UNSUPPORTED_ESM_URL_SCHEME — the runtime guard (if (globalThis.Bun) { await import('bun-pty') }) never got a chance to skip it.
fix: bundle-main.mjs marks @openchamber/web (+ its bun-pty / node-pty / better-sqlite3 transitives) as external. the dynamic \`await import('@openchamber/web/server/index.js')\` in main.mjs stays a runtime resolution; the conditional bun-pty import stays dynamic; native modules load from node_modules via the standard resolver.
* perf(web): classify UI-only deps as devDependencies, shrink packaged app
packages/web is a hybrid package: server code in server/, react UI source in src/, compiled UI output in dist/. the server serves dist/ as static files — it never imports react/radix/codemirror/etc. at runtime. but electron-builder, npm install, and similar tools treat everything under "dependencies" as shipping surface, so all of react + @radix-ui/* + @codemirror/* + @fontsource/* + @simplewebauthn/browser + cmdk + ghostty-web + ... were landing in app.asar even though the same code is already baked into dist/ chunks.
move ~24 UI-only packages to devDependencies. vite + its plugins still install them in dev (bun install fetches devDependencies in workspaces), so \`bun run build\` is unchanged. consumers doing \`npm install @openchamber/web\` no longer pull ~150MB of unused browser-side modules.
measured on aarch64 darwin build:
- app.asar: 281MB -> 44MB (-237MB, -84%)
- .dmg: 320MB -> 132MB (-59%)
- .zip: 305MB -> 129MB (-58%)
verified type-check, ui build, 73 vitest tests, packaged launch.
* chore(electron): center dmg installer icons, use cream brand background
dmg-builder 26 ignored our previous dmg.contents positions against its template background (they stayed at template coords, producing misalignment with the drawn arrow). switch to a solid backgroundColor (#FFFCF0, the splash light tone) so the template image is dropped entirely and our coordinates are authoritative. window tuned to 540x340, iconSize 100, iconTextSize 13.
dmgbuild treats contents coordinates as icon *centers* (not top-left), so with iconSize=100 in a 540 window, x=180 and x=360 place left and right clusters with equal 130px gaps on both sides of the window. y=140 vertically centres the icon+label pair.
* fix(electron): eliminate main-thread freezes in in-process server
Three blocking paths were running sync work on the Electron main event
loop, causing multi-second UI freezes under the new in-process server:
- package-manager.detectPackageManagerDetails fired spawnSync(pnpm/npm/
yarn/bun bin -g) with 10s timeouts. In desktop runtime PM detection is
pointless (app is .app bundle, updates via electron-updater) — short-
circuit when OPENCHAMBER_RUNTIME=desktop. This was the ~5s freeze.
- buildInstalledApps iterated 22 OPEN_IN_APPS × spawnSync(mdfind, sips).
Converted to execFile promises so child waits yield to the loop.
- orphan-project-file recovery re-scanned disk on every settings read
(3+/s from fs/list/etc). Cache the outcome per process lifetime.
Also: resolveProjectDirectory prefers settings.lastDirectory over
activeProjectId so file-open from sidebar/chat doesn't 400 with
"Path is outside of active workspace" after the user navigates.
Plus dropdown typeahead fixes in DesktopHostSwitcher/BranchSelector:
stopPropagation on input keys so cmdk doesn't swallow typing.
* feat(electron): restore desktop LAN access for in-process server
spawnLocalServer now reads settings.desktopLanAccessEnabled and binds
on 0.0.0.0 when enabled, so phones/tablets on the same Wi-Fi can open
the app via http://<lan-ip>:<port>. Adds desktop_get_lan_address IPC
(UDP-connect route lookup with networkInterfaces fallback) for the
settings UI to show the reachable URL.
UI and settings plumbing already existed from the sidecar build; only
the Electron main-process wiring was missing.
* chore: added electron package to version bump script
* fix(electron): address PR review — harden IPC surface + polish
P1 security:
- Gate openchamber:invoke and openchamber:dialog:open by webContents
origin. Only local (loopback / dev file://) senders can call desktop_*.
Blocks remote hosts loaded via DesktopHostSwitcher from reading local
files, opening apps, relaunching, etc.
- desktop_read_file now refuses paths outside $HOME / tmpdir and denies
.ssh/.aws/.gnupg/.config/gh/credentials + .env/.pem/.key by name
(defense-in-depth behind the origin gate).
P2:
- webPreferences.sandbox:false: add comment explaining preload needs Node
(contextBridge+ipcRenderer) and why flipping to true would break IPC.
- desktop_set_vibrancy: comment the intentional no-op (no Electron
equivalent for the Tauri NSVisualEffectView path), drop requiresRestart.
- desktopNative.ts: replace isTauriShell() guards with isDesktopShell()
so the semantics match (previous check worked only because Electron
preload exposes a __TAURI__ shim).
- AGENTS.md: correct entry description — server runs in-process, not as
a sidecar subprocess.
* fix(electron): stop leaking desktop shell APIs to remote renderer pages
Preload was exposing __TAURI__ and __OPENCHAMBER_ELECTRON__ unconditionally,
so after DesktopHostSwitcher navigated the window to a remote OpenChamber
instance the remote UI saw isDesktopShell() === true and tried to invoke
desktop_* IPC. The main-process origin gate then threw "IPC not available
for this origin", surfacing as a user-visible error on the onboarding
screen of the remote.
Preload re-runs on cross-origin navigation; compute current origin up
front and only expose the shell globals + the openchamber:emit listener
when the document is loopback / state.localOrigin / file://. Remote
pages now look like a plain web runtime — no IPC path to reject.
* fix(electron): restore remote UI shell integration via per-command gate
Previous commit stripped __TAURI__ / __OPENCHAMBER_ELECTRON__ from remote
pages wholesale, which broke DesktopHostSwitcher for anyone switched to
a remote instance: no hosts list, "Unknown" probe status, open-in-new-
window dead. Also lost window chrome affordances that the remote UI
needs to render correctly inside the Electron shell.
Switch from an origin-level gate to a per-command allowlist:
- preload.mjs exposes __TAURI__ and __OPENCHAMBER_ELECTRON__ on every
page (shell identity + IPC channel). __OPENCHAMBER_LOCAL_ORIGIN__ and
__OPENCHAMBER_MACOS_MAJOR__ also go everywhere since HostSwitcher and
window chrome depend on them and neither grants capability.
__OPENCHAMBER_HOME__ stays local-only (leaks the OS username and is
misleading if consumed as a workspace hint on a remote page).
- main.mjs ipcMain.handle accepts a curated COMMANDS_SAFE_FOR_REMOTE set
(hosts_get, host_probe, new_window, new_window_at_url, set_window_*,
is_window_fullscreen, start_window_drag, get_app_version,
get_lan_address). Filesystem, shell.openPath, installed-apps scans,
app relaunch, auto-update, hosts_set, dialog:open, read_file stay
local-only — remote UI doesn't need them and can't weaponize them.
* ci(release): rebuild native modules against Electron ABI before packaging
Electron job skipped rebuild:native so bun install's Node-ABI builds of
better-sqlite3/node-pty/bun-pty shipped into the asar — packaged app
would crash on require. Local bun run package runs the step via
scripts/rebuild-native.mjs (npmRebuild is disabled in package.json);
mirror it in CI and pass ELECTRON_BUILDER_ARCH so the x64 matrix
cross-builds from the arm64 runner.
Tauri job untouched — both builds continue to produce side-by-side
release artifacts (latest.json for Tauri, latest-mac.yml for Electron)
so each shell's updater finds its own manifest.
* ci(release): split Electron arm64/x64 onto native macOS runners
Both Electron matrix entries were running on macos-26 (arm64) and
cross-building x64 from there. Works for Rust/Tauri; brittle for
native Node modules — better-sqlite3, node-pty, bun-pty (with its
rust-pty crate) each have their own cross-target quirks.
Pin arm64 → macos-14 and x64 → macos-13 so node-gyp and
@electron/rebuild build against the host arch. ELECTRON_BUILDER_ARCH
now just mirrors the runner for clarity.
* Revert "ci(release): split Electron arm64/x64 onto native macOS runners"
This reverts commit f217880e49609cf1418818af0f837b333dbb6f42.
* ci(test-build): add Electron DMG job to arm64 dispatch workflow
Parallel job to the existing Tauri DMG builder, same runner + Apple
cert path. Mirrors the release workflow steps (build:web-assets,
bundle:main, rebuild:native, electron-builder) so maintainers can
smoke-test a signed+notarized Electron DMG before merging.
* ci: use electron-builder v26 boolean arch flags
v26 dropped --arch <name> in favour of per-arch booleans (--arm64,
--x64, etc.). Test build was failing at dispatch time; release job
had the same bug latent. Switch both to the supported form.
* fix(electron): route external links to the system browser
<a href> clicks and window.open calls with non-local URLs were loading
inside the Electron BrowserWindow (or spawning a second Electron window
as a makeshift browser). Add an origin-aware navigation guard to each
window: loopback / state.localOrigin / configured desktop hosts keep
their existing in-window behaviour (HostSwitcher, in-window probes);
everything else hands off to shell.openExternal so http/https links
open in the user's default browser.
|
||
|
|
d203ba2ae0 |
feat(sidebar): project notes/todos live in right sidebar context tab
Replace the header sticky-note popover/mobile overlay with a dedicated Context tab in the right sidebar. Context is cycled by the right-sidebar shortcut alongside git and files, and persisted across reloads. |
||
|
|
af7cf6ec9c | fix(sync): prefer freshest session status and persist active-now list | ||
|
|
ddc1039d1c | fix(sync): unify live session truth across chat and sidebar | ||
|
|
4f228f768d |
feat: add scheduled tasks with locale-aware scheduling and safer desktop quit flow (#920)
* 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 |
||
|
|
6d21cdc69e | fix: keep active sessions visible in Recent sidebar | ||
|
|
16270e4b03 |
fix: restore desktop window dragging on remote instances
- 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 |
||
|
|
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. |
||
|
|
8587db89b1 |
perf: reduce UI render fanout and scroll jitter
- Cut broad render fanout across the app by replacing shared-store whole-object subscriptions with leaf selectors, memoizing hot chrome boundaries, and isolating disabled global providers from live session/message state. This keeps header controls, composer toolbars, side panels, and other non-hot UI surfaces from repainting on every assistant update or keystroke. - Rework sidebar session ordering so recent, project groups, and worktree groups derive from one ordering source while avoiding streaming-time thrash. The sidebar now uses a stabilized session snapshot, preserves structural identity for unchanged rows, reads live row status/details per session, and applies a one-shot sort bump on idle->busy instead of continuously resorting during activity. - Fix chat/input scroll instability by separating viewport-resize handling from message-growth handling, disabling conflicting native scroll anchoring, and stopping textarea autosize from collapsing on every growth keystroke. This removes the multiline typing jiggle during streaming and reduces unnecessary composer rerenders. - Also gate voice context wiring behind voice-mode enablement and codify the learned render/scroll/order anti-patterns in AGENTS.md so future changes avoid the same classes of regressions. |
||
|
|
d982171689 | refactor: centralize git and pr store refresh | ||
|
|
f032df2298 |
fix(mobile): close settings drawers and remove extra top spacing (#770)
* fix(mobile): close side drawers when opening settings * fix(mobile): remove duplicate top inset in drawers --------- Co-authored-by: Jovines <jovines@qq.com> |
||
|
|
c9e31a0e6c |
perf: harden sync architecture and modularize runtimes (#803)
* fix: added desktop app background throttling
* perf: add streaming debug metrics panel
- Show streaming performance metrics in the debug panel
- Auto-enable stream profiling while the panel is open
- Add JSON export for sharing UI and VS Code metrics
* perf: batch streaming updates more aggressively
- Buffer message deltas and metadata updates to cut render churn
- Skip no-op part updates before they touch the message store
- Fix the desktop debug panel shortcut binding
* perf: split streaming event handling and coalesce deltas
- Move streaming content events onto a dedicated fast path
- Defer non-critical stream side effects off the hot path
- Merge repeated message delta events before they reach the UI
* perf: isolate streaming rows from chat rerenders
- Memoize chat rows against render-relevant message changes only
- Read live assistant text directly from store to narrow streaming updates
- Split the active streaming entry from the stable message list path
* perf: streamline chat streaming and SSE proxying
- Reduce chat rerenders around the active streaming path
- Simplify server SSE forwarding to avoid duplicate proxy work
* fix: preserve the first streaming text chunk
- Show the initial text chunk immediately before batched deltas arrive
- Bypass batching for the first text or reasoning part update
- Keep later streaming updates buffered for performance
* perf: align streaming/render hot paths with opencode parity
* perf: harden turn/cache stability and stale delta suppression
* fix: stabilize chat rendering and disable timeline interactions
- Disabled timeline dialog access from shortcuts, commands, and chat input
- Reduced chat render churn by simplifying message list and turn staging behavior
- Improved session-switch stability to prevent update-depth crashes
* perf: track static message rerenders during streaming
* perf: reduce sorted-mode activity rerender fanout
* perf: reduce chat rerender fanout and add active-turn metrics
- Reduced sorted-mode rerender coupling by tightening turn context propagation
- Added a metric for static rerenders outside the active turn during streaming
- Exposed new chat render counters in the debug panel for parity tracking
* fix: keep sorted activity mounted while stream grows
* fix: stabilize session and history scroll rendering
* refactor: decouple server routes from index
* refactor: extract fs module from server index
* refactor: move opencode route ownership into module
* refactor: extract notification route registration
* refactor: extract opencode and notification runtimes from index
* refactor: extract settings runtime and complete server modularization pass
* refactor: modularize server config, skills, icons, and tunnel routes
* refactor: extract server modules from monolithic index.js
Split proxy, routes, runtime helpers, and notification emitter
into dedicated modules under packages/web/server/lib/.
* refactor: replace session/message stores with SSE-driven sync layer
Delete ~9200 lines of old architecture (useEventStream, messageStore,
sessionStore, useSessionStore, questionStore, useTodoStore, client SSE).
New sync layer: event pipeline with coalescing + 16ms flush, pure event
reducer, per-directory child stores with LRU eviction, cursor pagination,
optimistic updates, deferred timeline staging, text throttle.
Migrate all UI consumers to sync hooks (useSessionMessages,
useSessionMessageRecords, useSessionStatus, useSessionPermissions, etc).
Strip session-ui-store to UI-only state, delegate SDK ops to
session-actions with abort-if-busy, optimistic store updates, and
response merging for revert/fork/archive/delete.
Add notification-store for SSE-driven session attention tracking,
cross-directory GlobalSessionStatusStore for sidebar indicators,
client-side diff snapshot sanitization to prevent memory bloat,
and revert message filtering via useVisibleSessionMessages.
* feat: notification store, session actions, activity detection
Add notification-store.ts for SSE-driven attention tracking.
Add sanitize.ts to strip diff snapshot memory bloat.
Add session-actions.ts with optimistic revert/fork/archive/delete.
Improve useSessionActivity with incomplete-message fallback.
Delete useServerSessionStatus polling hook.
* fix: add directory param to all SDK calls, fix command/shell/abort routing
All SDK calls in session-actions.ts now pass directory parameter —
required by OpenCode server to scope session operations. Without it,
abort, commands, revert, fork, and other operations returned 500.
Add routeMessage() in session-ui-store for shell mode (session.shell),
slash commands (session.command), and normal prompts. Command lookup
checks both sync child store and useCommandsStore. Handle /compact
locally via session.summarize().
Implement getContextUsage() to restore header context usage display —
reads token counts from last assistant message in sync store.
* refactor: replace custom API proxy with http-proxy-middleware
Remove ~280 lines of custom proxy code: forwardSseRequest,
forwardGenericApiRequest, collectRequestBodyBuffer, header
manipulation, hop-by-hop filtering, SSE block buffering.
Replace with single createProxyMiddleware() call that handles
SSE streaming, large bodies, and timeouts out of the box.
Dynamic router for OpenCode port changes after restarts.
Auth headers injected via proxyReq hook.
Keep: readiness gate, Windows session merge, API prefix detection.
* perf: targeted event draft cloning to fix streaming render cascade
Event handler was eagerly cloning all state slices on every event,
breaking Zustand selector referential equality. During streaming
(~60 events/sec), this caused every subscriber to re-render regardless
of which slice actually changed.
Now only clones fields the specific event type mutates. Also extracts
StatusRowContainer to isolate high-frequency useAssistantStatus
subscription, removes dead messageStreamStatesMap subscription from
ChatContainer, and narrows useAssistantStatus to only track last
assistant message parts.
MessageList renders: 1972 → 296 per streaming session (-85%).
* fix: null safety for sync state slices
Add defensive ?? {} guards on permission, question, session_status,
and message record access. Prevents crashes when child store state
is partially initialized during bootstrap.
* perf: dedup inflight SDK calls, extract concurrency util, delay PR tracking
Extract mapWithConcurrency to shared lib/concurrency.ts. Add in-flight
dedup for loadProviders/loadAgents to prevent concurrent duplicate SDK
calls. Delay initial PR background tracking by 5s to reduce startup
CPU burst.
* fix: header session lookup across all child stores
Session title and context panel click failed when session belonged to
a different directory than the current child store. Fall back to
getAllSyncSessions() to search all initialized stores.
* chore: bump @opencode-ai/sdk to 1.3.5
* docs: add sync event handling guide
* Optimize session prefetch and improve delete/archive UX
- Add settlement delay to session prefetch to avoid race conditions on
rapid session switches
- Reduce git diff prefetch and session cache limits for better performance
- Implement optimistic UI updates for session delete/archive operations
with proper rollback on failure
- Wire session prefetch hook into SessionSidebar with sync integration
* Add file content cache and sync optimizations
- Wrap FilesAPI with in-memory LRU cache for file content with dual
constraints (entry count and byte size)
- Optimize chat timeline scroll restoration using useLayoutEffect
- Preserve React references in message and part arrays to prevent
unnecessary re-renders when prepending history
- Add session prefetch TTL cache to prevent redundant fetches
- Integrate session prefetch cache clearing with eviction flow
* Improve session sidebar error handling and add diff prefetch filtering
Load active and archived sessions independently using Promise.allSettled
to prevent one failure from blocking the other. Add retry logic to session
API calls and skip large files during diff prefetch to improve performance.
* Replace sendMessage with optimisticSend wrapper
Introduces optimistic UI updates for normal chat messages to provide
instant feedback. Messages appear immediately in the UI while the API
call executes in the background, with automatic rollback on errors.
* perf: split stores, proper optimistic send, fix revert/directory bugs
- split session-ui-store into voice/input/selection/viewport stores
to reduce subscriber re-evaluation during streaming
- wire optimisticSend through useSync shadow Map infrastructure
matching OpenCode's pattern (no heuristic part detection)
- port OpenCode Identifier.ascending ID format for correct sorting
- pass messageID to promptAsync to prevent duplicate messages
- fix worktree directory not propagating to session actions
(dynamic dir() via opencodeClient.getDirectory)
- fix setCurrentSession accepting directoryHint for new sessions
- fix revert not hiding messages (session limit was 5, bumped to match loaded count)
- fix revert optimistic message removal from store
- fix load-more flicker (useLayoutEffect scroll compensation)
- add prefetch TTL cache, file content LRU cache
- add session prefetch for adjacent sessions
- add instant archive/delete (optimistic before SDK call)
- migrate legacy window.__zustand_session_store__ to session-ui-store
- add retry + independent error handling for archived sessions
- add AGENTS.md performance rules
* perf: startup optimization — dedup, caching, light git status, diff rendering gates
- defer diff prefetch to git tab open, reduce concurrency 4→2, skip >500 changed lines
- cap project git checks concurrency (2), directory status probe (3)
- dedup provider/agent loading, github auth, worktree list (in-flight + TTL caches)
- delay PR tracking 5s, cache 403 search failures per-repo
- coalesce settings PUT (200ms debounce), cache settings GET (2s TTL)
- cache canonical directory resolution (60s TTL)
- persist missing directory status to localStorage (10min TTL)
- light/heavy git status: polling skips numstat+line counting+rev-list
- large diff rendering gate (>500 lines → "render anyway" button)
- tokenization degradation for >500KB files in Pierre
- parallelize main.tsx pre-render awaits
- batch sidebar file tree expanded paths restoration (3 at a time)
- remove bare useConfigStore() subscription in AgentsPage
- sync worktree sandboxes to OpenCode SQLite DB
- fix RightSidebarTabs ternary → explicit tab matching
- defensive guards on sync state (session_status, permission, question, message)
* fix: add defensive guards on remaining sync state field accesses
guard session_status, permission, message, todo, part, config with ?? {}
in useDirectorySync selectors, session-cache, and bootstrap
* fix: add missing directory dep to useCallback in use-sync.ts
* fix: preserve diffStats when light-mode polling overwrites status
* perf: optimize startup git status polling and diff rendering
Preserves diff stats when lightweight polling updates repository status
Reduces startup overhead with smarter git polling and store updates
Adds detailed optimization and migration docs for next performance steps
* fix: keep chat diff stats stable during git status updates
Prevents lightweight git polling from dropping diff statistics
Keeps MessageList diff indicators consistent while status refreshes
Improves reliability of git-aware chat rendering
* fix: user animation replay, queued message variant, startup provider loading
- consume animation ID after first play to prevent re-animation
on neighbor assistant message completion
- capture send config (model/agent/variant) at queue time matching
OpenCode's FollowupDraft pattern instead of re-resolving at send time
- replace one-shot startup recovery effect with polling interval
that retries every 2s until providers and agents load
- fix optimistic bridge to avoid re-render loop (stable ref wrappers)
* chore: update tauri to 2.10.3 and all plugins to latest
- tauri 2.9.4 → 2.10.3
- tauri-build 2.5.3 → 2.5.6
- tauri-plugin-dialog 2.4.2 → 2.6.0
- tauri-plugin-log 2.7.1 → 2.8.0
- tauri-plugin-shell 2.3.3 → 2.3.5
- tauri-plugin-updater 2 (floating) → 2.10.0 (pinned)
- @tauri-apps/api ^2.9.0 → ^2.10.1
- wry 0.53.5 → 0.54.4 (transitive)
* refactor: decouple web server index orchestration runtimes
* fix: align VS Code runtime behavior with web and reduce draft view CPU load
- Queue VS Code bridge and SSE startup requests until API readiness to avoid false bootstrap failures
- Make agent manager actions directory-aware and remove real worktrees with safer partial-failure handling
- Replace heavy logo animation path with a lightweight pulse to cut draft-session CPU usage
* fix: restore auto-selected file sending in chat input
- Send server-selected files as proper file URLs in the message payload
- Include server-backed attachments in submit flow instead of dropping them
- Restore queued-message attachments through the refactored input store
* fix: restore session model selection consistently on session switch
- Restore agent, model, and variant from the latest loaded user message for each session
- Wait for session messages before applying restored selections to avoid stale or missing state
- Remove legacy session-choice inference paths that caused overlap and instability
* fix: restore permission replies and auto-accept across sessions
- Scope permission and question replies to the target session directory so answers take effect reliably
- Make permission auto-accept immediately handle pending requests and react to new permission prompts
- Keep parent-session handling working for child-session requests through the shared response path
* feat: add reusable fuzzy branch fuzzy-search helper and dialog integration (#798)
* feat: add reusable fuzzy branch search for worktrees
* chore: drop planning docs from feature branch
* feat: make worktree branch refresh manual
* feat: add configurable session retention action
* refactor: centralize global session state in ui store
* fix: cancel debounced permission push after reply
* docs: clarify global and directory session store architecture
* docs: refine agent development rules and session activity guidance
- Clarify agent code of conduct and durable development patterns
- Add explicit shared-store rerender and live-state guidance
- Narrow session activity fallback to avoid stale working state
* chore: updated .gitignore
---------
Co-authored-by: Iuliia Ivashko <yulia.ivashko@gmail.com>
|