- Enable project header manual reordering when project groups are present.
- Restrict dragging to the project icon handle instead of the entire row.
- Integrate autoscrolling during sidebar drags via requestAnimationFrame.
- Fix drop index comparison in drag-commit logic to handle filtered sibling indices correctly.
- Modularize drag contract, initialization, and measurement helpers.
* Relax source control action availability
Implements the behavior described in docs/lift-git-action-restrictions.md: keep eligible git and review actions clickable so existing git/provider errors can surface, preserve partially staged commit availability, and show a readable inline notice when Create Review is blocked.
* Address source control review follow-ups
* rm design doc
Reveals the standard workspace delete action from sidebar rows on hover or keyboard focus, while keeping primary/main worktrees protected.
Maintainer follow-up:
- prevent hidden delete buttons from receiving pointer events
- merge latest main and resolve localization conflict
Validated with CI PR Checks plus local typecheck, lint, Vitest, and Electron hover/focus/delete-confirmation checks.
Restore focus to the active panel when dismissing QuickOpen, including terminal, editor, simulator, local browser, and remote browser surfaces.
Reviewed-by: Codex
Maps Kimi AskUserQuestion PreToolUse events to waiting, exposes the Kimi hook status IPC/preload surface, and hardens Kimi session parsing plus remote hook path construction.
* fix(settings): surface relative-path behavior in Workspace Directory helper
The global Workspace Directory field's helper text only duplicated the
section description, so the per-project relative-path behavior was
undiscoverable: a relative value resolves under each repo root, an absolute
value is one shared location (worktree-logic.ts resolveWorkspaceDirForRepo).
The per-repo "Worktree Location" field already explains this; the global one
didn't. Replace the redundant line with a helper stating both modes, under a
new i18n key across all five locales.
Also translate the host-scope strings in this block (Client default, Apply
to, Overrides/Inherits client default, Reset) that were still English in
zh/ko/ja/es.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3VivWTrvHx31HdobBmPQ1
* fix(i18n): use 'Restablecer' for Reset action in es locale
'Reiniciar' reads as restarting Orca; 'Restablecer' is the correct
verb for resetting a setting to its default.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BnKfKNJJskzs7eWE7MyJDF
---------
Co-authored-by: Daniil Zinenko <dzinenko@welmi.ai>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Correct Korean locale mistranslations, preserve cross-locale overrides while splitting KO data, and add regression coverage for key-specific localization repairs.
createLaunchEnv adds a team (with nested panes map) per agent-team leader,
keyed by a fresh teamId. The only eviction (removeTeamForLeaderHandle) ran
solely from closeTerminal (explicit user-close). The natural-exit paths
onPtyExit and dropDisconnectedPtyRecord tore down every other per-pty map
but never the team, so a leader exiting on its own leaked the team + panes
map permanently.
Evict via the leader handle (handleByPtyId.get(ptyId)) in both onPtyExit
and dropDisconnectedPtyRecord, mirroring the closeTerminal call. Add
getActiveTeamCount for assertability.
Regression test drives the real onPtyExit/dropDisconnectedPtyRecord: fails
before the fix (teams leak) and passes after.
Co-authored-by: Orca <help@stably.ai>
dispatchCompletion writes a per-paneKey entry into the module-scoped
lastCompletionIdentityByPaneKey on every completion. paneKey is a
never-reused ${tabId}:${leafUUID}. The map is module-scoped so it survives
a live-stream remount (dispose-then-recreate, isLive() still true), but
dispose never evicted it, so genuine teardown (PTY gone) leaked one entry
per closed pane.
Evict in dispose only when !options.isLive() — the same discriminator the
cross-remount dedup tests rely on (remount disposes with isLive() true).
Regression test fails before the fix (identity survives teardown, 200 leak)
and passes after; the existing cross-remount dedup suite still passes.
Co-authored-by: Orca <help@stably.ai>
onData creates+sets a pty record before the stopped-state guard, and onExit
deletes the record instead of leaving a tombstone. A data chunk arriving
after onExit (the exit-then-data race in pty.ts shutdown) resurrected a
fresh record that nothing ever deleted, and double-counted the session.
ptyId is a fresh per-spawn UUID, so the three ptyId-keyed maps grew
unbounded.
Track exited ids in a bounded FIFO and refuse onData resurrection for them.
Regression test fails before the fix (record resurrected, 200 leaked) and
passes after.
Co-authored-by: Orca <help@stably.ai>
recordCoalescedCrashBreadcrumb set entries keyed by agent:${agentType}:${state}
with no prune and no cap. agentType is an open string (length-trimmed only,
never enum-checked) over the relay/SSH/OSC ingest paths, so the map grew
unbounded over a long multi-agent session — the sibling breadcrumbs array
is capped at 30, but this map was not.
Prune entries past their suppression window and LRU-cap to 128, mirroring
ProcessGoneDedupe's prune/maxKeys idiom.
Regression test fails before the fix (1000 keys) and passes after.
Co-authored-by: Orca <help@stably.ai>
handleWriteError adds a sessionId to disabledSessions on any best-effort
history write failure. closeSession (the normal PTY-exit teardown) deleted
the writer but never the disabled flag; the only other deletes are on id
reuse (never happens) and explicit user-kill. A session poisoned by a
transient mid-life write error then exiting normally leaked its id in
disabledSessions forever.
Delete the flag in closeSession after writers.delete. Add isSessionDisabled
and disabledSessionCount test accessors.
Regression test fails before the fix (50 ids leak) and passes after.
Co-authored-by: Orca <help@stably.ai>
removeProject hand-deleted only a handful of per-worktree maps and never
called the canonical purgeWorktreeTerminalState/buildWorktreePurgeState,
so ~30 worktree-scoped maps (unified tabs, groups, layout, git status,
browser, everActivatedWorktreeIds, …) retained an entry per worktree of
every removed repo with no reaper to recover them.
Route removeProject through purgeWorktreeTerminalState before its
repo-scoped set() (which still handles repo-level keys). The purge is a
pure state reducer with no PTY side effects.
Regression test fails before the fix (entries survive) and passes after.
Co-authored-by: Orca <help@stably.ai>
Three worktree-keyed maps (remoteStatusesByWorktree,
recentlyClosedEditorTabsByWorktree, defaultTerminalTabsAppliedByWorktreeId)
were re-keyed on rename but purged by neither removal path. Five
page/workspace-keyed browser maps (browserAnnotationsByPageId,
remoteBrowserPageHandlesByPageId, pendingAddressBarFocusByPageId/ByTabId,
recentlyClosedBrowserPagesByWorkspace) were cleaned only on the single
removeWorktree path; the bulk authoritative-scan reconcile missed them.
worktreeId / workspace id / page id are unbounded ephemeral key spaces,
so every removed worktree leaked entries for the session.
buildWorktreePurgeState now collects doomed page ids and omits all eight
maps; the single removeWorktree reducer deletes the three worktree-keyed
ones. A stale comment claiming purge already dropped them is corrected.
Regression test fails before the fix (entries survive removal) and passes
after.
Co-authored-by: Orca <help@stably.ai>
* fix(store): cap prRefreshStates the same way prRefreshSequences is capped
prRefreshStates is keyed by the same unbounded PR cache key
(execution-host/repo/branch) as prRefreshSequences, but was committed
uncapped one line below the capped sequences map. Status-only events
(paused/skipped) and upstream-error outcomes add entries that no prune
path ever removes, so it grew monotonically over a long session.
Bound it by insertion order via a shared capRecordByInsertionOrder helper
(capPrRefreshSequences now delegates to it), and delete-then-set the
touched key in the status writer so only idle keys are evicted.
Regression test fails before the fix (map grows past the cap) and passes
after.
Co-authored-by: Orca <help@stably.ai>
* test(store): fix prRefreshStates leak-test types for tsc/tsgo
The leak test ran under vitest (esbuild strips types) but failed tsgo:
the status-event helper widened literals and the seeded entries typed
reason as string. Annotate the helper as GitHubPRRefreshEvent (in-flight
variant) and type seeded values' reason as GitHubPRRefreshReason.
Co-authored-by: Orca <help@stably.ai>
* fix(store): make prRefreshStates eviction status-aware to prevent UI regression
prRefreshStates backs visible status pills, so a plain insertion-order cap
could drop an in-progress (in-flight/queued/paused) indicator under a large
working set. Raise the bound to 2000 (well above any realistic tracked-branch
count) and evict settled statuses (error/skipped) first; active entries are
dropped only as a last-resort hard bound. Evicted entries self-heal on the
next refresh event.
Test asserts settled-before-active eviction and that the bound holds; still
fails on the un-capped (main) behavior.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
The "Open any file, URL, agent, ..." new-tab entry had two gaps:
1. Agents were not really searchable. findMatchingTabAgentLaunchOptions only
matched on exact alias equality, so a partial name ("gemin", "clau") surfaced
no agent. It now scores prefix/token matches (exact alias wins; otherwise every
query token must prefix an alias token; mid-string and single-char hits are
rejected so agents — which rank above file results — don't noisily hijack the
list). The tokenizer is shared with the create-menu matcher via
query-token-match.ts.
2. Arrow keys didn't navigate the list. Navigation only ran when there were >1
result rows, and the just-opened (empty-query) state trapped focus in the input
so the static create/agent list below was unreachable. Arrows now move the
highlight for any result count, and with no result rows focus routes into the
enclosing Radix menu; ArrowUp on the first item returns to the search box so
the trip isn't one-way.
Also adds combobox/listbox + aria-activedescendant semantics so the highlight is
exposed to assistive tech, and splits the row presentation and option types into
focused modules to keep the component under the line cap.
* Allow deleting downloaded speech models on desktop and mobile
Implement safe deletion of local speech models across desktop and
mobile platforms.
- Add a shared helper to coordinate STT service teardown, file deletion,
and voice settings cleanup.
- Prevent deleting speech models that are currently in use or starting.
- Update desktop and mobile settings UIs to expose the delete action.
* Track multiple pending model deletions and update action disabling
- Replace the single pending delete ID string state with a Set of IDs on desktop settings to track multiple concurrently deleting models.
- Switch the desktop delete action to use the standard themed Button component.
- Disable all row interactions on the mobile voice model list when any model action is busy.
- Add unit test coverage verifying that individual row delete button disabled states behave correctly.