* 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>
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.
* feat: add 'Open files in preview mode' setting
- Add defaultFileViewerPreview setting to persist user preference for file viewer default mode
- Add checkbox in Settings → Sessions → Session Defaults to toggle the setting
- Files now open in preview mode by default when setting is enabled
- Respects per-file-type localStorage persistence for markdown/HTML/JSON files
- Setting persists across sessions via /api/config/settings endpoint
* fix: address PR review feedback
- FilesView: respect HTML localStorage preference in file-change effect,
falling back to global setting only when nothing stored
- DefaultsSettings: read defaultFileViewerPreview directly from config store
instead of redundant local state + separate fetch
- DefaultsSettings: extract duplicated toggle logic into
handleToggleFileViewerPreview callback
* fix: honor default preview setting for markdown files
Previously mdViewMode only read localStorage on mount (deps: []), so
when settingsDefaultFileViewerPreview was enabled and no MD_VIEWER_MODE_KEY
was stored, markdown files silently opened in edit mode — ignoring the
setting for the very file type users most want to preview.
Fold md init into the per-file-change effect, mirroring the html handling:
localStorage preference wins, falling back to the setting-derived default.
The saveMdViewMode callback stays untouched so user-initiated toggles
still persist.
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* fix(files): refresh open file content after external edits
Previously, opening a file in the Files view and then editing it externally
(e.g. via CLI or another editor) would show stale content. Even closing and
reopening the file returned cached content — a full page reload was required.
Root causes:
1. The in-memory readFile cache used path-only hits, with no metadata
validation. External edits were invisible until the cache was evicted.
2. No polling mechanism existed to detect external changes to the open file.
Fix:
- Add mtimeMs to statFile across all runtimes (web, VS Code, desktop).
- Cache layer (RuntimeAPIProvider): validate cache hits against current stat
metadata (mtimeMs + size). On miss, use stat→read→stat to avoid TOCTOU.
- UI layer (FilesView): poll the open file every 2s; on detected change, set
loadedFilePath=null to trigger the existing load effect once (no double
reload). Skip polling when tab is hidden or editor has unsaved changes.
- After save, refresh the stat ref so the next poll doesn't see a spurious
change from the save itself.
Addresses review feedback from PR #827 (double reload + TOCTOU).
* fix(files): address P2 review findings
- readFreshFile retry now uses stat→read→stat to maintain TOCTOU
protection during the retry path (not just the initial read).
- Replace isDirty in polling effect deps with isDirtyRef to avoid
unnecessary interval teardown/restart on every edit/save cycle.
* 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.
* 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.
Add "Copy Relative Path" menu item below "Copy Path" in the file tree
context menu. Uses the existing getDisplayPath helper to compute a path
relative to the project root, which is more useful when sharing file
references in AI chat or commit messages.
Move go-to-line UI into the editor action bar for a unified layout
Keep toolbar height stable with compact inline line input and Go action
Align file action icons to a single always-enabled visual style
Option+G now matches correctly on macOS keyboard layouts
Go-to-line shortcut no longer gets blocked by editor textbox guards
Prevents accidental symbol insertion when triggering go-to-line
Add a Go to Line dialog to the file editor that accepts a line number
and scrolls the CodeMirror editor to that position. Triggered via:
- Keyboard shortcut: Alt+G (macOS/Windows/Linux)
- Toolbar button next to the existing Find in file button
- Add JsonTreeViewer component with collapse/expand for nested objects and arrays
- Add rainbow colors by depth level using CSS color-mix() with syntax theme tokens
- Add virtualization support (@tanstack/react-virtual) for large JSON files (>200 nodes)
- Add JsonTreeView wrapper with Expand All / Collapse All toolbar
- Integrate into FilesView: tree/text toggle button, JSON file detection
- Integrate into ToolPart: auto-detect JSON tool outputs, render as tree
- Integrate into ToolOutputDialog: JSON tree view in expanded dialog
- Add jsonTreeUtils.ts: parse, flatten, path utilities
No new dependencies - uses existing @tanstack/react-virtual.
Colors derived from existing syntax.* theme tokens - works across all themes.
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
- Add isHtmlFile() helper to detect .html/.htm files
- Add htmlViewMode state for preview/edit toggle
- Add localStorage persistence for HTML view mode preference
- Show PreviewToggleButton for HTML files
- Render HTML in sandboxed iframe with srcdoc
- Inject base tag for relative CSS/JS/image paths to work
- Sandbox permissions: allow-scripts, same-origin, forms
- Reset view mode when switching files
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.
* fix: project label text hidden by selection overlay in dark mode
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.)
* feat: auto-save file editor with 1.5s debounce
Add debounced auto-save to the file editor so users no longer need to
manually save with Cmd/Ctrl+S or the save button. Files are automatically
saved 1.5 seconds after the user stops typing.
- Debounced auto-save effect that triggers on draft content changes
- Cmd/Ctrl+S still works for immediate save (cancels pending debounce)
- Updated save button UI with contextual status indicators:
'Saving...' spinner, 'Saved' checkmark, or save icon when dirty
- Reset auto-save status when switching between files
- Applied consistently to both normal and fullscreen editor views
* 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
* 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
* 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
* feat(session-folders): drag-to-folder DnD, sort by activity, and UX improvements
- Add DraggableSessionRow wrapping each session row so the whole row is
draggable; stopPropagation prevents outer group-reorder DnD from firing
- Add DroppableFolderWrapper + SessionFolderDndScope (inner DndContext
scoped per group) with closestCenter collision detection
- DragOverlay matches exact width/height of dragged row so cursor stays
aligned
- Folder header highlights (ring + primary colour) when a session hovers
over it during drag
- + button on folder header opens a dropdown: 'New session' / 'New folder'
- + button on each folder row creates a session scoped to that folder
- Empty folders are no longer auto-deleted (removed .filter(sessionIds.length>0)
from addSessionToFolder / removeSessionFromFolder / cleanupSessions)
- Sessions inside a folder are sorted by most-recent activity (same
compareSessionsByPinnedAndTime logic used everywhere else)
- Sort comparator now takes sessionAttentionStates so lastUserMessageAt /
lastStatusChangeAt is used when newer than session.time.updated; all
sort call-sites and their useMemo/useCallback deps updated accordingly
- Remove foldersMap from cleanup effect deps to prevent cascade re-renders
when folders change; read current value via getState() instead
* fix(session-folders): new session is placed into the correct folder
sendMessage() was calling useSessionManagementStore.createSession()
directly, bypassing the targetFolderId logic in useSessionStore.createSession.
Fix: read targetFolderId from draft at the top of the draft branch in
sendMessage, then call addSessionToFolder immediately after the session
is created and before the draft is closed. Also propagate targetFolderId
through openNewSessionDraft options and NewSessionDraftState type.
* feat(session-folders): add sub-folder support (one level deep)
- SessionFolder gains optional parentId field for hierarchy
- createFolder accepts parentId to create sub-folders
- deleteFolder cascades to remove all child sub-folders
- SessionFolderItem renders sub-folders before sessions in body;
new sub-folder button (RiFolderAddLine) visible at depth 0 only
- renderOneFolderItem in SessionSidebar builds the tree recursively;
sub-folders are indented via depth prop (ml-3 on root's children)
- Persist/hydrate parentId correctly from localStorage
* feat(session): add delete confirm dialogs and improve subtitle UX
- Add confirmation dialogs before deleting sessions or folders
- Show relative time (e.g., '2h ago', '35min ago') for recent sessions
- Replace +/- diff numbers with file change count (e.g., '3 files changed')
- New folders use default name without forcing rename
- Cleaner, less cluttered session list UI
* fix(session-folders): skip folder cleanup while sessions are loading
Prevents race condition on reload where cleanupSessions() runs before
the server returns the full session list, causing folder-session
assignments to be incorrectly wiped from localStorage.
* feat(files): add 'Reveal in Finder' to file tree context menus
Add a new context menu action to reveal files and folders in the system
file manager (Finder on macOS, Explorer on Windows, xdg-open on Linux).
- Add POST /api/fs/reveal server endpoint with cross-platform support
- Add revealPath() to FilesAPI interface and web implementation
- Add 'Reveal in Finder' menu item to SidebarFilesTree and FilesView
- Files are highlighted in Finder (open -R), folders are opened directly
* fix(plan-comments): restore embedded inline comment widgets in Plan view
Reinstate CodeMirror block-widget comments so plan annotations stay anchored to selected lines and preserve drag/selection behavior without floating overlays.
* fix(file-comments): return Files editor comments to embedded widgets
Use inline block widgets for file drafts while keeping full-path draft scoping, so similarly named files no longer risk comment collisions.
* fix(diff-comments): render inline comments through annotation portals
Replace absolute floating positioning with annotation-target portals to keep diff comments attached to their lines across shadow DOM updates.
* chore(comments): remove deprecated floating comment hook
Drop the unused floating-comment implementation now that plan, file, and diff views all use embedded comment rendering paths.
* fix(codemirror): expose gutter width as CSS variable
Measure the current CodeMirror gutter and publish --oc-editor-gutter-width on the editor host so inline widgets can size to the visible code area without hardcoded dimensions.
* fix(context-panel): publish panel width for embedded widgets
Set --oc-context-panel-width on the context panel in both docked and expanded modes so comment widgets can follow the active panel width dynamically.
* fix(file-comments): constrain inline input to editor content width
Use context-panel and editor-gutter CSS variables to cap comment input width to the visible editor content area, keeping action buttons fully visible in no-wrap mode.
* fix(file-comments): constrain inline comment cards to content area
Apply the same variable-based width cap to saved comment cards so card actions stay visible when long lines force horizontal scrolling.
* fix(diff-comments): stabilize new comment annotation identity
Derive new-comment annotation ids from selection side and line range, and reuse that id for portal target lookup and keys to avoid remount glitches.
* feat: tabbed right sidebar, context panel, floating diff comments
* fix: auto-close left sidebar when context panel opens
- Increase default context panel width from 520 to 600 pixels
- Increase sidebar minimum width from 200 to 300 pixels
- Replace collapsible component with custom button in diff view
* refactoring: rework sidebars, tabs, and file tree layout
- Rewrite AnimatedTabs as segment-style with sliding indicator
- Upgrade SidebarFilesTree to match FilesView features (context menus,
git status, file icons, CRUD dialogs, fuzzy search ranking)
- Restructure FilesView header: tabs row + actions row, remove breadcrumbs
- Show relative path in context panel header, track active tab
- Allow left sidebar to stay open alongside context panel
- Hide diff/files tabs from header on desktop (mobile-only)
- Move chevron after group name in session sidebar
- Compact tab heights in right sidebar and git view
- Size PreviewToggleButton to match other action buttons
- Remove directory loading spinner from folder icons
* feat: add project icon and color customization
- Enable users to assign custom icons to projects
- Allow users to choose accent colors for projects
- Stabilize repo status UI during project switching
* feat: add scroll fade indicators to editor tabs
* style: reduce spacing and icon sizes in header
* style: adjust tab component padding from uniform to vertical-horizontal
* feat: Add session state indicators to project tabs
* feat: Enhance session status handling and improve UI responsiveness
* fix: preserve upstream tracking on branch rename
* fix: improve initial remote selection for pull requests
- Uses saved remote name from previous session when available
- Selects remote based on tracking branch when possible
- Falls back to origin or first available remote
* perf(diff): faster highlight, stable stacked scroll
- split/unified Pierre worker pools; prefer shiki-wasm
- align diff CSS line-height; disable scroll anchoring; drop WebKit compositing hacks
- harden stacked pin/align (cancel on user scroll/input); prevent overscroll
- make overlay scrollbar MutationObserver optional; disable for diff container
* feat: handle binary files in diff view
* fix: adjust project tabs layout and drag regions
* style: update drag overlay visual styling
* feat: enable number keys to switch projects in the sidebar
* fix: recognize octet-stream as text-based MIME type
* feat: add keyboard navigation to context panel
* feat: add session pinning to sidebar
- Pin important sessions to keep them at the top
- Pinned sessions persist across browser sessions
* refactor: move context usage display from chat input to header
* fix: remove comment toast notifications
Remove success toasts after updating a comment
Remove success toasts after saving a comment draft
Keep saving logic and line reset intact without toast feedback
* fix(ui): remove success toast on draft save
Remove success toast after saving a draft
Suppress toast when updating an existing draft
* fix(planview): suppress toast on save and reset state
Remove toast notification after saving or updating a draft
Clear comment input and line selection after commit
Preserve draft save/update logic without triggering toast
* fix(FilesView): use file path for drafts labeling
Use file path for draft labels instead of file name for precise grouping
Filter and associate drafts by file path to ensure correct per-file draft retrieval
Improve handling of files with identical names across directories
* fix(PierreDiffViewer): use fileName as label for drafts
Use fileName as the draft label when editing or adding diffs
Fallback to 'unknown' if fileName is missing for draft labels
Align draft filtering to the provided fileName rather than basename
* refactor(ui): remove unused fileLabel computation
* refactor: adapt BlockWidget to use shared container map
Enable BlockWidget to reuse DOM containers via a shared map
Pass container map into widget decorations to preserve portals across updates
* feat: enable drag-to-select and guard new comments in FilesView
Guard new comment input from opening during drag-to-select actions
Enable drag-based line selection with initial drag state and mobile support
Reset dragging state on mouseup to end the selection
* fix(PlanView): gate new comment input during dragging
Add drag state to disable new comment input during selection drag
Indicate drag mode during mobile tap-extend to avoid unintended actions
Pass drag state to child UI to reflect ongoing drag
* feat: enable search panel in CodeMirrorEditor
Add enableSearch and searchOpen props to toggle search UI
Wire CodeMirror search extension and keybindings into the editor
Automatically open or close the search panel based on searchOpen
* feat(files): add breadcrumb navigation and inline comments
Add breadcrumb trail derived from file path and root to navigate directories
Introduce inline comment UI with draft storage for per-file discussions
Enable long-press gestures on items to reveal quick actions
* feat: add useLongPress hook for long-press gestures
Provide a reusable long-press hook with onLongPress and onTap callbacks.
Support pointer and touch events with movement threshold to cancel.
Optionally vibrate on long press for haptic feedback.
* feat: add expandedPaths state and expand actions for file tabs
Introduce expandedPaths per root to remember expanded folders
Add actions to toggle, expand a path, and expand multiple paths
Initialize expandedPaths for new roots and clamp state to valid roots
* feat: add onSearchOpenChange prop to CodeMirrorEditor
Expose onSearchOpenChange callback prop for search state
Notify parent when search panel visibility toggles
* feat: show branch info in session sidebar and enrich worktrees
* feat: refresh sidebar branch display when switching branches in git menu
* fix: ensure branch name displays on mobile by fixing flex alignment
* style: make branch name font smaller, especially on mobile
- stop button replaces send button in footer instead of overlay
- floating queue button above stop when input has content
- buttons same size, stop icon larger for emphasis
* feat: added themes system
* feat: smart sidebar auto-hide for files/diff tabs + lower files sidebar threshold
* feat: added Checkbox component and update theming
- Add reusable Checkbox component for toggles across UI
- Replace several inputs with Checkbox in settings and commands panels
- Add DiffIcon and apply surface/border theming to key UI areas
* feat: Add convert-vscode-theme.cjs to convert VS Code themes to OpenChamber format
* refactor: remove unused permission logic from ChatInput
- Remove unused permission rules parsing logic from ChatInput
- Memoize renderTheme in DiffWorkerProvider to avoid unnecessary recalculations
- Remove forceOpaque helper in vscode theme adapter
* fix: guard VSCode theme loading in MarkdownRenderer
* feat: add custom user themes loading and reload
- Load user themes from ~/.config/openchamber/themes at runtime
- Expose /api/config/themes to fetch custom themes
- Allow theme reloading from Settings → Theme → Reload themes in the UI
* feat: track and prune open files in FilesView
Add openFiles state to manage currently opened file tabs
Prune open files when a file is renamed or deleted to keep in sync
Reset openFiles on refresh and clear file content when selection changes
* style: improve file dropdown layout and truncation
Adjust button layout to allow long file names to truncate correctly
Keep dropdown arrow visible while resizing by preventing shrink
Add a centralized useEffectiveDirectory hook to resolve the active directory
Use the hook across DiffView, FilesView, GitView, and TerminalView
Fix list styling by rendering ul/ol bullets outside
* feat: add plan/build mode switching and Plan view tab
Add PlanView view and integrate plan tab into main layout
Introduce plan_enter and plan_exit tools with icons and status strings
Persist per-session agent selections in context to reduce UI flicker during mode switches
* feat: enchanced plan discovery checks in header and plan view
* fix(ui): align PlanView and Header with sessionDirectory logic
Remove dependency on context store in header and compute sessionDirectory from session or currentDirectory
Compute display and repo paths using sessionDirectory for PlanView and update path resolution
Poll plan content every 3 seconds to reflect changes without heavy retries
* feat(ui): improve header tab switching and add copy buttons for plan
Switch header tab navigation to central UI store and auto-switch when plan is unavailable
Show checkmark icon after copying file contents or path with auto-hide timeout
Extend plan view path resolution to prefer session directory when present