Commit Graph
100 Commits
Author SHA1 Message Date
Bohdan Triapitsyn 5614012acb fix(ui): keep streaming deltas through pipeline 2026-05-03 13:42:50 +03:00
Bohdan Triapitsyn f810a3316c fix(ui): prevent streaming text flicker and first-chunk loss 2026-05-03 02:32:05 +03:00
Bohdan Triapitsyn 4ad5d2f4b7 fix(ui): tolerate invalid message parts 2026-05-01 15:08:44 +03:00
Bohdan Triapitsyn a077735bcf fix: keep chat pinned during delayed rendering
Keeps long historical sessions scrolled to the bottom after load
Preserves bottom follow while assistant responses grow
2026-05-01 14:13:18 +03:00
Bohdan Triapitsyn d820e25ea1 fix: keep generated prompts scrolled into view
Force chat to bottom on normal sends
Scroll generated commit prompts into view
Preserve saved scroll restore for passive navigation
2026-05-01 13:48:07 +03:00
Bohdan Triapitsyn c924e85a29 feat(settings): add response style presets 2026-05-01 01:27:31 +03:00
Bohdan Triapitsyn f96374c738 fix(ui): remove unused lint leftovers 2026-04-30 23:47:43 +03:00
Bohdan Triapitsyn 68ef6a0b18 fix(settings): contain settings page scrolling 2026-04-30 23:45:17 +03:00
Bohdan Triapitsyn 123805a112 style(ui): disable text selection on buttons 2026-04-30 23:08:59 +03:00
Bohdan Triapitsyn 41cc8e556f fix(git): cap fetch/pull remotes dropdown size and reposition
Constrain width and add scroll so long remote URL lists stay inside the
dropdown, and anchor it to the left of the trigger to match the chat
input model picker.
2026-04-30 22:54:37 +03:00
Bohdan Triapitsyn 63da6e6292 feat(ui): unify overlay animations and trim tooltip delays
Use base-ui's transition-status pattern (data-starting-style /
data-ending-style) for Dialog, DropdownMenu, Select, Tooltip and Popover
so open/close animate consistently at 150ms ease-out without flicker.
Wrap dialog popups in a centered flex container so the scale animation
no longer fights with translate-based positioning, and switch
PendingChangesBar to a real Popover so it actually animates closed.

Convert ScheduledTaskEditorDialog from raw Radix to the shared Dialog
wrapper, give ScheduledTasksDialog a stable min-height to prevent layout
shift mid-animation, and reset NewWorktreeDialog form state on open
instead of close so fields don't empty during the close transition.

Drop tooltip delay from 700ms to 300ms globally and remove all
per-component overrides except the model/variant/agent selectors in the
chat input (600ms).
2026-04-30 22:41:33 +03:00
Bohdan Triapitsyn 24533dfe32 feat(palette): unify quick open into command palette with multi-source search
Merge file picker into command palette. Single Cmd+P entry searches
files, sessions, settings pages and commands; groups re-order by best
fuzzy score per source. Sessions show branch labels; git status is
lazily fetched for all session directories on open.

Drop QuickOpenDialog and Cmd+K shortcut.
2026-04-30 18:37:37 +03:00
Bohdan Triapitsyn 57eec76a7b fix: polish VS Code chat actions and bash tool text
Hide multi-run action in VS Code chat
Remove rounded bash tool text surfaces
2026-04-30 14:19:20 +03:00
Bohdan Triapitsyn 8152bd7808 perf: reduce desktop quit risk polling
Refresh quit risk only when quitting
Use in-process status for Electron local server
Avoid repeated scheduled task status scans
2026-04-30 13:54:58 +03:00
Bohdan Triapitsyn bbd83d60c6 fix: use valid Zen summaries for notes 2026-04-30 13:07:37 +03:00
Bohdan Triapitsyn 90c8c948f8 chore: update changelogs with unreleased changes 2026-04-30 12:22:49 +03:00
Bohdan Triapitsyn 371eac9db3 fix: stabilize remote preview proxy 2026-04-30 12:22:49 +03:00
Bohdan Triapitsyn 6cb5e4342f fix(chat): soften aborted turn message 2026-04-30 00:51:28 +03:00
Bohdan Triapitsyn e6c7cc5589 feat(chat): add wide layout setting 2026-04-30 00:45:25 +03:00
Bohdan Triapitsyn e9edfa5302 fix: proxy preview app requests 2026-04-30 00:29:35 +03:00
Bohdan Triapitsyn fb66542d87 fix: rewrite preview module imports 2026-04-30 00:21:25 +03:00
Bohdan Triapitsyn 17650becc0 release v1.9.10 2026-04-28 17:45:58 +03:00
Bohdan Triapitsyn 6186688e17 fix: polish pinned session indicators
Pinned recent sessions stay at top when already recent
Default session rows align activity, pin, and chevron indicators
Pinned icon no longer crowds session titles
2026-04-28 17:12:49 +03:00
Bohdan Triapitsyn 41369fa5f2 fix: align chat error message icon
Centers the error icon with message text
Improves error text wrapping
2026-04-28 16:37:15 +03:00
Bohdan Triapitsyn 958ffe063e fix: stabilize older message loading 2026-04-28 16:26:35 +03:00
Bohdan Triapitsyn 8861f79708 fix: default app language to English
Stop auto-detecting locale from system/browser on first launch
Use saved language preference when users explicitly choose one
2026-04-28 15:40:21 +03:00
Bohdan Triapitsyn 3ae3c526cf fix: show tab close action on icon hover
Close action now replaces the tab icon on hover
Tabs without icons keep the existing close fallback
2026-04-28 15:23:40 +03:00
Bohdan Triapitsyn 4f51abddf4 fix: improve external file and path handling
Open external context files read-only
Preserve leading-dot paths in UI
Keep workspace write operations guarded
2026-04-28 14:06:25 +03:00
Bohdan Triapitsyn 15338be265 fix: align git changes tree controls
Use consistent checkboxes for files and folders
Align tree row indentation
Use folder icons for expandable directories
2026-04-28 13:00:50 +03:00
Bohdan Triapitsyn 72e706972a fix: harden managed OpenCode startup 2026-04-28 12:35:00 +03:00
Bohdan Triapitsyn ccab4f49df fix: preserve shell PATH for managed OpenCode
Restores shell-first PATH resolution for desktop-launched OpenCode
Keeps app PATH entries as fallback
Adds coverage for managed OpenCode PATH merging
2026-04-28 01:19:12 +03:00
Bohdan Triapitsyn 6f2ffdb2d6 fix: adjustments to vesper themes 2026-04-27 23:12:58 +03:00
Bohdan Triapitsyn f8e29ded94 fix: ignore stale downgrade update suggestions
Prevents older reported versions from showing as updates
Falls back to registry data when update API is stale
2026-04-27 22:48:57 +03:00
Bohdan Triapitsyn d2121cee8c fix: allow linking GitHub issues in session drafts
Use current working directory for issue lookup before active project fallback
Prevent false "Not Found" errors when draft context differs from selected project
2026-04-27 18:44:05 +03:00
Bohdan Triapitsyn 390db58cc8 fix: add VS Code quota support for MiniMax and Ollama (fix for #1051)
Adds MiniMax coding plan quota support
Adds Ollama Cloud quota support
Detects configured providers in VS Code
2026-04-27 18:44:05 +03:00
Bohdan Triapitsyn c83986cd94 feat: improve project directory picker
Adds command-palette style project browsing
Keeps native Finder picker as optional desktop action
Supports disabled existing projects and create-and-add paths
2026-04-27 15:45:14 +03:00
Bohdan Triapitsyn 54a09914ac fix: open project action links in browser
Project action URLs now open in the system browser from Electron
Adds a safe Electron shell bridge for external HTTP links
2026-04-27 14:24:20 +03:00
Bohdan Triapitsyn b5c58320cf feat: rename workspace review command
Renamed OpenChamber review command to /workspace-review
Kept built-in /review available separately
Updated autocomplete and localized settings copy
2026-04-26 23:15:35 +03:00
Bohdan Triapitsyn 67f082988c release v1.9.9 2026-04-26 20:49:28 +03:00
Bohdan Triapitsyn 6a8f90ac53 fix: restore per-file file view switching
Adds preview/edit toggle for regular text files
Respects default preview setting on first open
Keeps per-file view choices during session
2026-04-26 20:39:45 +03:00
Bohdan Triapitsyn 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
2026-04-26 20:20:23 +03:00
Bohdan Triapitsyn 799998c5f8 feat: add selectable interface and code fonts (10 for each)
Adds separate UI and code font choices in settings
Applies font changes immediately
Lazy-loads selected remote fonts
2026-04-26 18:48:43 +03:00
Bohdan Triapitsyn a3b300f7a9 feat: add keyboard turn navigation
Navigate chat turns with ArrowUp and ArrowDown
Only triggers when the chat area is focused
Supports scrolling to the latest visible turn
2026-04-26 18:17:13 +03:00
Bohdan Triapitsyn c8eb9a6c5f feat: widen chat content area
Chat messages use more horizontal space
Chat input keeps its previous max width
2026-04-26 18:04:42 +03:00
Bohdan Triapitsyn 6176ee2b0f refactor: simplify mobile keyboard handling
Removed fragile global keyboard viewport manager
Restored native mobile viewport behavior
Fixed textarea content shifting outside inputs
2026-04-26 17:44:33 +03:00
Bohdan Triapitsyn be723fa58b fix: organize appearance settings sections
Separated theme and localization controls
Added localized section title
2026-04-26 17:21:52 +03:00
Bohdan Triapitsyn 7205d7a34f fix: skip stale session directories (issue #1010)
Filter active sessions to known project and worktree directories
Stop probing session directories with filesystem listing
2026-04-26 17:18:02 +03:00
Bohdan Triapitsyn 209ffc16e4 fix: handle multi-file tool diffs safely
Split multi-file patches before diff rendering
Fixed hook dependency lint warnings
2026-04-26 16:58:07 +03:00
Bohdan Triapitsyn 2fc2914fde fix: improve mobile text selection menu
Use a mobile bottom sheet layout for selection actions
Prevent long localized labels from being squeezed
2026-04-26 14:37:34 +03:00
Bohdan Triapitsyn a2dc5021bc fix: stabilize model and task UI hints
Keep thinking shortcut hint visible without layout shift
Restore JSON file icons across shared file lists
Use task status icons in the input status row
2026-04-26 14:27:06 +03:00
Bohdan Triapitsyn 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
2026-04-26 14:03:39 +03:00
Bohdan Triapitsyn 8cce0b4c7b docs: narrow validation expectations 2026-04-25 20:44:35 +03:00
Bohdan Triapitsyn 52593858a0 fix: guard service worker registration 2026-04-25 20:44:17 +03:00
Bohdan Triapitsyn 7049aeffd3 fix: show all mention results without tabs 2026-04-25 20:44:12 +03:00
Bohdan Triapitsyn 836d1b1b3b fix: preserve user PATH for managed opencode 2026-04-25 20:44:07 +03:00
Bohdan Triapitsyn d40041fccd Fix startup connection and chunk recovery 2026-04-25 16:56:37 +03:00
Bohdan Triapitsyn e9b117c88b fix: add border for tooltip for better visibility 2026-04-24 12:30:11 +03:00
Bohdan Triapitsyn 70ce38068f Split message stream websocket bridges 2026-04-24 12:30:11 +03:00
Bohdan Triapitsyn 7030549d78 Share upstream event stream hub 2026-04-24 12:30:11 +03:00
Bohdan Triapitsyn 8e610ca8b3 refactor: scope mention autocomplete lists to active tab
Files/directories/recentFiles render only on the 'files' tab; agents
only on 'agents'. Memoize visible slices and drop loading spinner when
the files tab is inactive.
2026-04-24 12:30:11 +03:00
Bohdan Triapitsyn 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.
2026-04-24 12:30:11 +03:00
Bohdan Triapitsyn 9d9af7e263 Improve event stream resilience 2026-04-24 12:30:11 +03:00
Bohdan Triapitsyn 522cebf127 fix: hide empty input status row
Only show the pending changes accessory when workspace changes exist
Remove wasted vertical space above the composer when the status row is empty
2026-04-23 10:55:27 +03:00
Bohdan Triapitsyn cb6ba34811 feat: add local workspace review slash command
Add /review command with high-signal diff review instructions
Expose workspace review prompts in Magic Prompts settings
Show /review in command autocomplete with OpenChamber command metadata
2026-04-23 10:45:41 +03:00
Bohdan Triapitsyn 480944b14b fix(electron): align desktop update restart with updater flow 2026-04-22 23:46:38 +03:00
Bohdan Triapitsyn c94da0c1a0 release v1.9.8 2026-04-22 22:15:23 +03:00
Bohdan Triapitsyn 6886713b8e fix: preserve duplicate slash commands in autocomplete
Show commands with identical names from different sources
Add stable command IDs so selection no longer depends on name dedupe
Keep command insertion flow working across chat and prompt editors
2026-04-22 22:08:34 +03:00
Bohdan Triapitsyn 863be8073b feat(chat): /summary slash command for non-destructive session summaries
Typing /summary (optionally with a topic hint) produces a structured
summary as a normal assistant message — without compacting or mutating
session history. Reuses the existing "Start new session from this answer"
button for hand-off.

Prompts are customizable in Settings → Magic Prompts under a new Session
group. Autocomplete shows an 'openchamber' badge to distinguish our
commands from OpenCode-built-in ones.
2026-04-22 21:56:35 +03:00
Bohdan Triapitsyn f24e6de21b fix: improve event stream reconnect reliability
Recover stalled event streams without dropping the session
Wait briefly for reconnection before showing connection lost errors
Persist Electron server logs for easier disconnect debugging
2026-04-22 21:03:02 +03:00
Bohdan Triapitsyn 16f1d1b353 fix(ui): update button blue fill/border in dark mode
variant=default applied dark: primary-tinted bg/border that overrode the
status-info overrides (which only set light-mode). Add dark: variants so
the pill stays blue end-to-end.
2026-04-22 19:50:11 +03:00
Bohdan Triapitsyn 1ab522e656 fix(session-folders): persist via server endpoint, not client-built home path
Client previously built `${homeDirectory}/.config/openchamber/sessions-directories.json`
using client-side homeDirectory, which in some boot paths resolved to the
active workspace and wrote the file inside the project. Replace with
GET/POST /api/session-folders — server uses os.homedir() directly, so the
file always lands in ~/.config/openchamber/ regardless of client state.
2026-04-22 19:39:02 +03:00
Bohdan Triapitsyn a2730b793e fix(notifications): suppress permission notifs when session auto-accepts
Server now mirrors client-side Permission Auto-Accept via
POST /api/notifications/auto-accept and short-circuits permission.asked
dispatch (walking the session parent chain). Prior 500ms debounce raced
the client auto-response and leaked notifications.

Also hint under Summarize Last Message that templates must contain
{last_message} for the setting to take effect.
2026-04-22 19:22:50 +03:00
Bohdan Triapitsyn 1ad64cc69e fix(electron): keep traffic lights visible during dock-restore
macOS snapshots the window at miniaturize; re-assert trafficLightPosition
on minimize/restore/show/focus so the snapshot and animation keep the
buttons placed. Also nudged y from 18 to 17 to align with sidebar icons.
2026-04-22 18:58:52 +03:00
Bohdan Triapitsyn ff1e043bbe fix(ui): scroll-shadow bottom fade + hide tasks row when nothing active
- ScrollShadow: 1px subpixel tolerance so bottom fade clears at scroll end
  on Retina (fractional scrollTop was keeping data-bottom-scroll stuck)
- StatusRow: hide tasks indicator when no in-progress/pending todos remain
2026-04-22 17:50:59 +03:00
Bohdan Triapitsyn ee9c37bea8 fix(electron): follow OS theme changes when Color Mode = system
desktop_set_window_theme and readThemeSource both checked themeVariant
before themeMode. When UI sends mode='system' with variant='dark' (the
resolved appearance at call time), main pinned nativeTheme.themeSource
to 'dark' — freezing Chromium's prefers-color-scheme and blocking the
renderer's matchMedia listener from reacting to OS theme changes.

Priority now: mode='system' → themeSource='system' (ignore variant).
Variant is only a fallback for callers that omit mode.
2026-04-22 15:53:57 +03:00
Bohdan Triapitsyn dcd68baa09 fix: cross-client settings sync and sidebar session pagination
- Paginate /experimental/session via time.updated fallback so sidebar
  loads past the 200-item first page when server omits x-next-cursor.
- VSCode extension persists all settings in shared
  ~/.config/openchamber/settings.json (not only opencodeBinary),
  matching Desktop and Web. Canonical read from disk with globalState
  fallback for eager migration of pre-existing users.
- Desktop settings sync: guard the contextBridge read-only
  __OPENCHAMBER_HOME__ assignment that was silently throwing a TypeError
  and skipping the whole applySettings chain — leaving server-synced
  values (autoDeleteAfterDays, autoDeleteEnabled, sessionRetentionAction)
  stuck at local defaults. Also wait for Zustand persist hydration before
  applying server settings to avoid overwrite races.
2026-04-22 15:43:15 +03:00
Bohdan Triapitsyn be65a9bd1f fix(electron): preserve changelog on download + reliable restart-to-update
- useUpdateStore: keep sidecar-sourced body when merging fresh desktopInfo
  (electron-updater returns 'See release notes at ...' which clobbered it)
- main.mjs: defer quitAndInstall/relaunch via setImmediate so IPC reply
  flushes first; wire update-downloaded and error events; log restart path
2026-04-22 00:57:58 +03:00
Bohdan Triapitsyn 2a0aef89d5 release v1.9.7 2026-04-22 00:36:25 +03:00
Bohdan Triapitsyn 5ec7f46105 fix: surface disconnect reason, switch health probe to /global/health (#978)
- event-pipeline: onDisconnect now carries a reason tag (ws_closed:code=N, ws_error_frame, ws_closed_before_ready, sse_error)
- useConfigStore: store lastDisconnectReason, clear on reconnect
- send guards: embed reason in Connection lost toast so we can tell which path tripped
- lifecycle: isOpenCodeProcessHealthy hits /global/health (healthy flag) with 5s timeout instead of /session with 2s, avoids false restarts under stream load
2026-04-22 00:33:21 +03:00
Bohdan Triapitsyn 630d9e3a82 fix: keep notifications alive so clicks still work on macOS
GC was collecting the JS Notification object after ~1 min, silently
killing click handlers. Hold a ref in a Set, release on click/close/
failed. Also order app.focus({steal}) before restore/show so the app
comes forward when minimized to Dock or Cmd+H'd.
2026-04-20 23:46:24 +03:00
Bohdan Triapitsyn 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.
2026-04-20 23:32:32 +03:00
Bohdan Triapitsyn daa485c5d9 refactor: drop ellipsis dots on active tool labels, use opacity only
Ellipsis dots after tool names were visually noisy. Replace with a
subtle opacity dip (100% -> 70% with 200ms transition) while the tool
is active.
2026-04-20 22:28:50 +03:00
Bohdan Triapitsyn 34a8cb3f3b fix: don't silently swallow bootstrap SDK errors
sdk.path.get() / sdk.project.list() / sdk.app.agents() etc. return
{ data, error, response } without throwing on non-2xx. The x.data! /
x.data ?? [] pattern turned HTTP 5xx warmup responses into empty state,
and retry() never saw an error to retry.

Wrap everything through unwrap(): preserve the HTTP status on the
thrown Error so retry()'s transient detection (5xx) fires. Fixes the
class of bugs where a slow opencode sidecar leaves agents/commands/
sessions looking empty after startup.
2026-04-20 22:28:41 +03:00
Bohdan Triapitsyn 70fd6aaacc fix: stop settings.json from being wiped on launch
Electron main, ssh-manager, and the embedded web server all write the
same settings.json. readJsonFile/readJsonRoot silently coerced any read
failure (including mid-write parse errors) to {}, and writes were plain
fs.writeFile. A partial read during a concurrent write let the reader's
next read-modify-write overwrite the whole file with only the field it
just set — wiping projects, desktopDefaultHostId, and more. Next launch
showed the welcome chooser because defaultHostId was gone, and the
sidebar was empty because projects were gone.

- Switch all writers to atomic tmp+rename so readers never see partial
  JSON.
- Add mutateSettingsRoot() in Electron main to serialize read-modify-
  write pairs across its own call sites (hosts config, window state,
  desktop port, ssh instances, vibrancy).
- Keep read-on-error returning {} to avoid crashing startup callers,
  but log loudly now so we can catch it if it ever happens again.
- useProjectsStore: don't clobber a populated cache with empty incoming
  settings. If settings ever do come back empty, the sidebar stays
  intact until a real, non-empty sync lands.
2026-04-20 22:28:32 +03:00
Bohdan Triapitsyn f1b84e3291 fix: always signal connected state on first successful connect
markConnected skipped onReconnect when hasConnected was false and there
was no prior disconnect. Consumer state (isConnected) starts at false and
stayed false — the send button threw "Connection lost" until the HTTP
health check in initializeApp happened to race a setState through.

Now onReconnect fires on every successful connect; hasConnected is no
longer needed and gets removed.
2026-04-20 22:28:18 +03:00
Bohdan Triapitsyn 895edaf557 fix: preserve sessions when bootstrap races with event stream
Two fixes for intermittent "sessions missing at app launch":

- loadSessions: if session.list comes back empty but the store already
  has sessions populated via WS events, don't clobber. OpenCode can
  answer HTTP with [] while the WS snapshot is still arriving.
- loadSessions: wrap SDK errors preserving HTTP status so retry()'s
  transient detection (5xx) actually fires. String(errorObject) was
  erasing the status and retry gave up after one attempt.
- retry: broaden transient 5xx window from just 502/503 to any 5xx.
  OpenCode can return 500/504 while warming up (session store reading
  from disk), and those deserve the same retry treatment.
- Log bootstrap empty-session retries for future diagnosis.
2026-04-20 21:42:16 +03:00
Bohdan Triapitsyn c2eeadd9a3 fix: unstick isConnected when first connect follows a disconnect
markConnected skipped onReconnect on the very first successful connect
(hasConnected=false branch), but an earlier failed attempt could have
already fired onDisconnect -> isConnected=false. Consumer state stayed
stuck at false forever. Fire onReconnect in that case too.

Repro: launch app while opencode sidecar is still warming up so the
first WS attempt fails. Second attempt succeeds but isConnected stays
false; user sees "Connection lost" toast on every send.
2026-04-20 21:42:04 +03:00
Bohdan Triapitsyn a8a336b0a0 perf: halve stream flush cadence from 60hz to 30hz
FLUSH_FRAME_MS 16 -> 33. Delta events keep coalescing, just into larger
windows. AssistantTextPart already throttles text at 100ms, so the extra
granularity produced no visible benefit — just extra store.setState calls
and subscriber notifications per second.
2026-04-20 20:56:24 +03:00
Bohdan Triapitsyn 9f81c8fa46 perf: trim observer work and defer Prism during streaming
- Drop characterData from scroll-follow and file-link MutationObservers;
  ResizeObserver already catches content growth.
- extractMermaidBlocks: bail out when markdown has no 'mermaid' at all.
- MarkdownCodeBlock: skip Prism highlighting while code is actively
  changing, render plain pre until settled for 300ms.
2026-04-20 20:52:57 +03:00
Bohdan Triapitsyn ad7790b3ec perf: event-driven scroll follow, kill 60hz RAF during streaming
Persist mode was rescheduling RAF every frame forever, forcing layout
reads (scrollHeight) on each tick. Swap to ResizeObserver+MutationObserver
that trigger short RAF bursts only when content actually changes.

-10pp renderer CPU under active streaming.
2026-04-20 19:15:43 +03:00
Bohdan Triapitsyn ec38468e93 refactor: refresh chat indicators, settings pages, and shared UI primitives 2026-04-20 18:48:33 +03:00
Bohdan Triapitsyn 1a2738a2e2 fix: sidebar dropdown works properly for sub-sessions 2026-04-20 18:03:34 +03:00
Bohdan Triapitsyn 0f753fa62e fix: restore attach PR dialog checkbox behavior
Fix include-diff checkbox so clicks reliably toggle state
Return checkbox to normal size in the attach PR dialog
Keep label and checkbox interaction consistent
2026-04-20 16:48:50 +03:00
Bohdan Triapitsyn 75c6277fa5 chore: update opencode sdk version 2026-04-20 16:02:54 +03:00
Bohdan Triapitsyn 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.
2026-04-20 15:41:15 +03:00
Bohdan Triapitsyn 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.
2026-04-19 15:52:54 +03:00
Bohdan Triapitsyn 3e52be95c5 feat: add configurable planning magic prompts 2026-04-19 01:49:50 +03:00
Bohdan Triapitsyn 46102ffbf3 feat: add ability to upload external plans and remove them from sidebar 2026-04-19 00:52:14 +03:00
Bohdan Triapitsyn f04d732e15 fix(chat): resolve parent project for worktree-session messages
Save-as-plan and Add-to-notes actions were disabled on messages whose session
ran in a worktree, because session.directory alone does not reflect the
worktree attachment — the authoritative cwd lives in session-worktree-store.

Use useEffectiveDirectory (attachment cwd > worktree metadata > session.directory)
and resolve the parent project via the worktree map the sidebar already builds,
extracted into lib/projectResolution so session-ui-store and chat actions share
one resolver. No extra git calls.
2026-04-18 16:05:55 +03:00
Bohdan Triapitsyn 4aa0dcdc1c fix(readme): remove vacation notice from README 2026-04-18 14:11:24 +03:00