mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 16:03:49 +00:00
ce834946ea520e50349e603fa80b4879989a3cae
5154
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b085e67c35 |
fix(popover): wheel-scroll a popover whose scroller is nested, not the content (#16206)
* fix(popover): wheel-scroll a popover whose scroller is nested, not the content The workspace-cleanup Filters panel cannot be scrolled with a wheel. Only the scrollbar drag and focus-scroll work. Two independent Electron QA passes measured it on main: wheel events over that popover arrive defaultPrevented, panel scrollTop 0 -> 0, while the candidate list inside the dialog subtree scrolls normally. Cause: the popover portals outside the Radix dialog subtree, so react-remove-scroll's scroll-lock cancels wheel there. #14629 added a shim for exactly this, but it scrolls `event.currentTarget` -- the PopoverContent -- and only when that element is itself overflowing. The Filters panel is a flex column holding a ScrollArea above a pinned footer, so PopoverContent is overflow-hidden and the real scroller is a descendant. The shim looked at the wrong element and returned early. The shim now resolves the nearest scrollable element between the wheel target and the content, inclusive of both, so it handles the nested-viewport shape as well as the flat one. Still opt-in via `popover-scroll-content`; popovers without the class are untouched. The cleanup Filters panel opts in. This is the unfixed half of the original report. #14629 fixed the panel being clipped; the wheel -- what you actually reach for -- stayed dead. The nested test fails against the shipped shim and the other two stay green, so it reproduces the bug without redefining existing behaviour. * fix(popover): split the wheel-shim opt-in from the scroll-container styling Two fixes on top of the reviewer's round. **The reviewer's change, kept.** `resolvePopoverScroller` no longer falls back to the content element unconditionally; it must match the overflow test like any other candidate. The reviewer flagged that this could break the existing opted-in popovers, so I checked: `.popover-scroll-content` sets `overflow-y: auto` (main.css:601-607), so every one of them still resolves. The test needs an inline style only because happy-dom does not apply the stylesheet -- noted in the test so nobody reads it as a production concern. **A bug of mine the review did not reach.** Opting the cleanup Filters panel in via `popover-scroll-content` would also have applied that class's `max-height: min(15rem, ...)`, crushing the panel's 471px flex column to 240px. The class conflates 'style me as a scroll container' with 'run the wheel shim', and a popover that manages its own layout needs only the second. So the shim now accepts `popover-wheel-scroll` as a styling-free marker, and the Filters panel uses that. `popover-scroll-content` keeps implying it, so no existing caller changes. |
||
|
|
d14923e968 |
fix(workspace-cleanup): stop a sync broadcast reverting a filter you just set (#15369)
* fix(workspace-cleanup): stop a sync broadcast reverting a filter you just set `workspaceCleanupBrowse` re-hydrated on every hydration source. Two lines below it, `activeView` is startup-guarded for the same reason and says so. How it fired: the browse writer debounces 250ms. Any other `ui.set` inside that window makes main broadcast to all windows (`main/ipc/ui.ts:54-61`) carrying state that still holds the previous filters. The window hydrated that over the edit, and the pending debounce then wrote `get().workspaceCleanupBrowse` -- by then the reverted value -- so the revert was persisted, not just displayed. Your own write echoing back is harmless; it is somebody else's write landing first that does the damage. Intermittent, which is why it reads as "Orca forgot my filter". Guarded to `source === 'startup'`, matching activeView. The cost is that browse state no longer syncs between windows or from mobile. That costs nothing today: `mobile/src` has no workspace-cleanup consumer, and the popout runs a separate store and JS context, so nothing else in the product edits browse state. Filters become per-window, which is the call already made for activeView. Rejected the alternative (last-write-wins via a local dirty generation): it preserves a cross-window sync nobody uses, in exchange for more state to get wrong on a path that just proved it can silently discard user input. Three of the five tests fail against the unguarded source; the two that stay green assert what must not change -- startup still restores, and dismissals still hydrate on sync because they are main-owned. * chore(workspace-cleanup): tighten hydration guard rationale |
||
|
|
43461fca46 |
fix(ai-vault): replace scanner internals with actionable panel copy (#16229)
* fix(ai-vault): replace scanner internals with actionable panel copy Agent Session History painted the scanner's supervision errors verbatim: "AI Vault service restart circuit is open." and "AI Vault service timed out after 130000ms." Neither tells a user what happened or what to do. Add a shared mapper that rewrites the supervision family into copy tied to an action, passing anything unrecognized through so scanner-authored messages (host name, remote path, cap) keep their own wording. It also strips Electron's `Error invoking remote method` wrapper, which this path never handled. Applied at both surfaces the panel paints: the local leg's scan-issue row and the thrown-rejection banner. Humanizing in main covers remote clients too; the raw text moves to the main log. Also make "Refresh to try again" true. The relay path lets a forced refresh reopen the restart circuit, but on the local path `force` stopped at the cache layer and never reached the supervisor, so the refresh button was inert for the full 60s fault window. The client file sat at 299/300 lines, so extract the child-listener and init-frame wiring into `attachAiVaultServiceChild`, next to the ready-waiter and retirement helpers it belongs with, rather than bumping the max-lines ceiling. * fix(ai-vault): humanize runtime scanner errors * fix(ai-vault): preserve runtime error metadata * fix(ai-vault): normalize wrapped scan errors * fix(ai-vault): preserve non-scanner relay errors * fix(ai-vault): make forced retries cancel backoff |
||
|
|
7e76bb3aec |
Fix rebase race by fetching to private ref before rebasing (#15990)
* Fix rebase race by fetching to private ref before rebasing
`git pull --rebase` is vulnerable to concurrent fetches modifying remote-tracking refs during execution. Fetch to a temporary private ref (refs/orca/rebase/*) first, then rebase from that stable ref to avoid the race condition.
* Fix rebase race by fetching to private ref with timeout
Concurrent fetches can interfere with remote-tracking refs between
fetch and rebase. Use a unique private ref and 60-second timeout to
isolate each rebase operation and prevent hangs on stalled remotes.
Extract gitPullRebaseFromBase to a dedicated module.
* fix rebase race by fetching to private ref with timeouts
Concurrent fetches can replace FETCH_HEAD and remote-tracking refs between
fetch and rebase, causing the rebase to fail. Fetch to a temporary private
ref instead, use --no-write-fetch-head when available (Git 2.29+), and
serialize FETCH_HEAD access for older versions. Add process termination
barriers to ensure proper cleanup and extend timeouts for SSH operations.
* Fix rebase race by fetching to both private and tracking refs
Concurrent fetches between source and rebase can replace remote-tracking refs,
causing rebases to use stale bases. Now fetch to both a private ref and the
remote-tracking ref simultaneously, ensuring the tracking ref stays current.
Also improves process termination for WSL guests with process-group tracking,
fixes process-tree termination timeouts on POSIX, and serializes FETCH_HEAD
operations for linked worktrees through their shared Git directory.
* Add WSL setsid --wait probe and barrier termination timeout
Probe for `setsid --wait` support and fall back to unwrapped execution for BusyBox compatibility. Add a deadline for process termination barriers to prevent hanging when tree termination cannot be verified. Update tests for cross-platform compatibility.
* Add wsl-process-group-termination to WSL invocation allowlist
* Serialize per-worktree git mutations to fix rebase race
Introduce operation locking for each worktree to prevent concurrent
mutations (like rebase) from interfering with each other. Ensures
rebasing a linked worktree doesn't affect the source worktree state.
Add SIGKILL fallback if process termination barriers cannot verify
tree termination.
* Serialize pull and fastForward operations per-worktree
- Extract generic git operation lock to reuse locking pattern
- Refactor existing locks to use the generic implementation
- Apply per-worktree serialization to pull and fastForward to prevent races
* Route WSL group termination through runWslProcess
|
||
|
|
9d87532ecf |
feat(workspace-cleanup): name every applied filter in the bar and make it removable (#15300)
* feat(workspace-cleanup): name every applied filter in the bar and make it removable Replaces the one-time filter migration this PR used to carry, and the per-group apply checkboxes that were planned to follow it. Both existed to answer one question -- why is a filter I never turned on hiding my workspaces -- and neither was the cheapest honest answer. The bar already read "Showing 546 of 799", so the *effect* was always visible. What was missing was the *cause*: which filter, and that it came from a previous session. Active constraints now render as removable chips in the bar, and Clear filters is promoted out of the popover it was buried in. Why this replaces the migration: a blanket clear cannot tell a wheel mutation from a deliberate choice, and the version here was worse than that -- it neutralized all ten groups, including location.repoIds and location.pathPrefix, which a wheel cannot set. With a chip, a stray threshold is visible on open and one click removes it. No marker, no provenance guessing, nobody's deliberate filters deleted. Why this replaces the apply toggle: every group already has a neutral resting state that does not constrain -- an empty numeric field, tri-states at 'any', the two booleans permissive. "Not applied" and "empty" are the same state today, so the toggle's only unique power was parking a value you are not using. That is a modest convenience against ten checkboxes, two-way drafts, a persistence model that could not use an 'enabled' flag without an older host dropping it, and a hydration guard. Chips are per-field, not per-group: "Activity" tells a reader nothing, while "Idle 20d+" names the thing hiding their workspaces. Zero handling matches the matchers: a 0 minimum is inert and shows no chip, a 0 maximum hides every measured non-empty workspace and does. * fix(workspace-cleanup): address the second review on the filter chips Four findings from the re-review of 3b88645aa1: - **Same-tick writes could restore a cleared chip.** `replaceFilters` read the store but `patchFilters` still rebuilt from the render snapshot, so a chip clear plus a facet patch in one tick left `idleMinDays` at 20. Every writer now derives from current store state. Tests cover both call orders, and reverting the reader reproduces the reported failure. - **Chip labels went stale across a language change.** They were memoized on `filters` alone, so unchanged filters reused the previous language's strings. Derivation is constant-size (one pass over the filter fields, not per row), so it just runs each render. - **The new catalog entries were English-only.** ko and zh now carry all 22 chip strings. The verifier stayed green because missing target-locale entries are allowed and fall back to English -- which is exactly the trap this series has now hit twice. - **Remove targets were 16x16.** They use the shared button primitive at the canonical `icon-xs` size. Also trimmed the defect-history comments to the repo's concise style. * fix(workspace-cleanup): unify chip clears on the merged updater form #15298 landed the functional-update `patchFilters`, so `replaceFilters` uses the same idiom rather than reading the store directly. Same guarantee, one pattern. |
||
|
|
aa32871a61 |
Improve cmd j ranking with recency (#16281)
* Track container-only tokens and tab focus for cmd+j ranking Previously ranked by whether any container-only matches existed (boolean); now counts tokens matching only containers for finer-grained ranking. Tab focus recency is now tracked explicitly so recent refocuses rank above stale worktree activity. Preserves worktree grouping by input order while applying focused-group MRU within each block. * fix(cmd-j): preserve duplicate recent tab occurrences * fix(cmd-j): preserve host scope during worktree purge * fix(cmd-j): scope repo purge for exact-id host twins * fix(cmd-j): scope ssh visit recency to local to survive restarts Boot hydration loads only local + runtime:* partitions, so routing ssh-qualified recency to ssh partitions strands it across restarts. - Keep ssh-qualified visit timestamps in local partition - Route runtime-qualified keys to their partition - Remove groupId from recent tab occurrence base (unstable on regroup) - Collapse bare and host-qualified timestamps, preserving max - Simplify repo pruning host-match logic - Add robustness: optional chaining, helper function * Scope focused tab recency by worktree to fix Cmd+J ranking Tab ids can be duplicated across worktrees; scoping recency keys to per-worktree prevents one worktree's MRU position from overwriting another's in Cmd+J. Scope worktree order blocks to (hostId, worktreeId) to keep same-id worktrees on different hosts separate. Also fix recency preservation during partial identity migrations and prune orphaned host keys on removal. |
||
|
|
94f231737d |
fix(agent-status): retire panes whose agent process is gone (STA-4612) (#15212)
* fix(agent-status): retire panes whose agent process is gone (STA-4612) Agent status can hold `working` on a pane where no work is outstanding, and nothing closes the gap. A pane's Claude state is a join of a lead turn and three latches — the subagent roster, the background-task gate and the session-cron gate — and each is set by a hook and cleared only by another hook. Claude Code emits no terminating hook on `/exit`, `/clear`, Ctrl+C, crash, SIGKILL or terminal close, so every one of those latches is a claim with no owner and no expiry. The join is also materialised at ingest time and persisted, so a stale `working` survives restart and blocks hibernation, which requires `done`. Registering `SessionEnd` is not the fix: it covers roughly a third of exit paths (measured on 2.1.231/2.1.233; upstream anthropics/claude-code#17885 and #6428 are both closed as not planned). Nor is a TTL — `AGENT_STATUS_STALE_AFTER_MS` only decays the sidebar dot at read time while the stored row stays non-terminal. So the backstop is built from evidence Orca already owns. A session id that changes means the conversation was replaced. On the first hook of the new session — whatever that hook is — the previous session's own claims are void: its session crons and its one-shot subagents. Deliberately not voided: the background-task gate (a background shell is an OS process that survives `/clear`, and the previous inventory is positive evidence it was running), and `confirmedTeammate` rows (persistent in-process teammates a lead swap cannot end). The lead record is left to the incoming event's own fold. A certified process exit retires the pane. Orca already does this on every attributable PTY exit — `clearProviderPtyState` resolves the pane key and calls `clearPaneState` — but that resolution depends on the spawn-time `ptyPaneKey` mapping, which a restored or reattached PTY may never rebuild. Those panes keep their row and latches for good. `onPtyExit` knows the keys teardown could not resolve, so it reconciles them from its own records. The certificate is `exitCode >= 0 || hostExitConfirmed || providerExitObserved`: a synthetic `-1` from a failed stop is not a death (the PTY can have survived it), while a real exit can also report `-1`, so neither the code nor the SSH surface predicate is sufficient alone. `providerExitObserved` is additive and separate from `hostExitConfirmed`, which also drives the liveness verdict and the SSH surface decision. A confirmed shell foreground is the `/exit` case: the agent died, the shell lived. That already dropped the row, but through `agentStatus:drop`, which by its own contract preserves a live pane's caches — so every latch survived and the next event resolved the pane back to `working`. It now routes through the reconciler instead, gated on a per-pane accepted-status generation rather than row identity: the confirming process read can take seconds, and `updatedAt` cannot order two writes inside one millisecond (the store deliberately admits equal timestamps). Cold start generalises the same way. The startup sweep required a restored subagent roster, so a stranded lead row, background-task gate or cron gate — the shapes with no child event left to reap them — were never candidates. Hibernation needs no change: with the above, those rows become genuinely `done` and the lockout resolves through the front door. A `restoredUnconfirmed` bypass in the planner would let it reclaim the heap of an agent that may be working. Not included: folding `background_tasks` from a child-attributed `SubagentStop`. Writing its test surfaced #11838's deliberate assertion that child inventories are not authoritative for lead-owned background work, and the listener says the same — "background_tasks is trusted only where unambiguous". An empty list on a `SubagentStop` does not prove the lead's shell ended, so the fold would have cleared a gate on evidence that establishes nothing. STA-4119's live-side question — whether a genuinely live background shell should hold the lead row after the lead turn ends — is untouched. This change extends gate-clearing to zero new triggers. * fix(agent-status): make the confirmed-shell reconcile survive its own drop The /exit leg never fired. `settleDeferredCommandFinishedStatusDrop` runs the paired drop before the reconcile, and `dropAgentStatus` cleared the per-pane accepted-status counter the reconcile's guard then read — so the guard compared a live anchor against a zeroed counter and skipped itself on every pane that had a status row, which is every pane worth reconciling. The existing test passed only because it used a pane with no row, where the drop early-returns and both sides read 0. Stop keying the guard on a counter a sibling teardown path can reset: the ordinal is now stamped on the row itself, derived from the row it replaces, so there is no side table to clear and a batched burst lands the same ordinals as the equivalent sequential writes. A removed row means "nothing reported", which is exactly what the paired drop leaves behind. Also: - Keep the `providerSessionOnly` resume identity that the paired dismissal mints when the shell outlived the agent; a certified PTY exit still takes it, since there is no pane left to resume into. - De-vacuum two guard tests. The confirmed-teammate pin never anchored a session owner, so the void it claimed to survive never ran; the unavailable-inspection pin asserted before the confirm ladder settled. Both now fail when their guard is removed. - Derive `hasLiveClaimsForPaneKey` from a predicate that lives beside `clearPaneCacheState`, so a new latch cannot be added to the teardown and silently missed by the claim check. - Drop the unreachable compact-`trigger` clauses; SessionStart is the whole guard. - Cover the connectionId arm of the exit certificate, where a provider-observed death and a preserved SSH surface are deliberately independent. * fix(agent-status): keep agent-status-types under its line cap main already sits exactly at the 300-line max-lines cap for this file, so the single `acceptedStatusSeq` field this branch adds pushed it to 301 once main's observation facet merged in. Declared the field as a mixin beside the observation facet instead. Both are per-write facets mixed into `AgentStatusEntry` rather than fields a reporter supplies, so they belong together — and the capped file loses a line rather than gaining one, since it already imports from that module. No lint suppression. * fix(agent-status): collapse the entry facets into one intersection The previous attempt still tripped max-lines: two mixins on one intersection wrap across two lines under oxfmt, so removing the field line bought nothing. Expose a single AgentStatusRowFacets that already includes the observation facet, so the entry intersects one short name on one line. The payload keeps intersecting the observation facet alone — it must not carry the renderer-local ordinal. Verified by formatting first and then linting, which is the order that catches this. * fix(agent-status): retire resume authority with dead panes |
||
|
|
7b9529da22 |
Add keyboard shortcut for workspace deletion (#16271)
* Add keyboard shortcut for workspace deletion Default Mod+Shift+Backspace (⌘⇧⌫ on Mac) lets users delete the hovered worktree or folder workspace immediately. The shortcut targets the sidebar hover state rather than requiring focus, and avoids terminal pane D-based split shortcuts on all platforms. Co-authored-by: Brennan Benson <brennankbenson@gmail.com> * Omit delete shortcut from disabled Delete Worktree for primary checkout - Remove shortcut badge from the disabled "Delete Worktree" action when it cannot be executed - Only show shortcut in multi-context delete actions where the command is available - Extract host identity parsing into reusable helper function to prevent inline string manipulation - Fix folder workspace deletion to use correct host-qualified identity comparison * Document host extraction safety for destructive worktree ops Unqualified identities must stay undefined rather than defaulting to 'local'. Destructive operations depend on correct host identification. Added tests and JSDoc to clarify this safety-critical behavior. * fix test --------- Co-authored-by: Brennan Benson <brennankbenson@gmail.com> |
||
|
|
c618ec7393 | test(reliability): protect recent P0 regression invariants (#16163) | ||
|
|
afd76a4df9 | fix(terminal): preserve synchronized frames on reveal (#16026) | ||
|
|
95633a7883 |
Fix stale task-source flashes in new workspace input (#16145)
* fix(new-workspace): prevent stale GitHub URL selection * fix(new-workspace): guard all task URL transitions * test(e2e): make task URL frame proof runner-safe * fix(new-workspace): guard Enter during task URL lookup |
||
|
|
4ee41fede2 | fix(automations): reveal full prompt from detail view (#16067) | ||
|
|
4bc9e6b00c |
fix(workspace-cleanup): stop a wheel tick setting a filter, and land every filter patch (#15298)
A profile carried `activity.idleMinDays = 20` that the user never set, hiding 253 of 799 workspaces on open. Chromium mutates a *focused* number input on every wheel tick, and before #14629 the facet panel could not scroll, so the natural response -- cursor into the panel, spin the wheel -- walked the threshold up and persisted it. Two fixes: - `FacetNumberField` renders `type="text" inputMode="numeric"`. A wheel cannot mutate a text input, and the parser already takes strings. `preventDefault` on a focused number input would also work, but it blocks the wheel's default action -- which includes scrolling the nearest scrollable ancestor -- and would re-break the panel scrolling #14629 just fixed, in exactly the reported gesture. All five numeric facets share this one field. - `patchFilters` closed over the render's `browse` snapshot, so two patches in one tick dropped one. It now writes through a functional update against the latest store state. `toggleSortField` and `clearFilters` had the same defect. Both tests were confirmed to fail against the unfixed source before being kept. |
||
|
|
d9aa09cc8c |
fix(runtime): retire terminal snapshot probes before waiter timeout (#16149)
* refactor(renderer): use terminal paste sanitizer directly * fix(runtime): order visible probe timeout settlement * fix(runtime): skip unorderable visible probes |
||
|
|
41ddcbde06 |
fix(workspace-cleanup): stop pre-selecting workspaces for deletion (#15152)
* fix(workspace-cleanup): stop pre-selecting workspaces for deletion 'Ready' is Orca's verdict about the user's own work. The dialog already refuses to display the tier as a workspace fact, then pre-checked rows based on it anyway — acting on the verdict more loudly than showing it would. Open with an empty selection and let the user decide. Removing auto-select unmasks two selection defects it was hiding, both fixed here because they become reachable the moment select-all is the primary path: - The header checkbox compared a canQueue-scoped selected count against a canSelect-scoped selectable count, so hand-picking review-tier rows flipped it to fully-checked and the next click cleared the entire selection. - A filter change silently dropped selected rows; auto-select used to refill them instantly, so the loss was invisible. It is now reported. Also drops the scan toast's 'N cleanup suggestions' clause, which published the same verdict outside the dialog, and labels select-all with the count it actually takes (the deletable subset, not every matched row). * chore(i18n): sync the catalog for the cleanup selection strings * fix(workspace-cleanup): preserve explicit selections |
||
|
|
4c44116197 |
fix(workspace-cleanup): name the local context instead of totalling it (#15070)
* fix(workspace-cleanup): name the local context instead of totalling it
The delete confirmation showed 'Context: 2', a sum of five unrelated things
(terminal tabs, clean editor tabs, browser tabs, diff notes, finished agents)
rendered as plain text with no icon or tooltip. A reader cannot tell what the
number counts, which is the one thing that screen exists to tell them.
Reuse the breakdown the expanded row already renders ('Terminal tabs: 1,
Browser tabs: 1') so the confirmation names what deleting would discard. No new
strings: the per-kind labels already exist and were already translated.
* fix(workspace-cleanup): keep context labels legible
|
||
|
|
3d74f5fe03 | test(remote): preserve HTML inventory RPC failures (STA-5210) (#16056) | ||
|
|
0753f0a8dc | fix(crash-reporting): stamp React #185 boundary attribution as unreliable (#16153) | ||
|
|
0926c25854 | refactor: reuse canonical regex escaping (#16150) | ||
|
|
b89b58bf77 | fix(renderer): keep navigation responsive under terminal load (#16138) | ||
|
|
f0c718ecd5 | refactor(renderer): use canonical attachment labels (#16135) | ||
|
|
8af02d658c |
Preserve code editor selections across tab switches (#16132)
* Preserve editor selections across tab switches * Defer editor selection caching to tab lifecycle |
||
|
|
7a72f341f7 |
Split pty-connection.ts into focused modules under 400 lines (#15166)
* refactor: split pty-connection.ts under 400 lines * rm design doc * refactor(pty-connection): extract reattach payload handlers as factories - Replace bindApplyReattachPayload with createReattachPayloadHandlers factory that returns handlers instead of mutating session directly, enabling better composability and testing - Extract waitForUserInitiatedSshConnect as standalone function for reuse across deferred session attach flows - Create ReattachPayloadSession type to document and isolate required session capabilities - Add test coverage for overlapping reattach payload attempts - Clean up comments to remove redundant prefixes (session.pane → pane, session.transport → transport) * fix(pty-connection): correct sequencing and state bugs in spawn and reat - Fix terminal tail slice to take prefix instead of suffix, preserving escape sequence markers needed by next scan - Clear pending pane serializer when direct SSH retry PTY is unclaimed - Initialize interrupt status baseline to undefined so first input advances sequence counter - Bump reattach generation only after confirming current attempt owns the stream, preventing superseded results from canceling in-flight prepaint * fix(pty-connection): correct sequencing and state bugs in spawn and reat - Fix terminal tail slice to take prefix instead of suffix, preserving escape sequence markers needed by next scan - Clear pending pane serializer when direct SSH retry PTY is unclaimed - Initialize interrupt status baseline to undefined so first input advances sequence counter - Bump reattach generation only after confirming current attempt owns the stream, preventing superseded results from canceling in-flight prepaint * fix(test): increase poll iterations to prevent Node 26 test leakage Increase event loop turns from 40 to 200 in the timer settlement loop. Node 26's libuv poll phase can briefly starve when concurrent workers transform tests, causing cleanup to leak into the next test. The higher iteration count ensures async operations complete before returning. * fix(foreground-output-budgets): use >= for budget window boundary check At the exact window boundary, the budget should roll over. Change the comparison from > to >= so the window resets when now equals windowStart + FOREGROUND_BUDGET_WINDOW_MS, not just after. Add tests to verify budget rejection and rollover behavior. * refactor(pty-connection): add status observations and routing improvemen - Track agent status observations with origin and transition metadata - Separate interactive redraw input timing from general terminal input - Restore pane authority on bind and reattach - Refine routing trust and confirmation state handling - Invoke queued startup callbacks when PTY is bound - Resolve Windows shell overrides with user settings * refactor: extract resolveLaunchAgentCandidate helper Consolidate duplicated launch-agent resolution logic into a shared helper to prevent future divergence between paneExpectsLaunchAgent and resolveExpectedLaunchTuiAgent. * refactor(pty-connection): use model snapshot for direct SSH reconnects Direct SSH reconnects now restore from the full SSH model snapshot (complete scrollback) when dimensions are compatible, instead of the bounded relay tail. Falls back gracefully when incompatible or alternate-screen was exited. * refactor(pty): retry unverifiable SSH reattaches via preserved bindings Preserve deferred SSH session IDs longer when they serve as the only retry binding, allowing the system to attempt recovery through direct SSH retries or PTY remounts when reattach fails in an unverifiable way. Simplify reconnect model restoration by removing the conditional model snapshot probe and using relay replay directly. * test: poll terminal readiness in expectSingleOwningPty Retry the terminal list assertion with polling to account for timing delays in PTY state reporting from the runtime. |
||
|
|
853afdf80e |
fix(status-bar): remove pet menu reserved space (#13067)
* fix(status-bar): remove pet menu reserved space * test(status-bar): add pet segment layout validation tests - Unit test guards against pr-[6.5rem] padding reintroduction - E2E test measures trailing overhang instead of total width delta for more accurate layout validation - Extract enableExperimentalPet helper for test clarity --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
b2902cb61e |
fix(agent-resume): restore Kimi Code sessions after restart (#15883)
Co-authored-by: Melih <mberatsanli@gmail.com> |
||
|
|
4c984d4c1b |
Fix Windows Git Bash console-capacity failures (#16045)
* fix(terminal): retain failed local console panes * fix(terminal): preserve failed pane restart context * fix(terminal): scope capacity recovery to PTY binding --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> |
||
|
|
0e9f02baf3 |
fix(settings): an empty agent detection must not erase the saved default (#16043)
Three bugs in one screen, reported in #15256 with a diff of the user's orca-data.json showing defaultTuiAgent going from "claude" to null. 1. The Auto pill's handler writes null, and it was rendered as the ACTIVE choice whenever the stored agent was merely not detected right now. So the pill that already looked selected was destructive: one click erased the setting, and a later successful detection did not bring it back. Auto is now active only when null is actually stored. Detection is a transient fact; the stored value is not, and this control reports the stored value. 2. With zero agents detected there were no agent pills at all, so the stored choice was both invisible and unrecoverable -- nothing to click to put it back. The stored agent is now always offered, labelled as saved but not currently detected. 3. Refresh lived inside the Installed section, which only renders when at least one agent was found, so the only retry control vanished in exactly the state that needs it. An empty result now renders its own Refresh. This matters more now than when it was filed: #16028 makes WSL detection legitimately return an empty set on machines where the only agent was a Windows binary reached through interop, so the empty path is about to get more traffic. Each of the three tests was verified to fail with its own fix reverted. |
||
|
|
9ea2636b38 |
fix(remote): keep editor focus after unfocused HTML side preview (STA-5001) (#15716)
Opening a remote HTML preview to the side created an empty split and made it the active group. The next host session-tab snapshot still had the terminal active, so the client treated that empty group as a terminal focus change. Do not activate the empty split for unfocused remote previews, and when a reserved preview group is still empty, keep the sibling editor as the visible tab instead of following the host terminal. |
||
|
|
0cb076f03c | fix(dictation): address visualizer review feedback (#16035) | ||
|
|
b851a5e13f |
fix(github): add GHES avatar fallback to PR and task views (#13981)
* fix(pr-page): route remaining user avatars through GitHubUserAvatar On a private-mode GitHub Enterprise instance the stored avatar URL 302s to /login, and the renderer's default Electron session carries no cookie, so the image never loads. #8784 added GitHubUserAvatar for exactly this — it degrades to an initials placeholder via onError — but three call sites in PullRequestPage kept a bare <img>: the reviewer picker, the comment author, and the @ mention suggestions. Each only guarded on avatarUrl being absent, so on GHE the URL is present, the placeholder branch never runs, and a broken image is left on screen. The authorAvatarUrl type comment already documents the intended contract ("falls back to the login URL and finally an initials placeholder"). GitHubUserAvatar was already imported in this file for the PR author, so this makes all five avatars in the page consistent. Note the three switched slots now carry the shared border/bg styling, matching the two that already did. Add a boundary test that fails if any avatar is rendered through a bare <img> again. Fixes #13976 * fix(task-page): route GitHub avatar cells through GitHubUserAvatar too Auditing the rest of the GHE avatar path turned up the same bare <img> in TaskPage: GitHubAssigneeAvatar, GHAssigneesCell and PRReviewCell. Fixing only the PR page would leave half of #13976 in place. GitHubAssigneeAvatar is the clearest case — ReviewChipAvatar directly above it already renders through GitHubUserAvatar, so two adjacent functions disagreed on how a GitHub user avatar is drawn. Its border also moves from border-border/40 to /50, matching the neighbour. Linear member avatars in this file are left alone; they use their own provider path and are out of scope here. Move the regression assertions into the existing repro-8784 file rather than a new boundary test — that file already guards PullRequestPage and TaskPage together, so it is where this belongs. The PR-page check matches the <img> pattern instead of specific field names, so a rename or a newly added avatar slot cannot slip past it; the TaskPage check is scoped per function to avoid catching the Linear cells. * test(github): scope the avatar guard per call site and cover TaskPage names Addresses review feedback on the regression guard. The field-name regex missed aliases and resolver expressions, and the PR-page assertion did not require GitHubUserAvatar in each migrated slot — deleting all three would have passed. Reject any bare <img> within the component scope instead, which is safe now that every assertion is scoped to one function. Drive all six slots from one table so each gets its own named case, and extend the display-name contract to TaskPage, which previously went unchecked. The ConversationTab entry carries displayName: null because PRComment has no display-name field. Reverting the fix now fails 11 cases instead of 3. |
||
|
|
36dfcc3955 |
fix(terminal): arm the restore baseline only on a snapshot that painted content (#16009)
The restored-snapshot baseline permanently drops every delivery chunk at or below the snapshot's seq, on the model's claim that those bytes are already painted. reconcileChunkAgainstRestoredSnapshot recovers when the baseline under-reports (a gap ahead re-restores; a rawLength mismatch re-restores) but has no path for a baseline that over-reports: those chunks return drop-duplicate forever, and an idle shell never re-sends them. Gate arming on whether the snapshot painted printable cells. A snapshot claiming seq > 0 while painting nothing cannot be the rendering of the output it claims to cover, so the claim is disproven and the redelivery is the only remaining copy. Also pins disposeHeadlessTerminal's two-part write ordering, which was previously unpinned and silently reversible. Refs STA-5179 |
||
|
|
dee48498b2 |
feat(dictation): add sound-reactive grape visualizer (#16017)
* feat(dictation): add sound-reactive grape visualizer * perf: scope dictation meter updates |
||
|
|
6785dc092d |
fix(composer): close the Create Workspace dialog on the first Escape (#16027)
* fix(composer): close the Create Workspace dialog on the first Escape The modal copied the page-level "Esc blurs the focused field, then closes" rule from TaskPage/Automations. On a page that rule protects a focus the user chose; this dialog auto-focuses the name input on open, so its capture-phase handler preventDefault'd every first Escape (which also suppressed Radix's dismissal, since DismissableLayer skips a defaultPrevented event) and the dialog could only be closed with two presses. Drop the Escape branch and let the dialog's dismissable layer own it. Radix dismisses only the topmost layer, so nested popovers, selects and dialogs still consume their own Escape first. * test(e2e): pin the composer's auto-focus as the reason one Escape must close it |
||
|
|
7ce11dcf55 |
fix(agent-resume): restore Copilot sessions after restart (#15879)
Co-authored-by: Melih <mberatsanli@gmail.com> |
||
|
|
ac103f5d90 | fix(project-groups): route rename and delete to the group's owning host (#15889) | ||
|
|
e9e238c883 |
refactor(wsl): delete the environment-policy layer the reviews kept failing on (#16007)
* refactor(wsl): delete the environment-policy layer the reviews kept failing on
A design council (Opus, Grok, GPT-5.6-Sol) reviewed the merged runner after it
took eleven review rounds to land. All three reached the same conclusion: the
invocation half is sound, the environment/probe half is not, and every round had
been debugging the second one.
The finding that settled it, from Opus: `environmentResolved` had **54
references, all in tests and the runner itself. Not one production reader.** The
safety mechanism the strict default existed for was never wired to anything, so
all 19 degrading sites reported absence with full confidence anyway -- #9725
live at every one, under comments claiming it was handled. Two of those comments
say so out loud; I wrote them.
Root cause, in one line: every knob existed only because a failed probe was
fatal. So it no longer is.
- `allowDegradedEnvironment` and `WslGuestEnvironmentUnavailableError` are gone.
A missing login PATH is a fact in the result, not an exception. That deletes
23 opt-outs, six catch-and-remap blocks, the transient/rejected cooldown
split, `probedWithBudget`, and the 1.5x re-probe heuristic -- none of which
had a reason to exist once the case stopped throwing.
- `lane` + `allowDegradedEnvironment` collapse into `loginPath: 'none' |
'preferred'`. 19 of 23 sites passed the opt-out, and two said in comments that
they did not want the login PATH at all: the flag had become the `'none'` the
union was missing.
- The `interactive` lane is deleted. It had zero production callers and kept ~30
lines of fence plumbing alive for tests only.
Net -98 production lines; the runner itself sheds 86 for 38.
Also carries three fixes from the W3 orphan-PR sweep I had not done:
- `WSL_UTF8=1` in the runner. My relay migration deleted the only place setting
it, so wsl.exe's own error text arrived UTF-16LE and read as NUL-riddled.
A regression I introduced. Credit: #9010 (Chang-Jin-Lee).
- `GITLAB_HOST` is now named in WSLENV, so a ported self-hosted host actually
crosses into a distro-routed glab (#12557). Credit: #12558 (makoto-developer).
- The WSL skill-setup command pipes into `sh` instead of `eval "$(...)"`, whose
nested quoting produced `word unexpected (expecting "in")` (#14292). Credit:
#14785 (innocarpe).
* fix(wsl): restore the login PATH for the Codex availability lookup
loginPath:'none' on a PATH lookup reports an nvm-installed codex as absent,
which is #9725. A miss without a resolved environment is now 'could not
check', not 'not installed'.
Also hardens the guards that should have caught it:
- bashism ratchet is per-call, not per-file, and fails closed on lexer desync
- blankStringContents handles regex literals (an apostrophe in /'/g desynced
the lexer, so the scan silently found zero calls)
- windowsHide allowlist 85 -> 80, stale once the lexer parsed those files
Credit: Grok (P0), GPT-Sol (ratchet gaps).
* test(wsl): close the two ratchet gaps that let planted spawns pass
- variable-indirected wsl.exe (`const b = 'wsl.exe'; spawnProcess(b)`) is now
tracked, so the 5 files recorded only in a comment become real allowlist
entries. Three actually spawn that way; the other two never spawned wsl.exe
at all, so the prose record was wrong by three in the hiding direction.
- promisify(renamedAlias) is now resolved, so `const run = promisify(execFile)`
behind an `execFile as x` import can no longer skip windowsHide.
Each verified by planting the violation, watching it fail, restoring, watching
it pass. Credit: GPT-Sol.
* fix(source-scan): stop the regex-literal reader from eating block comments
At index 0 there is no preceding token, so a file opening with a banner
comment had its `/*` read as a pattern and swallowed to the next slash --
110k characters of preload/index.ts, in the direction that hides offenders.
Measured across the tree, old lexer vs new: worst-case over-blanking drops
from -110564 to -1116 characters, and files that desync drop from 51 to 22.
The remaining extra blanking is regex interiors, which is the intent.
Regression tests for both lexer bugs, each verified to fail with its fix
reverted. The first draft of the comment test did not bind -- it asserted on
text after the swallowed span.
* fix(wsl): restore the unverifiable signal on the two remaining probe sites
Round 2. Three call sites used to throw when the login-PATH probe failed;
the redesign rewired one (Codex) and left two reporting confident absence.
- skill-wsl-provider-detection: the script ends in `|| true`, so a lookup
without the login PATH exits 0 with empty stdout -- identical to 'nothing
installed'. Callers skip the ~/.codex and ~/.claude skill roots on an empty
list, losing an nvm-installed provider's skills.
- wsl-cli-installer: the dead catch is replaced by an explicit check. Its
`case ":$PATH:"` probe otherwise answers from the distro default PATH and
Settings states as fact that the CLI is not on PATH. Timeout is checked
first, since a timed-out run also leaves the environment unresolved.
Also narrows the regex-literal prev-token set. '!', '+', '-', '>' and '}' are
value terminators as often as operators, so postfix `n-- / 2` and JSX
`<A size={14} /> : <B` were read as patterns and their spans blanked -- 13
live JSX spans, and one swallowed execFile call that left no desync behind.
False negatives only risk a desync, and desync fails closed.
Plus: WSL_UTF8 on the probe spawn (#9010 reached the runner, not the probe),
and the allowlist header I shuffled by sorting comments along with entries.
Credit: Grok (both P1s), Opus (lexer false positives).
* docs(wsl): drop the lane comments the redesign made false
The interactive lane is gone, so 'both lanes' and the fenced-stdout note
described code that no longer exists. Also states plainly that
environmentResolved is always true under loginPath:'none' -- the field cannot
rescue a PATH lookup that was mislabelled, which is how #9725 came back.
Credit: Grok.
* fix(wsl): stop piping user scripts into the shell's stdin
The W3 migration moved hooks from `wsl.exe --exec bash -c <script>` to a
script piped into `bash -s`. Anything the script runs that reads stdin then
drains the rest of the script, bash hits EOF and exits 0, and the caller logs
success -- an orca.yaml hook of `ssh -T git@github.com || true` followed by
`pnpm install` silently never installs.
Scripts now travel in argv by default, which is what the pre-migration code
did and what --exec makes safe. `scriptDelivery: 'stdin'` stays for the one
caller that needs it: the hook-relay installer embeds a base64 JS bundle far
past any command-line limit, and reads no stdin.
A runner test already described this exact EOF hazard -- for the login shell,
not for the guest command it was itself creating.
Credit: code review.
* fix(skills): make the unverifiable check unconditional, and stop double-probing
Round 3.
- provider detection threw only on an EMPTY result, so a degraded partial hit
slipped through: `claude` visible on the default PATH via Windows interop
plus an nvm-only `codex` returns a plausible ['claude'], and the caller then
skips the ~/.codex skill roots for a provider that is installed. The
installer already got this right with an unconditional throw.
- three sites asked for 'preferred' without needing it. The GROK_HOME probe
runs its own `"$login_shell" -lc`, so the runner's probe was a second login
shell eating up to half an 8s budget; the two skill scans are
find/base64/head/printf/stat over $HOME.
- the indirection binder missed `private readonly x = 'wsl.exe'` (the
modifier was captured as the name), backtick literals, and
`spawnProcess(this.x)`. Commit
|
||
|
|
063b804298 |
fix(i18n): match CheckRunJobs succeeded to its sibling count-label register (#16013)
succeeded rendered as casual declarative 성공했다 ('it succeeded') next to
skipped's polite 건너뛰었습니다 and pending's noun-phrase 보류 중, in a summary
that joins all three after a count: '3 성공했다 · 1 건너뛰었습니다'. Machine
translation read succeeded as a finished sentence instead of the noun label
the other two siblings use. Switch to 성공 and pin it in the key-override
file so the catalog regen script can't revert it; #15875 fixed five other
keys in the same family but its hardcoded regression map didn't cover this
one.
|
||
|
|
fb5c9a1fe6 |
fix(ui): clear agent attention icon when Floating Workspace tab activated via keyboard (#15745)
* fix(ui): clear agent attention icon when Floating Workspace tab activated via keyboard When a tab in the Floating Workspace is activated through keyboard shortcuts while the Floating Workspace is not the active worktree, the agent completion notifications were not being acknowledged, leaving the yellow attention icon visible. The issue was that `useAutoAckViewedAgent` only checked the global `activeTabId`, which doesn't change when the Floating Workspace is not the active worktree. Now the hook also watches the Floating Workspace's active tab (`activeTabIdByWorktree[FLOATING_TERMINAL_WORKTREE_ID]`) and acknowledges agents when they become visible in the Floating Workspace, regardless of whether it's the active worktree. Fixes #15700 * fix(ui): gate Floating Workspace auto-ack on panel visibility The floating-workspace scan added in the previous commit had no visibility gate. The panel stays mounted while closed (use-floating-workspace-panel: shouldMountPanel), so its layout still resolves an active leaf and the hook acked a floating agent completion the moment it landed — silently killing the minimized toggle's attention dot (selectFloatingWorkspaceHasUnread), which is the only "unseen floating activity" signal a closed panel has. - Scan the floating tab only while the panel is actually visible (isFloatingWorkspacePanelVisible), so a closed panel keeps its dot. - Move the activeView filter onto the main-worktree target only: the panel is an overlay above every view, so it must ack from the activity/tasks views too. - Carry the owning worktree with each target instead of re-deriving it by tab id, and keep the first entry on a tab-id collision, so a duplicate id can no longer clear the wrong worktree's unread. Drops the `as string[]` cast. - Re-scan on TOGGLE_FLOATING_TERMINAL_EVENT (next frame, after aria-hidden commits) since panel open/closed is React state the store never sees — opening onto an already-active completed tab now acks. - Cover the new resolveAutoAckTabTargets helper, including the closed-panel regression asserted against selectFloatingWorkspaceHasUnread. * fix(ui): re-scan floating workspace auto-ack on every panel-open path The visibility gate read the panel's aria-hidden and only re-scanned on TOGGLE_FLOATING_TERMINAL_EVENT, so the two paths that open the panel without that event — the floatingWorkspace.maximize keybinding and the default floating-button toggle — left an already-active completed tab's attention icon lit. Drive the gate from the committed `enabled && open` state instead: that is what aria-hidden is derived from, it covers every open path, and it drops the requestAnimationFrame that existed only to outrun the un-committed DOM read. Adds a hook-level test (happy-dom) that fails both when the gate is removed and when the open re-scan is removed. * fix(ui): re-read store state per auto-ack target Acking the first target writes to the store and re-enters the scan synchronously, so the pre-write snapshot could re-ack a target the nested pass already handled. Idempotent today; a footgun for the next non-idempotent action. --------- Co-authored-by: Claude <claude@anthropic.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> |
||
|
|
4828c6bed4 | UX: compact native density for context + dropdown menus (#15924) | ||
|
|
8d021c0b5a |
test(automations): assert in-place language switch on mounted picker (#15977)
* fix(automations): localize schedule weekday names and labels
The Weekly Day picker rendered a hardcoded English tuple, and shared
schedule labels built copy as `${day}s at ${time}` from an OS-locale
Intl weekday, so a non-English UI showed Sunday…Saturday (or 星期五s).
Shared now emits deterministic English (the CLI contract) plus a
locale-free AutomationScheduleDescriptor; the renderer formats that
descriptor through translate() with Intl/CLDR weekday names resolved
from getIntlLocale(). Fixes #14404.
* test(automations): assert localized weekday copy in rendered DOM
The existing coverage walked the React element tree, so nothing proved the
Day dropdown and cron status row reach the DOM localized. Mount the picker
under happy-dom with the Radix Select swapped for a native <select> (the
pattern RepositoryWorktreeDefaultsSection.test.tsx already uses, since Radix
portals its content only once opened) and read real option text.
Also key the weekday SelectItems by index rather than by translated copy, so
a runtime language switch reconciles instead of remounting all seven items.
* fix(automations): keep the weekday SelectItem key off the array index
react-doctor(no-array-index-as-key) rejects `key={index}`; the localized
weekday name is already unique per locale, so keep it as the key.
* fix(automations): match the real AutomationDraft shape in the render test
The fixture invented `repoId`/`branchMode`/`enabled` fields; runtime ignored
them but `tsc` did not. Mirror AutomationSchedulePicker.test.ts's fixture.
* fix(automations): localize the custom-cron field chips
The five cron field headers rendered one row above the status row this PR
localizes were still hardcoded English, so a Chinese UI showed
Minute/Hour/Day/Month/Weekday. Same defect shape as the deleted DAY_OPTIONS
array. Chip keys move to stable field ids so a locale that renders two fields
with the same word cannot collide, and the truncated header carries a title so
longer copy (es 'Dia de la semana') stays readable.
* fix(automations): keep weekday option keys stable
* test(automations): assert in-place language switch on mounted picker
Add a test that changes the language while the weekly picker remains mounted,
then checks that the localized labels update while the underlying values
("0"–"6") stay stable. This validates the regression-prevention that stable
index keys (added in #15884) support — without them, a locale change would
unmount and remount options, breaking the persisted dayOfWeek value.
Wrap the picker in a LanguageAwarePicker harness that calls useTranslation(),
mirroring the root-level subscription in main.tsx that drives real
re-renders on language change.
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
|
||
|
|
6c1286b592 |
Add Artifacts and Skills pages to navigation history (#15969)
* Add Artifacts and Skills pages to navigation history - Record Artifacts and Skills visits in back/forward navigation like Automations - Both pages properly rewind history when closed to the previous live entry - Extract rewindHistoryIndexPastView() helper to deduplicate close-page logic across all page types - Add test coverage for Artifacts/Skills navigation, separate entries, and shared link handling * Add Artifacts and Skills pages to navigation history Back/forward buttons now appear when navigating to Artifacts and Skills pages, consistent with Terminal, Tasks, and Automations. |
||
|
|
9ea1d28970 |
Fix Cmd+J Enter for worktree creation (#15970)
* fix(cmd-j): allow Enter to create worktree * test: verify create dialog closes on Escape |
||
|
|
6e18c18e58 |
fix(automations): localize schedule weekday names and labels (#15884)
* fix(automations): localize schedule weekday names and labels
The Weekly Day picker rendered a hardcoded English tuple, and shared
schedule labels built copy as `${day}s at ${time}` from an OS-locale
Intl weekday, so a non-English UI showed Sunday…Saturday (or 星期五s).
Shared now emits deterministic English (the CLI contract) plus a
locale-free AutomationScheduleDescriptor; the renderer formats that
descriptor through translate() with Intl/CLDR weekday names resolved
from getIntlLocale(). Fixes #14404.
* test(automations): assert localized weekday copy in rendered DOM
The existing coverage walked the React element tree, so nothing proved the
Day dropdown and cron status row reach the DOM localized. Mount the picker
under happy-dom with the Radix Select swapped for a native <select> (the
pattern RepositoryWorktreeDefaultsSection.test.tsx already uses, since Radix
portals its content only once opened) and read real option text.
Also key the weekday SelectItems by index rather than by translated copy, so
a runtime language switch reconciles instead of remounting all seven items.
* fix(automations): keep the weekday SelectItem key off the array index
react-doctor(no-array-index-as-key) rejects `key={index}`; the localized
weekday name is already unique per locale, so keep it as the key.
* fix(automations): match the real AutomationDraft shape in the render test
The fixture invented `repoId`/`branchMode`/`enabled` fields; runtime ignored
them but `tsc` did not. Mirror AutomationSchedulePicker.test.ts's fixture.
* fix(automations): localize the custom-cron field chips
The five cron field headers rendered one row above the status row this PR
localizes were still hardcoded English, so a Chinese UI showed
Minute/Hour/Day/Month/Weekday. Same defect shape as the deleted DAY_OPTIONS
array. Chip keys move to stable field ids so a locale that renders two fields
with the same word cannot collide, and the truncated header carries a title so
longer copy (es 'Dia de la semana') stays readable.
* fix(automations): keep weekday option keys stable
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
|
||
|
|
72f896d677 |
feat(automations): navigate table search results with arrows (#15805)
* feat(automations): navigate search results with arrows * Set overview tab for external automations on arrow selection External automations lack a runs tab, so the detail pane must default to overview when navigating via arrow keys to keep the tab selection valid when the automation is later opened. --------- Co-authored-by: m4air <m4air@m4airs-Air.localdomain> |
||
|
|
80b2a02377 |
Move worktree palette search to top of sidebar nav (#15854)
* Move worktree palette search to top of sidebar nav The Cmd+J search button is now displayed as the first item in the sidebar navigation, improving discoverability. Styling is simplified to match the nav item layout with flex-based display and consistent spacing. * Test: add guard clause for worktree palette search button - Add explicit type annotation for the search button querySelector - Guard against missing button with clear error message - Use direct property access now that button existence is verified |
||
|
|
990b23611e | fix(i18n): correct five Korean strings that changed meaning in machine translation (#15875) | ||
|
|
9725654855 |
Revert "Revert "Update Android download links to 0.0.44 (#15898)" (#15899)" (#15905)
This reverts commit
|
||
|
|
a04fe24c80 |
Revert "Update Android download links to 0.0.44 (#15898)" (#15899)
This reverts commit
|
||
|
|
5ce356cc4a | Update Android download links to 0.0.44 (#15898) | ||
|
|
1354ff534f | fix(cmd-j): host-qualify browser and simulator tab candidates (STA-4965) (#15686) |