bd9a91335cd4179c7cfb68ed014af43c2e7d88f2
144
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bd9a91335c |
feat(preview): embedded dev-server preview pane + dev shutdown controls (#1062)
* feat: embedded preview proxy for local dev servers Add a same-origin server proxy under /api/preview/proxy/:id and matching UI surfaces so local dev servers (Vite, Next, etc.) can be embedded inside OpenChamber. Server (packages/web/server): - New lib/preview/proxy-runtime.js: cookie-gated HTTP+WebSocket proxy to loopback hosts only, with TTL'd targets and SSRF allowlist. - index.js wires the runtime alongside terminal/event-stream. UI (packages/ui): - ContextPanel preview tab with iframe, reload, and open-in-browser. - Inline html code-block preview in MarkdownRenderer. - Terminal auto-detects loopback URLs and offers to open them. - i18n keys across en, es, pt-BR, uk, zh-CN. * perf(preview): cache proxy targets across PreviewPane remounts Module-scoped Map keyed by upstream URL so tab switches and component remounts within the same page session reuse the existing proxy registration instead of POSTing a fresh target each time. In-memory only by design: the server holds the target map in memory and the auth cookie is HttpOnly + scoped to the proxy id, so a stale persisted entry would 404 after a server restart. Entries are evicted on registration error and on a 30s safety margin before TTL expiry. * feat(preview): surface dev-server-down state with retry overlay Iframes don't expose HTTP status to the parent, so when the proxy returns a 502 (upstream dev server is offline) the iframe just renders the raw JSON error body. Probe the proxy URL out-of-band with HEAD (falling back to GET on 404/405) and replace the iframe with a friendly 'Dev server is not responding' overlay + retry button when the upstream is unreachable. Re-probes on reload, on URL change, and on proxy re-registration. * feat(preview): strip frame-busting response headers Many dev servers (Next.js, others) send X-Frame-Options: SAMEORIGIN and/or a CSP with frame-ancestors that block embedding inside the OpenChamber iframe. The proxy is same-origin and already authenticated per-target, so embedding is otherwise safe. - Drop X-Frame-Options outright on proxied responses. - Surgically remove only the frame-ancestors directive from Content-Security-Policy and Content-Security-Policy-Report-Only, preserving every other directive. Drops the header entirely if no directives remain. - Verified end-to-end: upstream sending both headers comes through with X-Frame-Options removed, CSP retaining default-src/script-src but no frame-ancestors, and unrelated headers untouched. * docs(preview): design for remote-host relay agent Design-only doc for the next phase of the embedded preview feature: when OpenChamber runs remotely (cloud/shared/tunnel) and the user's dev server runs on their local machine. Covers architecture (local agent + outbound control WebSocket + server dispatch), pairing flow, wire protocol, security model, failure modes, open questions, and implementation milestones. No code changes. * feat(preview): auto-open preview pane for loopback URLs in chat Detect http(s) loopback URLs in incoming assistant messages and open the preview pane automatically, deduped per (session, url) pair so re-renders or repeated mentions do not steal focus. Add an inline Preview button next to loopback links in chat markdown as a manual fallback when the auto-open was dismissed or the URL appeared in an older message. - url.ts: isLoopbackHttpUrl / extractLoopbackUrls helpers - ChatContainer: module-level dedupe Set + effect on active session tail - MarkdownRendererImpl: optional onPreviewLoopback in main renderer only (SimpleMarkdownRenderer for tool diffs is intentionally untouched) - Reuses existing terminalView.preview.open i18n keys * feat: preview enhancements, dev shutdown, and reliability fixes Add preview start/stop UI in ContextPanel/Header, improve URL detection (Python HTTP server logs, trailing punctuation, IPv6 loopback), fix proxy path filtering to avoid disrupting non-preview WebSockets. Add dev-only /api/system/dev-shutdown endpoint and Header button to terminate local dev processes and orphaned preview servers. Improve terminal cleanup with process group killing, event pipeline reconnect backoff. Update file read APIs with optional flag and cache control. Add /api/system/free-port endpoint, detectDevServer.ts utility, and preview/shutdown i18n strings for 5 languages. * fix: harden preview support * fix: keep terminal toolbar interactive * fix: keep expanded terminal below header * fix: keep preview iframe under proxy path * fix: respect project action preview urls * fix: rewrite preview asset urls * feat: capture preview console logs * feat: annotate preview elements * feat: attach preview annotation screenshots * fix: improve proxied preview hmr * feat: refine preview action UX * fix: address preview review feedback * fix: show auto-discover preview wait state --------- Co-authored-by: William Biggers <will@Williams-MacBook-Pro.local> Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com> |
||
|
|
4b171ee207 |
Prevent page overscroll in chat layout (#1034)
Co-authored-by: vhqtvn <8930337+vhqtvn@users.noreply.github.com> |
||
|
|
177631f123 |
fix: improve VS Code header context and settings visibility
Show language selection in VS Code settings Display context usage in the VS Code chat header Keep VS Code header usage controls visible in expanded layouts |
||
|
|
6176ee2b0f |
refactor: simplify mobile keyboard handling
Removed fragile global keyboard viewport manager Restored native mobile viewport behavior Fixed textarea content shifting outside inputs |
||
|
|
209ffc16e4 |
fix: handle multi-file tool diffs safely
Split multi-file patches before diff rendering Fixed hook dependency lint warnings |
||
|
|
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 |
||
|
|
87db2ea210 |
fix(ui): stabilize mobile keyboard viewport handling (#1026)
* fix(ui): stabilize mobile keyboard viewport handling * fix(ui): tighten mobile keyboard viewport handling * Avoid redundant keyboard open store writes --------- Co-authored-by: vhqtvn <8930337+vhqtvn@users.noreply.github.com> Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com> |
||
|
|
d40041fccd | Fix startup connection and chunk recovery | ||
|
|
6a70d51ea7 |
refactor: drop mobile keyboard/viewport hacks, rely on browser
Remove body position:fixed lock, visualViewport listener in MainLayout (keyboard-inset heuristics, scroll-to-zero lock, focusin/focusout RAF), [data-keyboard-avoid-active] translateY rule, and related CSS vars (--oc-keyboard-inset, --oc-keyboard-avoid-offset, --oc-keyboard-home-indicator, --oc-visual-viewport-offset-top). Strip data-keyboard-avoid* attrs and keyboardAvoid props from Dialog/ScrollableOverlay and consumers. Drop isKeyboardOpen from useUIStore. With body unlocked and the layout plain flex-col (h-dvh), the browser shrinks the viewport on keyboard open naturally — composer sits above the keyboard, header stops lagging, input no longer jitters. FilesView.nudgeEditorSelectionAboveKeyboard now derives the occluded bottom locally from visualViewport + documentElement.clientHeight. |
||
|
|
bf110527a5 | feat: implement window controls overlay layout and styles (#1001) | ||
|
|
a9830cfcf1 |
perf: drastically improve cold-start, bundle size, and streaming performance (#1000)
* perf: drastically improve cold-start, bundle size, and streaming performance
Cold-start optimizations:
- main.tsx: Remove blocking await on prefs I/O — render immediately with
defaults, hydrate persisted settings asynchronously. Cuts 50-200ms from
time-to-first-paint.
- bootstrap.ts: Split directory bootstrap into 3 phases:
* Phase 1 (blocking): path, config, provider, session status — minimum
data needed to render UI. Mark status complete after this phase.
path.get and session.status must both succeed; they have no fallback.
* Phase 2 (deferred): agents, commands, mcp, lsp, vcs, questions,
permissions — fetched after first paint without blocking.
* Phase 3 (lazy): session messages — loaded without blocking init.
- App.tsx: Keep identical provider tree before/after init to prevent
full subtree remount when isInitialized flips. FireworksProvider and
VoiceProvider are lightweight shells; overlays deferred until init.
Bundle-size optimizations:
- App.tsx + MainLayout.tsx + VSCodeLayout.tsx: Code-split heavy views
(SettingsView, GitView, DiffView, TerminalView, FilesView, PlanView,
OnboardingScreen, SettingsWindow, MultiRunWindow) with React.lazy.
Views load on demand when user switches panels.
- vite.config.ts: Lower chunkSizeWarningLimit from 1200KB to 500KB.
Streaming render optimizations:
- streaming.ts: Throttle streaming store writes ~60Hz → ~1Hz. Busy-session
only scan (Set, O(1)).
- MessageList.tsx: Lower virtualization threshold 40 → 15.
- ChatMessage.tsx: React.memo with areRenderRelevantMessagesEqual.
- MarkdownRenderer.tsx: React.memo with explicit prop comparators.
* fix: address Greptile review feedback on bootstrap and provider tree
- bootstrap.ts: Tighten Phase 1 error guard. path.get and session.status
must both succeed; they have no global fallback.
- bootstrap.ts: Replace dead .catch() on Promise.allSettled() with .then()
that inspects individual results for errors.
- App.tsx: Keep identical provider tree before/after init to prevent full
subtree remount when isInitialized flips.
* fix: ensure settings watchers always start and harden bootstrap phase-1 guard
- main.tsx: Start appearance/model/typography watchers unconditionally
after initializing appearance preferences. Previously they only started
inside the secondary settings Promise.all().then(), so a transient I/O
failure would silently leave auto-save disabled for the entire session.
- VSCodeLayout.tsx: Move React.lazy SettingsView const after all imports
to satisfy ESLint import/first rule.
- bootstrap.ts: Replace fragile hardcoded array indices in Phase 1 guard
with destructuring so reordering the Promise.allSettled array won't
silently break the critical-failure check.
---------
Co-authored-by: Shyamalan Kannan <yabuku@Shyamalans-MacBook-Pro.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
|
||
|
|
ecb22e19c3 |
perf: lazy-load heavy dependencies (MarkdownRenderer + CodeMirror languages) (#997)
* perf: drastically improve cold-start, bundle size, and streaming performance
Cold-start optimizations:
- main.tsx: Remove blocking await on prefs I/O — render immediately with
defaults, hydrate persisted settings asynchronously. Cuts 50-200ms from
time-to-first-paint.
- bootstrap.ts: Split directory bootstrap into 3 phases:
* Phase 1 (blocking): path, config, provider, session status — minimum
data needed to render UI. Mark status complete after this phase.
path.get and session.status must both succeed; they have no fallback.
* Phase 2 (deferred): agents, commands, mcp, lsp, vcs, questions,
permissions — fetched after first paint without blocking.
* Phase 3 (lazy): session messages — loaded without blocking init.
- App.tsx: Keep identical provider tree before/after init to prevent
full subtree remount when isInitialized flips. FireworksProvider and
VoiceProvider are lightweight shells; overlays deferred until init.
Bundle-size optimizations:
- App.tsx + MainLayout.tsx + VSCodeLayout.tsx: Code-split heavy views
(SettingsView, GitView, DiffView, TerminalView, FilesView, PlanView,
OnboardingScreen, SettingsWindow, MultiRunWindow) with React.lazy.
Views load on demand when user switches panels.
- vite.config.ts: Lower chunkSizeWarningLimit from 1200KB to 500KB.
Streaming render optimizations:
- streaming.ts: Throttle streaming store writes ~60Hz → ~1Hz. Busy-session
only scan (Set, O(1)).
- MessageList.tsx: Lower virtualization threshold 40 → 15.
- ChatMessage.tsx: React.memo with areRenderRelevantMessagesEqual.
- MarkdownRenderer.tsx: React.memo with explicit prop comparators.
* fix: address Greptile review feedback on bootstrap and provider tree
- bootstrap.ts: Tighten Phase 1 error guard. path.get and session.status
must both succeed; they have no global fallback.
- bootstrap.ts: Replace dead .catch() on Promise.allSettled() with .then()
that inspects individual results for errors.
- App.tsx: Keep identical provider tree before/after init to prevent full
subtree remount when isInitialized flips.
* perf: lazy-load heavy dependencies (MarkdownRenderer + CodeMirror languages)
MarkdownRenderer dynamic import:
- Move heavy implementation (marked, react-markdown, beautiful-mermaid,
react-syntax-highlighter, ~1500 lines) to MarkdownRendererImpl.tsx
- Replace MarkdownRenderer.tsx with thin lazy wrapper using React.lazy
- All 11 existing imports work unchanged — no consumer code modified
- Full markdown stack loads on first render of markdown content
CodeMirror language lazy loading:
- languageByExtension.ts: remove static imports for 10+ less-common
language packages (@codemirror/lang-go, lang-rust, lang-sql, etc.)
- Keep only 6 most common languages static: javascript, json, css, html,
markdown, python, shell
- Less common languages return null from languageByExtension, causing
callers to fall back to loadLanguageByExtension which dynamically
loads from @codemirror/language-data
- Reduces initial bundle by ~200KB+ of language parsers
---------
Co-authored-by: Shyamalan Kannan <yabuku@Shyamalans-MacBook-Pro.local>
|
||
|
|
bc6cb91954 |
Chat input: drag-drop files and folders from file tree with @folder autocomplete (#963)
* feat(chat): drag-drop files/folders from file tree into chat input Features: - Support drag-and-drop files and folders from file tree into chat input - Support @folder autocomplete for file/folder mentions in chat input - Add drag support to file tree nodes for dropping into chat input - Persist confirmed mentions to localStorage across sessions Fixes: - Fix drag state getting stuck with drag enter counter and onDragEnd cleanup - Fix pendingSearchRef counter leak on query cancellation - Fix confirmed mentions highlight/delete for folder paths - Fix clear confirmed mentions on send and autocomplete loading state - Guard drag-start against empty and root-relative paths - Reset loading state when pendingSearchRef reaches zero * fix(chat): address Greptile PR review findings - P1: move confirmedMentionsRef.clear() out of handleQueueMessage to prevent bare-name mentions from being lost when queued messages are sent via handleSubmit - P2: distinguish internal file-tree drag from external file drag in overlay text (Drop to insert as mention vs Drop files here to attach) - P2: add directories to marquee overflow effect dependency array * fix(chat): prune stale confirmed mentions --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com> |
||
|
|
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.
|
||
|
|
4506c18f53 |
UI refresh: Base UI migration + flat tinted button language + mobile polish (#960)
* deps: bump @opencode-ai/sdk to 1.4.6 and add @base-ui/react
* build: align vite configs with Base UI and packages update
* feat(ui): migrate primitives to Base UI
Replace Radix-based wrappers (button, checkbox, collapsible, command,
dialog, dropdown-menu, input, radio, scroll-area, select, separator,
sonner, switch, textarea, toggle, tooltip) with @base-ui/react
equivalents and a refreshed visual language: flexoki theme tokens,
typography utilities, and a redesigned CommandPalette. Add new
fancy-button variant set and a local slot helper for asChild composition.
* refactor(ui consumers): adopt Base UI primitives across the app
Update chat, layout, git views, settings, multirun, session and agent
management screens to use the new Base UI-backed primitives (anchor
width token rename, dropdown/select/dialog usage adjustments, new
component APIs).
* fix(ScrollableOverlay): forward `disabled` to inner element
Add `disabled` to the type so it propagates via rest spread to the
underlying element (e.g. textarea in simple mode). Resolves a type
error reported after the main merge.
* refactor(dropdown consumers): migrate preventDefault sites to closeOnClick
Base UI's Menu.Item does not check `event.defaultPrevented` before
closing the menu, so the Radix-era `onSelect={(e) => e.preventDefault()}`
pattern no longer keeps the menu open. Replace those sites with the
Base UI `closeOnClick={false}` prop (VSCodeLayout rate-limit info
rows; AgentGroupDetail worktree actions).
* docs(AGENTS): note Base UI as primary source for UI primitives
* fix(ContextPanel): center expand/close buttons in tab header
Header (h-8) had the action row pinned with items-end + pb-1.5, which
placed the h-7 buttons ~2px above the header top and made them look
cropped. Switch to items-center so they sit on the same baseline as
the tabs.
* style(tabs): drop transitions on default variant, soften active pill
Default (underline) variant now switches instantly — no transform/width
transition on the underline indicator, no color transition on tab
buttons. Active pill bg is color-mixed down to 55% of
--interactive-selection so light-theme selection doesn't scream.
* style(button): pill-style elevation and introduce chip variant
Solid variants (default/destructive/neutral/primary/basic) now use the
active-pill shadow stack — hairline navy outer ring + stacked tight/soft
drops — instead of the pure-black inset/border approach. Outline
variant (and FancyButton basic) gets the same elevation without the
white top highlight (no fill to catch light).
Add a new chip variant: flat `border border-border/60` button without
elevation, intended for one-of-N toggle groups where a raised
button would read as the wrong affordance.
* style(header): apply pill-style shadow to OpenIn and Project Actions
Both split-button wrappers previously used a 1px CSS border for
definition. Replace that with the pill elevation stack so the header
buttons match the new Button variants and feel less hard-edged.
* refactor(toggles): migrate one-of-N segmented groups to chip variant
Color Mode, When-sessions-expire, voice/STT providers, auth method,
agent mode, MCP transport, tunnel type and terminal Ctrl/Cmd modifier
toggles all read as flat chip selectors rather than raised buttons.
Switch them from variant="outline" to the new chip variant so the
raised pill-style elevation doesn't fight the selected-state tint.
* fix(elevation): theme-adaptive pill-style shadow for dark mode
The navy (rgba(14,18,27,*)) outer ring was invisible on dark surfaces,
erasing the button/pill edge. Add dark: variants that use a subtle
white outer ring (rgba(255,255,255,0.10)) and darker drop shadows
(rgba(0,0,0,0.30/0.20)) so the elevation reads on both themes.
Applies to Button solid/outline variants, FancyButton equivalents,
the OpenIn and Project Actions header split-buttons, and the active
pill indicator in SortableTabsStrip.
* style(button): flat tinted variants with squircle corner-shape
Replace elevated pill-shadow design with a flat tinted language: pale tinted
fill + hairline tinted border + saturated tinted text for default/destructive.
Add CSS corner-shape: squircle with @supports gate so Chrome 136+ gets the
softened superellipse corners at 50px, Safari falls back to a slightly larger
round radius.
* style(tabs): replace active pill shadow with tinted border
* style(header): flat border and squircle corners for OpenIn / Actions
Drop pill-style shadow stacks in favor of a plain hairline border and apply
the same squircle corner-shape treatment as Button so header controls match
the new visual language.
* style(checkbox): drop filled background for tinted checkmark
Checked / indeterminate states now keep the transparent box, show a
primary-base check/minus glyph, and use a softened primary-tinted inset
border (50% mix) so the control reads as selected without a solid swatch.
* style(layout): widen sidebar-to-chat corner radius to 10px
* style(button): retune tinted variants and unify outline fill
- Bump primary fill toward noticeable but still soft (light 10/16/22,
dark 16/22/30) and weaken both primary and destructive borders
(12% light / 20% dark) so the tone reads without shouting.
- Further calm destructive across fill and border so revert-style
actions match the muted reference.
- Align the outline variant fill with --surface-elevated so git-view
generate/commit buttons share the header action button surface.
* style(tabs): align pill variant with new button language
Active pill now uses --surface-elevated fill and border-border/60 to
match the outline button. Track and tab/pill geometry adopt the same
squircle treatment as Button: 9-10px fallback with 50px radius under
@supports(corner-shape:squircle). Also tighten track padding (inline
2px, block 2px) and grow active tab height so the indicator fills
the track instead of floating inside it.
* refactor(theme): hardcode radius scale, drop per-theme override
Radius values are now fixed in the design system (flexoki-dark's scale
becomes the app default). Themes can no longer override --radius-* — the
config.radius block is removed from every theme JSON, dropped from the
Theme type, and no longer emitted by the CSS generator.
* chore(ui): lint/type cleanup across Base UI wrappers
* style(tabs): dial pill track background down to near-invisible
* refactor(chip): aria-pressed drives the selected tinted state
The chip variant now carries the tinted primary palette (same as the
default filled button) under aria-pressed, so consumers toggle selection
via a single aria attribute instead of repeating the border/text/bg
override classes. All existing chip consumers migrate to aria-pressed
and drop their hand-rolled selected className.
* chore(defaults): humanize variant label in thinking selector
* fix(diff): render all lines in single-file mode
Single-file mode wrapped PierreDiffViewer in its own layout=fill which
creates a nested virtual-root; combined with the outer CSS containment
on the wrapper, Pierre's virtualizer measured a clamped viewport and
stopped rendering lines past ~150. Mirror the stacked-mode approach:
hoist a ScrollableOverlay as the sole virtual root/content and render
PierreDiffViewer inline, so the shared virtualizer sees the real scroll
viewport and paints every line.
Also drops an unused cn import in SessionRetentionSettings.
* feat(files): collapse floating editor controls behind icon button
The floating file editor toolbar now starts as a single pill icon button
and expands into the full control row on hover or click. Clicking outside
or moving the pointer away collapses it back, keeping the editor surface
unobstructed while the file is being read/edited. Fullscreen overlay
keeps its always-visible toolbar.
* fix(chat): align mobile session status bar radius with chat input
* fix(mobile): keep radio/checkbox/switch at their native size
Mobile CSS forced min 36x36px on every <button>, which swallowed our
Radio and Checkbox primitives (also buttons with role=radio/checkbox)
and ballooned them on touch devices. Exclude role=radio/checkbox/switch
from the touch-target rule and pin the Radio/Checkbox box size with
arbitrary px so padding-scale overrides cannot stretch them either.
* chore(mobile): drop unused cornerRadius prop plumbing
The status bar now hardcodes var(--radius-lg) on both views, so the
cornerRadius prop and the useUIStore selector that sourced it in
ChatInput are dead weight.
|
||
|
|
799a202e99 |
feat(plans): save assistant messages as editable project plans with improve/implement flows
Add Save-as-plan action on assistant messages that writes a markdown plan into the project plans directory and suggests a title from the text. Plan view opens a specific saved plan via targetPath, supports inline edits, and can kick off improve or implement flows in a new or worktree session. Ship the matching hidden magic prompts (plan.todo/improve/implement) so the instructions applied by these flows are visible and editable in the Magic Prompts page. Remove the plan editor font override, CodeMirror now uses the app font stack. |
||
|
|
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. |
||
|
|
fd8972a7d9 |
Add save actions and cross-platform file manager support (#848)
* files-download-feature * feat: add save option to file directory context menus and viewer * fix: open files in the system file manager |
||
|
|
fccf4bad32 |
feat: session worktree isolation (#913)
* 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> |
||
|
|
ddc1039d1c | fix(sync): unify live session truth across chat and 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 |
||
|
|
700138c9b5 |
fix(git): restore changes panel visibility and sidebar sync (#886)
* fix: restore git changes panel visibility and sidebar sync
Two independent fixes:
1. ChangesSection virtualizer returned empty rows due to useMemo caching
getVirtualItems() with a stale stable reference. First render produced
an empty array, and useMemo never recomputed because rowVirtualizer
reference never changed. Removed the useMemo to call getVirtualItems()
directly on every render. Also added a ResizeObserver to force
remeasurement on visibility transitions (defensive).
2. RightSidebarTabs now keeps git status fresh while the sidebar is open
via useRightSidebarGitSync hook (10s polling with ensureStatus).
Replaces the GitPollingProvider removed in commit
|
||
|
|
952132a08d | fix: remove desktop translucency to reduce macOS compositing cost | ||
|
|
8de1884f5c | refactor: remove plan polling, add message-driven detection via usePlanDetection hook | ||
|
|
4663abd6f3 |
refactor: isolate chat hot paths and lazy server watchers
Split static history from live tail, stabilize chat data identities, and reduce scroll-shell churn so old viewport content stops waking on stream deltas. Also defer watcher startup and health polling so idle runtime work better matches actual usage. |
||
|
|
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. |
||
|
|
c5497e9823 | perf: remove idle background polling hooks | ||
|
|
d982171689 | refactor: centralize git and pr store refresh | ||
|
|
5045d21821 |
perf: reduce idle desktop load by gating plan-mode checks
- Add startup feature flag for plan mode from OPENCODE_EXPERIMENTAL* env vars. - Stop recurring app health polling and keep a one-time startup check. - Skip plan-mode synthetic parsing and plan tab/file work when flag is off. |
||
|
|
9d78c48949 |
fix: keep selected session header details stable
- Prevent header flicker to Untitled during session refresh - Resolve header session data from global sessions first - Pass session directory hints on more session-switch paths |
||
|
|
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>
|
||
|
|
1231fd773e |
feat: improve VS Code dev flow and stabilize sidebar/chat behavior (#754)
* fix: improve session sidebar tooltip and truncation behavior - Keep new-draft tooltip anchored to its trigger button - Fix minimal-mode worktree/group header text truncation - Tune minimal-mode right padding to reduce early label clipping * fix: render reasoning through markdown pipeline - Use Streamdown rendering for reasoning in live chat mode - Remove italic styling from reasoning text - Render expanded reasoning content with MarkdownRenderer * chore: remove legacy electron dependencies - Removed unused Electron packages from root and UI manifests - Deleted obsolete Electron context menu type declaration - Regenerated lockfile after dependency cleanup * fix: handle non-repository folders in git status API - Prevent 500 errors when status is requested outside a valid Git repo - Improve repository detection using `git rev-parse --git-dir` - Reduce noisy server logs for expected non-repo status checks * fix unloaded session chat layout flicker * fix: reduce noisy TTS status polling Cache and dedupe TTS status requests, and only check provider availability when the related voice features are enabled so disabled voice setups stay quiet. * perf: throttle background PR git status refreshes * fix: improve VS Code Explorer file drop mentions in chat - Add Explorer context action to insert selected files as @mentions. - Handle Explorer drag-and-drop to prefill @file mentions instead of attachments. - Prevent duplicate plain-path text when dropping multiple files. * fix: deduplicate recent sessions in VS Code sidebar - Hide sessions from main list when already shown in recent - Apply dedup only in VS Code runtime - Keep session search behavior unchanged * feat: add true HMR dev flow for VS Code extension - Load VS Code webview from Vite dev server with React refresh preamble - Add `vscode:dev` runner that starts watchers and opens Extension Development Host - Update VS Code dev docs and scripts to use the new HMR startup flow * feat: polish VS Code session sidebar and attachment UX - Add resizable sessions sidebar in VS Code layout - Tighten session list spacing and hover behavior in VS Code - Remove bulk file/image attach success toasts while keeping error toasts |
||
|
|
53c2a0d919 |
feat: instant draft-first worktree creation and multi-run launcher redesign (#741)
## Summary - **Instant worktree creation from chat draft**: selecting "+ New worktree" in the draft branch selector immediately creates a session draft and bootstraps the worktree in the background — no modal interruption - **Redesigned multi-run launcher**: compact 2-column grid layout in a right-sized dialog with scroll shadow, sticky footer, tooltips replacing verbose descriptions, and project icons in the selector - **Branch selector aligned across surfaces**: multi-run and agent manager branch pickers now use the shared git store and match NewWorktreeDialog behavior (same default resolution cascade, no synthetic HEAD option, all branches shown) - **Opaque model multi-select dropdown**: fixes text bleed-through on translucent backgrounds by compositing `--surface-elevated` over `--surface-background` - **"+ New" inline button in sidebar worktree headers** for faster worktree creation ## Why Worktree creation was behind modal flow that interrupted the user's train of thought. The draft-first approach lets users start typing immediately while the worktree bootstraps. The multi-run launcher had an oversized form layout with redundant explanations, and its branch picker behaved differently from the main worktree dialog - causing confusion about which branches were available and what the default was. |
||
|
|
b4949c6e33 |
fix(files): incremental directory refresh on create/rename/delete (#721)
Replace the full-tree nuke-and-reload in refreshRoot() with a targeted refreshDirectory(parentPath) that only evicts and reloads the single parent directory whose contents changed. Before: every create/rename/delete call cleared the entire childrenByDir cache and reloaded only the root, causing all expanded subdirectories to flash empty and visually reset. After: only the parent directory of the affected entry is reloaded in-place; every other expanded directory keeps its cached children, so the tree state is preserved and there is no visible flash or scroll-position reset. Applies to both FilesView and SidebarFilesTree. |
||
|
|
bf61ccedc7 |
fix: external links in desktop app - context menu and open behavior (#716)
* fix: allow native context menu on links in chat messages The desktop app was blocking the context menu on all elements except specific allowlisted ones (terminal, input, textarea, etc.). This prevented users from right-clicking on HTTP links in chat messages to access the 'Open Link' option. Added 'a' (anchor) tag to the allowlist to restore native context menu functionality for links. Fixes #708 * fix: use tauri.shell.open for external links in desktop app - Add global window.open override to init_script that routes HTTP/HTTPS URLs through tauri.shell.open() instead of window.open() - Add openExternalUrl utility that prefers tauri.shell.open with window.open fallback - Add openExternalUrl to MarkdownRenderer for link safety.onLinkCheck - Replace window.open with openExternalUrl in ProvidersPage for OAuth URLs Fixes #708 * fix: unify external link opening across desktop and UI Added a shared URL opener that only allows http/https links. Replaced duplicated Tauri/window link-open logic in key UI sections. Removed fragile desktop window.open override and markdown external-link modal behavior. --------- Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com> |
||
|
|
321cc7252a |
Major UI refresh: sidebar redesign, theme expansion, and chat performance optimizations (#706)
## Summary Complete sidebar redesign and comprehensive UI polish pass with performance optimizations, theme system refinements, and desktop integration improvements. ## Key Changes **Sidebar & Navigation Redesign** - Redesigned sessions sidebar layout with unified button primitives - Added activity sections with project grouping and improved session organization - Refined sidebar corners, spacing, and visual hierarchy - Removed NavRail component in favor of streamlined sidebar - Stabilized sessions bar toggle position in fullscreen mode **Performance Optimizations** - Reduced chat streaming CPU usage and storage churn - Optimized task tool polling and live timers with debouncing - Prevented chat state races and reduced background request load - Debounced draft writes and coalesced session reloads - Optimized message store updates and turn tracking **Theme & Visual System** - Added theme-aware window corners (desktop) and border radius tokens - Introduced glassmorphism effects on desktop sidebar - Added backdrop blur to UI elements **Chat Experience** - Added session-based permission auto-accept toggle in chat input - Polished permission shield UX with improved icon sizing and spacing - Fixed chat scroll-to-bottom behavior and timeline tracking - Enhanced tool output display with better path label detection - Removed duplicate draft context details in chat header - Added text selection menu to chat messages **Git Improvements** - Refreshed git history visual design with cleaner dividers - Added remote removal action in sync selector - Stabilized git polling to prevent excessive requests - Improved tool output rendering for git operations **Settings & Panels** - Fixed mobile scrolling on settings pages - Made outside-click settings close instantly - Reduced settings load churn and CPU spikes - Improved services dropdown layout and spacing - Softened panel resize handles **Desktop Integration** - Synced macOS window theme with app theme - Restored window dragging in sidebar header zones - Fixed system window corners on macOS - Improved header session metadata and action controls **Button & Component Standardization** - Unified button primitives across all components - Standardized destructive action patterns - Removed unused button variants (button-large, button-small) - Aligned context tab close hit areas --------- Co-authored-by: Iuliia Ivashko <yulia.ivashko@gmail.com> |
||
|
|
edf0a197a8 |
perf: batch mobile keyboard viewport handlers with rAF and debounce (#660)
Reduce layout thrashing during virtual keyboard open/close on mobile by batching resize and visualViewport event handlers to once per animation frame. No visual or behavioral changes — desktop is unaffected. - MainLayout: rAF-batch visualViewport resize/scroll and window resize listeners (was firing 10-20x per frame during keyboard animation) - Header: rAF-batch ResizeObserver and resize/orientationchange callbacks - ChatContainer: rAF-batch ResizeObserver and resize fallback callbacks - useDeviceInfo: 150ms debounce on resize listener (device type does not change during keyboard transitions) |
||
|
|
7f37256e96 |
fix: unify update-check API flow across runtimes (#672)
* fix: align update flow across web, desktop, and vscode Unify runtime update handling in shared UI update state Adjust desktop and VS Code bridge integration for consistent behavior Improve server package-manager update plumbing for safer checks * fix: sync update checks between vscode and server Align VS Code bridge behavior with server package-manager logic Reduce mismatches in update detection across runtimes * fix: stabilize update checks across ui and server |
||
|
|
d88c782541 |
fix: project label text hidden by selection overlay in dark mode (#648)
In NavRail's ProjectTile, the expanded active item renders an absolutely positioned background overlay (--interactive-selection) that visually covers the text label. The label span was missing 'relative z-10', unlike NavRailActionButton which correctly uses it. Fixes text being invisible on selected project in dark themes (nord-dark, dracula-dark, monokai-dark, gruvbox-dark, etc.) |
||
|
|
e1542bdee6 | fix: submit project actions with Enter input (#625) | ||
|
|
d1a41000ca |
perf: speed up desktop startup and unify theme-aware branding (#596)
* fix: unify startup logo and loading theme behavior Show desktop window immediately with animated splash logo Align splash/logo colors with selected app theme and default themes Keep auth loading state on full-screen logo without size jump Make project SVG icons follow active app theme Use the active theme foreground color for project icons discovered from favicons Apply server-side SVG color overrides for currentColor icons via icon request params Keep non-SVG project icons unchanged while preserving existing fallback behavior * perf: speed up desktop startup and unify loading logo visuals Desktop startup now shows UI sooner while backend boot continues in background Startup host probing uses a faster local path with safer remote fallback retries OpenChamber logo cube highlights now match splash screens consistently * fix: keep macOS traffic-light buttons in the correct position on load Stop native window title updates on macOS during app initialization Prevent title bar relayout that reset custom traffic-light positioning * fix: recover missing providers and agents after fast startup Retries provider/agent loading when connection is up but core config is still empty Prevents cold-start state where models/agents appear only after manual project switch Keeps startup responsive with throttled background recovery in app bootstrap |
||
|
|
79143bff4c |
feat: massive chat reliability + UX pass (web/desktop/mobile/vscode) (#593)
## Added Features - Add VS Code save-as-image flow for assistant messages via webview bridge + native save dialog. - Add hourly desktop update checks after startup. - Add new tool output display mode: `Changes` (auto-expand edit/write/patch only; keep activity expanded; mode guidance text). - Add GitHub PR attachment flow in chat input with PR picker + attached PR chip/details. - Add mobile overlay presentation for GitHub Issue and PR pickers (shared with desktop picker content). ## Fixes - Save-gate project icon updates until explicit Save; allow icon removal with same save-gated behavior. - Restore clickable chat action buttons in sticky header mode (desktop + Firefox hit-target issue). - Clamp sticky user messages to bounded chat height and allow internal scrolling. - Prevent drawer context crash during iPad/tablet orientation switching. - Improve text-selection action menu placement on narrow screens. - Move assistant message time into clock tooltip; keep duration display clean. - Hide `Link GitHub Issue` row in VS Code chat input area (GitHub flow is not yet ready there). - Remove laggy close animation in text-selection popover; keep open motion/positioning behavior. - Fetch branches when picker opens and cache empty; show loading state instead of false “No branches found”. - Fix share-image export metadata rendering (theme background resolution, timestamp rendering, footer alignment). - Scope MCP services status/toggles to active directory to avoid cross-project leakage. - Improve long user-message clamp behavior (40% cap variant, hidden scrollbar, scroll shadows, expansion detection). - Fix desktop `Check for Updates` menu handler; prevent duplicate checks; show clear success/error toasts. - Stabilize long user-message scrolling behavior (follow-up hardening). - Avoid premature web update failure on slower servers. - Restore user message image previews + fullscreen gallery navigation payload. - Repair desktop chat drag-and-drop image attachments when native drop coords are missing. - Move GitHub issue linking entry into Add attachment menu. - Align header context usage percentage visuals with context panel. - Align `@` file search with active project in all runtimes. - Route `@` file discovery through OpenCode SDK `find.files`; remove legacy `/api/fs/search` reliance. - Make chat `@` mention behavior consistent with files-style behavior. - Keep status-row todos in stable order after status changes; add compact status icons; replace noisy priority labels. ## Refactors / UX Consistency - Simplify chat attachment model and remove project file picker path. - Keep composer focused on `@` mention file flow. - Use direct `Attach files` action in VS Code instead of attachment dropdown path. - Unify issue/PR picker behavior between desktop and mobile overlays. |
||
|
|
575cfa2604 | fix(ui): make interactive controls show pointer cursor (#590) | ||
|
|
ca754d6589 |
feat: make chat file references clickable and responsive (#587)
* docs: clarify named and quick Cloudflare tunnel usage * feat: make chat file paths openable from rendered responses * perf: speed up chat file-path links and open behavior * fix: open chat file references at mentioned lines * fix: prevent context panel flicker on blocked file opens |
||
|
|
bcbc2646fd |
Improve files view behavior and Open In integration (#579)
* feat: open focused file in selected desktop app * fix: make file tree indicators match active open tabs * fix: limit preview functionality to markdown files only * feat: add Open In action to file editor toolbar. add antigravity to openin app list |
||
|
|
bdad912ea5 |
feat: streamline worktree session flow (#577)
* feat: add unified worktree creation dialog with GitHub integration Unified dialog for creating worktrees with branch selection and GitHub integration New GitHub picker dialog for selecting issues and PRs with validation Persistent state when switching between new branch and existing branch modes * refactor: minimalistic compact design for worktree dialogs Redesigned dialogs with minimalistic compact layout Moved mode tabs inline with dialog header Inline validation errors with action buttons * fix: maintain consistent dialog height in all states Fixed dialog height fluctuations when loading or switching tabs Consistent 300px height for loading, empty, and populated states Nested container structure prevents layout shifts * refactor: minimalistic compact UI redesign for worktree dialogs Minimalistic compact layouts with reduced spacing and inline tabs Standardized SortableTabsStrip with pills variant for mode selection Two-row linked item display and inline validation with action buttons * feat: mobile overlays for worktree dialogs with branch pickers Mobile overlay panels replace dropdowns for branch selection Fixed footer button overlapping with proper flex layout Responsive design with conditional mobile/desktop rendering * refactor: relocate selected item badge to footer with proper alignment Selected item badge moved to footer left side on desktop Proper alignment with Cancel/Select buttons using h-8 height Cleaner header layout removing the selected item display * perf: use cached branches from git store for instant branch selection Branch list now loads instantly from git store cache No more loading delay when opening new worktree dialog Removes redundant API calls by using existing cached data * refactor: improve branch selection UI with grouped headers and compact layout Group branches into Local and Remote sections with clear headers Compact dropdowns with better sizing and text wrapping Bold labels and smaller issue/PR titles for cleaner layout * feat: add prefilled slugs and improve branch selection UX in new worktree dialog Prefilled unique branch name when opening new worktree dialog Group branches into Local and Remote sections with bold headers Instant branch list from cache with compact, wrapped dropdowns * feat: update GitHub button text to be more descriptive * feat: add GitHub issue linking to draft sessions Link GitHub issues to draft sessions with full context including body, labels and comments View issue author avatar, number and title in session input area Open linked issues in browser or remove link with inline action buttons * style: redesign queued messages with text-only row layout * style: redesign attached files with text-only row layout and file type icons Redesigned attached files with clean text-only rows matching linked issue style Split display into two rows: image previews and file list with proper type icons Removed internal Server/Local indicators to reduce visual clutter * refactor: remove New from PR and Manage branches from sessions UI Remove New from PR and Manage branches buttons from sessions sidebar Delete GitHubPullRequestPickerDialog component and related code Simplify GitView to sidebar-only mode with no mode prop * fix: simplify new session shortcuts and remove worktree toggle * fix: restore issue and PR context when creating worktree sessions Worktrees created from GitHub issues and PRs now seed the new session with visible and synthetic context messages Sidebar now opens the created linked session directly instead of creating an extra draft Visible prompts are cleaner and include issue or PR numbers for clarity |
||
|
|
b4cd16f55b |
feat(ui): polish chat and git workflows with mobile UX and reliability fixes (#569)
* feat: add chat option for user message rendering mode * feat: add chat option to toggle sticky user header * feat(ui): overhaul context panel with reusable tabs and embedded session chat Enable parallel context workflows with persistent tabbed views and isolated session chat while reducing resize and background runtime overhead. * feat: polish context panel and git sidebar tabs Refined context panel tab behavior and visuals for smoother switching and resizing Reused the new tabs component in right sidebar and git sidebar with fit layout Improved git section spacing, selection controls, and bulk revert confirmation flow * feat: open diff files in editor at changed lines Add edit actions in diff views to open files at the first changed line Support per-file open-in-editor from All Files headers and icon-only action in single-file view Improve file jump UX with load-aware navigation and reduced visual blink during line targeting * fix: stabilize pill tabs and prevent git commit pathspec failures Unified sortable tab variants to match animated styling behavior with responsive spacing and cleaner sidebar chrome Fixed active tab pill measurement so size/position recalculates correctly when dropdowns reopen Commit API now filters stale file paths before staging to avoid pathspec errors on deleted files * fix: align user message action row spacing and hover behavior * fix: persist user message view preferences in settings Save plain-text and sticky-header toggles to settings.json when changed Restore both chat display preferences from settings.json on startup Validate and accept both preference fields in the settings API * fix: improve git and sidebar tab layout on mobile * fix: refine mobile user message action row spacing Show mobile user-message actions in a consistent external row for sticky and non-sticky modes Tune button row height and vertical position to match both mobile variants Reduce sticky-header gradient tail and tighten assistant gap after user messages * fix: improve chat action hover zones and mobile top shadow logic Expand desktop trigger area so user action buttons reveal across the full row Add sticky-header phantom hover row so inline actions appear from the whole button lane Hide chat top scroll shadow on mobile only when sticky user headers are enabled * fix: remove commit message input scrollbar flicker Added optional scrollbar class support to shared textarea wrapper. Disabled overlay scrollbar for Git commit message input. Kept auto-resize behavior while preventing one-line empty-state micro-scroll. * feat: make model provider groups collapsible in selector Add collapsible provider headers in the chat model dropdown Persist expanded/collapsed provider state across sessions Refine provider header UX with inline chevrons and no hover highlight * feat: arrange chat settings into a compact two-column layout Places User Message Rendering next to Mermaid Rendering. Places Diff Layout next to Diff View Mode. Reduces right-column spacing to better match other settings sections. * fix: show worktree branch edit controls in draft sessions Detect worktree mode from current directory when session metadata is not yet bound Enable immediate branch rename UI in Git sidebar without session switching * feat: add beta badge to side panel menu action |