The timer existed to clean up sockets when the user navigated away from
a host for 30+ seconds. In practice it produced false 'Disconnected'
flashes when the user navigated home → host → home faster than React
could re-acquire on the home side: refcount drops to 0 between the
host-detail unmount and the home re-render's useEffect, the timer
fires 30s later, the WebSocket gets torn down, and the home card flips
to 'Disconnected' with no user action.
Connections still close on:
- Explicit user Disconnect (closeEntry)
- Host removal (closeEntry)
- App backgrounding (OS-level socket suspension)
- Provider unmount (app shutdown)
- Real WebSocket failure (handled by rpc-client reconnect path)
A foregrounded app with an idle authenticated WebSocket costs near-zero
battery / data; the 'don't leak sockets' problem the timer was trying
to solve is already handled by the OS, the desktop reaper, and the
cleanup paths above.
Co-authored-by: Orca <help@stably.ai>
The 'close all clients on unmount' effect listed [closeEntry] as its
dependency. closeEntry is a useCallback whose identity is stable across
renders within the same module evaluation — but hot reload re-evaluates
the module, which produces a new useCallback reference, which triggers
the effect's cleanup, which closes every open WebSocket.
This is the 'after a code change triggers Fast Refresh, hosts won't
connect' symptom: screens were holding now-closed client instances
(intentionallyClosed=true short-circuits everything) and only recovered
when their own effects happened to re-run with the fresh module.
Empty deps means the cleanup only runs on real provider unmount, which
is the actual intent of the comment.
Co-authored-by: Orca <help@stably.ai>
Worst-case auto-reconnect window was 16 seconds. Combined with
Android's suspended-timer behaviour during background → foreground
transitions, that's enough to feel like the UI is permanently stuck —
which is why tapping the manual Reconnect button felt like it was
'magic'. It was just bypassing a long pending setTimeout.
New schedule: 500ms, 1s, 2s, 4s, then plateau at 4s. Worst case is
4 seconds, which keeps auto-recovery responsive without hammering a
genuinely-down server.
Co-authored-by: Orca <help@stably.ai>
Apple HIG and App Store reviewer expectations both prefer Privacy and
Support to live one tap from the Settings screen, not nested behind
About. Added a 'Help' section to Settings with Privacy Policy and
Support rows. About is now lean (brand + version + social).
Co-authored-by: Orca <help@stably.ai>
Per App Store guideline 4.5.4 + Apple HIG, the iOS notification
permission prompt should fire on user-initiated action, not opportunistically.
Previously the toggle defaulted to true and the prompt fired the moment
the desktop sent its first notification — reads as unsolicited.
Now the toggle defaults to false; the OS prompt only fires when the
user explicitly enables notifications in Settings → Notifications.
Also fixes a brief 'on' flash on the Notifications screen before the
saved preference loads.
Co-authored-by: Orca <help@stably.ai>
- supportsTablet: true → false. We're not testing on iPad; opting out
avoids the iPad screenshot requirement on the App Store listing.
- About screen now exposes Privacy Policy and Support entry points so
users can find them without leaving the app.
Co-authored-by: Orca <help@stably.ai>
When a user on an RC build doesn't get offered a newer stable release,
we currently have zero on-machine visibility into which step failed:
the atom-feed resolver, electron-updater's manifest fetch, or some
race in between. The static analysis path looks correct, but a real
user is hitting the bug — so add the missing logs before adding any
speculative behavior change.
- Wire autoUpdater.logger to console so electron-updater's per-step
progress (channel file URL, fetched version, dedup decisions) lands
in main-process stdout (Console.app on macOS, --enable-logging
elsewhere). Previously these were silently dropped.
- Log the resolved tag and pinned URL from pinPrereleaseFeed in one
line, so we can tell at a glance whether the atom-feed resolver
picked the expected newer release or fell back.
No behavior change. Tests + typecheck pass.
Co-authored-by: Orca <help@stably.ai>
When the new-workspace draft flow targets pi (e.g. user picks a GH issue
in the smart input + selects pi as the agent), the URL was supposed to
land in pi's input box as an editable draft. It didn't, because pi has
no `--prefill` flag and our bracketed-paste-after-ready path waits for
1.5s of stream silence after `\x1b[?2004h` — pi's startup banner
(Skills/Prompts/Extensions/conflicts) prints continuously for 5+ seconds,
which keeps the quiet-timer resetting until the 8s budget expires and
the paste is dropped. Claude works because of `--prefill`; codex works
because its startup is short enough to hit the quiet window.
Fix mirrors Claude's `--prefill` semantics for pi:
- Orca's pi overlay now installs a tiny `orca-prefill.ts` extension
alongside the existing titlebar spinner. On `session_start` it reads
`ORCA_PI_PREFILL` and calls `pi.ui.setEditorText(text)`, then deletes
the env var so subsequent /new sessions don't re-prefill.
- `TuiAgentConfig` gains `draftPromptEnvVar`; pi sets it to
`ORCA_PI_PREFILL`.
- `buildAgentDraftLaunchPlan` returns `{ launchCommand: 'pi', env: {...} }`
for env-var agents; the env is plumbed through `startup.env` into
`pty:spawn` (already supported by pty-connection.ts).
The launched shell command stays a clean `pi` — no `FOO='...' pi` prefix
typed into the terminal — and the readiness race is sidestepped entirely.
Co-authored-by: Orca <help@stably.ai>
* fix(gh-project): diagnose env-shadowed gh tokens in auth errors
`gh auth refresh -s project` silently no-ops when GITHUB_TOKEN/GH_TOKEN
is exported in the user's shell — gh prefers env tokens and refuses to
modify them, exiting 0. Users follow the canned remediation, see no
error, retry, and stay stuck.
Add a one-shot `gh auth status` probe (gh:diagnoseAuth IPC) that:
- Detects env-shadowed credentials and rewrites the fix to `unset
GITHUB_TOKEN` plus a grep to find where it's exported.
- Detects missing gh install, plain missing-scope on a keyring login,
and SAML SSO authorization.
- Surfaces a tailored multi-button error UI in ProjectViewWrapper and
ProjectPicker instead of one canned 'Copy command'.
Co-authored-by: Orca <help@stably.ai>
* fix(gh-project): address review feedback
- Cross-platform shell guidance: PowerShell commands on Windows
(Get-ChildItem Env:, Remove-Item Env:, [Environment]::SetEnvironmentVariable)
via navigator.userAgent platform check.
- Use `window.api.shell.openUrl` for the docs button instead of
`window.open`, matching SidebarToolbar's external-URL pattern.
- Tighten gh auth status parser: accept single-label hostnames and
optional trailing colon; recover host from the inline 'Logged in to
<host>' line so a missed section header never silently drops accounts.
- Add tests for multi-host output and host-recovery fallback.
- Drop dead command/copy locals in ProjectViewWrapper.ErrorState by
short-circuiting the auth-error case before they're computed.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* feat(new-workspace): branch-tab create row, scoped empty hints, name under Advanced, focus polish
- Branch tab autocomplete now offers "Create new branch <name>" instead of a
separate + button + dialog (replaces the original UX from #1408 / #1400).
Suppressed when the typed query exactly matches an existing branch.
- "Use <X> as workspace name" row is restricted to Smart mode; on dedicated
source tabs (GitHub/Linear/Branch) it was off-topic noise.
- Per-mode empty-state hints ("search GitHub PRs and issues", "find a branch
or create a new one", etc.) replace the generic message.
- When a source (PR/issue/Linear/branch) is selected, the auto-derived
workspace name is exposed as a "Name" input under Advanced where it can be
reviewed/overridden. With an explicitly typed name the smart input itself
is the name field, so no duplicated control.
- Forward Tab from the Repo combobox now skips the Smart/GitHub/Branch/Linear
segmented control and lands directly in the search input. Shift-Tab from
the input still focuses the active tab trigger so the segmented control is
reachable in reverse. Implemented via a focusCapture interceptor on
TabsList that distinguishes outside-entry from intra-list moves and from
the input's own shift-tab.
- Trigger checkLinearConnection() on mount so the "Connect Linear in
Settings" hint isn't shown stalely when the composer is the first
Linear-aware surface to render in the session; gate the disconnected
message on linearStatusChecked.
Co-authored-by: Orca <help@stably.ai>
* fix(new-workspace): Enter commits typed text while results are stale
When the user types faster than the 200ms search debounce, rows and
commandValue still reflect the previous query, so Enter would silently
pick a stale source row (e.g. a PR from the prior letters). Treat
results as stale while loading or while debouncedQuery hasn't caught up
to the input value, and commit the typed text via onValueChange instead.
Co-authored-by: Orca <help@stably.ai>
* fix(new-workspace): suppress stale source rows in autocomplete while typing
Source rows (GitHub items / branches / Linear issues) are driven by
debouncedQuery, so they're stale until the user pauses for the 200ms
debounce. The previous attempt only short-circuited Enter in the
input's onKeyDown, but cmdk's Command component has its own Enter
handler that still fires onSelect on the highlighted (stale) row.
Filter source rows out of the rows list itself when debouncedQuery
hasn't caught up to value, leaving only the typed-text row (use-name
in Smart, create-branch in Branches). With no stale rows present,
neither cmdk nor our own handler can pick a wrong source on Enter,
and the popover gives visual feedback (collapses to the typed-text
row) while results catch up.
Co-authored-by: Orca <help@stably.ai>
* fix(new-workspace): keep stale results visible, force highlight to typed-text row
Previous attempt removed source rows while debouncedQuery hadn't caught
up to value, which caused funky disappear/reappear flicker as the user
typed (rows present briefly while fresh, gone after the next keystroke).
Keep all source rows visible at all times to preserve continuity, but
control which row is highlighted while stale:
- Smart/Branches: force highlight onto the typed-text row (use-name /
create-branch) so cmdk's Enter handler commits the typed text instead
of a stale issue/PR/branch.
- GitHub/Linear: no typed-text fallback row exists, so clear the
highlight; the input's onKeyDown falls through to onPlainEnter rather
than picking a stale source.
Also fixed the input's Enter handler to fall through to onPlainEnter
when no row matches the (possibly empty) commandValue, so the keypress
isn't inert in the cleared-highlight case.
Co-authored-by: Orca <help@stably.ai>
* feat(new-workspace): auto-highlight matching source for #NNN, GH URLs, Linear IDs
When the typed value is unambiguously a source reference, snap the
autocomplete highlight onto the matching source row once it appears
instead of leaving it on the typed-text fallback. Enter then picks the
intended source.
Recognized patterns:
- GitHub shorthand: #1234
- GitHub URL: https://github.com/<owner>/<repo>/issues/123 or /pull/123
- Linear identifier: STA-123 (case-insensitive [A-Z][A-Z0-9_]*-\d+)
Stale-results behavior is unchanged: while debouncedQuery hasn't caught
up to value, the typed-text row stays highlighted (Smart/Branches) or
the highlight is cleared (GitHub/Linear) regardless of intent.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Gate gh retries on idempotency (auto-detected from argv/-X/mutation), key parent-field probe by (owner,ownerType), accept 'to an' in not_found classifier, and replace bulk label PUT with parallel sing
Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
Wrapped FirstLaunchBanner handlers' bodies in try/finally so inFlight resets even if fetchSettings rejects, preventing the banner from going permanently inert.
Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
Fixed two TaskPage resume-state regressions: the debounced GitHub search now persists even when activeTaskPreset is non-null (drops the guard, persists current preset+query), and re-added the mid-sess
Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
* fix: address pr-bug-scan findings from #1444
Threaded linkedPRNumber through all ChecksPanel force-refresh paths; gated useComposerState's PR-URL-from-name recovery on selected repo slug match; wrapped main-process linkedPR fallback gh pr view i
* fix: satisfy react-hooks/exhaustive-deps in selectedRepoSlug effect
Extract selectedRepo?.path into a const so the effect's only dep is
the path string, removing the missing-dependency warning.
---------
Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
Co-authored-by: Neil <neil@nousresearch.com>
When a worktree is created from a PR via the source picker, the new
local branch differs from the PR's head ref, so the branch-keyed PR
lookup misses and the worktree card shows no PR strip. Pass the
worktree's linkedPR number through the IPC call and fall back to a
number-based lookup in the main process. Also recover linkedPR from
a PR URL pasted into the workspace name when the user skips the
source picker. PR strip now wraps the whole row as the PR link.
Co-authored-by: Orca <help@stably.ai>
Two related top-band issues:
1. Non-workspace view: Tasks page used pt-3 above its X/source-icons row,
placing the cluster's center 6px below the sidebar "Tasks" nav row's
center. Drop to pt-1.5 so both rows center on the same baseline.
2. Workspace view: the strip TaskPage renders to replace the full-width
titlebar was 42px, but PR #1356 standardized titlebar/titlebar-left/
right-sidebar header at 36px. Match that with h-9 so the band is
uniform across columns when a worktree is active.
Co-authored-by: Orca <help@stably.ai>
Persist transient Tasks page position (GitHub mode, active preset/query,
Linear preset/query) in PersistedUIState so reopening Tasks restores the
user's working context instead of falling back to defaults. Source, repo
selection, team selection, and active project keep using their existing
settings paths.
Co-authored-by: Orca <help@stably.ai>
* fix(layout): align right sidebar to top in non-workspace views
Wrap the left + center columns in a flex-col that owns the conditional
titlebar, and lift <RightSidebar /> to be a sibling of that wrapper.
The titlebar now spans only the left+center area, so the right sidebar's
own header anchors at the top of the window in landing/settings/tasks
views — matching the workspace-view alignment.
Also gate the titlebar's right-sidebar toggle on !rightSidebarOpen so
it no longer double-renders alongside the close button inside the
right sidebar's own header when the sidebar is open.
Closes#1354
* fix(titlebar): shrink non-workspace .titlebar to 36px
PR #1381 shrank .titlebar-left to 36px but left .titlebar at 42px.
Before the right-sidebar top-alignment fix, the 6px mismatch was
hidden because the right sidebar was pushed below the titlebar in
non-workspace views. With top-alignment, the 36px right-sidebar
header no longer lines up with the 42px titlebar — visible as a
6px seam on the Landing page when the right sidebar is open.
Match all three (titlebar, titlebar-left, right-sidebar header) at
36px so the top strip is uniform across views.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>