Commit Graph
3741 Commits
Author SHA1 Message Date
JinjingandOrca abd0ab9f49 fix(monaco): disable semantic validation in diff viewer (#1175)
- fix(monaco): disable semantic validation in diff viewer

Monaco's sandboxed TS worker cannot resolve cross-file imports, cascading
into a long tail of false semantic diagnostics beyond the previously-ignored
codes. Replace the growing ignore list with noSemanticValidation; keep
syntax validation for genuine parse errors.

Co-authored-by: Orca <help@stably.ai>
2026-04-27 09:58:41 -07:00
JinjingandOrca 2c2d970c14 fix(cmd-j-palette): increase selected row contrast (#1174)
- fix(cmd-j-palette): increase selected row contrast

Co-authored-by: Orca <help@stably.ai>
2026-04-27 09:57:08 -07:00
NeilandOrca 5fe4fd4853 fix(pty): honor Windows shell selection on daemon path + shell-specific icons (#1164)
* fix(pty): honor Windows shell selection on the daemon path + shell-specific icons

The "+" menu picker and the Settings → Default Shell preference both set a
shellOverride, but the daemon-backed PTY path never forwarded it. Every
Windows terminal ended up as PowerShell (or cmd.exe via COMSPEC) no matter
what the user picked.

Fix:
- Thread shellOverride through daemon-pty-adapter → createOrAttach RPC →
  terminal-host → pty-subprocess, and actually resolve the correct launch
  args (chcp for CMD, $PROFILE dot-sourcing for PowerShell, /mnt/<drive>
  cwd translation for WSL) on the daemon path.
- Extract the Windows shell-args decision into a shared helper
  (resolveWindowsShellLaunchArgs) so LocalPtyProvider and the daemon
  spawner cannot drift again.
- In ipc/pty.ts, fall back to the persisted terminalWindowsShell setting
  when no per-tab override is sent, so the daemon path honors the user's
  Default Shell preference the same way LocalPtyProvider already did.

UI polish on the "+" dropdown and tab strip:
- Drop the "Default" tag next to the top entry (takes too much space).
- Rename "Command Prompt" → "CMD Prompt" to fit next to the Ctrl+T hint.
- Replace the generic terminal glyph with brand-style icons per shell
  (ShellIcon). Both the "+" menu and the per-tab strip use the same
  icon set so a WSL tab is visually distinct from a PowerShell tab.

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

* fix(daemon): add oxlint-disable max-lines to daemon-server

CI counts 312 non-blank/non-comment lines for daemon-server.ts after the
shellOverride plumbing was added, exceeding the 300-line .ts override.
The file is a single RPC route table; splitting it would leak the host
reference across modules for no readability win, so add a scoped disable
with the rationale.

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

* chore(lint): disable max-lines on pre-existing 312-line tabs-hydration test

Unrelated to the shell-selection fix, but surfaced on PR CI: the file is
right at oxlint's 300-line .ts ceiling; each case in the table is a
minimal fixture + assertion, so splitting it across files would scatter
closely related regression coverage for a single reducer.

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

* chore(lint): disable max-lines on shared text-search module

The shared text-search module exceeds oxlint's 300-line .ts ceiling. It
is the single source of truth for rg arg construction, rg --json parsing,
git-grep submatch parsing, and relative-path normalization shared between
the local main process and the SSH relay. Re-splitting it would
re-introduce the maxBuffer divergence the design doc explicitly calls out.

Also reverts two speculative oxlint-disable directives on daemon-server
and tabs-hydration.test — those files were not the offender; CI's error
message elides the file path but running the lint locally against the
PR-merge commit pinpointed text-search.ts.

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

---------

Co-authored-by: Orca <help@stably.ai>
2026-04-27 00:47:15 -07:00
JinjingandOrca 099c4089a7 refactor(search): share rg/git-grep logic between main and relay (#1157)
Extracts rg and git-grep search logic into src/shared/text-search.ts so
the local main and SSH relay paths stop reinventing arg construction,
JSON parsing, submatch regex, and accumulator/truncation semantics.

Fixes silent truncation in the relay: searchWithRg used execFile with
a 50MB maxBuffer cap that rg --json easily exceeds on large repos,
dropping matches with no error surfaced to the user. The relay now
streams via spawn, matching the local path.

See docs/design/share-text-search.md for full rationale.

Co-authored-by: Orca <help@stably.ai>
2026-04-27 00:06:00 -07:00
JinjingandOrca afeac870b2 fix(search): keep include/exclude visible and prevent buried matches (#1161)
- Remove queryDetailsExpanded toggle; always show scope filters in the
  Search tab since it is a secondary destination used specifically for
  scoped search.
- Left-truncate pre-match text in MatchResultRow so the highlight stays
  visible at narrow sidebar widths instead of being pushed off the right
  edge. Mirrors VS Code's lcut behavior.

Co-authored-by: Orca <help@stably.ai>
2026-04-27 00:01:34 -07:00
NeilandOrca 3a1d506e93 fix(tab-bar): inline Windows shell choices in the "+" menu (#1159)
Replace the flaky Radix submenu on the "+" New-tab dropdown with a flat
list of all available shells (PowerShell, Command Prompt, WSL when
present). The configured default shell is pinned to the top, labeled
"Default", and carries the Ctrl+T shortcut hint so users can see at a
glance which shell Ctrl+T will open.

Co-authored-by: Orca <help@stably.ai>
2026-04-26 23:23:42 -07:00
NeilandNeil Parker 98ba693481 feat(terminal): WSL shell support for Windows users (#1154)
Co-authored-by: Neil Parker <nwparker@anthropic.com>
2026-04-26 22:43:54 -07:00
3653b55f38 feat(language-detect): add Svelte Monarch grammar for .svelte syntax highlighting (#1150)
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-26 22:31:17 -07:00
NeilandOrca 193c6ecab3 fix(linear): defer keychain decrypt, cache viewer, add Test connection (#1151)
Co-authored-by: Orca <help@stably.ai>
2026-04-26 21:45:38 -07:00
Brennan Benson 8b87acd72d feat(memory): add Memory & CPU status-bar popover (#1146) 2026-04-26 21:42:06 -07:00
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
Jinwoo Hong 294256843c Improve terminal restore resilience (#1073) 2026-04-26 14:25:11 -07: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
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
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 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
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
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
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
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
Jinjing 5f1161b1ab feat: add basic syntax highlighting for .astro files (#1084)
Maps .astro to Monaco's html language id, matching the existing
treatment of .vue and .svelte. This gives Astro files HTML-level
coloring instead of plain text. Proper SFC-aware highlighting
(frontmatter TS, template expressions) would require a Monarch
grammar — tracked separately.
2026-04-25 13:23:31 -07:00
Jinjing b4d688a580 fix(linear): clarify personal API key requirement and disconnect icon (#1087)
Users were tempted to click "New passkey" on the Linear Security page
instead of "New API key". Bolded key terms, spelled out the exact button
to click, and loosened the dialog header spacing.

Also swapped the disconnect button's chain-Link icon for Unlink, since
the linked-chain icon read as "connect".
2026-04-25 13:17:10 -07:00
Jinwoo Hong 3fc0a9680e fix: prevent pinned worktrees from expanding collapsed groups (#1081) 2026-04-25 00:29:34 -07:00
Neil 2796e5f21b Improve terminal attribution shims (#1079) 2026-04-24 22:50:30 -07:00
Neil 91f1e72cd5 fix(editor): stop infinitely flashing find-widget button tooltips (#1077) 2026-04-24 22:14:30 -07:00
Jinjing aa10811780 fix: address review findings (#1078)
- fix: address review findings
- wip
- wip
2026-04-24 21:58:21 -07:00
Neil a2928e62be refactor(terminal): remove FORCE_HYPERLINK feature and setting (#1075) 2026-04-24 21:06:21 -07:00
Jinwoo Hong cc47cad95f feat(tasks): add Linear team selector (#1074) 2026-04-24 21:03:30 -07:00
Neil f1a1f18cf8 Attribution (#1049) 2026-04-24 20:55:50 -07:00
Jinwoo Hong bec6a5c508 feat(tasks): persist last-used task source (GitHub/Linear) (#1071) 2026-04-24 20:29:41 -07:00
Neil 32d60308d7 fix(agents): detect Auggie using real auggie binary name (#1072) 2026-04-24 20:25:47 -07:00
Brennan Benson 1f354cb4c7 feat(sidebar): agent-status plumbing behind AGENT_DASHBOARD_ENABLED (dark launch) (#1070) 2026-04-24 18:53:00 -07:00