Orca's terminal already encodes Shift+Enter as the kitty CSI-u sequence
`\x1b[13;2u`, but without `vtExtensions.kittyKeyboard` xterm.js never
answers the `CSI ? u` probe. CLIs that gate enhanced input on that
handshake (Claude Code, Codex, etc.) therefore drop the extended bytes
and treat Shift+Enter as a plain Enter — most visibly when running
inside tmux, which strips extended-key encodings by default.
- Enable `vtExtensions.kittyKeyboard` in the default terminal options
(matches VS Code's xtermTerminal).
- Lock in the flag with a regression test in pane-lifecycle.test.ts.
- Add docs/terminal-extended-keys.md explaining the Orca side and the
tmux-side `set -s extended-keys on` + `terminal-features xterm*:extkeys`
users need for nested Shift+Enter to reach a CLI.
Verified end-to-end in Electron: `cat -v` + Shift+Enter now prints
`^[[13;2u`, and `printf '\e[?u'` elicits the expected `CSI ? 0 u` reply
from xterm.js.
Co-authored-by: Orca <help@stably.ai>
Extracts the double-rAF xterm focus helper into a shared module and calls
it from all four new-terminal entry points (Cmd+T, "+" menu, IPC-triggered,
and per-group newTerminalTab/newTerminalWithShell). Previously only the
"+" menu path focused the new tab, so keyboard and IPC-created terminals
would drop the first keystroke.
Co-authored-by: Orca <help@stably.ai>
Previously the "Open remote project" dialog showed disconnected SSH targets
as disabled rows with a "Not connected" label, forcing users out to Settings
to bring them up. Now each disconnected row has an inline Connect button,
and the row state updates live via the ssh:state-changed listener so the
user can proceed without reopening the dialog.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): restore Polish / US-Extended Option+letter composition (#1205)
The macOptionAsAlt layout probe only inspects `navigator.keyboard.getLayoutMap()`,
which surfaces the base (unshifted) layer. Polish Pro, US Extended, ABC
Extended, and the CJK Roman IMEs all ship a US-QWERTY base layer and put
their dead-key composition on the Option layer — invisible to the
fingerprint. The probe therefore classified them as `'us'`, flipped
`macOptionIsMeta=true`, and xterm.js translated every Option+letter into
an Esc+letter readline chord, silently swallowing ą, ć, ę, ł, etc.
Adds a main-process IPC that reads macOS's
`AppleCurrentKeyboardLayoutInputSourceID` (the same signal Ghostty uses
via TISCopyCurrentKeyboardInputSource) and an explicit denylist of input
source IDs whose Option layer composes. When the active ID is on the
denylist, the probe forces `'non-us'` so `effectiveMacOptionAsAlt`
resolves to `'false'` and Option+letter compositions reach the shell.
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): widen Option+letter composition fix to ABC + all non-US layouts
My earlier attempt at #1205 missed the broader bug: Option+A wasn't working
on the plain "ABC" layout either (Option+A → å got swallowed). Two
corrections:
1. systemPreferences.getUserDefault only reads NSGlobalDomain / the app's
own domain. AppleCurrentKeyboardLayoutInputSourceID lives in
com.apple.HIToolbox, so the previous IPC returned null on every
machine. Switched to execFileSync('/usr/bin/defaults') with a 500ms
timeout — the same mechanism Apple documents — so the real ID reaches
the renderer. Verified live: the IPC now returns
`com.apple.keylayout.ABC` instead of `null`.
2. The denylist approach was too narrow. macOS ships many layouts that
share a US-identical base layer but compose via Option (ABC, Polish
Pro, US Extended, ABC Extended, every CJK Roman IME). Flipped to an
allowlist matching Ghostty: only com.apple.keylayout.US and
com.apple.keylayout.USInternational-PC get Option-as-Meta; everything
else defaults to compose. When the IPC returns a concrete ID, the
classifier is authoritative and the layout fingerprint is skipped;
the fingerprint only runs as a fallback on non-Darwin / IPC failure.
Verified live in dev: on ABC, Option+A now produces å in the terminal.
Co-authored-by: Orca <help@stably.ai>
* fix(ipc): read keyboard input source ID asynchronously
Avoids blocking the main-process event loop on every focus-in probe
(up to 500ms). Same behavior, same timeout — just non-blocking.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* Render GitHub markdown safely
Co-authored-by: Orca <help@stably.ai>
* Support PR line comment ranges in diffs
Co-authored-by: Orca <help@stably.ai>
* Add GitHub item dialog review workflow
Co-authored-by: Orca <help@stably.ai>
* Make attribution tests environment-safe
Co-authored-by: Orca <help@stably.ai>
* Wire GitHubItemDialog for row clicks and bound file cache
Row clicks in the Tasks GitHub list now open the new GitHubItemDialog
instead of the legacy GitHubItemDrawer, making the PR review workflow
reachable from the main entry point. The unused drawer and its state
are removed.
Also bounds prFileContentCache at 64 entries with LRU eviction so the
module-level map can no longer grow without limit across many PR
openings in a session.
Co-authored-by: Orca <help@stably.ai>
* Convert GitHubItemDialog to a right-side drawer
Replaces the centered Dialog modal with a Sheet drawer (side=right,
max-w 960/1100/1280 across breakpoints) so the review surface slides
in from the side instead of popping over the task list.
Co-authored-by: Orca <help@stably.ai>
* Address review feedback on GitHub work-item details
- Restore full pagination in listAssignableUsers (REST /assignees --paginate)
so repos with >100 assignees no longer silently drop users from the picker.
- Drop redundant REST /users/<login> fan-out in getMentionParticipants; the
aliased GraphQL query already returns login/name/avatarUrl.
- Parallelize mention-participant lookup with checks/participants fetch.
- Avoid mutating caller-provided objects in mergeGitHubUsers.
- Type addIssueComment / addPRReviewComment(Reply) preload wrappers as
Promise<GitHubCommentResult> instead of Promise<unknown>.
- Use typeof startLine === 'number' to harden the review-comment guard.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* feat(nav): back/forward traverses Tasks page visits
Widens worktree nav history entries to string | 'tasks' so the titlebar
back/forward buttons and Cmd/Ctrl+Alt+Arrow shortcut work from the Tasks
page. openTaskPage records a 'tasks' entry; closeTaskPage rewinds the
index when parked on one; goBack/goForward dispatches Tasks entries via
a separate view activator to bypass openTaskPage side effects.
Co-authored-by: Orca <help@stably.ai>
* test(ui): include worktree-nav-history slice in test store
openTaskPage calls get().recordViewVisit('tasks'), which lives in
the worktree-nav-history slice. The ui.test.ts store was composing
only the UI slice, so the two settings-navigation tests threw
TypeError: get(...).recordViewVisit is not a function.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Widens worktree nav history entries to string | 'tasks' so the titlebar
back/forward buttons and Cmd/Ctrl+Alt+Arrow shortcut work from the Tasks
page. openTaskPage records a 'tasks' entry; closeTaskPage rewinds the
index when parked on one; goBack/goForward dispatches Tasks entries via
a separate view activator to bypass openTaskPage side effects.
Co-authored-by: Orca <help@stably.ai>
The sandboxed TS worker parses concatenated original+modified text in
the diff viewer, producing fake parse errors like "',' expected (1005)".
Disable syntax validation alongside the already-disabled semantic and
suggestion diagnostics; tokenization still provides colorization.
Co-authored-by: Orca <help@stably.ai>
Empty-query Cmd+J now uses pure recency (lastActivityAt) so it's a predictable jump-back surface. Typing still uses smart-sort for ranking. Adds a hint row when worktrees are capped and browser tabs are present.
Co-authored-by: Orca <help@stably.ai>
Skip the local shell.pathExists probe when a connectionId is present and
forward connectionId through to fs.createFile, so 'New Markdown File'
works on SSH worktrees.
Co-authored-by: Orca <help@stably.ai>
SSH workspaces previously rendered as Globe in some places and Wifi in
others. Globe implies web/internet and Wifi implies wireless network
strength — neither reads as "remote machine". Use Server/ServerOff
across every SSH context for a consistent mental model. Globe remains
in true browser/web surfaces (browser pane, address bar, tabs, clone
from URL, Browser settings pane).
Co-authored-by: Orca <help@stably.ai>