/ ENG, STA +1` or `All workspaces / ENG +1`
+ - If the active scope is `all` and selected team keys are duplicated or span multiple workspaces, prefer `All workspaces / 2 teams` over an ambiguous key list.
+ - Popover content:
+ - Search filters team rows by team name, team key, and workspace name.
+ - Workspace section appears only when more than one workspace is connected. It includes "All workspaces" and each workspace.
+ - Selecting a workspace must call the existing `selectLinearWorkspace` flow, clear selected issue/list/error/loading state the same way `TaskPage` does now, and must not mutate `defaultLinearTeamSelection`.
+ - Team section includes "All teams" and selectable team rows. "All teams" persists `defaultLinearTeamSelection: null`; explicit subsets persist an array. Normalize a selection equal to all available teams back to `null`, even if the user selected teams one by one. Never persist an empty array; keep the last team selected or save `null`.
+ - Team rows show name, key, and workspace name when active workspace is `all` or when multiple workspaces exist.
+ - Footer has "Add team access" with a key/link icon. Close the popover before opening the API-key dialog.
+ - Keep the external-link button as an icon-only sibling. It remains an action, and should stay enabled only when exactly one selected team has a URL.
+
+4. Keep team selection reconciliation, but document its limits.
+ - `reconcileLinearTeamSelection()` preserves sticky-all and drops stale team IDs.
+ - Because the saved team subset is global, switching workspaces may temporarily select all teams in the new workspace while preserving the old saved subset for later. Do not introduce per-workspace persistence here.
+
+5. Update Settings and onboarding to use `LinearApiKeyDialog`.
+ - Rename "Add workspace" to "Add Linear access" when disconnected and "Update access" or "Add workspace access" when connected.
+ - Replace "Each workspace uses its own locally stored API key" with copy that says each connected Linear workspace has one key stored by the active runtime; a full-access key can cover all visible teams in that workspace, and a restricted key can be replaced any time.
+ - Keep existing per-workspace Test and Disconnect controls.
+
+## Edge Cases
+
+- No Linear workspaces connected: no Tasks selector; Settings/onboarding still show Connect.
+- One workspace connected: hide the workspace section, but keep "Add team access" visible.
+- Multiple workspaces + `all`: team keys/names can repeat across workspaces; always show workspace names in rows.
+- Empty team result: show an empty state plus footer action. Do not claim the key is the only possible cause; absence can also come from private-team membership, archived teams, permissions, or a fetch failure.
+- Empty issue result after team selection: say no fetched/current issues match the selected teams. Do not imply Linear was queried exhaustively for those teams.
+- Legacy or missing `organizationUrlKey`: use global Linear settings URLs and keep the paste flow usable.
+- Replacement key for an existing workspace: select that workspace, invalidate caches, refresh status, and refetch teams immediately.
+- Auth errors: a backend call may clear a token. The selector must tolerate stale rows during the refresh, then drop disconnected workspaces when status updates.
+- Multi-window/external mutations: there is no real-time renderer broadcast. Force `checkLinearConnection(true)` after connect/disconnect/test flows and when opening the selector if stale status would be visible. Forced refresh must still update state and invalidate caches when workspace metadata changed but the workspace count did not. A same-workspace key replacement with identical viewer/workspace metadata is invisible to status today; do not claim that case is handled unless this change adds a non-secret credential revision or a renderer broadcast.
+- Remote runtime/SSH: continue routing through `connectLinear`, `selectLinearWorkspace`, `listLinearTeams`, and runtime RPC. Do not call local Electron APIs directly except for opening Linear URLs.
+- Accessibility: trigger is a combobox-style button, rows are keyboard-selectable, icon buttons have labels/tooltips, and the dialog traps focus.
+
+## Tests
+
+- `src/shared/linear-links.test.ts`: URL helpers, fallback behavior, and path encoding.
+- `src/main/linear/teams.test.ts`: paginated team fetching and `all` workspace aggregation.
+- `src/renderer/src/store/slices/linear.test.ts`: successful connect invalidates issue/search/team caches, drops stale in-flight writes, awaits refreshed status, and does not mark all Linear disconnected for one workspace auth failure.
+- `src/renderer/src/store/slices/linear.test.ts`: forced status refresh detects changed workspace ids/metadata with the same workspace count and invalidates Linear caches when the scope signature changes.
+- Component-independent label and selection-persistence helper tests for `LinearScopeSelector`, including selecting every visible team persisting sticky-all (`null`) rather than a frozen full-team array.
+- Avoid brittle popover DOM tests unless matching an existing selector test pattern.
+
+## Rollout
+
+1. Fix backend/store correctness: team pagination, connect invalidation, and auth-clear status refresh.
+2. Add Linear settings URL helpers and tests.
+3. Add `LinearApiKeyDialog`; migrate Settings, onboarding, and the existing Tasks connect dialog.
+4. Add `LinearScopeSelector` and replace only the Linear toolbar selector block in `TaskPage`.
+5. Run focused Linear tests, then `pnpm typecheck` and `pnpm lint`.
diff --git a/docs/readme/README.es.md b/docs/readme/README.es.md
index 108a8eacba2..06a2d45b803 100644
--- a/docs/readme/README.es.md
+++ b/docs/readme/README.es.md
@@ -14,7 +14,7 @@
El orquestador de IA para desarrolladores 100x.
- Ejecuta Claude Code, Codex, Grok, Antigravity u OpenCode en paralelo entre repositorios — cada uno en su propio worktree, todo administrado desde un solo lugar.
+ Ejecuta Claude Code, OpenClaude, Codex, Grok, Antigravity u OpenCode en paralelo entre repositorios — cada uno en su propio worktree, todo administrado desde un solo lugar.
Disponible para macOS, Windows y Linux.
@@ -32,6 +32,7 @@ Orca es compatible con cualquier agente CLI (_no solo los de esta lista_).
Claude Code
+
OpenClaude
Codex
Grok
Gemini
@@ -64,7 +65,7 @@ Orca es compatible con cualquier agente CLI (_no solo los de esta lista_).
## Características
-- **Sin login** — Usa tu propia suscripción de Claude Code, Codex, Grok o Antigravity.
+- **Sin login** — Usa tu propia suscripción de Claude Code, OpenClaude, Codex, Grok o Antigravity.
- **Nativo con worktrees** — Cada feature vive en su propio worktree. Nada de stash ni malabares entre ramas. Crea y cambia al instante.
- **Terminales multi-agente** — Ejecuta varios agentes de IA en paralelo en pestañas y paneles. Mira de un vistazo cuáles están activos.
- **Control de versiones integrado** — Revisa los diffs generados por IA, haz ediciones rápidas y haz commit sin salir de Orca.
diff --git a/docs/readme/README.ja.md b/docs/readme/README.ja.md
index 2bfe4360fef..389facb6757 100644
--- a/docs/readme/README.ja.md
+++ b/docs/readme/README.ja.md
@@ -14,7 +14,7 @@
100x ビルダーのための AI オーケストレーター。
- Claude Code、Codex、Grok、Antigravity、OpenCode をリポジトリをまたいで並行実行 — それぞれを専用のワークツリーで動かし、1 か所で追跡できます。
+ Claude Code、OpenClaude、Codex、Grok、Antigravity、OpenCode をリポジトリをまたいで並行実行 — それぞれを専用のワークツリーで動かし、1 か所で追跡できます。
macOS、Windows、Linux で利用できます。
@@ -32,6 +32,7 @@ Orca は任意の CLI エージェントに対応しています(_このリス
Claude Code
+
OpenClaude
Codex
Grok
Gemini
@@ -64,7 +65,7 @@ Orca は任意の CLI エージェントに対応しています(_このリス
## 機能
-- **ログイン不要** — お持ちの Claude Code、Codex、Grok、Antigravity サブスクリプションをそのまま利用できます。
+- **ログイン不要** — お持ちの Claude Code、OpenClaude、Codex、Grok、Antigravity サブスクリプションをそのまま利用できます。
- **ワークツリーネイティブ** — 各機能は専用のワークツリーで開発できます。スタッシュやブランチ切り替えに悩まず、すぐに作成して切り替えられます。
- **マルチエージェントターミナル** — 複数の AI エージェントをタブやペインで並行実行できます。どれがアクティブかを一目で確認できます。
- **組み込みソース管理** — AI が生成した Diff を確認し、すばやく編集して、Orca から離れずにコミットできます。
diff --git a/docs/readme/README.ko.md b/docs/readme/README.ko.md
index 7e25bb80829..1c20d6ea5c5 100644
--- a/docs/readme/README.ko.md
+++ b/docs/readme/README.ko.md
@@ -14,7 +14,7 @@
100x 빌더를 위한 AI 오케스트레이터.
- Claude Code, Codex, Grok, Antigravity, OpenCode를 여러 리포지토리에서 나란히 실행하세요. 각 에이전트는 자체 worktree에서 실행되고 한곳에서 추적됩니다.
+ Claude Code, OpenClaude, Codex, Grok, Antigravity, OpenCode를 여러 리포지토리에서 나란히 실행하세요. 각 에이전트는 자체 worktree에서 실행되고 한곳에서 추적됩니다.
macOS, Windows, Linux에서 사용할 수 있습니다.
@@ -32,6 +32,7 @@ Orca는 모든 CLI 에이전트를 지원합니다(_아래 목록에만 한정
Claude Code
+
OpenClaude
Codex
Grok
Gemini
@@ -64,7 +65,7 @@ Orca는 모든 CLI 에이전트를 지원합니다(_아래 목록에만 한정
## 기능
-- **로그인 불필요** — 보유한 Claude Code, Codex, Grok 또는 Antigravity 구독을 그대로 사용하세요.
+- **로그인 불필요** — 보유한 Claude Code, OpenClaude, Codex, Grok 또는 Antigravity 구독을 그대로 사용하세요.
- **Worktree 네이티브** — 모든 기능은 자체 worktree를 가집니다. stash나 브랜치 전환에 얽매이지 않고 즉시 만들고 전환할 수 있습니다.
- **멀티 에이전트 터미널** — 여러 AI 에이전트를 탭과 패널에서 나란히 실행하세요. 어떤 에이전트가 활성 상태인지 한눈에 볼 수 있습니다.
- **내장 소스 관리** — AI가 생성한 diff를 검토하고, 빠르게 수정하고, Orca를 떠나지 않고 커밋할 수 있습니다.
diff --git a/docs/readme/README.zh-CN.md b/docs/readme/README.zh-CN.md
index bf31b3bc0cd..386c3f47ddd 100644
--- a/docs/readme/README.zh-CN.md
+++ b/docs/readme/README.zh-CN.md
@@ -14,7 +14,7 @@
面向 100x 构建者的 AI 编排器。
- 跨仓库并排运行 Claude Code、Codex、Grok、Antigravity 或 OpenCode — 每个都在自己的 worktree 中运行,并在一个地方统一跟踪。
+ 跨仓库并排运行 Claude Code、OpenClaude、Codex、Grok、Antigravity 或 OpenCode — 每个都在自己的 worktree 中运行,并在一个地方统一跟踪。
支持 macOS、Windows 和 Linux。
@@ -32,6 +32,7 @@ Orca 支持任何 CLI 智能体(_不仅限于以下列表_)。
Claude Code
+
OpenClaude
Codex
Grok
Gemini
@@ -64,7 +65,7 @@ Orca 支持任何 CLI 智能体(_不仅限于以下列表_)。
## 特性
-- **无需登录** — 直接使用你自己的 Claude Code、Codex、Grok 或 Antigravity 订阅。
+- **无需登录** — 直接使用你自己的 Claude Code、OpenClaude、Codex、Grok 或 Antigravity 订阅。
- **原生 worktree 工作流** — 每个功能都有自己的 worktree。无需 stash,也不用来回切分支。立即创建,快速切换。
- **多智能体终端** — 在标签页和面板中并排运行多个 AI 智能体。一眼就能看到哪些正在活跃。
- **内置源码管理** — 查看 AI 生成的 diff,快速编辑,并且无需离开 Orca 就能提交。
diff --git a/docs/reference/direct-url-or-file-entry.md b/docs/reference/direct-url-or-file-entry.md
index b5ea3c0c7e8..8664965147f 100644
--- a/docs/reference/direct-url-or-file-entry.md
+++ b/docs/reference/direct-url-or-file-entry.md
@@ -43,17 +43,14 @@ The behavior must work from both the titlebar tab strip and split-group tab stri
`TabBar` owns input text, pending state, focus, and dropdown close behavior. It does not create browser tabs or files. `groupId` should be `groupId ?? worktreeId`, matching the existing `resolvedGroupId` fallback.
3. Add a small hook/helper pair instead of duplicating Quick Open logic:
-
- `useTabEntryFileList` loads the file list when the menu opens, using the same inputs as `QuickOpen`: active worktree path, `getConnectionId(worktreeId)`, nested worktree exclusions, and active SSH target status. It cancels stale requests on close or key changes.
- A pure classifier/opening helper accepts the query, file-list snapshot, load/error state, worktree metadata, runtime context, and target group.
4. Wire the callback in both owners:
-
- `Terminal.tsx` titlebar fallback resolves the current active worktree and target group the same way `handleNewTab` / `handleNewBrowserTab` do.
- `useTabGroupWorkspaceModel` passes its explicit `worktreeId` and `groupId`. Do not rely on ambient `activeGroupIdByWorktree`; split-group `+` can be invoked from an unfocused group.
5. Classify submissions in this order:
-
- Empty after trim: reject inline.
- Explicit URL: accept only `http://` and `https://` URLs with a parseable host.
- Existing file: once the file-list snapshot is ready, normalize query separators for matching, prefer exact relative-path match, then exact basename match, then `rankQuickOpenFiles`. Before opening, `statRuntimePath` the matched absolute path and reject directories/stale missing matches instead of blindly opening stale list entries.
@@ -61,14 +58,12 @@ The behavior must work from both the titlebar tab strip and split-group tab stri
- New file: only after file listing has completed successfully with no existing-file match and no host-like URL match. Treat the query as a relative worktree path. If listing fails, allow explicit `http://` / `https://` URLs only; keep bare host-like inputs blocked because they cannot be disambiguated from files.
6. Validate new file paths before joining:
-
- Reject POSIX absolute paths, Windows drive paths, UNC paths, `~`, empty path, trailing slash, control characters, `.` / `..` segments, and empty raw segments such as `a//b`.
- Normalize `\` to `/` only after absolute-path checks, then run segment validation on the normalized path too so traversal like `a\..\b` is still rejected.
- Build the absolute path with `joinPath(worktreePath, relativePath)`, then create with `createRuntimePath(context, absolutePath, 'file')`.
- On `EEXIST` / "exists", immediately `statRuntimePath`; if it is now a file, open it. If it is a directory, show an error. This handles another window/process winning the create race.
7. Open actions:
-
- URL: in paired web clients, call `createWebRuntimeSessionBrowserTab({ worktreeId, url, targetGroupId: groupId })`; otherwise call `createBrowserTab(worktreeId, url, { activate: true, targetGroupId: groupId, title: url })`. Do not use `openNewBrowserTabInActiveWorkspace`; it only opens the default URL.
- Existing/new file: call `openFile(fileInfo, { preview: false, targetGroupId: groupId })` with `language: detectLanguage(relativePath)`. Include the active runtime environment owner as `runtimeEnvironmentId` through the normal `openFile` fallback; do not suppress runtime ownership.
diff --git a/docs/reference/feature-discovery-interaction-tracking.md b/docs/reference/feature-discovery-interaction-tracking.md
index 539730b1b04..cdc2dafcc5b 100644
--- a/docs/reference/feature-discovery-interaction-tracking.md
+++ b/docs/reference/feature-discovery-interaction-tracking.md
@@ -21,37 +21,37 @@ Do not upload this state as broad analytics. Product analytics should continue t
## Feature Catalog
-| Feature | Interaction ID | Record when | Education use |
-| --- | --- | --- | --- |
-| Review notes to agent | `review-notes` | A diff or markdown review note is added, or review notes are marked sent to an agent. | Suppress or target a future review-notes tour/tip about adding line notes and sending focused feedback back to an agent. |
-| AI commit generation | `ai-commit-generation` | AI commit-message generation is enabled or an AI commit message is generated. | Suppress future education about AI commit generation. No contextual tour is planned for this branch. |
-| AI PR generation | `ai-pr-generation` | AI pull-request title/body/draft fields are generated. | Suppress future education about AI PR generation. No contextual tour is planned for this branch. |
-| Floating Workspace | `floating-workspace` | The floating workspace opens, is enabled, or is configured. | Suppress future tips about the global terminal/browser/markdown workspace. No contextual tour is planned for this branch. |
-| Quick Commands | `quick-commands` | A terminal quick command is created or edited. | Suppress future tips about saved terminal commands. No contextual tour is planned for this branch. |
-| Computer Use setup | `computer-use-setup` | Computer Use is selected in onboarding, a permission setup is opened, or the skill setup terminal opens. | Suppress setup-focused tips once the user has started setup. |
-| Computer Use | `computer-use` | A successful `computer.*` runtime method other than capability probing is handled. | Suppress future usage tips once an agent has actually invoked Computer Use. |
-| Mobile pairing | `mobile-pairing` | Mobile is enabled or a mobile pairing QR/code is generated. | Suppress future mobile-pairing tips. No contextual tour is planned for this branch. |
-| Browser element grab | `browser-grab` | Browser grab mode is started, an element is copied, or an element screenshot is copied. | Suppress future tips about grabbing page context once the user has used the element picker. |
-| Browser annotations | `browser-annotations` | Browser annotation mode is started, an annotation is added, browser annotations are copied, or browser annotations are cleared. | Suppress future tips about annotating local pages and sending concrete UI feedback to an agent. |
-| Cookie import | `cookie-import` | Browser cookies are imported from another browser or file, imported through runtime browser profile methods, or default imported cookies are cleared. | Suppress future cookie-import education without conflating cookie setup with Agent Browser Use itself. |
-| Workspace board actions | `workspace-board-actions` | A card/status action, lane configuration, density change, pin drop, or board drag action is used. | Keep the existing workspace-board tour, and avoid repeating deeper board-action education after real use. |
-| Automation creation | `automation-created` | A local automation or external Hermes cron is created. | Keep the existing Automations tour, and suppress creation-focused education after the user creates one. |
-| Automation run | `automation-run` | A local or external automation run is manually queued. | Keep the existing Automations tour, and suppress run/inspection education after the user queues a run. |
-| Resource Manager | `resource-manager` | The Resource Manager status-bar popover is opened, or its status-bar visibility is toggled in Appearance/status-bar controls. | Suppress future tips about CPU, memory, session, daemon, and workspace disk-scan controls after the user has found the manager. |
-| Workspace cleanup / disk space | `workspace-cleanup` | The Space page opens, a workspace disk scan starts/cancels, or cleanup removes scanned workspace rows. | Suppress future tips about scanning workspace disk usage and reclaiming old workspace storage. |
-| Ports | `ports` | The Ports popover opens, the Ports status-bar item is configured, external ports are expanded, or a port is opened/copied/stopped. | Suppress future tips about discovering and acting on workspace ports. |
-| SSH | `ssh` | SSH status opens, SSH status-bar visibility changes, a target is added/imported/tested, or an SSH target is connected/disconnected. | Suppress future SSH setup/status tips after the user has interacted with remote target controls. |
-| Provider usage tracking | `usage-tracking` | Stats & Usage is opened, Claude/Codex/OpenCode usage analytics are enabled, provider usage details are opened from the status bar, Gemini usage/OAuth is configured, or provider usage status-bar toggles are changed. | Suppress future tips about where to find token/rate-limit/usage tracking for Claude, Codex, Gemini, OpenCode, and related providers. |
-| Claude account switching | `claude-account-switching` | A Claude managed account is added, selected, reauthenticated, removed, or selected from the status bar. | Suppress future tips about using multiple Claude accounts once the user has started account management. |
-| Codex account switching | `codex-account-switching` | A Codex managed account is added, selected, reauthenticated, removed, or selected from the status bar. | Suppress future tips about Codex account switching and restart follow-up flows. |
-| Workspace tabs | `terminal-tabs` | A workspace tab is created, reordered, renamed, recolored, pinned/unpinned, moved, or closed. | Suppress future tips about tab-level workspace organization. These are real workspace tabs, not workspace board cards. |
-| Split panes | `terminal-panes` | A split group is created from the pane shortcut/menu, a split is resized, or panes are merged. | Suppress future tips about pane-level split workflows. |
-| Tab splits | `tab-splits` | A workspace tab is moved into another pane or split into a new pane from a tab split/drop action. | Suppress future tips about tab-level split workflows separately from pane creation/resizing. |
-| Agent Browser Use setup | `agent-browser-setup` | Browser Use is selected in onboarding, enabled in settings, or its setup terminal opens. | Suppress future setup tips once the user has started Browser Use setup. |
-| Agent Browser Use | `agent-browser-use` | A successful non-profile `browser.*` runtime method is handled. | Suppress future usage tips once an agent/runtime has actually driven Orca's browser. |
-| Agent Orchestration setup | `agent-orchestration-setup` | Orchestration is selected in onboarding, enabled in settings, or its setup terminal opens. | Suppress future setup tips once the user has started Orchestration setup. |
-| Agent Orchestration | `agent-orchestration` | A successful `orchestration.*` runtime method is handled. | Suppress future usage tips once an agent/runtime has actually used orchestration. |
-| Notifications | `notifications` | Notifications are enabled in onboarding/settings or a test notification is sent. | Suppress future notification setup tips. |
+| Feature | Interaction ID | Record when | Education use |
+| ------------------------------ | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
+| Review notes to agent | `review-notes` | A diff or markdown review note is added, or review notes are marked sent to an agent. | Suppress or target a future review-notes tour/tip about adding line notes and sending focused feedback back to an agent. |
+| AI commit generation | `ai-commit-generation` | AI commit-message generation is enabled or an AI commit message is generated. | Suppress future education about AI commit generation. No contextual tour is planned for this branch. |
+| AI PR generation | `ai-pr-generation` | AI pull-request title/body/draft fields are generated. | Suppress future education about AI PR generation. No contextual tour is planned for this branch. |
+| Floating Workspace | `floating-workspace` | The floating workspace opens, is enabled, or is configured. | Suppress future tips about the global terminal/browser/markdown workspace. No contextual tour is planned for this branch. |
+| Quick Commands | `quick-commands` | A terminal quick command is created or edited. | Suppress future tips about saved terminal commands. No contextual tour is planned for this branch. |
+| Computer Use setup | `computer-use-setup` | Computer Use is selected in onboarding, a permission setup is opened, or the skill setup terminal opens. | Suppress setup-focused tips once the user has started setup. |
+| Computer Use | `computer-use` | A successful `computer.*` runtime method other than capability probing is handled. | Suppress future usage tips once an agent has actually invoked Computer Use. |
+| Mobile pairing | `mobile-pairing` | Mobile is enabled or a mobile pairing QR/code is generated. | Suppress future mobile-pairing tips. No contextual tour is planned for this branch. |
+| Browser element grab | `browser-grab` | Browser grab mode is started, an element is copied, or an element screenshot is copied. | Suppress future tips about grabbing page context once the user has used the element picker. |
+| Browser annotations | `browser-annotations` | Browser annotation mode is started, an annotation is added, browser annotations are copied, or browser annotations are cleared. | Suppress future tips about annotating local pages and sending concrete UI feedback to an agent. |
+| Cookie import | `cookie-import` | Browser cookies are imported from another browser or file, imported through runtime browser profile methods, or default imported cookies are cleared. | Suppress future cookie-import education without conflating cookie setup with Agent Browser Use itself. |
+| Workspace board actions | `workspace-board-actions` | A card/status action, lane configuration, density change, pin drop, or board drag action is used. | Keep the existing workspace-board tour, and avoid repeating deeper board-action education after real use. |
+| Automation creation | `automation-created` | A local automation or external Hermes cron is created. | Keep the existing Automations tour, and suppress creation-focused education after the user creates one. |
+| Automation run | `automation-run` | A local or external automation run is manually queued. | Keep the existing Automations tour, and suppress run/inspection education after the user queues a run. |
+| Resource Manager | `resource-manager` | The Resource Manager status-bar popover is opened, or its status-bar visibility is toggled in Appearance/status-bar controls. | Suppress future tips about CPU, memory, session, daemon, and workspace disk-scan controls after the user has found the manager. |
+| Workspace cleanup / disk space | `workspace-cleanup` | The Space page opens, a workspace disk scan starts/cancels, or cleanup removes scanned workspace rows. | Suppress future tips about scanning workspace disk usage and reclaiming old workspace storage. |
+| Ports | `ports` | The Ports popover opens, the Ports status-bar item is configured, external ports are expanded, or a port is opened/copied/stopped. | Suppress future tips about discovering and acting on workspace ports. |
+| SSH | `ssh` | SSH status opens, SSH status-bar visibility changes, a target is added/imported/tested, or an SSH target is connected/disconnected. | Suppress future SSH setup/status tips after the user has interacted with remote target controls. |
+| Provider usage tracking | `usage-tracking` | Stats & Usage is opened, Claude/Codex/OpenCode usage analytics are enabled, provider usage details are opened from the status bar, Gemini usage/OAuth is configured, or provider usage status-bar toggles are changed. | Suppress future tips about where to find token/rate-limit/usage tracking for Claude, Codex, Gemini, OpenCode, and related providers. |
+| Claude account switching | `claude-account-switching` | A Claude managed account is added, selected, reauthenticated, removed, or selected from the status bar. | Suppress future tips about using multiple Claude accounts once the user has started account management. |
+| Codex account switching | `codex-account-switching` | A Codex managed account is added, selected, reauthenticated, removed, or selected from the status bar. | Suppress future tips about Codex account switching and restart follow-up flows. |
+| Workspace tabs | `terminal-tabs` | A workspace tab is created, reordered, renamed, recolored, pinned/unpinned, moved, or closed. | Suppress future tips about tab-level workspace organization. These are real workspace tabs, not workspace board cards. |
+| Split panes | `terminal-panes` | A split group is created from the pane shortcut/menu, a split is resized, or panes are merged. | Suppress future tips about pane-level split workflows. |
+| Tab splits | `tab-splits` | A workspace tab is moved into another pane or split into a new pane from a tab split/drop action. | Suppress future tips about tab-level split workflows separately from pane creation/resizing. |
+| Agent Browser Use setup | `agent-browser-setup` | Browser Use is selected in onboarding, enabled in settings, or its setup terminal opens. | Suppress future setup tips once the user has started Browser Use setup. |
+| Agent Browser Use | `agent-browser-use` | A successful non-profile `browser.*` runtime method is handled. | Suppress future usage tips once an agent/runtime has actually driven Orca's browser. |
+| Agent Orchestration setup | `agent-orchestration-setup` | Orchestration is selected in onboarding, enabled in settings, or its setup terminal opens. | Suppress future setup tips once the user has started Orchestration setup. |
+| Agent Orchestration | `agent-orchestration` | A successful `orchestration.*` runtime method is handled. | Suppress future usage tips once an agent/runtime has actually used orchestration. |
+| Notifications | `notifications` | Notifications are enabled in onboarding/settings or a test notification is sent. | Suppress future notification setup tips. |
## Surface-Level Features
diff --git a/docs/reference/memory-leak-audit-pass-2.md b/docs/reference/memory-leak-audit-pass-2.md
deleted file mode 100644
index 99e05d78c20..00000000000
--- a/docs/reference/memory-leak-audit-pass-2.md
+++ /dev/null
@@ -1,51 +0,0 @@
-# Memory Leak Audit Pass 2
-
-Started: 2026-05-29 PDT
-
-Branch: `nwparker/mem-leak-audit-pass-2`, based on latest `origin/main` after the first leak-fix PRs were merged. The held mobile notification PR #2887 is tracked separately and is not duplicated here.
-
-Objective: re-scan the entire Orca codebase for missed listener, timer, observer, worker, socket, watcher, subscription, and disposable leaks; open scoped PRs for confirmed new misses.
-
-## Codebase Inventory
-
-Tracked code files, from `git ls-files`:
-
-| Bucket | Files | Status |
-| ----------------- | ----: | ------- |
-| `src/renderer` | 1575 | Checked |
-| `src/main` | 856 | Checked |
-| `src/shared` | 245 | Checked |
-| `mobile/src` | 100 | Checked |
-| `tests/e2e` | 72 | Checked |
-| `src/relay` | 65 | Checked |
-| `src/cli` | 65 | Checked |
-| `config/scripts` | 33 | Checked |
-| `mobile/app` | 16 | Checked |
-| `mobile/packages` | 12 | Checked |
-| `other-code` | 10 | Checked |
-| `native` | 6 | Checked |
-| `src/preload` | 6 | Checked |
-| Total | 3061 | Checked |
-
-## Scan Log
-
-- 2026-05-29: Created fresh audit branch from latest `origin/main`; confirmed held mobile PR #2887 remains open and is not duplicated.
-- 2026-05-29: Counted 3532 broad risk-pattern hits across `src`, `mobile`, `tests`, `config`, and `native`.
-- 2026-05-29: Re-ran broad listener/timer/observer/worker heuristics after fixes. Remaining raw candidates are expected lifetime-bound listeners: React Native subscriptions with `.remove()`, WebView document-lifetime scripts, preload document-level file-drop handlers, pane DOM listeners whose pane elements are disposed, and singleton window focus cache invalidation.
-- 2026-05-29: Checked React `useEffect`/`useLayoutEffect` candidates with risky APIs. Remaining hits are either direct cleanup returns, one-frame focus `requestAnimationFrame` calls, or effects whose teardown is handled by adjacent owner lifecycle.
-- 2026-05-29: Checked main/runtime/browser/SSH/daemon/rate-limit/relay/shared/CLI/test/native buckets for unpaired timers, sockets, startup listeners, file watches, and abort listeners.
-- 2026-05-29: Opened and merged low-risk follow-up PRs #3043, #3045, #3046, #3047, and #3049 for the confirmed misses.
-
-## Findings
-
-- `src/main/speech/model-manager.ts`: cancelling a stalled speech-model download only flipped an `aborted` flag; if HTTPS never delivered another chunk, the request and promise could stay alive indefinitely. Fixed by wiring `AbortController` through the download request and redirect chain. Risk: low. PR: #3043, merged.
-- `src/main/runtime/rpc/unix-socket-transport.ts`: a long-poll request keepalive interval was only cleared on reply; if the Unix socket closed first and the handler never replied after abort, the interval retained the socket/request closure. Fixed by making connection close run per-dispatch cleanup. Risk: low. PR: #3045, merged.
-- `src/relay/dispatcher.ts`: relay disposal stopped keepalives but did not abort in-flight request contexts, so stale SSH-side scans/watchers could continue after the dispatcher could no longer send responses. Fixed by aborting all tracked request controllers on dispose. Risk: low. PR: #3046, merged.
-- `src/preload/index.ts`: notification sound playback registered `ended`/`error` listeners for each play, but a failed `audio.play()` or replaced cached audio could leave those listeners attached. Fixed by tracking and running explicit playback listener cleanup. Risk: low. PR: #3047, merged.
-- `src/main/browser/cdp-ws-proxy.ts`: CDP proxy startup errors after debugger attach rejected without releasing the debugger/server objects. Fixed the startup failure path to close partial servers and detach the debugger. Risk: low. PR: #3049, merged.
-
-## Validation
-
-- `pnpm vitest run --config config/vitest.config.ts src/main/browser/cdp-ws-proxy.test.ts src/main/speech/model-manager.test.ts src/main/runtime/rpc/unix-socket-transport.test.ts src/relay/dispatcher.test.ts`
-- `pnpm run typecheck:node`
-- `pnpm exec oxlint src/main/browser/cdp-ws-proxy.ts src/main/runtime/rpc/unix-socket-transport.ts src/main/runtime/rpc/unix-socket-transport.test.ts src/relay/client-request-aborts.ts src/relay/dispatcher.ts src/relay/dispatcher.test.ts src/preload/index.ts src/main/speech/model-manager.ts src/main/speech/model-manager.test.ts`
diff --git a/docs/reference/memory-leak-audit.md b/docs/reference/memory-leak-audit.md
deleted file mode 100644
index 6c3e6442eb2..00000000000
--- a/docs/reference/memory-leak-audit.md
+++ /dev/null
@@ -1,74 +0,0 @@
-# Memory Leak Audit
-
-Started: 2026-05-26 18:36 PDT
-
-Objective: scan the entire Orca codebase for memory leaks, fix confirmed leaks, and keep durable coverage notes until the repository-wide scan is complete.
-
-## Codebase Inventory
-
-Tracked code files, from `git ls-files`:
-
-| Bucket | Files | Status |
-| ----------------- | ----: | ------------------------------------------- |
-| `src/renderer` | 1534 | Checked |
-| `src/main` | 838 | Checked |
-| `src/shared` | 220 | Checked |
-| `mobile/src` | 94 | Checked |
-| `tests/e2e` | 71 | Checked |
-| `src/relay` | 65 | Checked |
-| `src/cli` | 64 | Checked |
-| `config/scripts` | 27 | Checked |
-| `mobile/app` | 16 | Checked |
-| `other-code` | 13 | Checked |
-| `mobile/packages` | 12 | Checked |
-| `native` | 6 | Checked |
-| `src/preload` | 6 | Checked by IPC subscription heuristic |
-| Total | 2966 | Repository-wide risk-pattern scan completed |
-
-## Scan Log
-
-- 2026-05-26: Loaded `$memory-leak-audit` checklist and built an initial inventory from `git ls-files`.
-- 2026-05-26: Ran a broad listener/timer/observer search across `src`, `mobile`, `tests`, and `config`.
-- 2026-05-26: Ran a React `useEffect` cleanup heuristic over 1845 TS/TSX files. Manual follow-up identified one confirmed issue in `src/renderer/src/components/editor/PdfViewer.tsx`: `EventBus.on('scalechanging', ...)` had no matching `off`.
-- 2026-05-26: Ran a preload IPC heuristic. `src/preload/index.ts` and `src/preload/runtime-environment-subscriptions.ts` had apparent cleanup for their `ipcRenderer.on` subscriptions.
-- 2026-05-26: Ran an `ipcMain.on` cleanup heuristic over `src/main`. The only unpaired hit was `src/main/ipc/session.ts`, which is a process-lifetime registration via `registerSessionHandlers`.
-- 2026-05-26: Manually reviewed main-process interval/service candidates. Confirmed `src/main/speech/stt-service.ts` retained a timed-out, terminated speech worker in service state until idle teardown.
-- 2026-05-26: Ran a Promise/EventEmitter heuristic over main, relay, and CLI code. Confirmed `src/main/ssh/ssh-connection.ts` left ssh2 startup listeners attached after a successful long-lived connection.
-- 2026-05-26: Confirmed `src/main/browser/cdp-ws-proxy.ts` retained its startup HTTP server `error` listener after successful bind.
-- 2026-05-26: Continued Promise/EventEmitter follow-up in daemon startup paths. Confirmed startup listeners were retained after successful bind/readiness in `src/main/daemon/daemon-server.ts`, `src/main/daemon/daemon-init.ts`, and `src/main/daemon/production-launcher.ts`.
-- 2026-05-26: Reviewed BrowserWindow/session listeners. Confirmed `src/main/window/attach-main-window-services.ts` stacked a persistent browser-session `will-download` listener on every main-window services attach.
-- 2026-05-26: Reviewed browser-session profile lifecycle. Confirmed deleted isolated/imported profiles left partition policy callbacks/listeners and configured-partition bookkeeping behind in `src/main/browser/browser-session-registry.ts`.
-- 2026-05-26: Reviewed mobile RPC subscriptions. Confirmed `mobile/src/notifications/mobile-notifications.ts` retained a local streaming callback when notification subscription cleanup ran before the desktop returned `ready`.
-- 2026-05-26: Reviewed speech model extraction timers. Confirmed `src/main/speech/model-manager.ts` left the abort polling interval alive when extraction abort/timeout rejected before the child emitted `close` or `error`.
-- 2026-05-26: Manually followed up renderer high-risk surfaces from the broad scan: pane manager, browser webviews, terminal pane lifecycle, editor integrations, sidebar/global document listeners, observers, and shared clocks. Remaining hits had cleanup, were process-lifetime globals, or were element-owned listeners that are collected with their DOM nodes.
-- 2026-05-26: Manually followed up main-process long-lived services from the broad scan: runtime RPC, SSH services, speech worker/model manager, rate-limit polling, filesystem watchers, BrowserWindow-scoped IPC/listeners, browser-session handlers, daemon startup, and relay-backed flows.
-- 2026-05-26: Checked mobile React Native subscriptions, WebView injected listeners, RPC subscription teardown, repeated polling intervals, and app/session screens. Confirmed one notifications subscription cleanup issue and no additional unpaired repeated-mount listener leaks.
-- 2026-05-26: Checked `src/shared`, `src/relay`, `src/cli`, `config/scripts`, `tests/e2e`, `mobile/packages`, and native computer-use runtimes for listener/timer/socket/watch patterns. Follow-up found existing cleanup or process/test-lifetime ownership.
-
-## Confirmed Fixes
-
-- `src/renderer/src/components/editor/PdfViewer.tsx`: unregister the pdf.js `scalechanging` listener on cleanup to avoid retaining stale component state after repeated PDF opens.
-- `src/main/speech/stt-service.ts`: when dictation stop times out, remove worker listeners, terminate the worker, and clear the warm-worker slot so later dictation cannot retain or reuse a dead worker.
-- `src/main/ssh/ssh-connection.ts`: remove ssh2 startup `ready`/`error` listeners once the connection resolves or rejects; steady-state disconnect listeners remain scoped to the live connection.
-- `src/main/browser/cdp-ws-proxy.ts`: remove the startup server `error` listener after the proxy binds so the resolved start promise closure is not retained for the proxy lifetime.
-- `src/main/daemon/daemon-server.ts`: remove the startup server `error` listener after the daemon socket binds.
-- `src/main/daemon/daemon-init.ts`: remove detached daemon child-process startup listeners after IPC readiness or startup failure.
-- `src/main/daemon/production-launcher.ts`: remove detached daemon child-process startup listeners after IPC readiness or startup failure.
-- `src/main/window/attach-main-window-services.ts`: replace the named persistent browser-session `will-download` handler before registering it so window re-attach cannot stack handlers.
-- `src/main/browser/browser-session-registry.ts`: clear deleted profile partition permission/display-media callbacks, remove the download listener, and drop configured-partition bookkeeping.
-- `mobile/src/notifications/mobile-notifications.ts`: always drop the local notification stream on cleanup, even if the desktop has not returned a subscription id yet.
-- `src/main/speech/model-manager.ts`: clear extraction timeout/abort pollers and remove child listeners directly on abort/timeout rejection, instead of depending on a later child `close`.
-
-## Validation
-
-- Passed: `pnpm vitest run --config config/vitest.config.ts src/main/speech/stt-service.test.ts src/main/speech/model-manager.test.ts src/main/ssh/ssh-connection.test.ts src/main/browser/cdp-ws-proxy.test.ts src/main/daemon/daemon-server.test.ts src/main/daemon/production-launcher.test.ts src/main/daemon/daemon-init.test.ts src/main/window/attach-main-window-services.test.ts src/main/browser/browser-session-registry.test.ts src/main/browser/browser-session-registry.persistence.test.ts` (10 files, 128 tests).
-- Passed: `pnpm run typecheck:node`.
-- Passed: `pnpm run typecheck:web`.
-- Passed: focused `pnpm exec oxlint` over changed TypeScript files.
-- Passed after final import cleanup: `pnpm vitest run --config config/vitest.config.ts src/main/daemon/daemon-server.test.ts src/main/daemon/production-launcher.test.ts`.
-- Passed: `git diff --check`.
-- Attempted: `./node_modules/.bin/vitest run mobile/src/notifications/mobile-notifications.test.ts`; blocked in this workspace because mobile dependencies are not installed (`expo/tsconfig.base` cannot be resolved). The mobile test file is included for the mobile package test environment.
-
-## Residual Risk
-
-- This was a repository-wide static/manual memory-leak audit of listener, timer, observer, worker, socket, watcher, subscription, and resource-pool patterns. It does not replace heap-snapshot profiling under a long interactive session, but no further confirmed leaks were found in the scanned patterns.
diff --git a/docs/reference/telemetry-availability.md b/docs/reference/telemetry-availability.md
index 4626c972eab..b84958731ef 100644
--- a/docs/reference/telemetry-availability.md
+++ b/docs/reference/telemetry-availability.md
@@ -15,11 +15,11 @@ This file records operational availability. Event contracts still live in `src/s
## Boundary Fields
-| Field | Meaning |
-| --- | --- |
-| `code_merged_at_utc` | When the implementation reached `main`. Useful for source history only. |
-| `first_released_at_utc` | First release commit/build that contains the code. This is the earliest possible user exposure. |
-| `first_seen_at_utc` | First observed PostHog row for the event/property. This proves at least one event flowed. |
+| Field | Meaning |
+| ------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
+| `code_merged_at_utc` | When the implementation reached `main`. Useful for source history only. |
+| `first_released_at_utc` | First release commit/build that contains the code. This is the earliest possible user exposure. |
+| `first_seen_at_utc` | First observed PostHog row for the event/property. This proves at least one event flowed. |
| `dashboard_ready_at_utc` | Earliest lower bound for saved dashboards after all required signals have first-valid rows and pass any validation window. |
First-valid means the released app version has been observed, the required event/property fields are populated, joins used by the dashboard are available, and any validation window listed in the rollout entry has passed.
@@ -48,32 +48,32 @@ D1+/D3+/D7+ retention means the user fired `app_opened` at least once after 24/7
Scope: `nth_repo_added` on repo/activation/retention events. Current schemas declare it on `app_opened`, `repo_added`, `add_repo_setup_step_action`, `add_repo_existing_workspaces_detected`, `workspace_created`, `workspace_create_failed`, `setup_script_prompt_shown`, `setup_script_prompt_action`, `agent_started`, `agent_prompt_sent`, and `agent_error`. The original rollout covered `app_opened`, `repo_added`, `add_repo_setup_step_action`, `workspace_created`, `workspace_create_failed`, `agent_started`, and `agent_error`; later events have their own first-seen timestamps below.
-| Field | Value |
-| --- | --- |
-| PR | `#1591` |
-| Merge commit | `002e2acc38cad262d694049cc86c0962ffd381ce` |
-| `code_merged_at_utc` | `2026-05-08T17:50:44Z` |
-| First release | `v1.3.42-rc.1` |
-| First release commit | `b6dab85aaa0e9b600ec93846824ba815542a1e1e` |
-| `first_released_at_utc` | `2026-05-08T17:56:35Z` |
-| Earliest `first_seen_at_utc` | `2026-05-08T18:40:00.354Z` on `app_opened` |
-| `dashboard_ready_at_utc` | Event-dependent. Use `2026-05-08T18:40:00.354Z` for general `app_opened` repo-count cuts; use `2026-05-08T20:01:06.364Z` or later for first-repo activation cuts requiring `repo_added`. |
+| Field | Value |
+| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| PR | `#1591` |
+| Merge commit | `002e2acc38cad262d694049cc86c0962ffd381ce` |
+| `code_merged_at_utc` | `2026-05-08T17:50:44Z` |
+| First release | `v1.3.42-rc.1` |
+| First release commit | `b6dab85aaa0e9b600ec93846824ba815542a1e1e` |
+| `first_released_at_utc` | `2026-05-08T17:56:35Z` |
+| Earliest `first_seen_at_utc` | `2026-05-08T18:40:00.354Z` on `app_opened` |
+| `dashboard_ready_at_utc` | Event-dependent. Use `2026-05-08T18:40:00.354Z` for general `app_opened` repo-count cuts; use `2026-05-08T20:01:06.364Z` or later for first-repo activation cuts requiring `repo_added`. |
PostHog evidence checked at `2026-05-23T23:34:32Z`:
-| Event | First seen with `nth_repo_added` |
-| --- | --- |
-| `app_opened` | `2026-05-08T18:40:00.354Z` (`1.3.42-rc.1`) |
-| `workspace_created` | `2026-05-08T19:15:06.913Z` |
-| `agent_started` | `2026-05-08T19:15:07.130Z` |
-| `agent_prompt_sent` | `TBD` |
-| `repo_added` | `2026-05-08T20:01:06.364Z` (`1.3.42`) |
-| `add_repo_setup_step_action` | `2026-05-08T20:01:20.897Z` |
-| `workspace_create_failed` | `2026-05-08T23:04:32.315Z` |
-| `agent_error` | `2026-05-13T14:13:16.096Z` |
-| `add_repo_existing_workspaces_detected` | `2026-05-20T05:33:20.160Z` |
-| `setup_script_prompt_shown` | `2026-05-21T04:09:04.231Z` |
-| `setup_script_prompt_action` | `2026-05-21T04:10:31.616Z` |
+| Event | First seen with `nth_repo_added` |
+| --------------------------------------- | ------------------------------------------ |
+| `app_opened` | `2026-05-08T18:40:00.354Z` (`1.3.42-rc.1`) |
+| `workspace_created` | `2026-05-08T19:15:06.913Z` |
+| `agent_started` | `2026-05-08T19:15:07.130Z` |
+| `agent_prompt_sent` | `TBD` |
+| `repo_added` | `2026-05-08T20:01:06.364Z` (`1.3.42`) |
+| `add_repo_setup_step_action` | `2026-05-08T20:01:20.897Z` |
+| `workspace_create_failed` | `2026-05-08T23:04:32.315Z` |
+| `agent_error` | `2026-05-13T14:13:16.096Z` |
+| `add_repo_existing_workspaces_detected` | `2026-05-20T05:33:20.160Z` |
+| `setup_script_prompt_shown` | `2026-05-21T04:09:04.231Z` |
+| `setup_script_prompt_action` | `2026-05-21T04:10:31.616Z` |
Dashboard caveats:
@@ -87,26 +87,26 @@ Scope: `cohort` on onboarding events. Current schemas declare it on `onboarding_
The original `#1608` rollout covered `onboarding_started`, `onboarding_step_viewed`, `onboarding_step_completed`, `onboarding_step_skipped`, `onboarding_step4_path_clicked`, `onboarding_step4_path_failed`, `onboarding_completed`, `onboarding_dismissed`, `onboarding_agent_picked`, and onboarding import events. Later onboarding events joined the roster by declaring `cohort` in their schemas.
-| Field | Value |
-| --- | --- |
-| PR | `#1608` |
-| Merge commit | `318e2b4c2c501b50280dc9e5b11ed34bd899bdd9` |
-| `code_merged_at_utc` | `2026-05-09T02:57:18Z` |
-| First release | `v1.3.44` |
-| First release commit | `e602decf1d35dc54c84eea516fc2b9afdc065cce` |
-| `first_released_at_utc` | `2026-05-09T08:05:39Z` |
-| Earliest `first_seen_at_utc` | `2026-05-09T09:54:03.038Z` on `onboarding_started { cohort: 'fresh_install' }` (`1.3.45`) |
-| `dashboard_ready_at_utc` | `2026-05-09T09:54:03.038Z` for fresh-install onboarding-start cohorts; use later event-specific first-seen timestamps where a tile requires another event. |
+| Field | Value |
+| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| PR | `#1608` |
+| Merge commit | `318e2b4c2c501b50280dc9e5b11ed34bd899bdd9` |
+| `code_merged_at_utc` | `2026-05-09T02:57:18Z` |
+| First release | `v1.3.44` |
+| First release commit | `e602decf1d35dc54c84eea516fc2b9afdc065cce` |
+| `first_released_at_utc` | `2026-05-09T08:05:39Z` |
+| Earliest `first_seen_at_utc` | `2026-05-09T09:54:03.038Z` on `onboarding_started { cohort: 'fresh_install' }` (`1.3.45`) |
+| `dashboard_ready_at_utc` | `2026-05-09T09:54:03.038Z` for fresh-install onboarding-start cohorts; use later event-specific first-seen timestamps where a tile requires another event. |
Later onboarding cohort events:
-| Event | PR | First release | `first_seen_at_utc` |
-| --- | --- | --- | --- |
-| `onboarding_feature_setup_run` | `#1853` (`2e5ac1c8ebdc93f6281622d9e4ef60ab14c71096`) | `v1.4.2-rc.1` at `2026-05-14T22:23:46Z` | `2026-05-15T05:55:37.248Z` (`1.4.2-rc.4`) |
-| `onboarding_feature_setup_terminal_opened` | `#1853` (`2e5ac1c8ebdc93f6281622d9e4ef60ab14c71096`) | `v1.4.2-rc.1` at `2026-05-14T22:23:46Z` | `2026-05-15T05:55:37.261Z` (`1.4.2-rc.4`) |
+| Event | PR | First release | `first_seen_at_utc` |
+| ---------------------------------------------- | ---------------------------------------------------- | --------------------------------------- | ----------------------------------------- |
+| `onboarding_feature_setup_run` | `#1853` (`2e5ac1c8ebdc93f6281622d9e4ef60ab14c71096`) | `v1.4.2-rc.1` at `2026-05-14T22:23:46Z` | `2026-05-15T05:55:37.248Z` (`1.4.2-rc.4`) |
+| `onboarding_feature_setup_terminal_opened` | `#1853` (`2e5ac1c8ebdc93f6281622d9e4ef60ab14c71096`) | `v1.4.2-rc.1` at `2026-05-14T22:23:46Z` | `2026-05-15T05:55:37.261Z` (`1.4.2-rc.4`) |
| `onboarding_feature_setup_terminal_interacted` | `#1853` (`2e5ac1c8ebdc93f6281622d9e4ef60ab14c71096`) | `v1.4.2-rc.1` at `2026-05-14T22:23:46Z` | `2026-05-15T11:47:28.022Z` (`1.4.2-rc.6`) |
-| `onboarding_feature_setup_toggled` | `#1853` (`2e5ac1c8ebdc93f6281622d9e4ef60ab14c71096`) | `v1.4.2-rc.1` at `2026-05-14T22:23:46Z` | `2026-05-17T03:03:54.535Z` (`1.4.3`) |
-| `onboarding_task_sources_snapshot` | `#2275` (`ececd27fd8203a7acb3e430fa016fb4653aaa93c`) | `v1.4.7-rc.1` at `2026-05-19T00:47:11Z` | `2026-05-19T04:18:23.872Z` (`1.4.7`) |
+| `onboarding_feature_setup_toggled` | `#1853` (`2e5ac1c8ebdc93f6281622d9e4ef60ab14c71096`) | `v1.4.2-rc.1` at `2026-05-14T22:23:46Z` | `2026-05-17T03:03:54.535Z` (`1.4.3`) |
+| `onboarding_task_sources_snapshot` | `#2275` (`ececd27fd8203a7acb3e430fa016fb4653aaa93c`) | `v1.4.7-rc.1` at `2026-05-19T00:47:11Z` | `2026-05-19T04:18:23.872Z` (`1.4.7`) |
Dashboard caveats:
@@ -121,26 +121,26 @@ Dashboard caveats:
Scope: first feature-wall tour event family before the inline onboarding tour. This added base open/close and tile activity telemetry. Historical `feature_wall_closed` rows from this rollout have `dwell_ms`; the source/depth fields needed for tour cohort retention only arrive in the later tour telemetry rollout.
-| Field | Value |
-| --- | --- |
-| PR | `#1772` |
-| Merge commit | `fdf7d9e97a46f27b3667d9bae6c8aca5d11345d8` |
-| `code_merged_at_utc` | `2026-05-14T22:20:50Z` |
-| First release | `v1.4.2-rc.1` |
-| First release commit | `c66e9801fe5f159cac23564ec7c028957973692a` |
-| `first_released_at_utc` | `2026-05-14T22:23:46Z` |
-| `first_seen_at_utc` | `2026-05-15T06:15:30.263Z` on `feature_wall_opened { source: 'popup' }` (`1.4.2-rc.4`) |
+| Field | Value |
+| ------------------------ | ---------------------------------------------------------------------------------------------------------------- |
+| PR | `#1772` |
+| Merge commit | `fdf7d9e97a46f27b3667d9bae6c8aca5d11345d8` |
+| `code_merged_at_utc` | `2026-05-14T22:20:50Z` |
+| First release | `v1.4.2-rc.1` |
+| First release commit | `c66e9801fe5f159cac23564ec7c028957973692a` |
+| `first_released_at_utc` | `2026-05-14T22:23:46Z` |
+| `first_seen_at_utc` | `2026-05-15T06:15:30.263Z` on `feature_wall_opened { source: 'popup' }` (`1.4.2-rc.4`) |
| `dashboard_ready_at_utc` | Event-dependent. Useful for base feature-wall usage after first-seen rows, not for inline-tour cohort retention. |
PostHog evidence checked at `2026-05-23T23:34:32Z`:
-| Event/property | `first_seen_at_utc` |
-| --- | --- |
-| `feature_wall_opened { source: 'popup' }` | `2026-05-15T06:15:30.263Z` (`1.4.2-rc.4`) |
-| `feature_wall_tile_focused` | `2026-05-15T06:15:31.073Z` (`1.4.2-rc.4`) |
-| `feature_wall_closed` with legacy `dwell_ms` only | `2026-05-15T06:18:22.677Z` |
-| `feature_wall_tile_clicked` | `2026-05-17T04:35:04.142Z` (`1.4.3`) |
-| `feature_wall_opened { source: 'help_menu' }` | `2026-05-17T07:02:23.278Z` (`1.4.3`) |
+| Event/property | `first_seen_at_utc` |
+| ------------------------------------------------- | ----------------------------------------- |
+| `feature_wall_opened { source: 'popup' }` | `2026-05-15T06:15:30.263Z` (`1.4.2-rc.4`) |
+| `feature_wall_tile_focused` | `2026-05-15T06:15:31.073Z` (`1.4.2-rc.4`) |
+| `feature_wall_closed` with legacy `dwell_ms` only | `2026-05-15T06:18:22.677Z` |
+| `feature_wall_tile_clicked` | `2026-05-17T04:35:04.142Z` (`1.4.3`) |
+| `feature_wall_opened { source: 'help_menu' }` | `2026-05-17T07:02:23.278Z` (`1.4.3`) |
Dashboard caveats:
@@ -151,16 +151,16 @@ Dashboard caveats:
Scope: optional "Explore Orca" tour during onboarding and the Help menu entry point. This shipped the product surface, not the full retention cohort instrumentation. It also added `source = 'onboarding'` to the feature-wall open source enum and added feature-wall group/feature/docs click events with source.
-| Field | Value |
-| --- | --- |
-| PR | `#2652` |
-| Merge commit | `669ade23133b9905fff1b6ed22755ee2911ea517` |
-| `code_merged_at_utc` | `2026-05-23T19:21:14Z` |
-| First release | `v1.4.23-rc.0` |
-| First release commit | `1f1171e0dee5d0fbf969de56845882bf8deb9037` |
-| `first_released_at_utc` | `2026-05-23T20:14:38Z` |
-| First app version observed | `1.4.23-rc.0` at `2026-05-23T20:54:12.343Z` |
-| `dashboard_ready_at_utc` | `TBD`; do not use this surface-only rollout for tour cohort retention tiles. |
+| Field | Value |
+| -------------------------- | ---------------------------------------------------------------------------- |
+| PR | `#2652` |
+| Merge commit | `669ade23133b9905fff1b6ed22755ee2911ea517` |
+| `code_merged_at_utc` | `2026-05-23T19:21:14Z` |
+| First release | `v1.4.23-rc.0` |
+| First release commit | `1f1171e0dee5d0fbf969de56845882bf8deb9037` |
+| `first_released_at_utc` | `2026-05-23T20:14:38Z` |
+| First app version observed | `1.4.23-rc.0` at `2026-05-23T20:54:12.343Z` |
+| `dashboard_ready_at_utc` | `TBD`; do not use this surface-only rollout for tour cohort retention tiles. |
Dashboard caveats:
@@ -179,32 +179,32 @@ Added/changed signals:
- `feature_wall_closed` optional `source`, `exit_action`, `furthest_step`, `last_group_id`, and bounded visited/completed depth counts.
- `feature_wall_opened { source: 'onboarding' }` through the inline tour surface.
-| Field | Value |
-| --- | --- |
-| PR | `#2713` |
-| Branch commit | `9115143927dce38547545a07e58b85d17796fd7a` |
-| Merge commit | `b9c55bb07127f799528761ec31d7e2b7f8598c07` |
-| `code_merged_at_utc` | `2026-05-23T21:15:11Z` |
-| First release | `v1.4.23-rc.1` |
-| First release commit | `c74765922e770c6540496735a5a2be838457256a` |
-| `first_released_at_utc` | `2026-05-23T21:16:38Z` |
-| `first_seen_at_utc` | `TBD` |
-| `dashboard_ready_at_utc` | `TBD` |
+| Field | Value |
+| ------------------------ | ------------------------------------------ |
+| PR | `#2713` |
+| Branch commit | `9115143927dce38547545a07e58b85d17796fd7a` |
+| Merge commit | `b9c55bb07127f799528761ec31d7e2b7f8598c07` |
+| `code_merged_at_utc` | `2026-05-23T21:15:11Z` |
+| First release | `v1.4.23-rc.1` |
+| First release commit | `c74765922e770c6540496735a5a2be838457256a` |
+| `first_released_at_utc` | `2026-05-23T21:16:38Z` |
+| `first_seen_at_utc` | `TBD` |
+| `dashboard_ready_at_utc` | `TBD` |
Required readiness signals:
-| Signal | `first_seen_at_utc` |
-| --- | --- |
-| `onboarding_tour_outcome` with valid `outcome` and joinable fresh-install start | `TBD` |
-| `onboarding_step_viewed { value_kind: 'tour' }` | `TBD` |
-| `feature_wall_closed` with non-null `source`, `exit_action`, and expected depth fields | `TBD` |
+| Signal | `first_seen_at_utc` |
+| -------------------------------------------------------------------------------------- | ------------------- |
+| `onboarding_tour_outcome` with valid `outcome` and joinable fresh-install start | `TBD` |
+| `onboarding_step_viewed { value_kind: 'tour' }` | `TBD` |
+| `feature_wall_closed` with non-null `source`, `exit_action`, and expected depth fields | `TBD` |
QA/opportunistic signals:
-| Signal | `first_seen_at_utc` |
-| --- | --- |
+| Signal | `first_seen_at_utc` |
+| ------------------------------------------------ | --------------------------------------------------------------- |
| `onboarding_step_skipped { value_kind: 'tour' }` | `TBD`; do not gate dashboard readiness on natural skip traffic. |
-| `feature_wall_opened { source: 'onboarding' }` | `TBD`; surface availability only. |
+| `feature_wall_opened { source: 'onboarding' }` | `TBD`; surface availability only. |
PostHog evidence checked at `2026-05-23T23:34:32Z`:
diff --git a/docs/ssh-config-target-compatibility.md b/docs/ssh-config-target-compatibility.md
new file mode 100644
index 00000000000..07817dc41f7
--- /dev/null
+++ b/docs/ssh-config-target-compatibility.md
@@ -0,0 +1,61 @@
+# SSH Config Target Compatibility
+
+## Problem
+
+- `src/main/ssh/ssh-connection-utils.ts:108` resolves OpenSSH config with `ssh -G`, but then prefers the persisted `target.host` over `resolved.hostname`.
+- `src/main/ssh/ssh-connection-utils.ts:109` treats persisted port `22` as an explicit override, so a config-host target can ignore a resolved non-default `Port`.
+- `src/main/ssh/ssh-config-parser.ts:210` imports config aliases with `host` set to the alias when the concrete `Host` block lacks an inline `HostName`; later `ssh -G` may know the real host, but the connection path ignores it.
+- `src/renderer/src/components/settings/SshPane.tsx:63` requires both host and username, even though OpenSSH config aliases can resolve the user and users commonly paste `user@host:port` targets.
+- `src/main/ssh/ssh-config-parser.ts:330` parses `ForwardAgent`, but `src/main/ssh/ssh-connection-utils.ts:112` did not pass it into ssh2, so remote git commands that rely on the local agent could fail.
+
+## Goal
+
+Make Orca behave like a mature SSH client for common config-host flows: aliases imported from `~/.ssh/config`, aliases with inherited `HostName`/`Port`, and pasted SSH targets should connect without users re-entering information that OpenSSH can resolve.
+
+## Non-goals
+
+- Do not add persistent secret storage for SSH passwords or key passphrases.
+- Do not redesign the whole SSH settings page.
+- Do not change relay deployment or remote PTY lease semantics.
+- Do not add a known-host trust UI in this patch.
+
+## Design
+
+1. Preserve explicit target overrides while letting config aliases use resolved values.
+ - In `buildConnectConfig`, prefer `resolved.hostname` only when the persisted host is blank, the same as `configHost`, or the same as the label.
+ - Prefer `resolved.port` when the target is a config-host target still on the default `22`; keep non-default target ports as explicit overrides.
+ - Continue using target username first, then resolved user.
+
+2. Honor resolved agent forwarding where ssh2 can support it.
+ - Set `agentForward` only when resolved config requested forwarding and an agent is actually configured.
+ - Leave system-SSH transport unchanged because it already delegates to OpenSSH config.
+
+3. Normalize settings form drafts before save.
+ - Accept `ssh://user@host:port`, `user@host:port`, and plain aliases in the Host field.
+ - Auto-fill username and port from pasted inputs only when the dedicated fields are still empty/default.
+ - Allow username to be omitted; `ssh -G` can provide it during connect.
+
+4. Keep UI changes small.
+ - Rename copy only where needed to avoid implying username is mandatory.
+ - Render username-less targets without a leading `@`.
+ - Do not introduce new colors, typography, or layout patterns.
+
+5. Cover behavior with focused tests.
+ - Add connection-config tests for config-host resolved hostname/port precedence.
+ - Add connection-config tests for `ForwardAgent yes`.
+ - Add renderer utility tests for pasted SSH target normalization.
+
+## Edge Cases
+
+- Explicit non-default port in Orca still wins over `ssh -G`.
+- Empty or unparsable host input remains invalid.
+- IPv6 bracket syntax is accepted for `ssh://` URLs and preserved conservatively for scp-like inputs.
+- Plain config aliases remain valid even without a username.
+
+## Rollout
+
+1. Add the renderer draft-normalization helper and tests.
+2. Wire the SSH settings form save path and labels to the helper.
+3. Update `buildConnectConfig` precedence/agent-forwarding and tests.
+4. Clean up username-less target display.
+5. Run focused tests and typecheck/lint where feasible.
diff --git a/mobile/.oxlintrc.json b/mobile/.oxlintrc.json
index 90d8894e282..8263b919379 100644
--- a/mobile/.oxlintrc.json
+++ b/mobile/.oxlintrc.json
@@ -1,3 +1,4 @@
{
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
"rules": {}
}
diff --git a/mobile/app/h/[hostId]/index.tsx b/mobile/app/h/[hostId]/index.tsx
index fc4765448c6..30069d6be2c 100644
--- a/mobile/app/h/[hostId]/index.tsx
+++ b/mobile/app/h/[hostId]/index.tsx
@@ -9,7 +9,7 @@ import {
TextInput
} from 'react-native'
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'
-import { useLocalSearchParams, useRouter } from 'expo-router'
+import { useFocusEffect, useLocalSearchParams, useRouter } from 'expo-router'
import {
Search,
X,
@@ -62,6 +62,11 @@ import {
loadPreferences,
savePreferences
} from '../../../src/storage/preferences'
+import {
+ createInitialHostRouteActionState,
+ resolveHostRouteActionState,
+ setHostRouteNewWorktreeVisible
+} from '../../../src/host-route-action-state'
// Why: locally-typed subset of the desktop's RuntimeStatus we read from
// `status.get`. Only the version fields matter to mobile today; everything
@@ -340,7 +345,9 @@ export default function HostScreen() {
const [actionTarget, setActionTarget] = useState(null)
const [confirmDelete, setConfirmDelete] = useState(null)
const [confirmRemoveHost, setConfirmRemoveHost] = useState(false)
- const [showNewWorktree, setShowNewWorktree] = useState(false)
+ const [routeActionState, setRouteActionState] = useState(() =>
+ createInitialHostRouteActionState(action)
+ )
const [sleptIds, setSleptIds] = useState>(new Set())
// Persisted pin state
@@ -348,9 +355,16 @@ export default function HostScreen() {
const [_prefsLoaded, setPrefsLoaded] = useState(false)
const [collapsedGroups, setCollapsedGroups] = useState>(new Set())
- useEffect(() => {
- if (action === 'newWorktree') setShowNewWorktree(true)
- }, [action])
+ const resolvedRouteActionState = resolveHostRouteActionState(routeActionState, action)
+ // Why: `action=newWorktree` is a route-derived open edge. Resolve it before
+ // commit, but don't reopen after the user closes while the same URL remains.
+ if (resolvedRouteActionState !== routeActionState) {
+ setRouteActionState(resolvedRouteActionState)
+ }
+ const showNewWorktree = resolvedRouteActionState.showNewWorktree
+ const setShowNewWorktreeVisible = useCallback((visible: boolean) => {
+ setRouteActionState((current) => setHostRouteNewWorktreeVisible(current, visible))
+ }, [])
// Load persisted pins and preferences
useEffect(() => {
@@ -481,12 +495,6 @@ export default function HostScreen() {
}
}, [client, connState, hostId])
- useEffect(() => {
- if (connState === 'connected') {
- void fetchWorktrees()
- }
- }, [connState, fetchWorktrees])
-
// Why: read desktop's protocol version from status.get on every connect
// and re-evaluate compatibility. If the desktop declares this mobile
// build too old (or vice versa via the local minimum), the host detail
@@ -528,13 +536,18 @@ export default function HostScreen() {
}
}, [connState, client])
- useEffect(() => {
- if (connState !== 'connected') return
- const interval = setInterval(() => {
+ useFocusEffect(
+ useCallback(() => {
+ if (connState !== 'connected') return
void fetchWorktrees()
- }, 3000)
- return () => clearInterval(interval)
- }, [connState, fetchWorktrees])
+ // Why: React Navigation keeps previous stack screens mounted; only
+ // poll the host list while this route is visible.
+ const interval = setInterval(() => {
+ void fetchWorktrees()
+ }, 3000)
+ return () => clearInterval(interval)
+ }, [connState, fetchWorktrees])
+ )
const updateLocalPins = useCallback(
(worktreeId: string, pinned: boolean) => {
@@ -871,7 +884,7 @@ export default function HostScreen() {
setShowNewWorktree(true)}
+ onPress={() => setShowNewWorktreeVisible(true)}
disabled={connState !== 'connected'}
>
setShowNewWorktree(false)}
+ onClose={() => setShowNewWorktreeVisible(false)}
/>
)
diff --git a/mobile/app/h/[hostId]/session/[worktreeId].tsx b/mobile/app/h/[hostId]/session/[worktreeId].tsx
index 26d42bab2cc..f30870d9a30 100644
--- a/mobile/app/h/[hostId]/session/[worktreeId].tsx
+++ b/mobile/app/h/[hostId]/session/[worktreeId].tsx
@@ -27,6 +27,7 @@ import {
ChevronLeft,
ChevronRight,
ChevronsRight,
+ Copy,
Eraser,
Folder,
File,
@@ -34,10 +35,12 @@ import {
GitBranch,
Globe,
Keyboard as KeyboardIcon,
+ MessageSquare,
Mic,
Monitor,
Plus,
RefreshCw,
+ Send,
Smartphone,
SquareTerminal,
X
@@ -67,8 +70,10 @@ import {
loadTerminalAccessoryLayout
} from '../../../../src/terminal/terminal-accessory-layout'
import {
+ clearTerminalLiveInputFocusTimer,
getTerminalLiveSpecialKeyBytes,
- isTerminalLiveInputWithinByteLimit
+ isTerminalLiveInputWithinByteLimit,
+ scheduleTerminalLiveInputFocus
} from '../../../../src/terminal/terminal-live-input'
import { countTerminalGestureInputSequences } from '../../../../src/terminal/terminal-gesture-input'
import { MobileBrowserPane, type MobileBrowserTab } from '../../../../src/browser/MobileBrowserPane'
@@ -88,6 +93,13 @@ import {
buildMobileDiffLines,
type MobileDiffLine
} from '../../../../src/session/mobile-diff-lines'
+import {
+ addMobileDiffComment,
+ formatDiffComments,
+ normalizeMobileDiffComments,
+ removeDeliveredMobileDiffComments,
+ removeMobileDiffComments
+} from '../../../../src/session/mobile-diff-comments'
import {
buildPlainMobileDiffSyntaxLines,
highlightMobileCode,
@@ -109,7 +121,13 @@ import {
type MobileNewTabAgentOption,
type MobileNewTabAgentSettings
} from '../../../../src/session/mobile-new-tab-agent-options'
+import {
+ createMobileSessionCreateWarningState,
+ dismissMobileSessionCreateWarningState,
+ reconcileMobileSessionCreateWarningState
+} from '../../../../src/session/mobile-session-create-warning-state'
import { colors, spacing, radii, typography } from '../../../../src/theme/mobile-theme'
+import type { DiffComment } from '../../../../../src/shared/types'
type Terminal = TerminalRecord
@@ -188,6 +206,20 @@ type FileDocState =
type RenderableDiffLine = MobileHighlightedDiffLine
+type DiffCommentActions = {
+ comments: DiffComment[]
+ busy: boolean
+ onAdd: (filePath: string, lineNumber: number, body: string) => Promise
+ onDelete: (commentId: string) => Promise
+ onCopyAll: () => Promise
+ onSendAll: () => void
+}
+
+type DiffNotesDelivery = {
+ prompt: string
+ comments: DiffComment[]
+}
+
type ReadyFileDocState = Extract
type FileSyntaxState = {
@@ -543,37 +575,136 @@ function SyntaxSegments({ segments }: { segments: MobileSyntaxSegment[] }) {
function DiffLineRow({
line,
title,
- index
+ index,
+ comments,
+ activeCommentLine,
+ commentDraft,
+ commentsBusy,
+ onStartComment,
+ onCancelComment,
+ onDraftChange,
+ onSubmitComment,
+ onDeleteComment
}: {
line: RenderableDiffLine
title: string
index: number
+ comments: DiffComment[]
+ activeCommentLine: number | null
+ commentDraft: string
+ commentsBusy: boolean
+ onStartComment: (lineNumber: number) => void
+ onCancelComment: () => void
+ onDraftChange: (value: string) => void
+ onSubmitComment: (lineNumber: number) => void
+ onDeleteComment: (commentId: string) => void
}) {
+ const commentLine = line.newLineNumber
+ const isCommenting = commentLine !== undefined && activeCommentLine === commentLine
+ const canComment = commentLine !== undefined
+ // Why: review notes are anchored to the modified side, so the single mobile
+ // gutter should show the same line number the note will reference.
+ const gutterLineNumber = line.newLineNumber ?? line.oldLineNumber ?? ''
return (
-
- {line.oldLineNumber ?? line.newLineNumber ?? ''}
-
+
+ {gutterLineNumber}
- {line.kind === 'add' ? '+ ' : line.kind === 'delete' ? '- ' : ' '}
+
+ {line.kind === 'add' ? '+ ' : line.kind === 'delete' ? '- ' : ' '}
+
+
-
-
+ {canComment ? (
+ [
+ styles.diffCommentAddButton,
+ pressed && styles.diffCommentAddButtonPressed,
+ commentsBusy && styles.diffCommentButtonDisabled
+ ]}
+ disabled={commentsBusy}
+ onPress={() => {
+ if (commentLine !== undefined) onStartComment(commentLine)
+ }}
+ accessibilityLabel={`Add note on line ${commentLine}`}
+ >
+
+
+ ) : null}
+
+ {comments.length > 0 ? (
+
+ {comments.map((comment) => (
+
+
+
+ Line {comment.lineNumber}
+ onDeleteComment(comment.id)}
+ accessibilityLabel={`Delete note on line ${comment.lineNumber}`}
+ >
+
+
+
+ {comment.body}
+
+ ))}
+
+ ) : null}
+ {isCommenting ? (
+
+
+
+
+ Cancel
+
+ {
+ if (commentLine !== undefined) onSubmitComment(commentLine)
+ }}
+ >
+ Save note
+
+
+
+ ) : null}
)
}
@@ -582,12 +713,14 @@ function FileReader({
doc,
title,
relativePath,
- language
+ language,
+ diffCommentActions
}: {
doc: FileDocState | undefined
title: string
relativePath: string
language?: string
+ diffCommentActions?: DiffCommentActions
}) {
const syntaxLanguage = useMemo(
() => resolveMobileSyntaxLanguage(relativePath || title, language),
@@ -595,6 +728,8 @@ function FileReader({
)
const [fileSyntax, setFileSyntax] = useState(null)
const [diffSyntax, setDiffSyntax] = useState(null)
+ const [activeCommentLine, setActiveCommentLine] = useState(null)
+ const [commentDraft, setCommentDraft] = useState('')
const plainDiffLines = useMemo(
() =>
doc?.status === 'ready' && doc.kind === 'diff'
@@ -602,9 +737,80 @@ function FileReader({
: [],
[doc]
)
+ const diffCommentsForFile = useMemo(
+ () =>
+ diffCommentActions?.comments.filter(
+ (comment) => comment.filePath === relativePath && comment.source !== 'markdown'
+ ) ?? [],
+ [diffCommentActions?.comments, relativePath]
+ )
+ const diffCommentsByLine = useMemo(() => {
+ const map = new Map()
+ for (const comment of diffCommentsForFile) {
+ const list = map.get(comment.lineNumber) ?? []
+ list.push(comment)
+ map.set(comment.lineNumber, list)
+ }
+ for (const list of map.values()) {
+ list.sort((a, b) => a.createdAt - b.createdAt)
+ }
+ return map
+ }, [diffCommentsForFile])
+
+ const startComment = useCallback((lineNumber: number) => {
+ setActiveCommentLine(lineNumber)
+ setCommentDraft('')
+ }, [])
+
+ const cancelComment = useCallback(() => {
+ setActiveCommentLine(null)
+ setCommentDraft('')
+ }, [])
+
+ const submitComment = useCallback(
+ (lineNumber: number) => {
+ if (!diffCommentActions) return
+ void diffCommentActions.onAdd(relativePath, lineNumber, commentDraft).then((added) => {
+ if (added) {
+ setActiveCommentLine(null)
+ setCommentDraft('')
+ }
+ })
+ },
+ [commentDraft, diffCommentActions, relativePath]
+ )
+
const renderDiffLine: ListRenderItem = useCallback(
- ({ item, index }) => ,
- [title]
+ ({ item, index }) => (
+ {
+ if (diffCommentActions) void diffCommentActions.onDelete(commentId)
+ }}
+ />
+ ),
+ [
+ activeCommentLine,
+ cancelComment,
+ commentDraft,
+ diffCommentActions,
+ diffCommentsByLine,
+ startComment,
+ submitComment,
+ title
+ ]
)
useEffect(() => {
@@ -651,8 +857,52 @@ function FileReader({
if (doc.kind === 'diff') {
const activeDiffSyntax =
diffSyntax?.doc === doc && diffSyntax.language === syntaxLanguage ? diffSyntax.lines : null
+ const commentCount = diffCommentActions?.comments.length ?? 0
+ const unsentCommentCount =
+ diffCommentActions?.comments.filter((comment) => !comment.sentAt).length ?? 0
+ const commentsBusy = diffCommentActions?.busy === true
+ const canCopyNotes = commentCount > 0 && !commentsBusy
+ const canSendNotes = unsentCommentCount > 0 && !commentsBusy
return (
+ {diffCommentActions ? (
+
+
+
+
+ {commentCount === 0
+ ? 'No review notes'
+ : `${commentCount} review ${commentCount === 1 ? 'note' : 'notes'}`}
+
+
+
+ void diffCommentActions.onCopyAll()}
+ accessibilityLabel="Copy review notes"
+ >
+
+ Copy
+
+
+
+ Send
+
+
+
+ ) : null}
)
@@ -726,11 +977,18 @@ export default function SessionScreen() {
const [markdownDocs, setMarkdownDocs] = useState