Commit Graph
4303 Commits
Author SHA1 Message Date
NeilandOrca 32c1651732 fix(terminal): stop scroll drift during resize (#1149)
* fix(terminal): stop terminal scroll from drifting upward during resize

xterm's terminal.resize() natively preserves viewportY across reflows (see
scroll-reflow.test.ts "reference: undisturbed"). The capture/restore dance
in the default safeFit path was fighting that — findLineByContent's 40-char
prefix match collides across duplicate scrollback lines (prompts, bullets,
borders) in long Claude sessions, so the viewport anchored to a slightly
wrong line each frame and the error compounded upward.

- Remove capture/restore from the default safeFit and fitAllPanesInternal
  paths. Just fitAddon.fit(), matching how Superset and VSCode handle this.
- Keep pendingSplitScrollState and pendingDragScrollState branches intact —
  split reparenting and divider drags still need those.
- Sidebar open/close is an instantaneous width change, so dispatch
  SYNC_FIT_PANES_EVENT from a useLayoutEffect and let the terminal fit
  synchronously pre-paint, eliminating the one-frame "old cols, new
  container width" flash users saw when fits were deferred to a
  ResizeObserver rAF.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): remove scroll-restore delay from in-pane divider drag

The divider drag was still using lockDragScroll/unlockDragScroll, which set
pendingDragScrollState on each affected pane so safeFit would
captureScrollState → fit → findLineByContent → scrollToLine on every frame.
findLineByContent scans the whole scrollback for a 40-char prefix match —
expensive on long Claude sessions and the cause of the drag feeling laggy.

Now that the previous commit established xterm's native viewportY
preservation is enough (see scroll-reflow.test.ts "reference: undisturbed"),
the divider doesn't need the snapshot either. Drop the lock calls from
onPointerDown/onPointerUp and the double-click equalize path; the default
safeFit path (plain fitAddon.fit()) handles it.

lockDragScroll / unlockDragScroll remain in the codebase for now — removing
them is a wider interface cleanup through pane-tree-ops and pane-drag-reorder
that can land separately.

Co-authored-by: Orca <help@stably.ai>

* refactor(pane-manager): drop unused drag-scroll lock machinery

The previous two commits showed xterm preserves viewportY natively across
resize, so safeFit's default path is just fit() and the divider no longer
sets pendingDragScrollState. That leaves a chain of dead plumbing:

- pane-drag-scroll.ts (lockDragScroll / unlockDragScroll) — nothing invokes
  these anymore. Delete the file.
- ManagedPaneInternal.pendingDragScrollState — no writers, no readers. Drop
  the field and the "null" initializer in createPaneDOM and test fixtures.
- pendingDragScrollState branches in safeFit and fitAllPanesInternal —
  unreachable. Remove them. fitAllPanesInternal collapses to a simple loop
  of safeFit calls.
- lockDragScroll / unlockDragScroll callback entries in TreeOpsCallbacks,
  DragReorderCallbacks, DividerCallbacks, and PaneManager's callback
  builders — drop.
- pane-lifecycle.ts WebGL context-loss handler: the three-branch
  pendingSplit / pendingDrag / default capture-restore dance collapses to a
  plain fit() + refresh(), relying on xterm's native viewportY preservation
  and scheduleSplitScrollRestore for the split-reparent case.
- restoreScrollState import in pane-lifecycle.ts — now unused.
- Stale "Why: we previously locked..." comment block in pane-divider —
  remove; the new code is self-explanatory and the motivation lives in the
  pane-tree-ops safeFit comment.

pendingSplitScrollState stays: it's still load-bearing in
pane-split-scroll.ts where it gates the early double-rAF restore during
wrapInSplit DOM reparenting.

Net: -123 lines. No behavior change — verified by the full renderer test
suite (1116/1116).

Co-authored-by: Orca <help@stably.ai>

* docs: tighten sidebar-toggle comment in App.tsx

The comment referenced 'restoring its scroll' but after the drag-scroll
lock cleanup the fit no longer restores anything — xterm preserves
viewportY natively. Reword to match actual behavior.

Co-authored-by: Orca <help@stably.ai>

* fix(terminal): preserve scroll when exiting expanded pane mode

Cmd+Enter enter/exit expanded mode (toggleExpandPane) runs refreshPaneSizes,
which still had the same content-hash capture/restore dance that the rest
of the resize paths were cleared of: captureScrollState → fit →
findLineByContent → scrollToLine. On long Claude sessions the 40-char
prefix match collides with duplicate scrollback lines (prompts, bullets,
borders) and jumps to the wrong line, leaving the viewport scrolled up
after exiting expanded mode.

xterm preserves viewportY natively across resize (see scroll-reflow.test.ts
"reference: undisturbed"), so a bare fit() is enough — matching every other
resize path in this codebase.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-04-26 20:51:00 -07:00
Jinwoo HongandOrca f11aacc795 feat(ssh): add port forwarding for remote workspaces (#1145)
Co-authored-by: Orca <help@stably.ai>
2026-04-26 19:21:26 -07:00
f5ad7aa249 feat(settings): import Ghostty config with preview, color overrides, opacity and blur (#1001)
* feat(settings): add Ghostty config import

Add safe one-shot Ghostty import with preview and success summary,
and probe documented Ghostty config paths before applying changes.

Refs #958

* chore(git): ignore atl artifacts

* feat(settings): map font-weight, cursor-blink and focus-follows-mouse from Ghostty

Adds three safe direct-mapping Ghostty keys that have clear equivalents
in GlobalSettings: font-weight, cursor-style-blink, and focus-follows-mouse.

Refs #958

* feat(settings): expand Ghostty import to support colors, opacity and option-as-alt

- Add TerminalColorOverrides type grouping 21 optional xterm ITheme fields
- Add terminalBackgroundOpacity, terminalPanePaddingColor, terminalPaddingBalance
  to GlobalSettings
- Extend parser to collect repeated keys as string[] (needed for palette lines)
- Map background-opacity, background, foreground, cursor-color,
  selection-background/foreground, palette (0-15), window-padding-color,
  window-padding-balance, macos-option-as-alt in mapper
- Merge terminalColorOverrides into xterm ITheme at theme resolution; apply
  opacity as rgba() with allowTransparency enabled
- Accept hex colors with or without leading # (Ghostty omits it)
- Fix preview diff to use deep equality for object values so already-applied
  color overrides no longer reappear on next import

* feat(settings): support background-blur-radius and window-padding-color extend in Ghostty import

- Map background-blur-radius > 0 to windowBackgroundBlur: true; apply
  vibrancy on macOS and backgroundMaterial acrylic on Windows at window
  creation (blur requires restart — no hot-reload IPC exists)
- Accept window-padding-color = extend/background as valid Ghostty values;
  both map to default Orca padding behavior (undefined field) instead of
  landing in unsupportedKeys
- Split mapper.test.ts into domain-scoped describes to stay under 300-line limit

* feat(settings): add Window section to Terminal settings panel

Expose terminalBackgroundOpacity, windowBackgroundBlur, terminalPaddingBalance,
terminalPanePaddingColor, and terminalColorOverrides in the settings UI so
imported Ghostty values can be viewed and changed manually.

- New TerminalWindowSection component (extracted from TerminalPane to stay
  under the 400-line limit)
- Collapsible color overrides sub-section with ColorField for all 21 xterm
  ITheme fields grouped as base, ANSI normal, and ANSI bright
- Reset button clears all color overrides at once
- Window blur toggle shows restart-required note (blur applies at window
  creation, no hot-reload IPC exists)
- Search entries added for all new controls

* feat(settings): expand Ghostty import with scrollback, padding, divider, cursor and word-chars keys

Map 9 additional Ghostty keys to Orca settings:

- split-divider-color → terminalDividerColorDark + terminalDividerColorLight
  (single value applies to both; Ghostty has no dark/light distinction)
- unfocused-split-opacity → terminalInactivePaneOpacity (direct float 0-1)
- scrollback-limit → terminalScrollbackLimit; applied to xterm scrollback option
- window-padding-x / window-padding-y → terminalPaddingX/Y; applied as CSS
  vars --pane-padding-x / --pane-padding-y in terminal.css
- cursor-text → terminalColorOverrides.cursorAccent (xterm ITheme field)
- bold-color → terminalColorOverrides.bold (persisted; xterm ITheme has no
  bold field yet — stored for future xterm upgrade)
- cursor-opacity → terminalCursorOpacity; blended into cursor rgba at theme
  resolution time
- selection-word-chars → terminalWordSeparator; applied to xterm wordSeparator
- mouse-hide-while-typing → terminalMouseHideWhileTyping field added; renderer
  application deferred (needs per-pane disposable + global mousemove listener)

* feat(settings): expose new Ghostty-imported settings in Terminal Settings UI

- Window section: scrollback limit, horizontal/vertical padding, hide mouse
  while typing toggle, cursor text and bold color in Color Overrides
- Cursor section: cursor opacity NumberField
- Advanced section: word separators text input
- Search entries added for all new controls
- terminalDividerColorDark/Light and terminalInactivePaneOpacity skipped —
  already present in Theme and Pane Styling sections respectively

* feat(terminal): implement mouse-hide-while-typing per pane

Register terminal.onData → cursor:none and mousemove → restore, scoped to
the pane container element. Uses the existing IDisposable per-pane pattern
(same as selectionDisposablesRef). Cleans up on pane close and effect teardown.

* refactor(settings): address ghostty import code review findings

- Centralize GhosttyImportPreview type in shared/types (remove duplicate from mapper)
- Fix parser to strip inline comments without breaking hex color values (#1a1a1a)
- Extract HEX_COLOR_RE to shared/color-validation to avoid duplication
- Remove redundant Number.isNaN checks after Number.isFinite (4 sites)
- Replace unsafe catch-all assignment with explicit font-family branch
- Migrate 280-line if-chain in mapGhosttyToOrca to FIELD_PARSERS registry
- Add human-readable setting labels in GhosttyImportModal via setting-labels map
- Add clarifying comment in index.ts re JSON.stringify undefined behavior

* fix(settings): harden ghostty import from judgment-day review

- Surface readFile errors in GhosttyImportPreview.error instead of
  showing misleading 'No config found' on permission denied
- Guard handleApply against double-apply when already applied
- Strip surrounding quotes from parsed config values (font-family)
- Return null from palette handler when all entries fail validation
- Inform user when background-blur-radius radius is not preserved
- Add valuesEqual key-order stability via stableStringify
- Normalize hex colors to #-prefixed format across all color mappers
- Reject blank values before numeric parsing (Number('') === 0 trap)
- Remove selection-word-chars mapping (inverted xterm semantics)
- Guard window-padding-x/y against negative integers
- Reactive mouse-hide-while-typing on existing panes when setting toggles
- Merge terminalColorOverrides on import instead of replacing

* fix(ghostty): drop broken imports, tighten parsing, prompt restart for blur

Review found three high-impact issues in the Ghostty import: scrollback-limit
semantics are inverted/rescaled (Ghostty is bytes with 0=unlimited, xterm is
rows with 0=disabled), and window-padding-color + window-padding-balance set
CSS custom properties (--pane-padding-color, --pane-padding-balance) that
have no consuming rule anywhere in the tree — so users confirming "changes"
to those keys would see nothing happen.

Because none of the three keys have a safe mapping today, drop them from the
import and remove the dead UI controls + GlobalSettings fields + CSS var
plumbing. The mapper now lists them as unsupportedKeys alongside the
existing window-decoration / keybind / custom-shader entries.

Other fixes in the same review:

- allowTransparency now clears when background-opacity returns to 1 (prior
  code only ever set it to true, leaving a stale flag with measurable render
  cost).
- background-blur-radius = 0 no longer emits a misleading "radius value not
  preserved" note (0 cleanly maps to blur=false with no radius to lose).
- Add a 1 MB size cap on the config read so a pathological or symlinked file
  cannot OOM the main process.
- Make handleApply async and surface IPC errors inline in the modal instead
  of flipping straight to "Import complete" on failure.
- Type settings.previewGhosttyImport as Promise<GhosttyImportPreview> in
  preload so shape drift is caught at compile time.
- Make stableStringify recursive so future nested settings round-trip
  cleanly through valuesEqual.
- Restrict window-padding-x/y and background-blur-radius to decimal ints;
  prior code accepted exponent notation (1e10 sails through Number.isInteger)
  and would have landed absurd values in the store.
- Window blur now shows a "Restart required" banner with a Restart now
  button when the setting differs from the mount-time snapshot, mirroring
  the ExperimentalPane daemon pattern. Blur only applies at BrowserWindow
  creation on macOS/Windows.

Co-authored-by: Orca <help@stably.ai>

* feat(settings): move Ghostty import trigger to Terminal section header

Per review feedback: the "Import from Ghostty" row was taking its own slot
in the Terminal settings list alongside real configuration sections. Move
the trigger into the Terminal section's header (upper-right corner) as a
headerAction, next to the Terminal heading — it's a one-shot action, not a
setting.

- SettingsSection gains an optional `headerAction` slot rendered to the
  right of the section title/description.
- The useGhosttyImport hook is lifted from TerminalPane into Settings.tsx
  so the section header button (owned by Settings.tsx) and the modal
  (still rendered inside TerminalPane) share one state instance.
- TerminalPane drops its own "Import" section + the TERMINAL_GHOSTTY_IMPORT
  search entry group is no longer referenced there.
- Button carries the official Ghostty mark as a 16x16 icon so it reads
  clearly as a cross-app import even before users parse the label.

useGhosttyImport now accepts `GlobalSettings | null` so the parent can call
it above the pre-load spinner guard without violating hook ordering; the
apply path no-ops until settings arrive. Related test file updated to
pass the new `ghostty` prop and to assert the trigger is *not* rendered
inside TerminalPane anymore.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-04-26 17:00:46 -07:00
Brennan Benson d0dff1a144 fix: keep right sidebar toggle icon vertically centered when toggling open/close (#1144) 2026-04-26 16:55:03 -07:00
b037ca6864 fix(tabs): repair mixed tab shortcut switching (#1124)
* fix(tabs): repair mixed tab shortcut switching

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(typecheck): unblock tc:web project checks

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>

* fix(tabs): split group/fallback id matching into separate branches

Why: matching both `tabId` and `id` in one findIndex predicate mixed two
identifier domains and risked a pathological collision between a tab's
backing entity id and another tab's unified id. Keep the group-path
(strict tabId match) and the fallback-path (backing-id match) in
separate branches.

Also clarify the comment on the dual `setActiveFile` + `activateTab`
write so future readers know why both calls are needed for split-group
disambiguation.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
2026-04-26 16:39:14 -07:00
JinjingandOrca e2b4029826 feat(new-workspace): default Start-from picker to Pull requests tab (#1142)
Reorders tabs so Pull requests is first and becomes the default
selection (falling back to Branches for remote SSH repos where PRs
are disabled). Also auto-prefills the worktree note with
"PR #<n> — <title>" when a PR is picked, guarded by a ref so
user-typed notes are never clobbered.

Co-authored-by: Orca <help@stably.ai>
2026-04-26 16:34:08 -07:00
Ramzi 8e44541c4c Add task provider icon setting (#1128) 2026-04-26 16:01:16 -07:00
Brennan Benson 1b02fcd505 fix(sidebar): constrain worktree list within add-repo dialog bounds (#1140) 2026-04-26 15:33:17 -07:00
github-actions[bot] ffb6688fd2 release: 1.3.21-rc.1 [rc-slot:2026-04-26-15] v1.3.21-rc.1 2026-04-26 22:07:45 +00:00
Jinwoo Hong 294256843c Improve terminal restore resilience (#1073) 2026-04-26 14:25:11 -07:00
github-actions[bot] a4da565430 release: v1.3.21-rc.0 v1.3.21-rc.0 2026-04-26 21:16:40 +00:00
Jinwoo HongandOrca 451d7cab42 fix(ssh): await root registration before remote worktree creation (#1120)
Co-authored-by: Orca <help@stably.ai>
2026-04-26 14:14:30 -07:00
JinjingandOrca 0431dd51f5 feat: improve SSH file search with configurable limits and rg fallback (#1130)
* feat: improve SSH file search with configurable limits and rg fallback

- fix: address lint issues (string template, ternary, max-lines via extraction)

Co-authored-by: Orca <help@stably.ai>

* fix: typecheck and improve rg listing correctness

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-04-26 13:38:34 -07:00
fe6eecba0c feat(sidebar): improve PR checks and comments (#1134)
* feat(sidebar): improve PR checks and comments

Co-authored-by: Orca <help@stably.ai>

* fix(sidebar): classify bot comments via GitHub user.type instead of login heuristic

Third-party review bots like qodo-ai-reviewer, coderabbitai, and sonarcloud
don't follow the [bot] suffix or "bot"/"automation" substring convention, so
the regex-based detector misclassified them as human. Plumb REST
`user.type === 'Bot'` and GraphQL `author.__typename === 'Bot'` through to the
renderer as an authoritative isBot flag; fall back to the login heuristic only
when the data source can't report it.

Co-authored-by: Orca <help@stably.ai>

* fix(sidebar): allowlist AI review services that sign in as User accounts

qodo-ai-reviewer, coderabbitai, codium-ai and similar third-party review
services register as regular GitHub user accounts, so REST `user.type` is
"User" and their logins contain no "bot"/"automation" tokens. The previous
fix relied on the GitHub-reported type, which fails for these. Add an
explicit substring allowlist of known automation services so they still
land in the Bots tab.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-04-26 13:28:09 -07:00
Jinwoo HongandOrca 7720ed136c feat(tasks): server-side scope filtering, pagination, and draft bug fix (#1135)
Co-authored-by: Orca <help@stably.ai>
2026-04-26 12:30:22 -07:00
github-actions[bot] 738a08b712 release: v1.3.20 v1.3.20 2026-04-26 18:19:03 +00:00
NeilandOrca dd9bf888e9 fix(terminal): make Claude Code OSC 8 hyperlinks clickable (#1132)
Co-authored-by: Orca <help@stably.ai>
2026-04-26 11:18:02 -07:00
NeilandOrca a351b7a1cc fix(menu): auto-hide menu bar on Windows and Linux (#1133)
Keeping the menu bar always visible on Windows/Linux added a dedicated
row of vertical space on every launch, which users didn't want. Restore
the native convention (File Explorer, Firefox, etc.): menu bar is
hidden by default and revealed with Alt. The previous PR's template
restructure (File → Settings/Exit, Help → About/Check for Updates,
no redundant "Orca" entry) still applies when the menu is shown.

Co-authored-by: Orca <help@stably.ai>
2026-04-26 11:16:59 -07:00
github-actions[bot] 49d64294a3 release: 1.3.19-rc.13 [rc-slot:2026-04-26-03] v1.3.19-rc.13 2026-04-26 10:13:11 +00:00
github-actions[bot] eb0c952830 release: v1.3.19-rc.12 v1.3.19-rc.12 2026-04-26 07:49:02 +00:00
Neil bedb0d44c0 fix(persistence): unlink tmp file when write/rename fails (#1121) 2026-04-25 23:54:53 -07:00
github-actions[bot] 3a0149982f release: v1.3.19-rc.11 v1.3.19-rc.11 2026-04-26 06:23:26 +00:00
NeilandOrca ae9ddeab5c feat(cursor): first-class Cursor CLI agent status via hooks.json (#1108)
* feat(cursor): first-class Cursor CLI agent status via ~/.cursor/hooks.json

Give cursor-agent the same hook-driven status pipeline Claude/Codex/Gemini/
OpenCode already use so working/done/permission transitions show the proper
sidebar spinner instead of falling back to title heuristics — cursor-agent
only sets its OSC title to the literal string "Cursor Agent" during a turn,
so title-based detection cannot see working→done transitions on its own.

- New CursorHookService installs a managed shell script and registers it
  under ~/.cursor/hooks.json for beforeSubmitPrompt, preToolUse, postToolUse,
  postToolUseFailure, beforeShellExecution, beforeMCPExecution, stop, and
  afterAgentResponse (the subset that marks turn boundaries and surfaces
  in-flight tool context).
- AgentHookServer gains a /hook/cursor route and a normalizeCursorEvent
  mapping the camelCase cursor events to working/done/waiting, with tool
  previews for preToolUse/shell/MCP and lastAssistantMessage from
  afterAgentResponse. stop with status != "completed" surfaces as
  interrupted (matches Claude's is_interrupt behavior).
- cursorHookService joins the startup install loop alongside Claude/Codex/
  Gemini, and the IPC status handler is exposed via preload.
- 'cursor' added to WellKnownAgentType and to the renderer's
  WELL_KNOWN_LABELS so the dashboard prints "Cursor" rather than the raw
  'cursor' id.

Verified end-to-end against a real cursor-agent 2026.04.17-787b533 binary:
a mock hook receiver pointed at by ~/.cursor/hooks.json observes
beforeSubmitPrompt → stop for a live turn.

Co-authored-by: Orca <help@stably.ai>

* feat(cursor): wire hook events into sidebar spinner + unread pipeline

The initial hook wiring landed behind AGENT_DASHBOARD_ENABLED, which is
still false. That meant cursor-agent panes lit up no spinner and no
unread indicator — cursor's native OSC title stays literally "Cursor
Agent" across a turn, so title-based detection cannot transition.

Plumb cursor's hook stream into the existing, shipped title-tracker
pipeline (the one Claude/Codex/Pi drive working/idle/unread off) by
synthesizing OSC title sequences in the main-process hook listener:

  - `working`  → `\x1b]0;⠋ Cursor Agent\x07`   (braille prefix → working)
  - `waiting`  → `\x1b]0;Cursor - action required\x07\x07`
  - `done`     → `\x1b]0;Cursor ready\x07\x07`

The two trailing BELs on done/waiting are load-bearing: the unread badge
keys off BEL (0x07 outside any OSC), and cursor-agent emits none on its
own. The first BEL is consumed as the OSC terminator; the second fires
the bell detector.

Also treats the bare native "Cursor Agent" title as a no-op in
`detectAgentStatusFromTitle` so cursor's own per-turn re-emissions cannot
stomp our synthesized working state back to idle. `isClaudeAgent`
excludes cursor-bearing braille titles so the Claude prompt-cache timer
doesn't fire for cursor panes.

Scope: the hook server + cursor install run unconditionally now, but
Claude/Codex/Gemini installs stay gated behind AGENT_DASHBOARD_ENABLED,
so only cursor events flow through the pipeline. No dashboard surface
is turned on.

Verified end-to-end in Electron (dev build): launched cursor-agent via
the Cursor menu item, submitted three prompts (including a tool-use
turn reading package.json). Observed tab title flip to "Cursor ready"
on done, and the tab + worktree both transitioned to unread
(unreadTerminalTabs[tabId]=true, worktree.isUnread=true). Parallel
Claude Code pane untouched.

Co-authored-by: Orca <help@stably.ai>

* fix(cursor): animate spinner frames + filter bare native title so the spinner doesn't go solid mid-turn

cursor-agent re-emits its bare "Cursor Agent" OSC title on every internal
redraw, which was stomping the single synthesized "⠋ Cursor Agent" frame
in runtimePaneTitlesByTabId within milliseconds and flipping the sidebar
dot back to solid. Two-part fix:

- Main: drive an 80ms Pi-style braille spinner from the cursor hook
  channel, keyed by paneKey, torn down on pty exit via a new
  registerPaneKeyTeardownListener hook in ipc/pty.
- Renderer: drop bare "Cursor Agent" titles in pty-transport so cursor's
  native re-emissions cannot overwrite the synthesized working/idle
  titles.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
2026-04-25 23:14:31 -07:00
Brennan Benson 2154f032c2 test(tabs): guard first tab visibility when sidebar is collapsed (#1117) 2026-04-25 23:01:35 -07:00
github-actions[bot] a37fb32efb release: v1.3.19-rc.10 v1.3.19-rc.10 2026-04-26 05:30:10 +00:00
Jinjing e6ea8b466f fix: focus address bar for new browser tabs (#1119) 2026-04-25 22:28:42 -07:00
NeilandOrca d4399b4c92 feat(terminal): add programming-ligatures support (#1118)
Co-authored-by: Orca <help@stably.ai>
2026-04-25 22:26:33 -07:00
NeilandOrca 6e2c6e3585 feat: reduce drag grab area height from 8px to 6px (#1116)
Co-authored-by: Orca <help@stably.ai>
2026-04-25 21:56:08 -07:00
JinjingandOrca 7c8f990128 fix(tabs): always consume tab-switch chords to prevent xterm escape leaks (#1114)
Ctrl+PageUp/PageDown and Cmd+Shift+]/[ now always preventDefault and stop
propagation, even when the switch is a no-op (e.g. single terminal). xterm
otherwise translates these into escape sequences that leak into the shell
and flip the tab's bell indicator. Also skip redundant setActiveTab writes
in handleSwitchTerminalTab when the target is already active.

Co-authored-by: Orca <help@stably.ai>
2026-04-25 21:54:06 -07:00
NeilandOrca 6d3d366d45 feat(menu): show File/Edit/View/Window menu bar on Windows & Linux (#1111)
Co-authored-by: Orca <help@stably.ai>
2026-04-25 21:53:32 -07:00
NeilandOrca f8a0e07513 fix(terminal): submit startup commands on Windows without requiring manual Enter (#1115)
Co-authored-by: Orca <help@stably.ai>
2026-04-25 21:52:49 -07:00
NeilandOrca e61ffbcbe3 fix(tabs): prevent Cmd+B collapse from occluding first tab (#1112)
Co-authored-by: Orca <help@stably.ai>
2026-04-25 21:51:18 -07:00
NeilandOrca 6d585e821e ci: cache electron + electron-builder tool downloads on release (#1110)
electron-builder fetches the Electron binary and its platform tools
(notarytool, winCodeSign, nsis, squirrel, AppImage) on every run.
Cache those per-platform so repeat releases skip the re-download.
Saves ~30-90s per job, including the macOS long pole.

Key includes pnpm-lock.yaml so a bump to electron/electron-builder
invalidates stale cached binaries; restore-keys falls back to the
latest per-platform cache in between lockfile changes.

Co-authored-by: Orca <help@stably.ai>
2026-04-25 21:30:52 -07:00
Jinwoo Hong 7b25a02d1c feat(tasks): improve editability affordances (#1107) 2026-04-25 20:42:31 -07:00
Jinwoo Hong 6729825bc0 feat(ssh): persist and auto-restore SSH sessions across app restarts (#1026) 2026-04-25 20:15:02 -07:00
Yax PatelandJinwoo-H 2551758c88 feat/markdown preview (#849)
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
2026-04-25 19:27:47 -07:00
github-actions[bot] b65a3d7cb8 release: v1.3.19-rc.8 v1.3.19-rc.8 2026-04-26 02:19:36 +00:00
Brennan Benson a5e5ce2900 feat(tabs): slimmer tabs with window-drag strip on top (#1059) 2026-04-25 19:06:39 -07:00
Brennan Benson 7c75e9a853 feat(terminal): show bell indicator until user interacts (#1068) 2026-04-25 19:05:53 -07:00
Matt Van HornandMatt Van Horn ff70ac0814 fix(diff): collapse redundant line-number gutters in inline diff view (#1091)
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-04-25 19:04:03 -07:00
NeilandOrca eeffbf0a73 ci: cache pnpm store on release + centralize node version in .nvmrc (#1106)
Co-authored-by: Orca <help@stably.ai>
2026-04-25 19:03:05 -07:00
Neil b9646acbe4 fix(pi): surface Pi working spinner on worktree card (#1083 follow-up) (#1105)
Pi's titlebar extension emits OSC 0 titles every 80ms during agent work
(`⠋ π - cwd`, `⠙ π - cwd`, …) and a trailing idle title on `agent_end`
(`π - cwd`). Node-pty on the main side batches output every 8ms, so the
renderer frequently receives multiple title updates in a single `pty:data`
chunk.

The pty-transport's data handler used `extractLastOscTitle`, which returns
only the final OSC title in a chunk. For fast agents (e.g. Pi with a small
local model) the whole working→idle cycle often lands in one IPC payload —
the intermediate working frames were silently dropped, and the worktree
card's `detectAgentStatusFromTitle` only ever saw the idle title.

Empirically on macOS: polling `runtimePaneTitlesByTabId` 20×/0.25s during
a multi-second Pi working window showed the stored title stuck at "Pi"
throughout, while a raw `ipcRenderer.on('pty:data')` listener captured
the working frames flowing through IPC correctly. After the fix, the same
polling captures "⠋ Pi" alongside "Pi".

- Add `extractAllOscTitles` in the shared agent-detection module and feed
  every OSC title in the chunk through `applyObservedTerminalTitle` in
  order, so the working→idle transition reaches the store and the agent
  tracker.
- Keep `extractLastOscTitle` exported for the main-process stats tracker
  and orca-runtime, which only care about the most recent title.
- Pin the regression with three focused tests: per-chunk spinner frames,
  dispatcher-routed end-to-end, and the exact coalesced-chunk scenario
  that used to swallow the working state.
2026-04-25 19:01:43 -07:00
555ab95d5a feat(shortcuts): add Ctrl+PageDown/Up to cycle terminal tabs only (#1094)
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-04-25 18:45:21 -07:00
NeilandOrca c871f46ed3 fix(updater): let RC users upgrade to newer RC *and* stable releases (#1104)
PR #1053 fixed RC→RC by switching prerelease users to electron-updater's
native github provider with allowPrerelease=true. That silently broke
RC→stable: GitHubProvider.getLatestVersion() filters the atom feed by
channel, so when the running build's channel is "rc" any stable release
(channel=null) gets skipped — trapping the user on the RC channel.

Fix: keep the generic provider and resolve the newest tag ourselves by
parsing GitHub's /releases.atom (any channel, semver-newest strictly >
current version). For prerelease users we re-pin the feed to
/releases/download/<tag>/ before each check, which handles BOTH the
RC→newer-RC case from #1053 and the RC→stable case it broke. Stable
users keep the default /releases/latest/download/ feed untouched, and
the Shift-click RC opt-in path (enableIncludePrerelease) is unchanged.

Co-authored-by: Orca <help@stably.ai>
2026-04-25 17:49:35 -07:00
NeilandOrca 3f8ebb258d ci: remove pr-size-labeler backfill workflow (#1103)
The action refuses to run unless GITHUB_EVENT_NAME=="pull_request",
so workflow_dispatch no-ops. Backfill was completed out-of-band via
a local script calling the REST labels endpoint; this one-off
workflow is no longer useful and is being deleted.

Co-authored-by: Orca <help@stably.ai>
2026-04-25 17:40:26 -07:00
NeilandOrca 64c77d735a ci: add one-off workflow to backfill size labels on open PRs (#1102)
A manually-dispatched workflow that fans out across all open PRs and
runs the size labeler against each. This is a one-time verification
of the labeler against the existing PR set; the workflow will be
removed in a follow-up after it runs.

Co-authored-by: Orca <help@stably.ai>
2026-04-25 17:30:44 -07:00
NeilandOrca b96dc93d72 ci: auto-label PRs by size via cbrgm/pr-size-labeler-action (#1101)
Adds a GitHub Actions workflow and config that labels every PR with
size/xs..size/xl based on files changed and lines added, so reviewers
can see at a glance how big a PR is. Lock files and generated/vendored
paths are excluded, and deletions aren't counted so refactors that
remove code aren't over-penalized.

Co-authored-by: Orca <help@stably.ai>
2026-04-25 17:20:42 -07:00
JinjingandOrca 39364f975f fix(cli): unpack zod so packaged CLI can resolve it at runtime (#1098)
The CLI is compiled by tsc (not bundled) and launched via
ELECTRON_RUN_AS_NODE, which bypasses Electron's asar integration. After
#1090 introduced zod for envelope validation, the packaged CLI crashed
with MODULE_NOT_FOUND because zod was inside app.asar. Add it to
asarUnpack so require() can resolve it from app.asar.unpacked/node_modules.

Follow-up tracked in #1097 to add CI smoke-testing of the packaged CLI.

Co-authored-by: Orca <help@stably.ai>
2026-04-25 16:55:43 -07:00
Jinjing 50ebd11003 feat(fs): raise preview size cap for PDFs and images (#1096)
Local IPC bumps to 50MB; SSH relay bumps to 10MB (bounded by the 16MB JSON-RPC frame cap). Text and search paths keep the 5MB cap.
2026-04-25 16:45:01 -07:00
3bf2b31c96 feat(language-detect): add Vue Monarch grammar for .vue syntax highlighting (#1093)
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
2026-04-25 16:28:06 -07:00