mirror of
https://github.com/stablyai/orca.git
synced 2026-10-21 00:03:46 +00:00
eb35c7fa3e24c463d9b9f55643cf34a2dc6d0ea2
136
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5165cd1e19 |
fix(browser): scope Cmd/Ctrl+F find to the focused split (#11348) (#11351)
* fix(browser): scope Cmd/Ctrl+F find to the focused split (#11348) The browser pane's renderer-path Find handler is a window-global capture-phase keydown listener, but it armed on `isActive` (the active tab within its own group) rather than on whether its split holds focus. In a terminal+browser split, the browser was therefore `isActive` even while the terminal held keyboard focus, so it swallowed Cmd/Ctrl+F and opened find-in-page in the browser instead of find-in-terminal. Thread a focused-split signal (`isFocused`) from BrowserPaneOverlayLayer — derived from `activeGroupIdByWorktree` — down to the Find handler and gate the listener on it. This mirrors how terminal leaves already gate global shortcuts via `focusedGroupId` in TabGroupSplitLayout. Floating browser panels omit the prop and fall back to `isActive`, preserving their behavior. The IPC path (webview guest focused) is unchanged; it only fires when the guest genuinely has focus. Not platform-specific: the chord resolves through `keybindingMatchesAction` (Mod -> metaKey on macOS, ctrlKey elsewhere), so the same path is fixed on macOS, Linux, and Windows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(browser): preserve Find before split focus settles * fix(browser): handle stale focused split IDs * fix(browser): route guest Find to source page * test(browser): wait for split address bar * test(browser): focus split before Find routing --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
2b88931b93 |
Bug floating workspace shortcuts route to main w (#10433)
* fix(floating-workspace): route panel shortcuts to the floating panel, not the main window Floating-workspace close/index keyboard shortcuts leaked to the main window behind the panel. Route them through the floating panel across all four keydown layers via an atomic focus signal, panel-owned indexed switching with a tri-state outcome, an event-target-aware close guard, and a floating-scoped guest IPC bridge. Changes A-E and findings F2/F3/F4/F6/F7/F8/F9/F11/F-adv/F-dl/F-feas. Co-authored-by: Orca <help@stably.ai> Co-authored-by: feelgom <littlestork4@gmail.com> Co-authored-by: Wooseong Kim <innocarpe@gmail.com> * fix(review): clear stale floating-panel reclaim intent on panel close The module-singleton reclaim intent (F3) is armed at an emptying-close but only consumed by the visibleFloatingItemCount->0 effect. If a concurrent tab-create keeps the panel from reaching 0, the intent stays armed and could survive to a later empty-panel mount and steal keyboard focus. The !open release effect now clears it (defense-in-depth), matching the outside-pointerdown/window-blur paths. Flagged by 4 review personas (correctness, adversarial, julik-races, maintainability). Co-authored-by: Orca <help@stably.ai> * test(floating-workspace): cover L1 index-chord yield and deferred-close reclaim-arm timing Two additive R2-review tests for the #10288 floating-workspace shortcut routing change set: - createMainWindow: assert L1 yields the initial indexed-switch chord (tab-index and worktree-index) to the floating panel without preventDefault or dispatch, and contains held-key auto-repeats in main (preventDefault, no dispatch). Closes the untested Change B (F4) path. - FloatingTerminalPanel: assert an emptying, panel-owned close whose closeTerminalTab defers/cancels (onClosed never fires) leaves the reclaim intent unarmed, so no later empty-panel mount can reclaim focus for a close that never happened. The prior mock fired onClosed unconditionally, so this arm-timing (F3) branch was uncovered. Co-authored-by: Orca <help@stably.ai> * fix(review): resolve round-1 findings F-1..F-6 - F-1: re-derive panel emptiness from live store at arm time; clear stale reclaim intent on repopulating create so an unrelated later close can't consume it and steal keyboard focus from the main workspace. - F-2/F-5a: single-source the panel's non-creation shortcut claims via matchFloatingWorkspacePanelShortcut(); shared isTerminalPaneCloseChord() predicate for L2/L3; App.tsx gate + both FloatingTerminalPanel call sites now call the SSOT so index/rename/max-min ownership can't drift. - F-4: L2 keydown gate is event-target-aware (matches L1 yield) so an L1-yielded chord is still consumed during a transient panel blur. - F-5b: export clearReportedFloatingFocusCache() + reset it in test setup. - F-5c: split floating-workspace-item-actions.ts into focus-reclaim + guest-bridge modules (AGENTS.md file-naming). - F-6: trim verbose design-code comments to single-line WHY. Co-authored-by: Orca <help@stably.ai> * fix(floating-workspace): remove finding reference labels These internal review labels (F1–F7) and change identifiers were used during development and are no longer needed in the code. * fix(floating-workspace): preserve reclaim for deferred dirty closes Dirty editor closes defer to the save dialog and complete asynchronously. The reclaim-arm check must survive the queue and execute when the file leaves— otherwise the next Cmd/Ctrl+T misses the floating panel entirely. Also resolve browser guest page ids to their owning workspace for correct routing. * perf(floating-workspace): single-pass shortcut match and stable listeners Three hot-path cleanups with no routing behavior change: - Match each keydown once. App.tsx's yield gate now calls one matchFloatingWorkspacePanelChord instead of scanning the creation table and the chrome table separately, and the panel splits dispatch into resolveFloatingPanelShortcut + applyFloatingPanelShortcut so the surface keydown preflight shares its resolution instead of re-matching. - Pin the window-capture and guest-bridge listeners to [open] by reading the live closures (tab order, activate, close helpers, dispatch) through a ref, so a tab switch or reorder no longer re-subscribes them. - Cache the per-tab TerminalPane ref callback so a parent render stops detaching and re-attaching every pane handle. Creation chords stay target-gated and chrome chords stay ungated, matching the two matchers the combined one composes. Pre-commit hook bypassed: config/oxlint-react-doctor.json fails to parse against this worktree's stale node_modules (oxlint 1.71.0 / react-doctor 0.2.10 vs the pinned ^1.75.0 / 0.9.1) for any file. oxlint, oxfmt --check, tsc, the max-lines ratchet, and the targeted vitest runs were run manually. * fix(floating-workspace): keep TerminalPane ref callback identity stable The per-tab ref callback cache deleted its own entry on detach. After a same-id remount (key is tab.id + generation) React detaches the old element *after* the new render already read the cache, so the delete dropped the entry that render had just written — every later render minted a fresh identity and forced React to detach/re-attach the pane, the churn the cache existed to prevent. Move the cache into terminal-pane-handle-registry.ts: detach clears only the handle, attach re-arms the cache entry, and dead tab ids are pruned from an effect keyed on the live tab list. Unit-tests cover attach/detach identity stability — FloatingTerminalPanel.test.tsx's React mock discards effect deps and ref identity, so component tests can't catch this class of bug. Also softened the combined-matcher comment: App.tsx's old `||` already short-circuited, so that call site buys drift-safety, not fewer scans. Gates: tsc (web), oxlint, oxfmt --check, max-lines ratchet, 332 focused vitest tests. Pre-commit hook bypassed: config/oxlint-react-doctor.json fails to parse against this worktree's stale node_modules (oxlint 1.71.0 + react-doctor 0.2.10 vs the pinned ^1.75.0 / 0.9.1) on untouched files too. * fix(floating-workspace): pure registry init for react-doctor Replace null-guarded ref mutation during render with useState lazy init so CI check:react-doctor:changed stops failing on FloatingTerminalPanel. * fix(floating-workspace): drop unused registry type import Satisfies oxlint no-unused-vars after pure useState registry init. Local pre-commit react-doctor config fails on stale node_modules; CI has current plugins. --------- Co-authored-by: Orca <help@stably.ai> Co-authored-by: feelgom <littlestork4@gmail.com> Co-authored-by: Wooseong Kim <innocarpe@gmail.com> |
||
|
|
badf91101b |
fix(quality): enforce performance-safe lint baseline (#11074)
* fix(quality): clear safe existing lint findings * fix(quality): keep lint cleanup allocation-free * fix(quality): enforce performance-safe baseline * test(terminal): drain deferred confirmation cleanup |
||
|
+21 |
b31e66ed48 |
fix(browser): survive a transient Windows lock during cookie import (#10697)
* fix(browser): retry a transient Windows lock on the Chromium cookie snapshot copy The snapshot attempt loop only reacts to a `false` return, so a throwing copy escaped it entirely. On Windows, AV/EDR briefly opens a file literally named "Cookies" with FILE_SHARE_NONE, which turns an otherwise-fine copyFileSync into `EBUSY errno -4082 syscall=copyfile` and aborts the whole import (#9355). Route the main-database copy through the existing `copyFileWithWindowsRetry`, already used for the same AV window in #1507. It is a no-op off Windows (maxAttempts=1), so POSIX still fails fast and ENOENT is never retried on any platform. * fix(browser): degrade the cookie-import staging DB instead of aborting the import Staging exists only to back the cold-restart replay for cookies the in-memory path rejects, but three points in it were fatal to the whole import (#9355): - the staging copy from the live partition DB — also a file named "Cookies", so the same AV/EDR handle blocks it; - opening/PRAGMA-ing that staged file; - both were unguarded, so a throw escaped to the catch-all and returned `ok: false` even when every cookie could load in memory. Each is now non-fatal and diagnosed. Two invariants keep the degraded path honest: `imported++` moved out of the staging insert so the summary counts importable cookies rather than staged rows, and `setPendingCookieImport` is never called when staging is unavailable — registering a path that was never written would replay a missing or partial DB over the live partition on cold start. * fix(browser): stop a degraded cookie import from replaying a stale staged database Review round 1 found the staging-degradation path could leave an older pendingCookieImports entry registered while the import rewrote the live session, so the next cold start replayed the stale DB over fresh cookies. - add clearPendingCookieImport so a degraded import retires the old entry - degrade staging on BEGIN/insert/COMMIT failure instead of aborting the import - discard the staged cookie copy on every non-registering path - pin the stagingAvailable guard, which previously survived mutation Co-authored-by: Orca <help@stably.ai> * fix(browser): report a degraded cookie import honestly instead of as a clean success Making the staging failure non-fatal introduced a silent-loss path: the import clears the live jar before loading cookies, so when staging was unavailable AND Electron rejected cookies, the user lost their old jar, got none of the new cookies, and still saw "Imported N cookies". Adds an optional `warning` to the import summary, set only on that degraded branch, and routes every cookie-import toast through a shared emitter that raises a warning toast instead of an unqualified success. Also closes three test holes found in review: - clearPendingCookieImport had no direct tests; deleting the wrong partition key survived all 10 registry persistence tests. Now covered, mutation killed. - The staging-insert-failure test was vacuous (memoryFailed === 0 suppressed registration on its own). It now forces a memory failure. - No test pinned the success-path clear; removing it survived. Now covered. * fix(github): resolve owner/repo through SSH Host aliases (#10284) (#10361) * fix(github): resolve owner/repo through SSH Host aliases (#10284) Expand OpenSSH Host → HostName via ssh -G before classifying github.com identity so PR merge works when origin is git@alias:owner/repo.git. Transport URLs stay unchanged so IdentityFile selection is preserved. Do not long-negative-cache indeterminate ssh -G failures. * fix(github): harden SSH alias resolution * Update README downloads badge * fix(persistence): fsync state writes so a rename is actually durable (#10631) * fix(persistence): fsync state writes so a rename is actually durable `Store` wrote `orca-data.json` to a temp file and renamed it. rename() is atomic for readers but says nothing about durability: without an fsync the directory entry can reach disk before the data does. After power loss or a hard crash the file can come back holding the previous state or, worse, zero bytes — and `JSON.parse('')` throws, so an empty file takes the full corrupt-file path rather than degrading. This is the same empty-file symptom as #1158 from a different cause. That issue fixed a logic path that persisted empty state and added the .bak ring as a safety net; the ring also catches this, which is why it went unnoticed. Recovery costs up to an hour of tabs/layouts/session state (backups are throttled to >=1h spacing), and a user in their first hour has no backup slot yet, so they land on defaults indistinguishable from a fresh install. Both write paths now fsync the temp file *before* the rename, then fsync the containing directory. Directory fsync is best-effort by design: Windows cannot open a directory for fsync and some filesystems reject it, so it is swallowed. The file fsync is the load-bearing part and works everywhere. Measured cost on a 3 MB payload: ~0.2 ms per write, against a 1s debounce. The async path does not block the main thread. The syscall-order test mocks `node:fs` and counts fsync targets at the module boundary, asserting ['file', 'directory'] — proving the ordering rather than inferring it from reading the implementation, since a fsync after the rename would still pass every content assertion. * test(persistence): make the syscall proof platform-aware and actually prove the order Two problems, both found from CodeRabbit's Windows observation. The assertion hardcoded ['file', 'directory']. Directory fsync is deliberately best-effort — Windows cannot open a directory for fsync and some filesystems reject it — so on Windows the helper swallows the failure, only the file fsync is observed, and the test fails. The expectation now probes the real platform instead of assuming, keeping the guarantee tight where directory fsync works rather than dropping it everywhere. Worse, the test did not prove what its name claimed. Moving the fsync to *after* the rename still passes: the file is fsynced either way, and only fsyncs were recorded, so the correct and broken orders produced an identical log. Mutation-testing the "before rename" claim is what surfaced this — the mutation passed. The rename is now recorded in the same sequence, since it is the boundary the ordering is defined against. Re-running the same mutation fails, so the ordering claim is now backed by the test rather than asserted in a comment. --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> * fix(window): stop burning macOS GPU on an invisible blur effect (#8482) (#10682) Co-authored-by: Orca <help@stably.ai> * feat(sidebar): distinguish and filter CLI-created workspaces (#10712) * perf(relay): stop snapshotting the whole pending-PTY map every drain tick (#10670) * perf(sidebar): share one worktree-keyed agent orchestration index (#10678) Co-authored-by: Orca <help@stably.ai> * fix(mobile): recover unreliable relay connections (#10709) * fix(mobile): recover unreliable relay connections * test(mobile): use valid raster preview fixtures --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> * release: v1.4.157-rc.0 * docs(relay): trim the pending-output drain comment (#10714) Co-authored-by: Orca <help@stably.ai> * fix(history): quarantine unreadable recovery generations. (#10713) * fix(history): quarantine unreadable recovery files * fix(history): preserve mixed recovery generations * fix(history): reanchor reconciled live sessions * fix(history): serialize final checkpoint queue * fix(history): drain sleep shutdowns before disconnect * fix(history): restore legacy wide sessions * fix(history): preserve malformed mixed logs * fix(history): preserve malformed log tails * refactor tests to reduce file size * refactor(history-recovery): extract freeze helper and improve test robus - Extract takeRecoveryFreeze to eliminate duplicated freeze-and-clear pattern across five call sites - Skip permission-mode tests on root CI containers (chmod 0o500 doesn't block root writes) - Replace fixed sleep with deterministic wait for queued exclusive checkpoints - Distinguish ENOENT (missing) from corrupt in history metadata reads - Add ceiling-dimension restore test and torn-tail exclusion assertion - Wrap chmod operations in try/finally to prevent leaked permissions from masking test failures - Add .catch() to checkpoint promise to prevent unhandled rejections from finally re-throws * test(history-recovery): consolidate checkpoint assertions Wait for both the checkpoint call and set clear atomically to avoid a timing race where the spy fires before the set is cleared. * fix(persistence): unbreak main by expecting the new 'cli' card property in fresh defaults (#10722) Co-authored-by: Orca <help@stably.ai> * fix(sidebar): stop worktree drag from spazzing when cards resize mid-drag (#10725) * fix(sidebar): make Cmd/Ctrl+1-9 match the rendered card order when the sidebar is closed (#10693) * fix(native-chat): wrap question text and option descriptions instead of truncating (#10025) * fix(mobile): pop to home when leaving a host so the back chevron animates backward (#9723) * fix(i18n/zh): correct technical literals and clear sense errors (#10048) * feat(speech): add Korean streaming zipformer STT model (#9893) Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> * feat(source-control): add copy relative path (#9018) * fix(gitlab): stop refresh button overlapping dialog close X (#9445) * fix(gitlab): stop refresh button overlapping dialog close X The GitLab item dialog's SheetContent renders its own close (X) at absolute right-4, but the header refresh button sat at the header's px-5 right padding and overlapped it. Reserve pr-10 on the header so the refresh button clears the close X, and lift it -mt-1.5 so its icon aligns with the close X on the same line. * fix(gitlab): integrate sheet controls into header --------- Co-authored-by: viniciussilva <vinicius.silva@plus10.de> Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> * fix(sidebar): raise selected workspace contrast in dark mode (#8321) * feat(editor): toggle Word Wrap from file tab actions and Alt+Z (#10086) * feat(editor): toggle Word Wrap from file tab actions and Alt+Z Long single-line and structured files wrap by default and misalign. Surface Word Wrap on the editor more-actions menu for normal file tabs (diff already had it) and add editor.toggleWordWrap (Alt+Z) so users can unwrap without opening Settings. Closes #9974 * fix(editor): toggle diffWordWrap for diff surfaces on Alt+Z CodeRabbit: Alt+Z previously always flipped editorWordWrap, leaving diff panes out of sync with the markdown actions menu. * test(editor): verify word wrap shortcut routing Cover editor/diff setting callbacks and the cross-platform Alt+Z binding. --------- Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> * fix(repo-icon): detect Tauri and WebP icons (#7942) Expand repository icon auto-detection to conventional Tauri and public/icon paths with PNG/WebP magic and dimension validation. Bound SSH probing while preserving candidate priority and PNG-only user uploads; SVG remains rejected. * Add bulk tab closing to mobile long-press sheets (Close Others / Left / Right) and complete the desktop tab context menus (#9323) * Add Close Tabs to the Left and complete Close Others across tab menus and mobile long-press sheets * Fold the per-sheet Close action into the bulk-close module (session route max-lines) * fix(mobile): preserve pinned tabs during bulk close --------- Co-authored-by: Tom de Bres <tomdebres@users.noreply.github.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> * feat: implement Cmd+Enter as commit shortcut in Source Control (#9773) * feat: implement Cmd+Enter as commit shortcut in Source Control * test: add unit tests for commit shortcut and tooltip formatting * fix: address review feedback on modifier keys and test coverage * test: split mac and windows/linux shortcut and keydown tests --------- Co-authored-by: Andres Van Reepingen <andres.vanreepingen@datacamp.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> * Add SenseVoice speech-to-text model (Korean/Japanese support) (#7436) * Add SenseVoice speech-to-text model (Korean/Japanese support) SenseVoice (zh/en/ja/ko/yue) is the only bundled local STT model with Korean and Japanese support. The existing local models cover only English and Chinese (Parakeet, Zipformer, Paraformer); Whisper Tiny is multilingual but trades accuracy for breadth. - Add 'senseVoice' to SpeechModelType - Register the sherpa-onnx SenseVoice archive in the model catalog (pinned SHA-256, single-file model.int8.onnx + tokens.txt layout) - Handle the senseVoice type in the STT worker via createOfflineRecognizer with the senseVoice model config (auto language detection + ITN) - Add model-catalog regression tests for the new entry Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(speech): use int8-only SenseVoice archive * fix(speech): refresh SenseVoice catalog metadata --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: LauraGPT <LauraGPT@users.noreply.github.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> * fix(settings): show a way back to local accounts when a remote server owns provider-account scope (#8188) Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> * feat(speech): add Parakeet TDT-CTC 0.6B JA voice model (#8207) * Add SenseVoice speech-to-text model (Korean/Japanese support) SenseVoice (zh/en/ja/ko/yue) is the only bundled local STT model with Korean and Japanese support. The existing local models cover only English and Chinese (Parakeet, Zipformer, Paraformer); Whisper Tiny is multilingual but trades accuracy for breadth. - Add 'senseVoice' to SpeechModelType - Register the sherpa-onnx SenseVoice archive in the model catalog (pinned SHA-256, single-file model.int8.onnx + tokens.txt layout) - Handle the senseVoice type in the STT worker via createOfflineRecognizer with the senseVoice model config (auto language detection + ITN) - Add model-catalog regression tests for the new entry Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(speech): add Parakeet TDT-CTC 0.6B JA to the speech model catalog * test(speech): cover stt-worker-model-config file resolution incl. single-file models * feat(speech): decode Parakeet TDT-CTC JA via sherpa-onnx nemoCtc offline recognizer * fix(speech): use int8-only SenseVoice archive * fix(speech): refresh SenseVoice catalog metadata --------- Co-authored-by: xsacdw <xsacdw@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: LauraGPT <LauraGPT@users.noreply.github.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> * fix(terminal): stop switch bold flash and Windows lag (#10692) * fix(terminal): stop bold flash on worktree switch Worktree hide disposes WebGL and falls back to xterm's DOM renderer. On reveal, resume ran after paint and flushed backlog against DOM first, so the first frame used heavier CSS-AA glyphs before WebGL settled. Resume in useLayoutEffect and reattach WebGL before backlog flush so the first painted frame stays on the GPU path. No cold-park policy change. Co-authored-by: Orca <help@stably.ai> * fix(terminal): fit WebGL grid before backlog flush on resume Adversarial review: resume-before-flush alone wrote TUI backlog onto the transient DOM↔WebGL one-column-off metrics window. Order is now resume → fitAllRevealedPanes → flush on heavy reveal and window wake. Co-authored-by: Orca <help@stably.ai> * fix(terminal): latch viewport intent before WebGL wake resume Adversarial review: wake path synced intents after resume/fit, which can re-latch a pinned viewport as followOutput. Capture before reattach and drop the post-resume re-sync on heavy reveal (outer path already latched). Co-authored-by: Orca <help@stably.ai> * fix(terminal): complete visibility bookkeeping before PaneManager exists useLayoutEffect runs before the passive lifecycle creates PaneManager, so the mount-visible path never set hasCompletedVisibleResume. The first intra-worktree hide then wrongly suspended WebGL. Bookkeep completion even when managerRef is still null (extracted helper for max-lines). Co-authored-by: Orca <help@stably.ai> * fix(terminal): re-sync pin geometry after resume backlog flush Keep the pre-resume intent latch (reattach must not re-latch pins as followOutput), then re-sync after flush with preservePinnedAtBottom so scrollback trim updates absolute pin lines before enforce. Co-authored-by: Orca <help@stably.ai> * fix(terminal): drop same-tick post-flush intent re-sync flushTerminalOutput only queues terminal.write and returns before parse, so a same-tick re-sync read pre-parse resume/fit geometry and could overwrite pre-resume pins. Keep pre-resume latch + enforce only. Co-authored-by: Orca <help@stably.ai> * fix(test): expect default worktree card properties to include cli #10712 added 'cli' to DEFAULT_WORKTREE_CARD_PROPERTIES, but the fresh default-profile assertion still omitted it and fails verify. Co-authored-by: Orca <help@stably.ai> * perf(terminal): retain Windows WebGL across worktree hides * perf(terminal): bound retained WebGL contexts * fix(terminal): harden retained WebGL lifecycle * fix(terminal): preserve healthy WebGL on wake * fix(terminal): preserve reveal recovery ordering --------- Co-authored-by: Orca <help@stably.ai> * fix(mobile): clear native-chat composer optimistically at send time (#10226) * fix(mobile): clear native-chat composer optimistically at send time Over relay the send RPC round trip is visible and a lost ack (or a relay/direct cutover) could strand the sent prompt in the composer forever: the unconfirmed-send deadline dropped its tracking entry, so a late transcript echo could never clear the draft. Clear the draft at send time and restore it only on a definite rejection. holdUnconfirmedSend now only manages the delivery-unconfirmed notice; it no longer touches drafts. * fix(mobile): isolate question answers from composer drafts * fix(cli): bound orchestration ask timeouts (#10689) * fix(cli): bound orchestration ask timeouts * fix(cli): harden remote timeout boundaries * fix(crash-reporting): stop fit-retry bursts from erasing the pre-crash trail (#10729) * fix(crash-reporting): stop fit-retry bursts from erasing the pre-crash trail Windows renderer OOM F0BKR84AHEH (0xE0000008) arrived with a 30-entry breadcrumb ring in which two `terminal_safe_fit_retry_exhausted` bursts consumed 26-90% of the slots. Every hidden pane is `display:none` -> 0x0 -> unmeasurable, so one post-reload reattach wave exhausts the retry budget once per mounted pane inside ~60ms. The bursts were also uninterpretable: `pane.id` restarts at 1 per PaneManager and there is one manager per tab, so 34 identical `paneId: 1` crumbs cannot distinguish one pane looping from 34 panes firing once. Coalesce the crumb by name and carry the live-pane census on the payload instead, so the count survives without costing 34 ring slots. Same treatment for WebGL diagnostics, which were worse off: context-loss and atlas-reset crumbs only reached a DevTools-only ring (`window.n()`), so a renderer that dies takes them with it. That bundle had three GPU-process deaths in the 65s before the renderer OOM and zero WebGL evidence - absence of instrumentation, not absence of the event. Mirror them into the crash report, coalesced per kind so a routine atlas reset cannot mask a context loss. Evidence-only: no behavior, rendering, or lifecycle path changes. Co-authored-by: Orca <help@stably.ai> * perf(pane-manager): count panes without materializing public views The census runs on the crash path; getPanes() allocates a full ManagedPane projection per pane just to read .length. Co-authored-by: Orca <help@stably.ai> * test(crash-reporting): pin the fit-retry burst against the 30-entry ring Reproduces the F0BKR84AHEH ring loss directly: 10 pre-crash crumbs plus a 34-crumb per-pane burst. Uncoalesced, the burst takes all 30 slots and zero pre-crash crumbs survive; coalesced, it takes one slot, all 10 survive, and the pane count rides on the payload instead of on the crumb multiplicity. Co-authored-by: Orca <help@stably.ai> * fix(crash-reporting): name the WebGL census the same as the fit-retry census The context-loss crumb spread getLivePaneCensus() raw, so one ring described one measurement two ways: managers/panes here, livePanes/livePaneManagers on the fit crumb. Spreading also meant renaming the census return keys would silently reshape the crumb. Name the fields at the call site and pin them. Co-authored-by: Orca <help@stably.ai> * fix(crash-reporting): keep a hot coalesce key from being the first LRU eviction The suppression path returned before the delete-then-set that re-anchors recency, so a key hit continuously never moved from its original insertion slot and became the first eviction candidate — the inverse of the LRU's stated intent. `renderer_error` keys carry message+stack identity, so one noisy render loop mints unbounded distinct keys. Within a single 30s window that churn evicted the `terminal_safe_fit_retry_exhausted` key mid-burst, un-suppressing it and re-arming the exact ring flush the coalescing exists to prevent. Re-anchor position only; `recordedAt` is left alone so the suppression window still expires on schedule rather than renewing on every hit. Found while adversarially probing the LRU claim in #10729's own description, which asserted these keys "cannot evict live keys". * fix(crash-reporting): report the newest census of a coalesced burst The suppression path wrote nothing to the ring, so a coalesced burst froze its FIRST event. Panes mount progressively, so pane 1 exhausting alone legitimately measures livePanes: 1 -- and the 33 later crumbs, each carrying a truer census, were dropped. A 34-pane wave was recorded as `livePanes: 1` with no count: the exact "one pane looping" misread that coalescing by name was introduced to prevent. The existing burst test missed this because it fed a constant census on every crumb, making frozen-first and newest-wins indistinguishable. Stash the newest payload and fold it into the ring entry the key already owns: still one slot, now reading livePanes: 34 + suppressedSinceLast: 33. Resolution is deferred to snapshot time -- sanitizing per suppressed hit of a 1459/min crash loop measured 2194 ns/op vs 185 ns/op deferred. Two follow-on defects fixed alongside: an expiring key dropped its pending payload (it loses its only handle on the ring entry), and resolving the re-emitting key's own old slot double-counted a burst. --------- Co-authored-by: Orca <help@stably.ai> * fix(speech): download verified model artifacts directly (#10735) * perf(renderer): give owner-routed settings a stable identity (#10743) Co-authored-by: Orca <help@stably.ai> * perf(agent-status): validate hook payloads without the JSON round trip (#10752) * fix(gpu-fallback): make the crash window rolling, not launch-anchored (#10707) * fix(gpu-fallback): make the crash window rolling, not launch-anchored Software-rendering fallback only ever considered GPU child crashes in the first 30s after launch: `if (msSinceLaunch > this.windowMs) return`. Session 12e6ee64 crashed the GPU child 4 times (242s / 920s / 926s / 946s since launch). The last three span 26.0s — inside windowMs, exactly threshold — but every one was rejected because the burst began 920s in. The renderer died of process OOM (0xE0000008) 39s later. GPU work is demand-driven, so the first heavy compositing often happens minutes into a session. What distinguishes a broken driver from normal Chromium churn is that the crashes *cluster*, not when the cluster starts. Keep a sorted array of recent crash times pruned to windowMs behind the newest, and engage when the count reaches threshold. Measured against real field telemetry (341 distinct win32 launches with >=1 GPU crash, from process_gone_suppressed breadcrumb trails): the rolling window engages on 2/341 launches (0.59%), one of which is 12e6ee64. Max GPU crashes in any single launch is 4. The closest non-firing sequence ([0, 29531, 55136, 74178] — consecutive gaps that each fit the window but never put 3 inside it) is pinned as a regression test. Also destroy the Windows tray before app.exit(0) on this path, matching the app:relaunch IPC handler — app.exit skips before-quit, and this can now fire deep into a session rather than only in the first 30s. Mutation-tested: 6 mutants (launch-anchored gate, dropped pruning, dropped monotonic clamp, cutoff </<=, threshold >=/>, dropped engaged latch), all killed by the suite. Co-authored-by: Orca <help@stably.ai> * fix(gpu-fallback): ask before restarting --------- Co-authored-by: Orca <help@stably.ai> * feat(sidebar): add a filter to hide detached-HEAD workspaces (#10786) Adds "Hide detached HEAD" alongside the existing sidebar filters, wired through the same pipeline as Hide CLI-created: sidebar list, Cmd+J empty-query list, workspace board, active-filter badges, Clear/Reset Filters, and persisted UI state. The predicate reuses getWorktreeGitIdentityDisplay so the filter targets exactly what the card renders a Detached HEAD badge for. Requiring a real head (not just an empty branch) keeps folder workspaces and SSH-synthesized rows — which carry both empty — out of the filter. Activating a hidden detached workspace clears the filter, matching the existing reveal escape hatch for automation- and CLI-created workspaces. Splits the filter-state describes out of visible-worktrees.test.ts into sidebar-filter-state.test.ts to stay under the max-lines budget. Co-authored-by: Orca <help@stably.ai> * feat(daemon): add daemon_lifecycle replaced/retired telemetry event (#10058) * feat(daemon): add daemon_lifecycle replaced/retired telemetry event Implements STA-2376. Adds track('daemon_lifecycle', {transition, reason, live_session_count_bucket, version_skew?}) covering 'replaced' (unhealthy_resolver / stale_bundle / different_app_path / failed_health_check at daemon-init launcher sites) and 'retired' (died_respawn at the adapter respawn closures). Enum-only + .strict() + bucketed counts keep paths, versions, and raw counts off the wire; preserve-path transitions emit nothing. Cross-platform and SSH-safe; no-op in non-official builds. Test plan: affected vitest (158) green; typecheck/lint clean except pre-existing unrelated failures. * fix(daemon): prevent false lifecycle telemetry * test(daemon): restore once-ness on respawn reason assertions Keep STA-2376 reason checks without dropping concurrent-respawn coalescing coverage that prevents double died_respawn telemetry. * fix(daemon): emit replaced telemetry on runtime unhealthy_resolver respawn CodeRabbit: adapter-driven macOS resolver replacements forked a new daemon without a lifecycle event. Emit trackDaemonReplaced (not retired) so field diagnosis of #7936 covers the runtime path without mislabeling it as death. * fix(daemon): stop double-counting resolver replaces; drop redundant version_skew Three telemetry-correctness fixes to the STA-2376 daemon_lifecycle event. 1. The runtime macOS resolver respawn double-counted. doRespawn() disconnects but never kills the daemon, so the ensureRunning() that follows re-enters createOutOfProcessLauncher, which re-detects healthy + resolver-unhealthy + 0 sessions and emits the replace itself. The closure emitted a second one. It also emitted before the outcome was known, so a resolver that recovered mid-flight (or a session appearing) left a 'replaced' on the wire for a daemon the launcher went on to preserve. The launcher's emit is gated on a confirmed kill, so it is the correct sole emitter; this reverts the emit added in |
||
|
|
8b154d686c |
perf(runtime): remove timer clamps from cooperative yields (#10908)
* perf(runtime): remove timer clamps from cooperative yields Renderer paste and input loops can schedule more than a thousand zero-delay timer yields for a maximum-size payload. Chromium clamps nested timers to 4ms, adding seconds of idle wall time. Use MessageChannel tasks in renderer runtimes and setImmediate in Node while retaining a timer fallback for tests and unsupported environments. * fix(runtime): preserve pacing and release yield callbacks Adversarial review found that concurrent producers could retain resolved callbacks until global quiescence. Route renderer yields by token and delete each resolver before resuming its producer. Keep timer pacing in terminal paste and accepted-write loops where SSH and local PTYs do not provide drain acknowledgement. Use the shared scheduler for the OpenCode scanner. |
||
|
|
34caad787c |
fix(browser): unstick Ctrl+Tab switcher when opened from a focused browser guest (#9966)
Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
aab112933e |
Revert "fix(memory): bound OOM-prone accumulators (#10179)" (#10255)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
8f40ddf328 | fix(memory): bound OOM-prone accumulators (#10179) | ||
|
|
b0e36169d8 |
fix: return native promise from awaitClick on Zone.js pages (#9995)
Zone.js patches the global Promise with a non-native thenable. When a bare
`new Promise(...)` crosses the Electron executeJavaScript boundary, it's
serialized as-is, losing { page, target } and exposing __zone_symbol__*
fields instead. Wrap in an async IIFE to return a native promise that
Electron always unwraps correctly.
|
||
|
|
b81e782ffc |
fix(browser): stop failing goto when a redirect or download aborts the load (#9822)
* fix(browser): stop failing goto when a redirect or download aborts the load Electron's loadURL rejects with ERR_ABORTED (-3) when the initial navigation is superseded — a client-side/meta redirect (common in SSO flows) or a download-triggered load. Since #9633 drives goto through wc.loadURL directly, that rejection surfaced as a spurious 'Failed to navigate' browser_error even though the page landed fine. Treat ERR_ABORTED like offscreen-browser-backend already does: resolve with the page's actual URL/title. Every other loadURL failure still fails closed. * fix(browser): settle replacement navigation after abort * fix(browser): clean up aborted navigation destruction race |
||
|
|
085fc6ad89 |
Keep embedded browser commands on the registered Orca target (#9633)
* fix(browser): keep embedded commands on registered target * fix(browser): cover internal helper ownership paths * test(browser): cover stale helper routing in Electron * fix(browser): preserve direct command result semantics --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
190de8223e |
refactor(comments): slim verbose comments in main integrations (git/providers/…) (#9543)
Collapse multi-line explanatory comment blocks into single-line "why" statements
per AGENTS.md ("Document the Why, Briefly"): drop restatements of the code and
mechanism narration; keep the non-obvious reason, external refs, and directives.
Comments-only — verified no code changed via a Babel/esbuild comment-strip
token-equality gate against origin/main; typecheck and oxlint clean.
Area: main — git, source-control, providers & integrations. 40 files changed, 1432 insertions(+), 4473 deletions(-).
Co-authored-by: Orca <help@stably.ai>
|
||
|
|
e074bc3f05 |
Gate certificate trust proceed action on runtime capability (#9070)
Only offer 'Proceed Anyway (Unsafe)' if the connected remote runtime advertises browser.certificate-trust.v1 support. Older runtimes cannot honor the request and would fail silently, creating a false affordance. Centralize certificate error normalization to prevent divergence between main and renderer certificate matching, and unify URL redaction for Kagi session token stripping across load-error paths. |
||
|
|
5907816457 |
feat(keybindings): swap tab-switch chords to the common convention for new users (#9240)
* feat(keybindings): swap tab-switch chords to the common convention for new users New installs now get the widespread mapping — Mod+Shift+[ / ] cycles across all tabs, Mod+Alt+[ / ] cycles within the active tab type. Pre-existing installs keep today's mapping: a one-time cohort seed (frozen on first launch via the fileExistedOnLoad signal, mirroring the telemetry migration) pins the legacy chords into keybindings.json, skipping any action the user already customized. - shared registry: swap the four tab.*SameType / tab.*AllTypes defaults; export LEGACY_TAB_SWITCH_BINDINGS for the seed - persistence: migrateTabSwitchKeybindings freezes the existing-vs-fresh cohort (tabSwitchKeybindingSeed = pending | done) - keybinding-file: seedLegacyTabSwitchBindings writes the legacy pins into the active-platform section so Settings reset still works - refresh stale default-chord comments - tests for the swapped defaults, the seed (fresh/existing/customized/idempotent), and the cohort migration * test(keybindings): prove existing-user parity + make the seed strictly per-action seedLegacyTabSwitchBindings now pins each un-customized action individually instead of skipping all four when any one is customized. A partially-customized existing user keeps their rebound action AND the pre-swap default on the rest; no existing user's behavior changes. The skip check keys on this platform's effective overrides so a foreign-platform-only override can't leave the active platform on a new default. Adds keybinding-service.test.ts: constructs a real KeybindingService and asserts effective bindings + real keystroke matching for both cohorts across darwin/ linux/win32, plus partial-customization, idempotency, and seed-failure retry. * fix(keybindings): preserve legacy files during tab shortcut seed * fix(keybindings): preserve valid pre-swap overrides |
||
|
|
6e91ca6c0e |
fix(browser): local HTTPS Try HTTPS + cert proceed (#8454) (#9104)
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> |
||
|
|
f62ad69260 |
Click a link in orca browser should not open a new tab (#8782)
* Route new-tab link clicks to Orca tabs instead of popup windows - Adds an isolated-world click/auxclick listener that relabels browser-native new-tab intents (target=_blank, cmd/ctrl-click, middle-click) with a private frame name, so setWindowOpenHandler can distinguish them from opener-dependent window.open() calls without breaking OAuth popups. - Wires matched clicks through to a new browser:open-link-in-orca-tab IPC payload carrying foreground/background activation intent, so the renderer opens a worktree tab instead of a native window. - Adds unit and e2e coverage for modifier/middle-click routing, cancellation/rewrite handling, and cleanup on guest teardown. * Route new-tab link clicks to Orca tabs and stop background popups Plain target=_blank clicks (main frame and iframes) now navigate the current Orca tab in place instead of opening a new browsing context, while explicit new-tab gestures (Cmd/Ctrl-click, middle-click, Shift+modifier) route into Orca tabs via one-use private frame names, including child frames. Drops the foreground/background frame-name split and the activate flag now that all routed links always open active — no more silent background popups from a plain link click. |
||
|
|
b379610cbe |
fix(browser): handle Cmd-click popups without adopted WebContents (#8659)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
01dcbcb007 |
fix: host browser popups in an Orca origin-bar window instead of chrome-less child windows (#8343)
* fix: host browser popups in an Orca origin-bar window instead of chrome-less child windows A guest-opened popup previously became a default Electron child window with no address bar, so users could not verify a popup's origin — a phishing surface flagged in the prod-release scan of #7392. The reverted #8332 tried gating on disposition, which is bypassable and breaks featureless window.open() OAuth flows. Instead, keep hosting popups in-app (preserving the shared session partition and live window.opener handle OAuth depends on) but build the child window ourselves via setWindowOpenHandler's createWindow callback: a BaseWindow with an Orca-controlled origin-bar WebContentsView on top and a content WebContentsView that adopts the pre-created popup WebContents. The bar shows only the destination origin (never path or query), updates on navigation, and flags plain http to remote hosts. Also pins secure webPreferences (contextIsolation, no nodeIntegration, sandbox, no webviewTag) on popup children via SAFE_POPUP_WINDOW_OPTIONS, attaches guest policies to popup contents directly (did-create-window does not fire for createWindow children), emits the existing opened-in-orca renderer notice, and closes popups with their opener guest. Co-authored-by: Orca <help@stably.ai> * fix: show page title in popup title bar instead of doubling the origin The native title bar and the origin bar both showed the origin, reading as a doubled header. Match Chrome popup behavior: title bar shows the page title (reset to origin on navigation so a stale title cannot outlive its origin); the origin bar below remains the unspoofable trust surface. Co-authored-by: Orca <help@stably.ai> * fix: elide the start of long popup origins so the registrable domain stays visible Adversarial review findings on the origin bar: - The bar right-ellipsized long hostnames, hiding exactly the part that matters: window.open('https://accounts.google.com.<...>.evil.com', '', 'width=360') rendered as 'https://accounts.google.com.signin.s…'. Clip from the left instead (rtl container + isolated ltr bdi so host and port keep normal character order), matching how Chrome elides. - Re-assert the origin on the popup's did-finish-load so a transiently dropped executeJavaScript write cannot leave a stale origin up. - Re-pin view layout on enter/leave-full-screen: verified at runtime that HTML5 fullscreen keeps the bar visible on macOS (resize fires); the explicit events make that hold on every platform. - Test hardening: fake WebContents now flips isDestroyed after 'destroyed' so the double-close guard is actually exercised. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
81dd08ffd3 |
fix(browser): make fill update rich text editors via execCommand (#6060)
Rich editors reconcile browser editing transactions, while direct value/textContent writes can leave framework state stale. Classify explicit contenteditables through the requested agent-browser target, keep native fill and clear behavior for plain controls, and perform rich replacement or clear as one target-focused eval over stdin. Fail when the browser editing command is unavailable instead of presenting stale DOM state. Preserves input/change behavior and spinbutton handling for standard fields. Verified against Draft.js and ProseMirror model state plus focused-target clear regressions. Co-authored-by: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> |
||
|
|
efa6b7b945 |
fix(browser): snapshot Chromium cookie WAL safely (#8195)
* fix(browser): snapshot Chromium cookie WAL safely * fix(browser): clean partial cookie staging copies |
||
|
|
1afec6cbf9 |
fix(browser): allow trusted embedded popup windows (#7392)
* fix(browser): allow trusted embedded popup windows * fix(browser): harden embedded popup windows Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
970d446d50 |
fix(browser): auto-grant pointerLock permission (#7697)
* fix(browser): auto-grant pointerLock permission Orca's browser deny-by-defaults Chromium permission requests unless the permission is in AUTO_GRANTED_BROWSER_PERMISSIONS. pointerLock is not in that set, so immersive web apps (3D editors, FPS-style games) that call requestPointerLock() get a confusing toast: "<url> asked for pointer lock, and Orca denied it." with no in-app toggle to allow it. Add 'pointerLock' to the auto-grant set. This mirrors the resolution of the identical persistent-storage denial in #4801 / #4849. Chromium still requires a user gesture before requestPointerLock() can engage, so this only lifts Orca's application-level gate — it does not bypass the spec's user-activation requirement. * test(browser): cover pointer lock permission paths Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
693ac1fb25 |
fix(browser): import Chromium cookies from Network DBs (#6875) (#7882)
* fix(browser): import Chromium cookies from Network DBs (#6875) * fix(browser): complete Chromium cookie import paths Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
79abeeaa2a |
fix(browser): allow CDP browser target attach (#7033) (#7891)
* fix(browser): allow CDP browser target attach (#7033) * review: preserve nested CDP session identity Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
06afbc4a4b |
Add optional Orca account sign-in (#7515)
* auth v1 * fable review * lint * Account menu with org membership management Default UX is a compact account menu (sign in, organization selection, sign out) that renders only when cloud auth is configured; adds an organization members dialog (invite, role, remove) gated on server-side role checks. The multi-profile switcher UI is preserved behind ORCA_MULTI_PROFILE_UI=1. Co-authored-by: Orca <help@stably.ai> * Gate the optional account sign-in UI to dev builds The account switcher stays hidden in packaged builds while the feature is in progress. Dev builds still show it when the client env vars are set, and a dev-only Settings > Dev Tools > Orca Cloud section mirrors it. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
e33b2006f4 |
Remove stale max-lines lint disables from files under the limit (#7548)
110 files carried an eslint/oxlint-disable max-lines directive but are already under the default max-lines budget (300 .ts / 400 .tsx / 600 .mjs / 800 test), so the suppression is dead. Removing it restores real max-lines coverage on these files with zero behavior change. Each removed directive had max-lines as its only rule; verified via a full oxlint run (0 max-lines violations, 0 new errors). Diff is pure deletions (200 lines, 0 additions) — no code touched. Co-authored-by: Orca <help@stably.ai> |
||
|
|
ce687221d3 |
lint(unicorn): enable prefer-number-properties, prefer-array-find, prefer-array-index-of (#7516)
Enable three unicorn rules — one correctness, two performance — and fix every existing violation repo-wide so the rules pass as errors. prefer-number-properties (76 sites) - parseInt/parseFloat/NaN -> Number.* : safe aliases (autofixed). - isNaN -> Number.isNaN (12 sites, hand-converted): global isNaN coerces its argument, Number.isNaN does not. Verified every call site already passes a number (Number.parseInt results, number-typed fields, Date.getTime()), so the conversion is behavior-preserving today and guards against a future non-numeric argument silently coercing. prefer-array-find (26 sites) - .filter(pred)[0] -> .find(pred); .filter(pred).at(-1) / .pop() -> .findLast(pred). Drops the intermediate array and short-circuits. prefer-array-index-of (5 sites) - .findIndex(x => x === v) -> .indexOf(v). Verified: typecheck (node/cli/web) clean, 53 affected suites pass (1679 tests), oxlint clean repo-wide. mobile/ uses findLast safely (already ships ES2023 .toReversed()); config scripts and e2e helpers run on Node 24. |
||
|
|
13db3b327f |
fix(browser): fill spinbutton inputs through editable controls (#7285) (#7389)
* fix(browser): fill spinbutton inputs through editable controls (#7285) * fix(browser): retarget spinbutton fill to editable inputs --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> |
||
|
|
ccfd6727c9 |
fix(browser): route focused guest zoom shortcuts to page zoom (#6744)
Route focused browser guest zoom shortcuts to the browser page zoom path, including native Electron zoom-command fallback and wheel/native dedupe. |
||
|
|
2789a67604 |
feat(browser): add Copy to context menu when text is selected (#7159)
Co-authored-by: Orca <help@stably.ai> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> |
||
|
|
cede986d38 |
fix(browser): restore programmatic focus for CDP text insertion (#7041)
* fix(browser): restore programmatic focus for CDP text insertion * test(browser): cover the DOM focus replay error path * fix(browser): serialize DOM.focus replay against pipelined Input.insertText * refactor(browser): consolidate pending-focus invalidation into one guard Collapse the three per-handler deletes (Page.bringToFront, Page.captureScreenshot, catch-all) of the pending DOM.focus replay into a single guarded delete keyed on 'not DOM.focus and not Input.insertText'. States the invariant in one place and removes the trap where a future early-returning handler forgets to clear the stale focus. Also clear the map on client teardown and shorten the field comment. Co-authored-by: Orca <help@stably.ai> * test(browser): annotate mock webContents return type for portable declaration emit Co-authored-by: Orca <help@stably.ai> * fix(browser): drop pending focus replay when the client disconnects mid-flight Co-authored-by: Orca <help@stably.ai> * fix(browser): re-check active client after DOM.focus replay round-trip Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
42de074152 |
fix(browser): keep isolated profile storage on its partition (#6958)
* fix(browser): keep isolated profile storage on its partition * fix(browser): keep cloned isolated tabs on their resolved partition * fix(browser): thread resolved partition through UI profile switches Keep the isolated-storage invariant universal: UI-initiated profile switch/create now persist the resolved partition alongside the profile id, matching the runtime path, so a tab stays on its partition even if the renderer profile mirror is later stale (issue #6923). Co-authored-by: Orca <help@stably.ai> * fix(browser): persist resolved partition through session restore Add sessionPartition to the workspace-session zod schema so the resolved partition survives persist->load; without it zod strips the field and a restored isolated tab whose profile mirror is stale at startup falls back to the shared default partition, reopening the storage leak (#6923). Fold the webview teardown branches (parent drift or partition mismatch) into one and re-resolve the viewport container once, matching the pre-refactor null-guard behavior. Split the sleeping-agent parse tests into their own file to keep both under the 800-line cap. Co-authored-by: Orca <help@stably.ai> * fix(browser): keep CLI-created tabs inheriting the default profile browserTabCreate without an explicit profile was sending sessionProfileId: null, which the renderer store treats as 'no inheritance' (its guard is !== undefined). That forced CLI-created tabs onto the shared default partition even when the user had configured a default browser session profile, silently changing behavior a #6923 fix should not touch. Leave sessionProfileId/sessionPartition undefined when no profile is named so the renderer applies default-profile inheritance; thread the resolved partition only when a profile is explicitly chosen. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
700b492b1b |
Fix main typecheck: pin Buffer<ArrayBuffer> in cdp-ws-proxy mid-print test (#7263)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
273fee79fa |
Fix CDP Page.printToPDF support (#7044)
* Fix CDP printToPDF proxy * Harden CDP PDF stream handling * Fix PDF margin units * refactor(cdp): extract printToPDF param mapping + stream store into cdp-print-to-pdf Move the CDP Page.printToPDF option translation and the ReturnAsStream handle store out of cdp-ws-proxy into a focused cdp-print-to-pdf module, mirroring the existing cdp-screenshot sibling. Keeps the over-budget proxy from growing further and gives the PDF logic direct unit coverage. No behavior change: all existing proxy tests pass unmodified. Co-authored-by: Orca <help@stably.ai> * fix(cdp): drop print result if client disconnected during printToPDF A ReturnAsStream print that resolved after the requesting client disconnected (or was replaced) registered its buffer in pdfStreams after the disconnect's clear() already ran, orphaning a potentially large PDF until the 5-minute TTL. Re-check isActiveClient after the await, mirroring navigateWithLifecycle/reloadWithLifecycle. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Neil <foxtrot-eng@stably.ai> Co-authored-by: Orca <help@stably.ai> |
||
|
|
09fc57e113 |
fix(browser): keep CDP reload from closing Orca browser tabs
Fix the per-tab CDP proxy reload path so raw Page.reload keeps the Orca browser tab alive. Lifecycle priming is bounded and root reloads use the same safer webContents reload behavior as Orca's own browser reload path. |
||
|
|
cbd06a7671 |
feat(tab-bar): add shortcut to open commands for active tab group (#6325)
* Add keyboard shortcut to toggle the Quick Commands menu - New `tab.openQuickCommandsMenu` keybinding action (no default binding) - TabBarQuickCommandsMenu listens for the binding and toggles open/closed - Scoped to the active tab group naturally since the component only mounts when its group is focused * Show keyboard shortcut in Quick Commands menu trigger tooltip * Add tests * expand tests * Expand keyboard toggle to call handleOpenChange and skip repeated keys - Replace `setMenuOpen` toggle with `handleOpenChange(!menuOpen)` so closing via keyboard runs the same reset logic (query, focus frame, value override) - Guard against key-repeat events to prevent rapid toggling on held key - Wrap `handleOpenChange` in `useCallback` so it's stable enough to include in the `useEffect` dependency array without causing spurious re-registrations - Update tests to reflect that re-running the effect between presses is required for the close path, and add a repeat-event test * Add docstring to withShortcutHint func * review: harden quick commands menu shortcut Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
46646d7ff1 |
chore(lint): upgrade oxlint to 1.71 + enable 7 new rules (autofixed backlog) (#6841)
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day minimum-release-age supply-chain guard; nothing here needs it). The bump is a no-op on the existing config. Enable 3 error rules (backlog autofixed to zero in this commit) and 4 warn rules (surface signal without gating CI): error (autofixed, behavior-preserving): - unicorn/prefer-node-protocol (~1531 sites: bare builtin -> node:) - typescript/no-import-type-side-effects (~36: all-inline-type -> import type) - unicorn/no-array-reverse (19: copy-then-reverse -> toReversed) warn (real signal, current fires are test-only/correct): - unicorn/no-array-fill-with-reference-type (aliasing footgun guard) - typescript/no-unsafe-function-type (bans bare Function type) - unicorn/prefer-array-flat-map (map().flat() -> flatMap()) - unicorn/prefer-regexp-test (.match() in bool ctx -> .test()) mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix ran from root and covered mobile/ too. Verification (all green): oxlint 0 errors (root+mobile+aux configs), oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed, builds (electron-vite + web + cli) succeed. node: rewrites confirmed to skip embedded SSH/CLI string payloads (AST-only); all toReversed sites verified to operate on fresh copies or write-once locals. * chore(lint): bump mobile oxlint to 1.71 so inherited rules parse mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile && oxlint' failed to parse the new rule. Bump mobile to match root (1.71). Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit pass, vitest 978 passed / 0 failed. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
4febefd454 |
feat(browser): add Helium as a Chromium cookie-import source (#6768)
Register Helium (imput, bundle id net.imput.helium) so it is auto-detected and importable from the picker on macOS like Chrome/Edge/Arc/Brave/Comet, instead of only via the "From File..." JSON path. Reuses the existing macOS Chromium decryption path unchanged. Helium quirks (verified on a real install): its Keychain entry is service "Helium Storage Key" / account "Helium" (not the usual "<Browser> Safe Storage"), it stores data under its bundle id, and it keeps cookies at the legacy <Profile>/Cookies path. Mac-only for now; Windows/Linux data dirs are unverified so winRoot/linuxRoot are omitted and the source defaults off there. Claude-Session: https://claude.ai/code/session_01SZzX6ktZaaCUg5PWnzniuC |
||
|
|
da48fa71dd |
Refuse cross-worktree global fallback for browser text insertion (#5093) (#6563)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
1f968b92c9 |
Fix browser pane stuck in HTML fullscreen (#6442) (#6451)
* Fix browser pane stuck in HTML fullscreen (#6442) Guest content in a browser pane could enter HTML fullscreen but not exit it: requestFullscreen() resized the host BrowserWindow into native fullscreen, and exitFullscreen() had nothing to restore, leaving the pane stuck. Set the webview's disableHtmlFullscreenWindowResize preference so HTML fullscreen is contained to the <webview> element. Exit then works and fullscreenchange fires as guests expect. * Apply browser guest fullscreen policy consistently Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Wolfgang Schoenberger <221313372+wolfiesch@users.noreply.github.com> Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
14afd1314d |
Zoom the app after leaving terminal focus (#6301)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
404e45f022 |
Improve built-in browser downloads (#5940)
* Improve built-in browser downloads Implements docs/browser-normal-download-behavior.md with automatic OS Downloads saving, collision-safe destinations, and renderer download controls. * Clean up browser downloads on tab closure and guest destruction - Cancel and drop pending download records when webview guests are destroyed or closed to prevent memory leaks and dangling state. - Purge tracking entries for cancelled downloads lacking an active tab. - Document filename normalization and reservation assumptions in download builder. - Translate "Copy Terminal ID" context menu action in Spanish and Chinese locales. |
||
|
|
553d47bf4f |
Fix Windows workspace deletion runtime resolution (#5888)
* Add test file for workspace delete bug Co-authored-by: Orca <help@stably.ai> * Fix Windows workspace deletion runtime resolution Resolve project-created workspace deletion through the selected project runtime so Windows paths are listed strictly without falsely tripping the unregistered worktree guard. Design doc: docs/delete-workspace-windows-unregistered.md --------- Co-authored-by: Orca <help@stably.ai> |
||
|
|
972078f2c4 |
Fix paste ownership, input bounds, and IPC validation
Supersedes #5745, #5746, and #5747. |
||
|
|
6d6af7af1a |
Support browser panes on headless remote Orca servers (#5412)
Co-authored-by: Orca <help@stably.ai> |
||
|
|
d46349ce82 | fix: improve mobile link modifier handling (#5597) | ||
|
|
1368ba84b8 |
feat(shortcuts): double-tap modifier keybindings (#5516)
* docs: design for double-tap modifier keybindings * feat(keybindings): parse and normalize DoubleTap+<Mod> grammar * test(keybindings): cover DoubleTap+Ctrl positive case * feat(keybindings): match DoubleTap bindings against synthetic input * test(keybindings): cover Ctrl-on-darwin double-tap miss; document input invariant * feat(keybindings): format DoubleTap bindings as a doubled glyph * test(keybindings): cover DoubleTap+Ctrl glyph on mac; note title label semantics * feat(keybindings): add pure ModifierDoubleTapDetector state machine * test(keybindings): cover missed-keyup double-tap edge; clarify keyUp guard * feat(keybindings): capture double-tap gestures into DoubleTap+<Mod> * test(keybindings): cover Ctrl/linux and Cmd/linux double-tap canonicalization * feat(keybindings): resolve double-tap input in window shortcut policy * feat(shortcuts): detect double-tap modifiers in main before-input-event * docs(shortcuts): note dictation guards live in the renderer * feat(shortcuts): dispatch double-tap modifiers in the renderer window handler * fix(shortcuts): clear armed double-tap state on dangling modifier keyup * feat(settings): record double-tap modifier shortcuts in the editor * i18n(shortcuts): localize ShortcutKeyCombo double-tap tooltip * Fix double-tap shortcut review gaps Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Devin345458 <devin@appleidimagination.com> Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai> |
||
|
|
8eb96c4ec5 | Add configurable workspace board shortcut (#5638) | ||
|
|
0ec3882cb8 |
Add project Windows runtime selection (#5519)
* Add project Windows runtime selection * Fix project Windows runtime selection Co-authored-by: Orca <help@stably.ai> * fix: preserve WSL shell variables --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai> Co-authored-by: Neil <neil@stably.ai> |
||
|
|
5429e659d4 |
Show PDF export only in Markdown controls (#5548)
Co-authored-by: Orca <help@stably.ai> |