From 0310b032bb7acf45bfe42415cbe7cda01a6dce5c Mon Sep 17 00:00:00 2001 From: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> Date: Thu, 11 Jun 2026 22:48:41 -0700 Subject: [PATCH 01/15] Redesign README for clarity and conversion (#5244) Co-authored-by: Orca --- README.md | 305 ++++++++++++++++++++++------------- docs/assets/readme-hero.jpg | Bin 0 -> 329737 bytes docs/readme/README.es.md | 307 ++++++++++++++++++++++++------------ docs/readme/README.ja.md | 307 ++++++++++++++++++++++++------------ docs/readme/README.ko.md | 307 ++++++++++++++++++++++++------------ docs/readme/README.zh-CN.md | 307 ++++++++++++++++++++++++------------ 6 files changed, 1017 insertions(+), 516 deletions(-) create mode 100644 docs/assets/readme-hero.jpg diff --git a/README.md b/README.md index 4f164b24ad6..fc2b643ac77 100644 --- a/README.md +++ b/README.md @@ -4,149 +4,230 @@

GitHub stars - Latest stable release + Total downloads across all releases License + Join the Orca Discord Supported platforms: macOS, Windows, and Linux

- English · Español · 中文 · 日本語 · 한국어 + Español · 中文 · 日本語 · 한국어

The AI Orchestrator for 100x builders.
- Run Claude Code, OpenClaude, Codex, Grok, Antigravity, or OpenCode side-by-side across repos — each in its own worktree, tracked in one place.
- Available for macOS, Windows, and Linux. + Run Claude Code, OpenClaude, Codex, or OpenCode side-by-side — each in its own worktree, tracked in one place.

+

Download Orca

+

- Download Orca + Orca desktop app running agents in parallel worktrees, with the Orca mobile companion app in the corner

-

- Orca feature showcase cycling through parallel worktrees, terminal splits, design mode, GitHub and Linear workflows, CLI agents, and SSH worktrees -

- -## Supported Agents - -Orca supports any CLI agent (_not just this list_). - -

- Claude Code   - OpenClaude   - Codex   - Grok   - Gemini   - Antigravity   - Pi   - oh-my-pi   - Hermes Agent   - OpenCode   - Goose   - Amp   - Auggie   - Autohand Code   - Charm   - Cline   - Codebuff   - Command Code   - Continue   - Cursor   - Droid   - GitHub Copilot   - Kilocode   - Kimi   - Kiro   - Mistral Vibe   - Qwen Code   - Rovo Dev -

- ---- - ## Features -**Run agents in parallel** + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
-- **Bring your own subscription** — Use Claude Code, OpenClaude, Codex, Grok, Antigravity, OpenCode, or any other CLI agent without an Orca login. -- **Worktree-native tasks** — Give each task its own worktree so agents can work side-by-side without stashing or branch juggling. -- **Multi-agent terminals** — Run agents in tabs and split panes, then see active, waiting, and finished sessions at a glance. +### Mobile Companion -**Review and ship** +Monitor and steer your agents from your phone — get notified when an agent finishes and send follow-ups from anywhere. -- **Agent-ready browser** — Open local apps, inspect pages, annotate UI, and pass precise browser context back to agents. -- **Design Mode** — Iterate on frontend changes with an embedded browser built for visual review and quick fixes. -- **Source control built in** — Review AI-generated diffs, make quick edits, and commit without leaving Orca. -- **GitHub workflow links** — Keep PRs, issues, and Actions checks connected to the worktree doing the work. +[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) · [Docs →](https://www.onorca.dev/docs/mobile) -**Work anywhere** + + Orca desktop with the mobile companion app +
-- **SSH support** — Connect to remote machines and run agents there directly from Orca. -- **Mobile companion** — Monitor and steer agents from your phone, with emulator-friendly mobile workflows. -- **Computer Use** — Let agents operate desktop apps and visible UI when a workflow needs real interaction. -- **Notifications and unread state** — Know when an agent finishes or needs attention, then mark threads unread to come back later. +### Parallel Worktrees + +Fan one prompt across five agents, each in its own isolated git worktree — compare the results and merge the winner. + +[Docs →](https://www.onorca.dev/docs/model/worktrees) + + + Parallel worktree orchestration +
+ +### Terminal Splits + +Ghostty-class terminals with WebGL rendering, infinite splits, and scrollback that survives restarts. + +[Docs →](https://www.onorca.dev/docs/terminal) + + + Terminal splits +
+ +### Design Mode + +Click any UI element in a real Chromium window to send its HTML, CSS, and a cropped screenshot straight into your agent's prompt. + +[Docs →](https://www.onorca.dev/docs/browser/design-mode) + + + Embedded browser and Design Mode +
+ +### GitHub & Linear, Native + +Browse PRs, issues, and project boards in-app — open a worktree from any task and review without a context switch. + +[Docs →](https://www.onorca.dev/docs/review/linear) + + + GitHub and Linear task workflows in Orca +
+ +### SSH Worktrees + +Run agents on a beefy remote box with full file editing, git, and terminals — auto-reconnect and port forwarding included. + +[Docs →](https://www.onorca.dev/docs/ssh) + + + Remote worktrees over SSH +
+ +### Annotate AI Diffs + +Drop comments on any diff line and ship them back to the agent — review, edit, and commit without leaving Orca. + +[Docs →](https://www.onorca.dev/docs/review/annotate-ai-diff) + + + Annotate AI-generated diffs +
+ +### Drag Files to Agents + +VS Code's editor with autosave everywhere — drag files or images straight into an agent prompt. + +[Docs →](https://www.onorca.dev/docs/editing/file-explorer) + + + Drag files and images into an agent prompt +
+ +### Orca CLI + +Agents drive Orca too — script every workflow with `orca worktree create`, `snapshot`, `click`, and `fill`. + +[Docs →](https://www.onorca.dev/docs/cli/overview) + + + Script Orca from the CLI +
+ +**Also in the box:** + +- **[Quick open](https://www.onorca.dev/docs/model/quick-open)** — Search across worktrees, files, agents, commands, and repo context without leaving your flow. +- **[Account switcher & usage tracking](https://www.onorca.dev/docs/agents/usage-tracking)** — See Claude and Codex usage and rate-limit resets, and hot-swap accounts without re-logging in. +- **[Rich repo previews](https://www.onorca.dev/docs/editing/markdown)** — Preview Markdown, images, PDFs, and repo docs in the workspace. +- **[Computer Use](https://www.onorca.dev/docs/cli/computer-use)** — Let agents operate desktop apps and visible UI when a workflow needs real interaction. +- **[Notifications and unread state](https://www.onorca.dev/docs/notifications)** — Know when an agent finishes or needs attention, then mark threads unread to come back later. +- **And many, many more** — we ship daily, so this list is perpetually behind. The [changelog](https://github.com/stablyai/orca/releases) is the real feature list. + +--- + +## Supported Agents + +Works with **any CLI agent** — if it runs in a terminal, it runs in Orca. + +

+ Claude Code logo Claude Code   + Codex logo Codex   + Grok logo Grok   + Gemini logo Gemini   + Cursor logo Cursor   + GitHub Copilot logo GitHub Copilot   + OpenCode logo OpenCode   + Amp logo Amp   + OpenClaude logo OpenClaude   + Antigravity logo Antigravity   + Pi logo Pi   + oh-my-pi logo oh-my-pi   + Hermes Agent logo Hermes Agent   + Goose logo Goose   + Auggie logo Auggie   + Autohand Code logo Autohand Code   + Charm logo Charm   + Cline logo Cline   + Codebuff logo Codebuff   + Command Code logo Command Code   + Continue logo Continue   + Droid logo Droid   + Kilocode logo Kilocode   + Kimi logo Kimi   + Kiro logo Kiro   + Mistral Vibe logo Mistral Vibe   + Qwen Code logo Qwen Code   + Rovo Dev logo Rovo Dev   + + any CLI agent +

--- ## Install -### Mac, Linux, Windows +### Desktop — macOS, Windows, Linux -- **[Download from onOrca.dev](https://onOrca.dev)** -- Or via **[GitHub Releases page](https://github.com/stablyai/orca/releases/latest)** +- **[Download from onOrca.dev](https://onorca.dev/download)** +- Or grab a build directly: [macOS Apple Silicon](https://github.com/stablyai/orca/releases/latest/download/orca-macos-arm64.dmg) · [macOS Intel](https://github.com/stablyai/orca/releases/latest/download/orca-macos-x64.dmg) · [Windows (.exe)](https://github.com/stablyai/orca/releases/latest/download/orca-windows-setup.exe) · [Linux AppImage](https://github.com/stablyai/orca/releases/latest/download/orca-linux.AppImage) · [All builds](https://github.com/stablyai/orca/releases/latest) -_Alternatively, install from a package manager:_ - -### macOS (Homebrew) +_Or via a package manager:_ ```bash +# macOS (Homebrew) brew install --cask stablyai/orca/orca -``` -### Arch Linux (AUR) - -```bash -# Precompiled binary +# Arch Linux (AUR) — or stably-orca-git to build from source yay -S stably-orca-bin - -# Build from GitHub source -yay -S stably-orca-git ``` ---- +### Mobile Companion — iOS, Android -## Mobile Companion App +Pair with your desktop app to monitor and steer your agents from your phone. -Control your agents from your phone. - -

- Orca desktop with the mobile companion app -

- -- **iOS:** [Download from App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) -- **Android:** [Download APK from GitHub Releases](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) - ---- - -## Feature Showcase - -Click any tile to explore the workflow. - -

- Parallel Worktrees

Parallel worktree orchestration
   - Terminal Splits

Ghostty-class terminal splits


- Design Mode

Embedded browser and Design Mode
   - GitHub & Linear, Native

GitHub and Linear task workflows in Orca


- Every CLI Agent

Works with every CLI agent
   - SSH Worktrees

Remote worktrees over SSH


- Drag Files to Agents

Drag files and images into an agent prompt
   - Annotate AI Diffs

Annotate AI-generated diffs


- Orca CLI

Script Orca from the CLI
   - Native Search

Native search across Orca workflows


- Account Switcher & Usage Tracking

Account switching and usage tracking
   - Rich Repo Previews

Markdown, images, PDFs, and repo document previews


- Split Anything

Split panes for agents, terminals, browsers, and files
-

+- **iOS:** [Download on the App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) +- **Android:** [Download the APK](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) --- @@ -155,11 +236,19 @@ Click any tile to explore the workflow. - **Discord:** Join the community on **[Discord](https://discord.gg/fzjDKHxv8Q)**. - **Twitter / X:** Follow **[@orca_build](https://x.com/orca_build)** for updates and announcements. - **Feedback & Ideas:** We ship fast. Missing something? [Request a new feature](https://github.com/stablyai/orca/issues). -- **Privacy:** See the [privacy & telemetry docs](https://www.onorca.dev/docs/telemetry) for what anonymous usage data Orca collects and how to opt out. -- **Show Support:** Star this repo to follow along with our daily ships. +- **Privacy:** See the [privacy & telemetry docs](https://www.onorca.dev/docs/telemetry) for what anonymous usage data Orca collects and how to opt out. +- **Show Support:** [Star](https://github.com/stablyai/orca) this repo to follow along with our daily ships. --- ## Developing Want to contribute or run locally? See our [CONTRIBUTING.md](.github/CONTRIBUTING.md) guide. + + + Orca contributors + + +## License + +Orca is free and open source under the [MIT License](LICENSE). diff --git a/docs/assets/readme-hero.jpg b/docs/assets/readme-hero.jpg new file mode 100644 index 0000000000000000000000000000000000000000..83d2c73cabc5e7719d40d64debaa4b0c92c4528c GIT binary patch literal 329737 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

- Plataformas compatibles - Discord - Seguir en X + Estrellas en GitHub + Descargas totales en todas las versiones + Licencia + Únete al Discord de Orca + Plataformas compatibles: macOS, Windows y Linux

- English · 中文 · 日本語 · 한국어 · Español + English · 中文 · 日本語 · 한국어

El orquestador de IA para desarrolladores 100x.
- 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. + Ejecuta Claude Code, OpenClaude, Codex u OpenCode en paralelo — cada uno en su propio worktree, supervisados desde un solo lugar.

+

Descargar Orca

+

- Descargar 🐋 + La app de escritorio de Orca ejecutando agentes en worktrees paralelos, con la app companion móvil de Orca en la esquina

-

- Captura de Orca -

- -## Agentes compatibles - -Orca es compatible con cualquier agente CLI (_no solo los de esta lista_). - -

- Claude Code   - OpenClaude   - Codex   - Grok   - Gemini   - Antigravity   - Pi   - oh-my-pi   - Hermes Agent   - OpenCode   - Goose   - Amp   - Auggie   - Autohand Code   - Charm   - Cline   - Codebuff   - Command Code   - Continue   - Cursor   - Droid   - GitHub Copilot   - Kilocode   - Kimi   - Kiro   - Mistral Vibe   - Qwen Code   - Rovo Dev -

- ---- - ## Características -- **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. -- **Integración con GitHub** — PRs, issues y checks de Actions vinculados automáticamente a cada worktree. -- **Soporte SSH** — Conéctate a máquinas remotas y ejecuta agentes en ellas directamente desde Orca. -- **Notificaciones** — Entérate cuando un agente termine o necesite tu atención. Marca hilos como no leídos para retomarlos después. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +### App companion móvil + +Supervisa y dirige a tus agentes desde el teléfono — recibe una notificación cuando un agente termine y envía instrucciones de seguimiento desde cualquier lugar. + +[App Store de iOS](https://apps.apple.com/us/app/orca-ide/id6766130217) · [APK para Android](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) · [Docs →](https://www.onorca.dev/docs/mobile) + + + Orca de escritorio con la app companion móvil +
+ +### Worktrees en paralelo + +Lanza un mismo prompt a cinco agentes, cada uno en su propio worktree de git aislado — compara los resultados y haz merge del ganador. + +[Docs →](https://www.onorca.dev/docs/model/worktrees) + + + Orquestación de worktrees en paralelo +
+ +### Terminales divididas + +Terminales de nivel Ghostty con renderizado WebGL, divisiones infinitas y un scrollback que sobrevive a los reinicios. + +[Docs →](https://www.onorca.dev/docs/terminal) + + + Terminales divididas +
+ +### Modo diseño + +Haz clic en cualquier elemento de UI en una ventana real de Chromium para enviar su HTML, su CSS y una captura recortada directo al prompt de tu agente. + +[Docs →](https://www.onorca.dev/docs/browser/design-mode) + + + Navegador integrado y modo diseño +
+ +### GitHub y Linear, nativos + +Explora PRs, issues y tableros de proyecto dentro de la app — abre un worktree desde cualquier tarea y revisa sin cambiar de contexto. + +[Docs →](https://www.onorca.dev/docs/review/linear) + + + Flujos de trabajo de GitHub y Linear en Orca +
+ +### Worktrees por SSH + +Ejecuta agentes en una máquina remota potente con edición completa de archivos, git y terminales — con reconexión automática y reenvío de puertos incluidos. + +[Docs →](https://www.onorca.dev/docs/ssh) + + + Worktrees remotos por SSH +
+ +### Anotar diffs de IA + +Deja comentarios en cualquier línea de un diff y envíalos de vuelta al agente — revisa, edita y haz commit sin salir de Orca. + +[Docs →](https://www.onorca.dev/docs/review/annotate-ai-diff) + + + Anotar diffs generados por IA +
+ +### Arrastra archivos a los agentes + +El editor de VS Code con autoguardado en todas partes — arrastra archivos o imágenes directo al prompt de un agente. + +[Docs →](https://www.onorca.dev/docs/editing/file-explorer) + + + Arrastra archivos e imágenes al prompt de un agente +
+ +### Orca CLI + +Los agentes también manejan Orca — automatiza cualquier flujo de trabajo con `orca worktree create`, `snapshot`, `click` y `fill`. + +[Docs →](https://www.onorca.dev/docs/cli/overview) + + + Automatiza Orca desde la CLI +
+ +**También incluye:** + +- **[Apertura rápida](https://www.onorca.dev/docs/model/quick-open)** — Busca entre worktrees, archivos, agentes, comandos y contexto del repo sin salir de tu flujo. +- **[Cambio de cuenta y seguimiento de uso](https://www.onorca.dev/docs/agents/usage-tracking)** — Consulta el uso de Claude y Codex y los reinicios de límites de uso, y cambia de cuenta al instante sin volver a iniciar sesión. +- **[Previews ricos del repo](https://www.onorca.dev/docs/editing/markdown)** — Previsualiza Markdown, imágenes, PDFs y documentos del repo en el workspace. +- **[Computer Use](https://www.onorca.dev/docs/cli/computer-use)** — Deja que los agentes manejen apps de escritorio y UI visible cuando un flujo de trabajo necesita interacción real. +- **[Notificaciones y estado de no leído](https://www.onorca.dev/docs/notifications)** — Entérate cuando un agente termine o necesite tu atención, y marca hilos como no leídos para retomarlos después. +- **Y muchas, muchas más** — lanzamos a diario, así que esta lista siempre va atrasada. El [changelog](https://github.com/stablyai/orca/releases) es la verdadera lista de funciones. + +--- + +## Agentes compatibles + +Funciona con **cualquier agente CLI** — si corre en una terminal, corre en Orca. + +

+ Claude Code logo Claude Code   + Codex logo Codex   + Grok logo Grok   + Gemini logo Gemini   + Cursor logo Cursor   + GitHub Copilot logo GitHub Copilot   + OpenCode logo OpenCode   + Amp logo Amp   + OpenClaude logo OpenClaude   + Antigravity logo Antigravity   + Pi logo Pi   + oh-my-pi logo oh-my-pi   + Hermes Agent logo Hermes Agent   + Goose logo Goose   + Auggie logo Auggie   + Autohand Code logo Autohand Code   + Charm logo Charm   + Cline logo Cline   + Codebuff logo Codebuff   + Command Code logo Command Code   + Continue logo Continue   + Droid logo Droid   + Kilocode logo Kilocode   + Kimi logo Kimi   + Kiro logo Kiro   + Mistral Vibe logo Mistral Vibe   + Qwen Code logo Qwen Code   + Rovo Dev logo Rovo Dev   + + any CLI agent +

--- ## Instalación -### Mac, Linux, Windows +### Escritorio — macOS, Windows, Linux -- **[Descarga desde onOrca.dev](https://onOrca.dev)** -- O desde la **[página de GitHub Releases](https://github.com/stablyai/orca/releases/latest)** +- **[Descarga desde onOrca.dev](https://onorca.dev/download)** +- O descarga un build directamente: [macOS Apple Silicon](https://github.com/stablyai/orca/releases/latest/download/orca-macos-arm64.dmg) · [macOS Intel](https://github.com/stablyai/orca/releases/latest/download/orca-macos-x64.dmg) · [Windows (.exe)](https://github.com/stablyai/orca/releases/latest/download/orca-windows-setup.exe) · [Linux AppImage](https://github.com/stablyai/orca/releases/latest/download/orca-linux.AppImage) · [Todos los builds](https://github.com/stablyai/orca/releases/latest) -_También puedes instalar desde un gestor de paquetes:_ - -### macOS (Homebrew) +_O mediante un gestor de paquetes:_ ```bash +# macOS (Homebrew) brew install --cask stablyai/orca/orca -``` -### Arch Linux (AUR) - -```bash -# Binario precompilado +# Arch Linux (AUR) — or stably-orca-git to build from source yay -S stably-orca-bin - -# Compilar desde el código de GitHub -yay -S stably-orca-git ``` ---- +### App companion móvil — iOS, Android -## App companion móvil - -Controla tus agentes desde el teléfono. - -

- Orca de escritorio con la app companion móvil -

+Vincúlala con tu app de escritorio para supervisar y dirigir a tus agentes desde el teléfono. - **iOS:** [Descargar desde App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) -- **Android:** [Descargar APK desde GitHub Releases](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) - ---- - -## Showcase de funciones - -Haz clic en cualquier tarjeta para explorar el flujo de trabajo. - -

- Worktrees en paralelo

Orquestación de worktrees en paralelo
   - Terminales divididas

Terminales divididas de nivel Ghostty


- Modo diseño

Navegador integrado y modo diseño
   - GitHub y Linear nativos

Flujos de GitHub y Linear en Orca


- Cualquier agente CLI

Compatible con cualquier agente CLI
   - Worktrees por SSH

Worktrees remotos por SSH


- Archivos a agentes

Arrastra archivos e imágenes al prompt de un agente
   - Anotar diffs de IA

Anotar diffs generados por IA


- Orca CLI

Automatiza Orca desde la CLI
   - Búsqueda nativa

Búsqueda nativa en los flujos de Orca


- Cambio de cuenta y seguimiento de uso

Cambio de cuenta y seguimiento de uso
   - Previews ricos del repo

Previsualización de Markdown, imágenes, PDFs y documentos del repo


- Divide cualquier cosa

Paneles divididos para agentes, terminales, navegadores y archivos
-

+- **Android:** [Descargar el APK](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) --- @@ -142,10 +236,19 @@ Haz clic en cualquier tarjeta para explorar el flujo de trabajo. - **Discord:** Únete a la comunidad en **[Discord](https://discord.gg/fzjDKHxv8Q)**. - **Twitter / X:** Sigue a **[@orca_build](https://x.com/orca_build)** para novedades y anuncios. - **Feedback e ideas:** Lanzamos rápido. ¿Te falta algo? [Pide una nueva feature](https://github.com/stablyai/orca/issues). -- **Muéstranos tu apoyo:** Dale una estrella al repo para seguir nuestros lanzamientos diarios. +- **Privacidad:** Consulta la [documentación de privacidad y telemetría](https://www.onorca.dev/docs/telemetry) para saber qué datos anónimos de uso recopila Orca y cómo desactivar su envío. +- **Muéstranos tu apoyo:** Dale una [estrella](https://github.com/stablyai/orca) a este repo para seguir nuestros lanzamientos diarios. --- ## Desarrollo -¿Quieres contribuir o ejecutar Orca localmente? Consulta nuestra guía [CONTRIBUTING.md](../.github/CONTRIBUTING.md). +¿Quieres contribuir o ejecutar Orca localmente? Consulta nuestra guía [CONTRIBUTING.md](../../.github/CONTRIBUTING.md). + + + Contribuidores de Orca + + +## Licencia + +Orca es libre y de código abierto bajo la [Licencia MIT](../../LICENSE). diff --git a/docs/readme/README.ja.md b/docs/readme/README.ja.md index 07ad4e312b4..fbb3d010d3e 100644 --- a/docs/readme/README.ja.md +++ b/docs/readme/README.ja.md @@ -3,137 +3,231 @@

- 対応プラットフォーム - Discord - X でフォロー + GitHub スター数 + 全リリースの合計ダウンロード数 + ライセンス + Orca の Discord に参加 + 対応プラットフォーム: macOS、Windows、Linux

- English · 中文 · 日本語 · 한국어 · Español + English · Español · 中文 · 한국어

100x ビルダーのための AI オーケストレーター。
- Claude Code、OpenClaude、Codex、Grok、Antigravity、OpenCode をリポジトリをまたいで並行実行 — それぞれを専用のワークツリーで動かし、1 か所で追跡できます。
- macOS、Windows、Linux で利用できます。 + Claude Code、OpenClaude、Codex、OpenCode を並べて実行 — それぞれを専用のワークツリーで動かし、1 か所で追跡できます。

+

Orca をダウンロード

+

- ダウンロード 🐋 + 並列ワークツリーでエージェントを実行する Orca デスクトップアプリと、隅に表示された Orca モバイル companion アプリ

-

- Orca Screenshot -

- -## 対応するエージェント - -Orca は任意の CLI エージェントに対応しています(_このリストに限定されません_)。 - -

- Claude Code   - OpenClaude   - Codex   - Grok   - Gemini   - Antigravity   - Pi   - oh-my-pi   - Hermes Agent   - OpenCode   - Goose   - Amp   - Auggie   - Autohand Code   - Charm   - Cline   - Codebuff   - Command Code   - Continue   - Cursor   - Droid   - GitHub Copilot   - Kilocode   - Kimi   - Kiro   - Mistral Vibe   - Qwen Code   - Rovo Dev -

- ---- - ## 機能 -- **ログイン不要** — お持ちの Claude Code、OpenClaude、Codex、Grok、Antigravity サブスクリプションをそのまま利用できます。 -- **ワークツリーネイティブ** — 各機能は専用のワークツリーで開発できます。スタッシュやブランチ切り替えに悩まず、すぐに作成して切り替えられます。 -- **マルチエージェントターミナル** — 複数の AI エージェントをタブやペインで並行実行できます。どれがアクティブかを一目で確認できます。 -- **組み込みソース管理** — AI が生成した Diff を確認し、すばやく編集して、Orca から離れずにコミットできます。 -- **GitHub 連携** — PR、Issue、Actions チェックが各ワークツリーに自動で紐づきます。 -- **SSH サポート** — リモートマシンに接続し、Orca から直接エージェントを実行できます。 -- **通知** — エージェントが完了したときや注意が必要なときに通知します。スレッドを未読にして後で戻ることもできます。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +### モバイル Companion + +スマートフォンからエージェントを監視・操作 — エージェントの完了を通知で受け取り、どこからでもフォローアップを送信できます。 + +[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) · [ドキュメント →](https://www.onorca.dev/docs/mobile) + + + Orca デスクトップとモバイル companion アプリ +
+ +### 並列ワークツリー + +1 つのプロンプトを 5 つのエージェントに展開し、それぞれを独立した git ワークツリーで実行 — 結果を比較して、最良のものをマージできます。 + +[ドキュメント →](https://www.onorca.dev/docs/model/worktrees) + + + 並列ワークツリーのオーケストレーション +
+ +### ターミナル分割 + +WebGL レンダリング、無制限の分割、再起動後も残るスクロールバックを備えた Ghostty クラスのターミナル。 + +[ドキュメント →](https://www.onorca.dev/docs/terminal) + + + ターミナル分割 +
+ +### デザインモード + +実際の Chromium ウィンドウで任意の UI 要素をクリックすると、その HTML、CSS、切り抜いたスクリーンショットがそのままエージェントのプロンプトに送られます。 + +[ドキュメント →](https://www.onorca.dev/docs/browser/design-mode) + + + 組み込みブラウザとデザインモード +
+ +### GitHub & Linear をネイティブに + +PR、Issue、プロジェクトボードをアプリ内で閲覧 — 任意のタスクからワークツリーを開き、コンテキストスイッチなしでレビューできます。 + +[ドキュメント →](https://www.onorca.dev/docs/review/linear) + + + Orca の GitHub と Linear タスクワークフロー +
+ +### SSH ワークツリー + +強力なリモートマシン上でエージェントを実行 — ファイル編集、git、ターミナルをフルに使え、自動再接続とポートフォワーディングも付属します。 + +[ドキュメント →](https://www.onorca.dev/docs/ssh) + + + SSH 経由のリモートワークツリー +
+ +### AI Diff に注釈 + +任意の Diff 行にコメントを付けてエージェントへ送り返せます — Orca から離れずにレビュー、編集、コミットまで完結します。 + +[ドキュメント →](https://www.onorca.dev/docs/review/annotate-ai-diff) + + + AI が生成した Diff への注釈 +
+ +### ファイルをエージェントへドラッグ + +オートセーブが全面的に効く VS Code のエディタ — ファイルや画像をそのままエージェントのプロンプトへドラッグできます。 + +[ドキュメント →](https://www.onorca.dev/docs/editing/file-explorer) + + + ファイルや画像をエージェントのプロンプトへドラッグ +
+ +### Orca CLI + +エージェント自身も Orca を操作できます — `orca worktree create`、`snapshot`、`click`、`fill` であらゆるワークフローをスクリプト化できます。 + +[ドキュメント →](https://www.onorca.dev/docs/cli/overview) + + + CLI から Orca をスクリプト操作 +
+ +**さらに同梱:** + +- **[クイックオープン](https://www.onorca.dev/docs/model/quick-open)** — フローを離れずに、ワークツリー、ファイル、エージェント、コマンド、リポジトリコンテキストを横断検索できます。 +- **[アカウント切り替えと使用量トラッキング](https://www.onorca.dev/docs/agents/usage-tracking)** — Claude と Codex の使用量やレート制限のリセットを確認し、再ログインなしでアカウントを切り替えられます。 +- **[リッチなリポジトリプレビュー](https://www.onorca.dev/docs/editing/markdown)** — Markdown、画像、PDF、リポジトリ文書をワークスペース内でプレビューできます。 +- **[Computer Use](https://www.onorca.dev/docs/cli/computer-use)** — 実際の操作が必要なワークフローでは、エージェントにデスクトップアプリや画面上の UI を操作させられます。 +- **[通知と未読ステータス](https://www.onorca.dev/docs/notifications)** — エージェントの完了や要対応をすぐに把握し、スレッドを未読に戻して後で確認できます。 +- **その他、まだまだたくさん** — 毎日リリースしているので、このリストは常に追いついていません。本当の機能一覧は[チェンジログ](https://github.com/stablyai/orca/releases)です。 + +--- + +## 対応するエージェント + +**あらゆる CLI エージェント**で動作します — ターミナルで動くものなら、Orca でも動きます。 + +

+ Claude Code logo Claude Code   + Codex logo Codex   + Grok logo Grok   + Gemini logo Gemini   + Cursor logo Cursor   + GitHub Copilot logo GitHub Copilot   + OpenCode logo OpenCode   + Amp logo Amp   + OpenClaude logo OpenClaude   + Antigravity logo Antigravity   + Pi logo Pi   + oh-my-pi logo oh-my-pi   + Hermes Agent logo Hermes Agent   + Goose logo Goose   + Auggie logo Auggie   + Autohand Code logo Autohand Code   + Charm logo Charm   + Cline logo Cline   + Codebuff logo Codebuff   + Command Code logo Command Code   + Continue logo Continue   + Droid logo Droid   + Kilocode logo Kilocode   + Kimi logo Kimi   + Kiro logo Kiro   + Mistral Vibe logo Mistral Vibe   + Qwen Code logo Qwen Code   + Rovo Dev logo Rovo Dev   + + any CLI agent +

--- ## インストール -### Mac, Linux, Windows +### デスクトップ — macOS, Windows, Linux -- **[onOrca.dev からダウンロード](https://onOrca.dev)** -- または **[GitHub Releases ページ](https://github.com/stablyai/orca/releases/latest)** から入手 +- **[onOrca.dev からダウンロード](https://onorca.dev/download)** +- またはビルドを直接入手: [macOS Apple Silicon](https://github.com/stablyai/orca/releases/latest/download/orca-macos-arm64.dmg) · [macOS Intel](https://github.com/stablyai/orca/releases/latest/download/orca-macos-x64.dmg) · [Windows (.exe)](https://github.com/stablyai/orca/releases/latest/download/orca-windows-setup.exe) · [Linux AppImage](https://github.com/stablyai/orca/releases/latest/download/orca-linux.AppImage) · [すべてのビルド](https://github.com/stablyai/orca/releases/latest) _パッケージマネージャーからもインストールできます:_ -### macOS (Homebrew) - ```bash +# macOS (Homebrew) brew install --cask stablyai/orca/orca -``` -### Arch Linux (AUR) - -```bash -# ビルド済みバイナリ +# Arch Linux (AUR) — or stably-orca-git to build from source yay -S stably-orca-bin - -# GitHub ソースからビルド -yay -S stably-orca-git ``` ---- +### モバイル Companion — iOS, Android -## モバイル Companion アプリ - -スマートフォンからエージェントを操作できます。 - -

- Orca デスクトップとモバイル companion アプリ -

+デスクトップアプリとペアリングして、スマートフォンからエージェントを監視・操作できます。 - **iOS:** [App Store からダウンロード](https://apps.apple.com/us/app/orca-ide/id6766130217) -- **Android:** [GitHub Releases から APK をダウンロード](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) - ---- - -## 機能ショーケース - -各タイルをクリックすると、そのワークフローを確認できます。 - -

- 並列ワークツリー

並列ワークツリーのオーケストレーション
   - ターミナル分割

Ghostty クラスのターミナル分割


- デザインモード

組み込みブラウザとデザインモード
   - GitHub と Linear をネイティブに

Orca の GitHub と Linear ワークフロー


- 任意の CLI エージェント

任意の CLI エージェントに対応
   - SSH ワークツリー

SSH 経由のリモートワークツリー


- ファイルをエージェントへ

ファイルや画像をエージェントのプロンプトへドラッグ
   - AI Diff 注釈

AI が生成した Diff への注釈


- Orca CLI

CLI から Orca をスクリプト操作
   - ネイティブ検索

Orca ワークフロー全体のネイティブ検索


- アカウント切り替えと使用量トラッキング

アカウント切り替えと使用量トラッキング
   - リッチなリポジトリプレビュー

Markdown、画像、PDF、リポジトリ文書のプレビュー


- 何でも分割表示

エージェント、ターミナル、ブラウザ、ファイルの分割表示
-

+- **Android:** [APK をダウンロード](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) --- @@ -141,11 +235,20 @@ yay -S stably-orca-git - **Discord:** **[Discord](https://discord.gg/fzjDKHxv8Q)** のコミュニティに参加してください。 - **Twitter / X:** アップデートやお知らせは **[@orca_build](https://x.com/orca_build)** をフォローしてください。 -- **フィードバックとアイデア:** 私たちは高速にリリースしています。足りない機能がありますか?[機能リクエストを送信](https://github.com/stablyai/orca/issues) してください。 -- **応援する:** 毎日のリリースを追うために、このリポジトリにスターを付けてください。 +- **フィードバックとアイデア:** 私たちは高速にリリースしています。足りない機能がありますか?[機能リクエストを送信](https://github.com/stablyai/orca/issues)してください。 +- **プライバシー:** Orca が収集する匿名の利用データとオプトアウトの方法については、[プライバシーとテレメトリーのドキュメント](https://www.onorca.dev/docs/telemetry)をご覧ください。 +- **応援する:** 毎日のリリースを追うために、このリポジトリに[スター](https://github.com/stablyai/orca)を付けてください。 --- ## 開発について -貢献したい、またはローカルで実行したいですか? [CONTRIBUTING.md](../.github/CONTRIBUTING.md) ガイドをご覧ください。 +貢献したい、またはローカルで実行したいですか? [CONTRIBUTING.md](../../.github/CONTRIBUTING.md) ガイドをご覧ください。 + + + Orca のコントリビューター + + +## ライセンス + +Orca は [MIT License](../../LICENSE) の下で無料かつオープンソースです。 diff --git a/docs/readme/README.ko.md b/docs/readme/README.ko.md index 6568f629885..bcd972e3da5 100644 --- a/docs/readme/README.ko.md +++ b/docs/readme/README.ko.md @@ -3,137 +3,231 @@

- 지원 플랫폼 - Discord - X에서 팔로우 + GitHub 스타 + 전체 릴리스 누적 다운로드 수 + 라이선스 + Orca Discord 참여 + 지원 플랫폼: macOS, Windows, Linux

- English · 中文 · 日本語 · 한국어 · Español + English · Español · 中文 · 日本語

100x 빌더를 위한 AI 오케스트레이터.
- Claude Code, OpenClaude, Codex, Grok, Antigravity, OpenCode를 여러 리포지토리에서 나란히 실행하세요. 각 에이전트는 자체 worktree에서 실행되고 한곳에서 추적됩니다.
- macOS, Windows, Linux에서 사용할 수 있습니다. + Claude Code, OpenClaude, Codex, OpenCode를 나란히 실행하세요 — 각 에이전트는 자체 worktree에서 실행되고 한곳에서 추적됩니다.

+

Orca 다운로드

+

- 다운로드 🐋 + 병렬 worktree에서 에이전트를 실행 중인 Orca 데스크톱 앱과 한쪽 모서리에 보이는 Orca 모바일 companion 앱

-

- Orca 스크린샷 -

- -## 지원 에이전트 - -Orca는 모든 CLI 에이전트를 지원합니다(_아래 목록에만 한정되지 않습니다_). - -

- Claude Code   - OpenClaude   - Codex   - Grok   - Gemini   - Antigravity   - Pi   - oh-my-pi   - Hermes Agent   - OpenCode   - Goose   - Amp   - Auggie   - Autohand Code   - Charm   - Cline   - Codebuff   - Command Code   - Continue   - Cursor   - Droid   - GitHub Copilot   - Kilocode   - Kimi   - Kiro   - Mistral Vibe   - Qwen Code   - Rovo Dev -

- ---- - ## 기능 -- **로그인 불필요** — 보유한 Claude Code, OpenClaude, Codex, Grok 또는 Antigravity 구독을 그대로 사용하세요. -- **Worktree 네이티브** — 모든 기능은 자체 worktree를 가집니다. stash나 브랜치 전환에 얽매이지 않고 즉시 만들고 전환할 수 있습니다. -- **멀티 에이전트 터미널** — 여러 AI 에이전트를 탭과 패널에서 나란히 실행하세요. 어떤 에이전트가 활성 상태인지 한눈에 볼 수 있습니다. -- **내장 소스 관리** — AI가 생성한 diff를 검토하고, 빠르게 수정하고, Orca를 떠나지 않고 커밋할 수 있습니다. -- **GitHub 통합** — PR, issue, Actions 체크가 각 worktree에 자동으로 연결됩니다. -- **SSH 지원** — 원격 머신에 연결하고 Orca에서 직접 에이전트를 실행할 수 있습니다. -- **알림** — 에이전트가 완료되거나 주의가 필요할 때 알려줍니다. 스레드를 읽지 않음으로 표시해 나중에 다시 볼 수 있습니다. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +### 모바일 Companion + +휴대폰에서 에이전트를 모니터링하고 조종하세요 — 에이전트가 완료되면 알림을 받고 어디서든 후속 지시를 보낼 수 있습니다. + +[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) · [문서 →](https://www.onorca.dev/docs/mobile) + + + Orca 데스크톱과 모바일 companion 앱 +
+ +### 병렬 Worktree + +하나의 프롬프트를 다섯 에이전트에 동시에 보내세요. 각 에이전트는 격리된 자체 git worktree에서 실행됩니다 — 결과를 비교하고 가장 좋은 것을 머지하세요. + +[문서 →](https://www.onorca.dev/docs/model/worktrees) + + + 병렬 worktree 오케스트레이션 +
+ +### 터미널 분할 + +WebGL 렌더링, 무한 분할, 재시작 후에도 유지되는 스크롤백을 갖춘 Ghostty급 터미널. + +[문서 →](https://www.onorca.dev/docs/terminal) + + + 터미널 분할 +
+ +### 디자인 모드 + +실제 Chromium 창에서 UI 요소를 클릭하면 해당 HTML, CSS, 잘라낸 스크린샷이 에이전트 프롬프트로 바로 전송됩니다. + +[문서 →](https://www.onorca.dev/docs/browser/design-mode) + + + 내장 브라우저와 디자인 모드 +
+ +### GitHub & Linear 네이티브 + +PR, issue, 프로젝트 보드를 앱 안에서 탐색하세요 — 어떤 작업에서든 worktree를 열고 컨텍스트 전환 없이 리뷰할 수 있습니다. + +[문서 →](https://www.onorca.dev/docs/review/linear) + + + Orca의 GitHub 및 Linear 작업 워크플로 +
+ +### SSH Worktree + +강력한 원격 머신에서 에이전트를 실행하세요. 파일 편집, git, 터미널을 모두 지원하며 자동 재연결과 포트 포워딩도 포함됩니다. + +[문서 →](https://www.onorca.dev/docs/ssh) + + + SSH를 통한 원격 worktree +
+ +### AI Diff 주석 + +diff의 어느 줄에든 코멘트를 남기고 에이전트에게 바로 보내세요 — Orca를 떠나지 않고 리뷰하고 수정하고 커밋할 수 있습니다. + +[문서 →](https://www.onorca.dev/docs/review/annotate-ai-diff) + + + AI가 생성한 diff에 주석 달기 +
+ +### 에이전트로 파일 드래그 + +어디서나 자동 저장되는 VS Code 에디터 — 파일이나 이미지를 에이전트 프롬프트로 바로 드래그하세요. + +[문서 →](https://www.onorca.dev/docs/editing/file-explorer) + + + 파일과 이미지를 에이전트 프롬프트로 드래그 +
+ +### Orca CLI + +에이전트도 Orca를 조작할 수 있습니다 — `orca worktree create`, `snapshot`, `click`, `fill`로 모든 워크플로를 스크립팅하세요. + +[문서 →](https://www.onorca.dev/docs/cli/overview) + + + CLI에서 Orca 스크립팅 +
+ +**그 밖에 기본으로 제공되는 기능:** + +- **[빠른 열기](https://www.onorca.dev/docs/model/quick-open)** — 작업 흐름을 벗어나지 않고 worktree, 파일, 에이전트, 커맨드, 리포지토리 컨텍스트를 검색하세요. +- **[계정 전환 및 사용량 추적](https://www.onorca.dev/docs/agents/usage-tracking)** — Claude와 Codex의 사용량과 rate limit 초기화 시점을 확인하고, 다시 로그인하지 않고 계정을 바로 전환하세요. +- **[풍부한 리포지토리 미리보기](https://www.onorca.dev/docs/editing/markdown)** — Markdown, 이미지, PDF, 리포지토리 문서를 워크스페이스에서 미리 볼 수 있습니다. +- **[Computer Use](https://www.onorca.dev/docs/cli/computer-use)** — 워크플로에 실제 상호작용이 필요할 때 에이전트가 데스크톱 앱과 화면에 보이는 UI를 직접 조작하게 하세요. +- **[알림과 읽지 않음 상태](https://www.onorca.dev/docs/notifications)** — 에이전트가 완료되거나 주의가 필요할 때 알림을 받고, 스레드를 읽지 않음으로 표시해 나중에 다시 확인하세요. +- **그리고 훨씬 더 많은 기능** — 매일 출시하기 때문에 이 목록은 항상 뒤처져 있습니다. 진짜 기능 목록은 [체인지로그](https://github.com/stablyai/orca/releases)입니다. + +--- + +## 지원 에이전트 + +**모든 CLI 에이전트**와 함께 작동합니다 — 터미널에서 실행되는 에이전트라면 Orca에서도 실행됩니다. + +

+ Claude Code logo Claude Code   + Codex logo Codex   + Grok logo Grok   + Gemini logo Gemini   + Cursor logo Cursor   + GitHub Copilot logo GitHub Copilot   + OpenCode logo OpenCode   + Amp logo Amp   + OpenClaude logo OpenClaude   + Antigravity logo Antigravity   + Pi logo Pi   + oh-my-pi logo oh-my-pi   + Hermes Agent logo Hermes Agent   + Goose logo Goose   + Auggie logo Auggie   + Autohand Code logo Autohand Code   + Charm logo Charm   + Cline logo Cline   + Codebuff logo Codebuff   + Command Code logo Command Code   + Continue logo Continue   + Droid logo Droid   + Kilocode logo Kilocode   + Kimi logo Kimi   + Kiro logo Kiro   + Mistral Vibe logo Mistral Vibe   + Qwen Code logo Qwen Code   + Rovo Dev logo Rovo Dev   + + any CLI agent +

--- ## 설치 -### Mac, Linux, Windows +### 데스크톱 — macOS, Windows, Linux -- **[onOrca.dev에서 다운로드](https://onOrca.dev)** -- 또는 **[GitHub Releases 페이지](https://github.com/stablyai/orca/releases/latest)** 에서 받기 +- **[onOrca.dev에서 다운로드](https://onorca.dev/download)** +- 또는 빌드를 직접 받기: [macOS Apple Silicon](https://github.com/stablyai/orca/releases/latest/download/orca-macos-arm64.dmg) · [macOS Intel](https://github.com/stablyai/orca/releases/latest/download/orca-macos-x64.dmg) · [Windows (.exe)](https://github.com/stablyai/orca/releases/latest/download/orca-windows-setup.exe) · [Linux AppImage](https://github.com/stablyai/orca/releases/latest/download/orca-linux.AppImage) · [전체 빌드](https://github.com/stablyai/orca/releases/latest) -_패키지 매니저로도 설치할 수 있습니다:_ - -### macOS (Homebrew) +_또는 패키지 매니저로 설치:_ ```bash +# macOS (Homebrew) brew install --cask stablyai/orca/orca -``` -### Arch Linux (AUR) - -```bash -# 사전 컴파일된 바이너리 +# Arch Linux (AUR) — or stably-orca-git to build from source yay -S stably-orca-bin - -# GitHub 소스에서 빌드 -yay -S stably-orca-git ``` ---- +### 모바일 Companion — iOS, Android -## 모바일 Companion 앱 - -휴대폰에서 에이전트를 제어하세요. - -

- Orca 데스크톱과 모바일 companion 앱 -

+데스크톱 앱과 페어링해 휴대폰에서 에이전트를 모니터링하고 조종하세요. - **iOS:** [App Store에서 다운로드](https://apps.apple.com/us/app/orca-ide/id6766130217) -- **Android:** [GitHub Releases에서 APK 다운로드](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) - ---- - -## 기능 쇼케이스 - -타일을 클릭해 각 워크플로를 살펴보세요. - -

- 병렬 Worktree

병렬 worktree 오케스트레이션
   - 터미널 분할

Ghostty급 터미널 분할


- 디자인 모드

내장 브라우저와 디자인 모드
   - GitHub 및 Linear 네이티브

Orca의 GitHub 및 Linear 워크플로


- 모든 CLI 에이전트

모든 CLI 에이전트 지원
   - SSH Worktree

SSH를 통한 원격 worktree


- 에이전트로 파일 드래그

파일과 이미지를 에이전트 프롬프트로 드래그
   - AI Diff 주석

AI가 생성한 diff에 주석 달기


- Orca CLI

CLI에서 Orca 스크립팅
   - 네이티브 검색

Orca 워크플로 전반의 네이티브 검색


- 계정 전환 및 사용량 추적

계정 전환 및 사용량 추적
   - 풍부한 리포지토리 미리보기

Markdown, 이미지, PDF, 리포지토리 문서 미리보기


- 무엇이든 분할

에이전트, 터미널, 브라우저, 파일을 위한 분할 패널
-

+- **Android:** [APK 다운로드](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) --- @@ -142,10 +236,19 @@ yay -S stably-orca-git - **Discord:** **[Discord](https://discord.gg/fzjDKHxv8Q)** 커뮤니티에 참여하세요. - **Twitter / X:** 업데이트와 공지는 **[@orca_build](https://x.com/orca_build)** 를 팔로우하세요. - **피드백과 아이디어:** 우리는 빠르게 출시합니다. 필요한 기능이 있나요? [새 기능을 요청](https://github.com/stablyai/orca/issues)하세요. -- **응원하기:** 이 리포지토리에 star를 눌러 일일 릴리스를 따라와 주세요. +- **개인정보 보호:** Orca가 수집하는 익명 사용 데이터와 수집 거부 방법은 [개인정보 및 텔레메트리 문서](https://www.onorca.dev/docs/telemetry)를 참고하세요. +- **응원하기:** 이 리포지토리에 [Star](https://github.com/stablyai/orca)를 눌러 매일의 릴리스를 따라와 주세요. --- ## 개발 -기여하거나 로컬에서 실행하고 싶으신가요? [CONTRIBUTING.md](../.github/CONTRIBUTING.md) 가이드를 확인하세요. +기여하거나 로컬에서 실행하고 싶으신가요? [CONTRIBUTING.md](../../.github/CONTRIBUTING.md) 가이드를 확인하세요. + + + Orca 기여자 + + +## 라이선스 + +Orca는 [MIT 라이선스](../../LICENSE)에 따라 자유롭게 사용할 수 있는 오픈 소스입니다. diff --git a/docs/readme/README.zh-CN.md b/docs/readme/README.zh-CN.md index 5615bf81cd0..5e8fac5b266 100644 --- a/docs/readme/README.zh-CN.md +++ b/docs/readme/README.zh-CN.md @@ -3,149 +3,252 @@

- 支持的平台 - Discord - 在 X 上关注 + GitHub Star 数 + 所有版本的总下载量 + 许可证 + 加入 Orca Discord + 支持的平台:macOS、Windows 和 Linux

- English · 中文 · 日本語 · 한국어 · Español + English · Español · 日本語 · 한국어

面向 100x 构建者的 AI 编排器。
- 跨仓库并排运行 Claude Code、OpenClaude、Codex、Grok、Antigravity 或 OpenCode — 每个都在自己的 worktree 中运行,并在一个地方统一跟踪。
- 支持 macOS、Windows 和 Linux。 + 并排运行 Claude Code、OpenClaude、Codex 或 OpenCode — 每个都在自己的 worktree 中运行,并在一个地方统一跟踪。

+

下载 Orca

+

- 下载 🐋 + Orca 桌面应用在并行 worktree 中运行智能体,角落里是 Orca 移动 companion 应用

-

- Orca Screenshot -

- -## 支持的智能体 - -Orca 支持任何 CLI 智能体(_不仅限于以下列表_)。 - -

- Claude Code   - OpenClaude   - Codex   - Grok   - Gemini   - Antigravity   - Pi   - oh-my-pi   - Hermes Agent   - OpenCode   - Goose   - Amp   - Auggie   - Autohand Code   - Charm   - Cline   - Codebuff   - Command Code   - Continue   - Cursor   - Droid   - GitHub Copilot   - Kilocode   - Kimi   - Kiro   - Mistral Vibe   - Qwen Code   - Rovo Dev -

- ---- - ## 特性 -- **无需登录** — 直接使用你自己的 Claude Code、OpenClaude、Codex、Grok 或 Antigravity 订阅。 -- **原生 worktree 工作流** — 每个功能都有自己的 worktree。无需 stash,也不用来回切分支。立即创建,快速切换。 -- **多智能体终端** — 在标签页和面板中并排运行多个 AI 智能体。一眼就能看到哪些正在活跃。 -- **内置源码管理** — 查看 AI 生成的 diff,快速编辑,并且无需离开 Orca 就能提交。 -- **GitHub 集成** — PR、issue 和 Actions 检查会自动链接到对应的 worktree。 -- **SSH 支持** — 连接远程机器,并直接从 Orca 在远程机器上运行智能体。 -- **通知** — 智能体完成任务或需要关注时及时通知你。可将会话标记为未读,方便稍后返回处理。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +### 移动 Companion 应用 + +用手机监控并指挥你的智能体 — 智能体完成时收到通知,随时随地发送后续指令。 + +[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [Android APK](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) · [文档 →](https://www.onorca.dev/docs/mobile) + + + Orca 桌面端与移动 companion 应用 +
+ +### 并行 Worktree + +把一个提示同时分发给五个智能体,每个都在自己隔离的 git worktree 中运行 — 比较结果,合并最佳方案。 + +[文档 →](https://www.onorca.dev/docs/model/worktrees) + + + 并行 worktree 编排 +
+ +### 终端分屏 + +Ghostty 级终端,支持 WebGL 渲染、无限分屏,以及重启后依然保留的滚动历史。 + +[文档 →](https://www.onorca.dev/docs/terminal) + + + 终端分屏 +
+ +### 设计模式 + +在真实的 Chromium 窗口中点击任意 UI 元素,把它的 HTML、CSS 和裁剪好的截图直接发送到智能体的提示中。 + +[文档 →](https://www.onorca.dev/docs/browser/design-mode) + + + 内置浏览器与设计模式 +
+ +### GitHub & Linear 原生集成 + +在应用内浏览 PR、issue 和项目看板 — 从任意任务打开 worktree,无需切换上下文即可完成评审。 + +[文档 →](https://www.onorca.dev/docs/review/linear) + + + Orca 中的 GitHub 与 Linear 任务工作流 +
+ +### SSH Worktree + +在高性能远程机器上运行智能体,完整支持文件编辑、git 和终端 — 自动重连与端口转发一应俱全。 + +[文档 →](https://www.onorca.dev/docs/ssh) + + + 通过 SSH 使用远程 worktree +
+ +### 标注 AI Diff + +在任意 diff 行上添加评论并发回给智能体 — 评审、编辑、提交,全程无需离开 Orca。 + +[文档 →](https://www.onorca.dev/docs/review/annotate-ai-diff) + + + 标注 AI 生成的 diff +
+ +### 拖文件给智能体 + +VS Code 的编辑器,处处自动保存 — 把文件或图片直接拖入智能体提示。 + +[文档 →](https://www.onorca.dev/docs/editing/file-explorer) + + + 将文件和图片拖入智能体提示 +
+ +### Orca CLI + +智能体也能驱动 Orca — 用 `orca worktree create`、`snapshot`、`click` 和 `fill` 把每个工作流脚本化。 + +[文档 →](https://www.onorca.dev/docs/cli/overview) + + + 从 CLI 脚本化 Orca +
+ +**开箱即用的还有:** + +- **[快速打开](https://www.onorca.dev/docs/model/quick-open)** — 在 worktree、文件、智能体、命令和仓库上下文之间搜索,不打断你的心流。 +- **[账号切换与用量追踪](https://www.onorca.dev/docs/agents/usage-tracking)** — 查看 Claude 和 Codex 的用量与限额重置时间,并且无需重新登录即可热切换账号。 +- **[丰富仓库预览](https://www.onorca.dev/docs/editing/markdown)** — 在工作区中预览 Markdown、图片、PDF 和仓库文档。 +- **[Computer Use](https://www.onorca.dev/docs/cli/computer-use)** — 当工作流需要真实交互时,让智能体操作桌面应用和可见 UI。 +- **[通知与未读状态](https://www.onorca.dev/docs/notifications)** — 第一时间知道智能体何时完成或需要关注,并可将会话标记为未读,稍后再回来处理。 +- **还有很多很多** — 我们每天发布新功能,这个列表永远跟不上。[更新日志](https://github.com/stablyai/orca/releases)才是真正的功能列表。 + +--- + +## 支持的智能体 + +适配**任何 CLI 智能体** — 只要能在终端里运行,就能在 Orca 里运行。 + +

+ Claude Code logo Claude Code   + Codex logo Codex   + Grok logo Grok   + Gemini logo Gemini   + Cursor logo Cursor   + GitHub Copilot logo GitHub Copilot   + OpenCode logo OpenCode   + Amp logo Amp   + OpenClaude logo OpenClaude   + Antigravity logo Antigravity   + Pi logo Pi   + oh-my-pi logo oh-my-pi   + Hermes Agent logo Hermes Agent   + Goose logo Goose   + Auggie logo Auggie   + Autohand Code logo Autohand Code   + Charm logo Charm   + Cline logo Cline   + Codebuff logo Codebuff   + Command Code logo Command Code   + Continue logo Continue   + Droid logo Droid   + Kilocode logo Kilocode   + Kimi logo Kimi   + Kiro logo Kiro   + Mistral Vibe logo Mistral Vibe   + Qwen Code logo Qwen Code   + Rovo Dev logo Rovo Dev   + + 任何 CLI 智能体 +

--- ## 安装 -### Mac, Linux, Windows +### 桌面端 — macOS、Windows、Linux -- **[从 onOrca.dev 下载](https://onOrca.dev)** -- 或通过 **[GitHub Releases 页面](https://github.com/stablyai/orca/releases/latest)** 获取 +- **[从 onOrca.dev 下载](https://onorca.dev/download)** +- 或直接获取安装包:[macOS Apple Silicon](https://github.com/stablyai/orca/releases/latest/download/orca-macos-arm64.dmg) · [macOS Intel](https://github.com/stablyai/orca/releases/latest/download/orca-macos-x64.dmg) · [Windows (.exe)](https://github.com/stablyai/orca/releases/latest/download/orca-windows-setup.exe) · [Linux AppImage](https://github.com/stablyai/orca/releases/latest/download/orca-linux.AppImage) · [全部构建](https://github.com/stablyai/orca/releases/latest) _也可以通过包管理器安装:_ -### macOS (Homebrew) - ```bash +# macOS (Homebrew) brew install --cask stablyai/orca/orca -``` -### Arch Linux (AUR) - -```bash -# 预编译二进制 +# Arch Linux (AUR) — or stably-orca-git to build from source yay -S stably-orca-bin - -# 从 GitHub 源码构建 -yay -S stably-orca-git ``` ---- +### 移动 Companion 应用 — iOS、Android -## 移动 Companion 应用 - -用手机控制你的智能体。 - -

- Orca 桌面端与移动 companion 应用 -

+与桌面应用配对,用手机监控并指挥你的智能体。 - **iOS:** [从 App Store 下载](https://apps.apple.com/us/app/orca-ide/id6766130217) -- **Android:** [从 GitHub Releases 下载 APK](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) - ---- - -## 功能展示 - -点击任意卡片了解对应工作流。 - -

- 并行 Worktree

并行 worktree 编排
   - 终端分屏

Ghostty 级终端分屏


- 设计模式

内置浏览器与设计模式
   - GitHub 与 Linear 原生集成

Orca 中的 GitHub 与 Linear 工作流


- 任意 CLI 智能体

支持任意 CLI 智能体
   - SSH Worktree

通过 SSH 使用远程 worktree


- 拖文件给智能体

将文件和图片拖入智能体提示
   - 标注 AI Diff

标注 AI 生成的 diff


- Orca CLI

从 CLI 脚本化 Orca
   - 原生搜索

贯穿 Orca 工作流的原生搜索


- 账号切换与用量追踪

账号切换与用量追踪
   - 丰富仓库预览

Markdown、图片、PDF 和仓库文档预览


- 任意分屏

为智能体、终端、浏览器和文件分屏
-

+- **Android:** [下载 APK](https://github.com/stablyai/orca/releases/download/mobile-v0.0.12/app-release.apk) --- ## 社区与支持 -- **Discord:** 加入我们的 **[Discord](https://discord.gg/fzjDKHxv8Q)** 社区。 +- **Discord:** 加入 **[Discord](https://discord.gg/fzjDKHxv8Q)** 社区。 - **Twitter / X:** 关注 **[@orca_build](https://x.com/orca_build)** 获取更新和公告。 - **反馈与想法:** 我们发布很快。缺少什么功能?[提交功能请求](https://github.com/stablyai/orca/issues)。 -- **支持我们:** 给这个仓库点 Star,关注我们的日常发布。 +- **隐私:** 查看[隐私与遥测文档](https://www.onorca.dev/docs/telemetry),了解 Orca 收集哪些匿名使用数据以及如何退出。 +- **支持我们:** 给这个仓库点 [Star](https://github.com/stablyai/orca),关注我们的日常发布。 --- ## 开发 -想要贡献代码或在本地运行?请参阅我们的 [CONTRIBUTING.md](../.github/CONTRIBUTING.md) 指南。 +想要贡献代码或在本地运行?请参阅我们的 [CONTRIBUTING.md](../../.github/CONTRIBUTING.md) 指南。 + + + Orca 贡献者 + + +## 许可证 + +Orca 是自由且开源的软件,遵循 [MIT 许可证](../../LICENSE)。 From 2899a60872de62645524c7d8c179a12404852288 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Thu, 11 Jun 2026 23:36:46 -0700 Subject: [PATCH 02/15] Durable persistence for commit message generation across worktree switches (#5243) * Persist commit message generation state across worktree switches Introduce a new global store slice for commit message generation to prevent in-flight requests from being orphaned or lost when the Source Control sidebar is unmounted or when switching between worktrees. - Key commit generation records by worktree ID/path in the Zustand store - Hydrate completed messages back into the draft input when returning to the originating worktree without overwriting active user edits - Ensure the agent-selection dialog state remains bound to the target worktree that initiated the generation - Wait for the PR composer eligibility check before hydrating completed PR field generations on sidebar remounts * Route cancellations to starting runtime and prune stale worktree records Ensure commit message and pull request generation cancellation requests are always routed to the runtime environment that was selected when the generation started, even if active settings change before cancellation. Additionally, prune commit message and pull request generation records when worktrees are removed to prevent stale records from lingering in the store. --- ...eControl.commit-generation-records.test.ts | 158 ++++++ ...ourceControl.pr-generation-records.test.ts | 52 +- .../right-sidebar/SourceControl.tsx | 516 ++++++++++++++---- src/renderer/src/store/index.ts | 4 +- .../store/slices/commit-message-generation.ts | 189 +++++++ .../src/store/slices/diffComments.test.ts | 4 +- .../store/slices/pull-request-generation.ts | 25 + .../src/store/slices/store-test-helpers.ts | 4 +- src/renderer/src/store/types.ts | 4 +- ...ource-control-pr-generation-switch.spec.ts | 128 +++++ 10 files changed, 967 insertions(+), 117 deletions(-) create mode 100644 src/renderer/src/components/right-sidebar/SourceControl.commit-generation-records.test.ts create mode 100644 src/renderer/src/store/slices/commit-message-generation.ts diff --git a/src/renderer/src/components/right-sidebar/SourceControl.commit-generation-records.test.ts b/src/renderer/src/components/right-sidebar/SourceControl.commit-generation-records.test.ts new file mode 100644 index 00000000000..7581d393ade --- /dev/null +++ b/src/renderer/src/components/right-sidebar/SourceControl.commit-generation-records.test.ts @@ -0,0 +1,158 @@ +import { describe, expect, it } from 'vitest' +import { create } from 'zustand' +import { + createCommitMessageGenerationSlice, + createRunningCommitMessageGenerationRecord, + getCommitMessageGenerationRecordKey, + markCommitMessageGenerationHydrated, + resolveCommitMessageGenerationCancel, + resolveCommitMessageGenerationFailure, + resolveCommitMessageGenerationSuccess, + type CommitMessageGenerationRecord, + type CommitMessageGenerationSlice +} from '@/store/slices/commit-message-generation' + +function runningRecord(overrides: Partial = {}) { + return { + context: { + worktreeId: 'wt-a', + worktreePath: '/repo/a', + connectionId: 'conn-a', + requestId: 3 + }, + status: 'running' as const, + message: null, + error: null, + hydrated: false, + ...overrides + } +} + +function createCommitMessageGenerationTestStore() { + return create()((...args) => + createCommitMessageGenerationSlice( + ...(args as unknown as Parameters) + ) + ) +} + +describe('SourceControl commit message generation records', () => { + it('keys commit-message generation by worktree id and falls back to path', () => { + expect(getCommitMessageGenerationRecordKey('wt-a', '/repo/a')).toBe('wt-a') + expect(getCommitMessageGenerationRecordKey(null, '/repo/a')).toBe('/repo/a') + expect(getCommitMessageGenerationRecordKey(null, '')).toBeNull() + }) + + it('applies generated messages only to the original running request', () => { + expect( + resolveCommitMessageGenerationSuccess({ + record: runningRecord(), + requestId: 3, + message: 'feat: generated' + }) + ).toMatchObject({ + status: 'succeeded', + message: 'feat: generated', + hydrated: false + }) + + expect( + resolveCommitMessageGenerationSuccess({ + record: runningRecord(), + requestId: 4, + message: 'feat: stale' + }) + ).toBeNull() + + expect( + resolveCommitMessageGenerationSuccess({ + record: runningRecord({ status: 'canceled' }), + requestId: 3, + message: 'feat: stale' + }) + ).toBeNull() + }) + + it('preserves cancellation over later generator resolution', () => { + const canceled = resolveCommitMessageGenerationCancel(runningRecord()) + + expect(canceled).toMatchObject({ + status: 'canceled', + error: null + }) + expect( + resolveCommitMessageGenerationFailure({ + record: canceled, + requestId: 3, + canceled: true, + error: null + }) + ).toBeNull() + }) + + it('marks completed messages as hydrated once the UI consumes them', () => { + const hydrated = markCommitMessageGenerationHydrated( + runningRecord({ + status: 'succeeded', + message: 'docs: generated' + }) + ) + + expect(hydrated).toMatchObject({ + status: 'succeeded', + message: 'docs: generated', + hydrated: true + }) + }) + + it('stores running records in the generation slice', () => { + const store = createCommitMessageGenerationTestStore() + const key = getCommitMessageGenerationRecordKey('wt-a', '/repo/a')! + const requestId = store.getState().allocateCommitMessageGenerationRequestId() + + store.getState().setCommitMessageGenerationRecord( + key, + createRunningCommitMessageGenerationRecord({ + worktreeId: 'wt-a', + worktreePath: '/repo/a', + connectionId: 'conn-a', + requestId, + runtimeTargetSettings: { activeRuntimeEnvironmentId: 'runtime-a' } + }) + ) + + expect(store.getState().commitMessageGenerationRecords[key]).toMatchObject({ + context: { + requestId, + worktreeId: 'wt-a', + worktreePath: '/repo/a', + runtimeTargetSettings: { activeRuntimeEnvironmentId: 'runtime-a' } + }, + status: 'running' + }) + }) + + it('prunes commit generation records for removed worktrees', () => { + const store = createCommitMessageGenerationTestStore() + store.getState().setCommitMessageGenerationRecord( + 'wt-a', + createRunningCommitMessageGenerationRecord({ + worktreeId: 'wt-a', + worktreePath: '/repo/a', + requestId: 1 + }) + ) + store.getState().setCommitMessageGenerationRecord( + 'wt-b', + createRunningCommitMessageGenerationRecord({ + worktreeId: 'wt-b', + worktreePath: '/repo/b', + requestId: 2 + }) + ) + + store.getState().pruneCommitMessageGenerationRecords(new Set(['wt-a'])) + + expect(Object.keys(store.getState().commitMessageGenerationRecords)).toEqual(['wt-a']) + }) +}) diff --git a/src/renderer/src/components/right-sidebar/SourceControl.pr-generation-records.test.ts b/src/renderer/src/components/right-sidebar/SourceControl.pr-generation-records.test.ts index fe1916328a1..aabaf5e5e8b 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.pr-generation-records.test.ts +++ b/src/renderer/src/components/right-sidebar/SourceControl.pr-generation-records.test.ts @@ -186,7 +186,8 @@ describe('SourceControl pull request generation records', () => { connectionId: 'conn-a', requestId: 1, repoId: 'repo-1', - branch: 'feature-a' + branch: 'feature-a', + runtimeTargetSettings: { activeRuntimeEnvironmentId: 'runtime-a' } }, seed, fieldRevisions @@ -208,12 +209,61 @@ describe('SourceControl pull request generation records', () => { ) expect(store.getState().pullRequestGenerationRecords[key!]).toMatchObject({ + context: { runtimeTargetSettings: { activeRuntimeEnvironmentId: 'runtime-a' } }, status: 'succeeded', result: generated, hydrated: false }) }) + it('prunes PR generation records for removed worktrees', () => { + const store = createPullRequestGenerationTestStore() + const keyA = getPullRequestGenerationRecordKey({ + worktreeId: 'wt-a', + worktreePath: '/repo/a', + repoId: 'repo-1', + branch: 'feature-a' + })! + const keyB = getPullRequestGenerationRecordKey({ + worktreeId: 'wt-b', + worktreePath: '/repo/b', + repoId: 'repo-1', + branch: 'feature-b' + })! + store.getState().setPullRequestGenerationRecord( + keyA, + createRunningPullRequestGenerationRecord( + { + worktreeId: 'wt-a', + worktreePath: '/repo/a', + requestId: 1, + repoId: 'repo-1', + branch: 'feature-a' + }, + seed, + fieldRevisions + ) + ) + store.getState().setPullRequestGenerationRecord( + keyB, + createRunningPullRequestGenerationRecord( + { + worktreeId: 'wt-b', + worktreePath: '/repo/b', + requestId: 2, + repoId: 'repo-1', + branch: 'feature-b' + }, + seed, + fieldRevisions + ) + ) + + store.getState().prunePullRequestGenerationRecords(new Set(['wt-a'])) + + expect(Object.keys(store.getState().pullRequestGenerationRecords)).toEqual([keyA]) + }) + it('does not reuse PR generation request ids across composer remounts', () => { const store = createPullRequestGenerationTestStore() const key = getPullRequestGenerationRecordKey({ diff --git a/src/renderer/src/components/right-sidebar/SourceControl.tsx b/src/renderer/src/components/right-sidebar/SourceControl.tsx index 83be74d11ca..a93c5f611d9 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControl.tsx @@ -155,6 +155,8 @@ import type { GitBranchCompareSummary, GitConflictOperation, GitStatusEntry, + GlobalSettings, + Repo, SourceControlViewMode, TuiAgent } from '../../../../shared/types' @@ -207,6 +209,14 @@ import { type PullRequestGenerationContext, type PullRequestGenerationFields } from '@/store/slices/pull-request-generation' +import { + createRunningCommitMessageGenerationRecord, + getCommitMessageGenerationRecordKey, + markCommitMessageGenerationHydrated, + resolveCommitMessageGenerationCancel, + resolveCommitMessageGenerationFailure, + resolveCommitMessageGenerationSuccess +} from '@/store/slices/commit-message-generation' export { appendCommitFailureCustomInstruction, @@ -223,6 +233,30 @@ export { export type SourceControlScope = 'all' | 'uncommitted' type AbortConflictOperation = Extract type AbortActionErrorKind = 'abort_merge' | 'abort_rebase' +type CommitMessageGenerationTargetSnapshot = { + worktreeId: string + worktreePath: string + connectionId?: string + repo: Pick | null + settings: GlobalSettings | null + discoveryHostKey: string +} +type CommitMessageGenerationOptions = RuntimeGenerateCommitMessageOverrides & { + target?: CommitMessageGenerationTargetSnapshot +} +type PullRequestGenerationTargetSnapshot = { + worktreeId: string | null + worktreePath: string + connectionId?: string + repo: Pick | null + repoId: string + branch: string + settings: GlobalSettings | null + discoveryHostKey: string + fields: PullRequestGenerationFields + fieldRevisions: PullRequestFieldRevisions +} + export type SourceControlActionError = { kind: RemoteOpKind | AbortActionErrorKind message: string @@ -844,15 +878,6 @@ function SourceControlInner(): React.JSX.Element { const isAbortingOperation = abortOperationInFlightByWorktree[activeWorktreeId ?? ''] ?? false const confirmAction = useConfirmationDialog() const isCommitting = commitInFlightByWorktree[activeWorktreeId ?? ''] ?? false - // Why: parallel state to commit. Same per-worktree shape so navigating between - // worktrees mid-generation never silently cancels the in-flight request. - const generateInFlightRef = useRef>({}) - const [generateInFlightByWorktree, setGenerateInFlightByWorktree] = useState< - Record - >({}) - const [generateErrors, setGenerateErrors] = useState>({}) - const isGenerating = generateInFlightByWorktree[activeWorktreeId ?? ''] ?? false - const generateError = generateErrors[activeWorktreeId ?? ''] ?? null const [hostedReviewCreationState, setHostedReviewCreationState] = useState(null) const createPrInFlightRef = useRef>({}) @@ -862,12 +887,24 @@ function SourceControlInner(): React.JSX.Element { const [createPrErrors, setCreatePrErrors] = useState>({}) const isCreatingPr = createPrInFlightByWorktree[activeWorktreeId ?? ''] ?? false const createPrError = createPrErrors[activeWorktreeId ?? ''] ?? null + const commitGenerationRecords = useAppStore((s) => s.commitMessageGenerationRecords) + const allocateCommitMessageGenerationRequestId = useAppStore( + (s) => s.allocateCommitMessageGenerationRequestId + ) + const setCommitMessageGenerationRecord = useAppStore((s) => s.setCommitMessageGenerationRecord) + const updateCommitMessageGenerationRecord = useAppStore( + (s) => s.updateCommitMessageGenerationRecord + ) + const pruneCommitMessageGenerationRecords = useAppStore( + (s) => s.pruneCommitMessageGenerationRecords + ) const prGenerationRecords = useAppStore((s) => s.pullRequestGenerationRecords) const allocatePullRequestGenerationRequestId = useAppStore( (s) => s.allocatePullRequestGenerationRequestId ) const setPullRequestGenerationRecord = useAppStore((s) => s.setPullRequestGenerationRecord) const updatePullRequestGenerationRecord = useAppStore((s) => s.updatePullRequestGenerationRecord) + const prunePullRequestGenerationRecords = useAppStore((s) => s.prunePullRequestGenerationRecords) const filterInputRef = useRef(null) const commitMessage = readCommitDraftForWorktree(commitDrafts, activeWorktreeId) const commitError = commitErrors[activeWorktreeId ?? ''] ?? null @@ -894,6 +931,16 @@ function SourceControlInner(): React.JSX.Element { const gitIdentityDisplay = activeWorktree ? getWorktreeGitIdentityDisplay(activeWorktree) : null const detachedHeadDisplay = gitIdentityDisplay?.kind === 'detached' ? gitIdentityDisplay : null const branchName = gitIdentityDisplay?.kind === 'branch' ? gitIdentityDisplay.branchName : '' + const activeCommitGenerationKey = getCommitMessageGenerationRecordKey( + activeWorktreeId, + worktreePath + ) + const activeCommitGenerationRecord = activeCommitGenerationKey + ? (commitGenerationRecords[activeCommitGenerationKey] ?? null) + : null + const isGenerating = activeCommitGenerationRecord?.status === 'running' + const generateError = + activeCommitGenerationRecord?.status === 'failed' ? activeCommitGenerationRecord.error : null const activePullRequestGenerationKey = getPullRequestGenerationRecordKey({ worktreeId: activeWorktreeId, worktreePath, @@ -1287,17 +1334,70 @@ function SourceControlInner(): React.JSX.Element { openSettingsPage }) + const [commitGenerationDialogTarget, setCommitGenerationDialogTarget] = + useState(null) + const [pullRequestGenerationDialogTarget, setPullRequestGenerationDialogTarget] = + useState(null) + + const createCommitMessageGenerationTarget = useCallback(() => { + if (!activeWorktreeId || !worktreePath) { + return null + } + return { + worktreeId: activeWorktreeId, + worktreePath, + connectionId: activeConnectionId ?? undefined, + repo: activeRepo ?? null, + settings, + discoveryHostKey: sourceControlAiDiscoveryHostKey + } + }, [ + activeConnectionId, + activeRepo, + activeWorktreeId, + settings, + sourceControlAiDiscoveryHostKey, + worktreePath + ]) + + const handleCommitGenerationDialogOpenChange = useCallback( + (open: boolean): void => { + setCommitGenerationDialogOpen(open) + if (!open) { + setCommitGenerationDialogTarget(null) + } + }, + [setCommitGenerationDialogOpen] + ) + + const handlePullRequestGenerationDialogOpenChange = useCallback( + (open: boolean): void => { + setPullRequestGenerationDialogOpen(open) + if (!open) { + setPullRequestGenerationDialogTarget(null) + } + }, + [setPullRequestGenerationDialogOpen] + ) + // Why: orphaned draft/error/in-flight entries accumulate when worktrees are // removed from the store (long sessions with many create/destroy cycles). // Prune them so a deleted-then-reused worktree ID doesn't inherit stale // state — especially commitInFlightRef, which would permanently disable // Commit for that ID if left stuck at `true`. useEffect(() => { + const liveWorktreeKeys = new Set() + for (const worktree of worktreeMap.values()) { + liveWorktreeKeys.add(worktree.id) + if (worktree.path.trim()) { + liveWorktreeKeys.add(worktree.path) + } + } const pruneRecord = (prev: Record): Record => { let changed = false const next: Record = {} for (const key of Object.keys(prev)) { - if (worktreeMap.has(key)) { + if (liveWorktreeKeys.has(key)) { next[key] = prev[key] } else { changed = true @@ -1310,26 +1410,21 @@ function SourceControlInner(): React.JSX.Element { setRemoteActionErrors((prev) => pruneRecord(prev)) setCommitInFlightByWorktree((prev) => pruneRecord(prev)) setAbortOperationInFlightByWorktree((prev) => pruneRecord(prev)) - setGenerateInFlightByWorktree((prev) => pruneRecord(prev)) - setGenerateErrors((prev) => pruneRecord(prev)) setGitHistoryByWorktree((prev) => pruneRecord(prev)) + pruneCommitMessageGenerationRecords(liveWorktreeKeys) + prunePullRequestGenerationRecords(liveWorktreeKeys) // Refs don't need setState — mutate in place to drop stale keys. for (const key of Object.keys(commitInFlightRef.current)) { - if (!worktreeMap.has(key)) { + if (!liveWorktreeKeys.has(key)) { delete commitInFlightRef.current[key] } } - for (const key of Object.keys(generateInFlightRef.current)) { - if (!worktreeMap.has(key)) { - delete generateInFlightRef.current[key] - } - } for (const key of Object.keys(gitHistoryRequestByWorktreeRef.current)) { - if (!worktreeMap.has(key)) { + if (!liveWorktreeKeys.has(key)) { delete gitHistoryRequestByWorktreeRef.current[key] } } - }, [worktreeMap]) + }, [pruneCommitMessageGenerationRecords, prunePullRequestGenerationRecords, worktreeMap]) useEffect(() => { // Why: users often finish merge/rebase conflicts in a terminal. Once git @@ -1474,117 +1569,220 @@ function SourceControlInner(): React.JSX.Element { ]) const handleGenerate = useCallback( - async (overrides?: RuntimeGenerateCommitMessageOverrides): Promise => { - if (!activeWorktreeId || !worktreePath) { + async (options?: CommitMessageGenerationOptions): Promise => { + const target = options?.target ?? createCommitMessageGenerationTarget() + if (!target) { return } - if (generateInFlightRef.current[activeWorktreeId]) { + const overrides: RuntimeGenerateCommitMessageOverrides = { + ...(options?.sourceControlAiResolvedParams + ? { sourceControlAiResolvedParams: options.sourceControlAiResolvedParams } + : {}), + ...(options?.sourceControlAi ? { sourceControlAi: options.sourceControlAi } : {}), + ...(options?.agentCmdOverrides ? { agentCmdOverrides: options.agentCmdOverrides } : {}) + } + const { worktreeId, worktreePath: generationWorktreePath, connectionId } = target + const generationKey = getCommitMessageGenerationRecordKey(worktreeId, generationWorktreePath) + if (!generationKey) { return } - if (!overrides?.sourceControlAiResolvedParams && resolvedCommitMessageAi?.ok !== true) { + if ( + useAppStore.getState().commitMessageGenerationRecords[generationKey]?.status === 'running' + ) { + return + } + if (!overrides.sourceControlAiResolvedParams && resolvedCommitMessageAi?.ok !== true) { return } if ( - !overrides?.sourceControlAiResolvedParams && + !overrides.sourceControlAiResolvedParams && resolvedCommitMessageAi?.ok === true && isCustomAgentId(resolvedCommitMessageAi.value.params.agentId) ) { const command = resolvedCommitMessageAi.value.params.customAgentCommand?.trim() ?? '' if (!command) { - setGenerateErrors((prev) => ({ - ...prev, - [activeWorktreeId]: - 'Custom command is empty. Add one in Settings -> Git -> Source Control AI.' - })) + const requestId = allocateCommitMessageGenerationRequestId() + const failedRecord = resolveCommitMessageGenerationFailure({ + record: createRunningCommitMessageGenerationRecord({ + worktreeId, + worktreePath: generationWorktreePath, + connectionId, + requestId, + runtimeTargetSettings: { + activeRuntimeEnvironmentId: target.settings?.activeRuntimeEnvironmentId ?? null + } + }), + requestId, + error: 'Custom command is empty. Add one in Settings -> Git -> Source Control AI.' + }) + if (failedRecord) { + setCommitMessageGenerationRecord(generationKey, failedRecord) + } return } } - generateInFlightRef.current[activeWorktreeId] = true - const connectionId = getConnectionId(activeWorktreeId) ?? undefined - setGenerateInFlightByWorktree((prev) => ({ ...prev, [activeWorktreeId]: true })) - setGenerateErrors((prev) => ({ ...prev, [activeWorktreeId]: null })) + const requestId = allocateCommitMessageGenerationRequestId() + // Why: Stop must route to the runtime selected at generation start, even + // if the user changes settings before cancellation. + setCommitMessageGenerationRecord( + generationKey, + createRunningCommitMessageGenerationRecord({ + worktreeId, + worktreePath: generationWorktreePath, + connectionId, + requestId, + runtimeTargetSettings: { + activeRuntimeEnvironmentId: target.settings?.activeRuntimeEnvironmentId ?? null + } + }) + ) try { const result = await generateRuntimeCommitMessage( { - settings: useAppStore.getState().settings, - worktreeId: activeWorktreeId, - worktreePath, + settings: target.settings ?? useAppStore.getState().settings, + worktreeId, + worktreePath: generationWorktreePath, connectionId }, overrides ) if (!result.success) { - // Why: cancellation is a deliberate user action, not a failure to - // surface. Clear any prior error and stay quiet. - if (result.canceled) { - setGenerateErrors((prev) => ({ ...prev, [activeWorktreeId]: null })) - return - } - setGenerateErrors((prev) => ({ - ...prev, - [activeWorktreeId]: result.error - })) + updateCommitMessageGenerationRecord(generationKey, (record) => + resolveCommitMessageGenerationFailure({ + record, + requestId, + canceled: result.canceled, + error: result.canceled ? null : result.error + }) + ) return } - // Why: race protection — the user may have started typing into the - // textarea while the agent was running. In that case we silently drop - // the generated message rather than overwrite their in-progress edits. - setCommitDrafts((prev) => { - const current = prev[activeWorktreeId] - if (current && current.length > 0) { - return prev - } - return writeCommitDraftForWorktree(prev, activeWorktreeId, result.message) - }) useAppStore.getState().recordFeatureInteraction('ai-commit-generation') - setGenerateErrors((prev) => ({ ...prev, [activeWorktreeId]: null })) + updateCommitMessageGenerationRecord(generationKey, (record) => + resolveCommitMessageGenerationSuccess({ + record, + requestId, + message: result.message + }) + ) } catch (error) { - setGenerateErrors((prev) => ({ - ...prev, - [activeWorktreeId]: - error instanceof Error ? error.message : 'Failed to generate commit message' - })) - } finally { - setGenerateInFlightByWorktree((prev) => ({ ...prev, [activeWorktreeId]: false })) - generateInFlightRef.current[activeWorktreeId] = false + updateCommitMessageGenerationRecord(generationKey, (record) => + resolveCommitMessageGenerationFailure({ + record, + requestId, + error: error instanceof Error ? error.message : 'Failed to generate commit message' + }) + ) } }, - [activeWorktreeId, resolvedCommitMessageAi, worktreePath] + [ + allocateCommitMessageGenerationRequestId, + createCommitMessageGenerationTarget, + resolvedCommitMessageAi, + setCommitMessageGenerationRecord, + updateCommitMessageGenerationRecord + ] ) + useEffect(() => { + if ( + !activeCommitGenerationKey || + !activeCommitGenerationRecord || + activeCommitGenerationRecord.status !== 'succeeded' || + !activeCommitGenerationRecord.message || + activeCommitGenerationRecord.hydrated + ) { + return + } + + const { + context: { worktreeId }, + message + } = activeCommitGenerationRecord + // Why: generation can finish while Source Control is showing a different + // worktree or is temporarily unmounted; hydrate the saved result when its + // originating worktree is visible again without overwriting user edits. + setCommitDrafts((prev) => { + const current = prev[worktreeId] + if (current && current.length > 0) { + return prev + } + return writeCommitDraftForWorktree(prev, worktreeId, message) + }) + updateCommitMessageGenerationRecord( + activeCommitGenerationKey, + markCommitMessageGenerationHydrated + ) + }, [activeCommitGenerationKey, activeCommitGenerationRecord, updateCommitMessageGenerationRecord]) + const handleGenerateCommitMessageClick = useCallback((): void => { + const target = createCommitMessageGenerationTarget() + if (!target) { + return + } if ( hasConfiguredCommitMessageGenerationDefaults({ settings, repo: activeRepo ?? null }) && resolvedCommitMessageAi?.ok ) { - void handleGenerate({ sourceControlAiResolvedParams: resolvedCommitMessageAi.value.params }) + void handleGenerate({ + sourceControlAiResolvedParams: resolvedCommitMessageAi.value.params, + target + }) return } + // Why: the dialog remains open while users can switch worktrees; keep it + // bound to the worktree that requested generation. + setCommitGenerationDialogTarget(target) openCommitGenerationDialog() - }, [activeRepo, handleGenerate, openCommitGenerationDialog, resolvedCommitMessageAi, settings]) + }, [ + activeRepo, + createCommitMessageGenerationTarget, + handleGenerate, + openCommitGenerationDialog, + resolvedCommitMessageAi, + settings + ]) const handleCancelGenerate = useCallback((): void => { - if (!activeWorktreeId || !worktreePath) { + if ( + !activeCommitGenerationKey || + !activeCommitGenerationRecord || + activeCommitGenerationRecord.status !== 'running' + ) { return } - if (!generateInFlightRef.current[activeWorktreeId]) { - return - } - const connectionId = getConnectionId(activeWorktreeId) ?? undefined + const { context } = activeCommitGenerationRecord + updateCommitMessageGenerationRecord( + activeCommitGenerationKey, + resolveCommitMessageGenerationCancel + ) // Why: fire-and-forget — the in-flight generateCommitMessage promise - // resolves with `{canceled: true}` once the kill propagates, which is - // where the spinner is cleared. Awaiting here would just delay UI feedback. + // resolves with `{canceled: true}` once the kill propagates, while the + // durable record lets the visible spinner clear immediately. void cancelRuntimeGenerateCommitMessage({ - settings: useAppStore.getState().settings, - worktreeId: activeWorktreeId, - worktreePath, - connectionId + settings: context.runtimeTargetSettings, + worktreeId: context.worktreeId, + worktreePath: context.worktreePath, + connectionId: context.connectionId + }).catch((error) => { + updateCommitMessageGenerationRecord(activeCommitGenerationKey, (record) => { + if (!record || record.context.requestId !== context.requestId) { + return null + } + return { + ...record, + status: 'failed', + error: + error instanceof Error ? error.message : 'Failed to stop commit message generation', + hydrated: false + } + }) }) - }, [activeWorktreeId, worktreePath]) + }, [activeCommitGenerationKey, activeCommitGenerationRecord, updateCommitMessageGenerationRecord]) // Why: a single dispatcher for every remote-only action the split button or // chevron dropdown can trigger. Keeps the error-swallow pattern in one @@ -1912,42 +2110,90 @@ function SourceControlInner(): React.JSX.Element { await refreshActiveGitStatusAfterMutation() }, [refreshActiveGitStatusAfterMutation]) + const createPullRequestGenerationTarget = useCallback( + ( + fields: PullRequestGenerationFields, + fieldRevisions: PullRequestFieldRevisions + ): PullRequestGenerationTargetSnapshot | null => { + if (!activeRepo || !worktreePath || !branchName) { + return null + } + return { + worktreeId: activeWorktreeId, + worktreePath, + connectionId: activeConnectionId ?? undefined, + repo: activeRepo, + repoId: activeRepo.id, + branch: branchName, + settings, + discoveryHostKey: sourceControlAiDiscoveryHostKey, + fields: { ...fields }, + fieldRevisions: { ...fieldRevisions } + } + }, + [ + activeConnectionId, + activeRepo, + activeWorktreeId, + branchName, + settings, + sourceControlAiDiscoveryHostKey, + worktreePath + ] + ) + const handleGeneratePullRequestFieldsForActive = useCallback( async ( fields: PullRequestGenerationFields, fieldRevisions: PullRequestFieldRevisions, - overrides?: RuntimeGeneratePullRequestFieldsOverrides + overrides?: RuntimeGeneratePullRequestFieldsOverrides, + targetOverride?: PullRequestGenerationTargetSnapshot ): Promise => { - if (!activeRepo || !activePullRequestGenerationKey || !worktreePath || !branchName) { + const target = targetOverride ?? createPullRequestGenerationTarget(fields, fieldRevisions) + if (!target) { + return + } + const generationKey = getPullRequestGenerationRecordKey({ + worktreeId: target.worktreeId, + worktreePath: target.worktreePath, + repoId: target.repoId, + branch: target.branch + }) + if (!generationKey) { return } - const generationKey = activePullRequestGenerationKey if ( useAppStore.getState().pullRequestGenerationRecords[generationKey]?.status === 'running' ) { return } const requestId = allocatePullRequestGenerationRequestId() + // Why: Stop must route to the runtime selected at generation start, even + // if the user changes settings before cancellation. const context: PullRequestGenerationContext = { - worktreeId: activeWorktreeId, - worktreePath, - connectionId: getConnectionId(activeWorktreeId) ?? undefined, + worktreeId: target.worktreeId, + worktreePath: target.worktreePath, + connectionId: target.connectionId, requestId, - repoId: activeRepo.id, - branch: branchName + repoId: target.repoId, + branch: target.branch, + runtimeTargetSettings: { + activeRuntimeEnvironmentId: target.settings?.activeRuntimeEnvironmentId ?? null + } } - const seed = { ...fields } - // Why: SourceControl can unmount on tab switches; persisting the running - // record lets the embedded PR composer resume when the user returns. + const seed = { ...target.fields } + // Why: SourceControl can unmount on tab/worktree switches; the record is + // keyed to the originating repo/worktree/branch so stale UI cannot retarget + // or orphan the generated hosted-review fields. setPullRequestGenerationRecord( generationKey, - createRunningPullRequestGenerationRecord(context, seed, fieldRevisions) + createRunningPullRequestGenerationRecord(context, seed, target.fieldRevisions) ) try { const result = await generateRuntimePullRequestFields( { - settings: useAppStore.getState().settings, + settings: target.settings ?? useAppStore.getState().settings, worktreeId: context.worktreeId, worktreePath: context.worktreePath, connectionId: context.connectionId @@ -2001,15 +2247,11 @@ function SourceControlInner(): React.JSX.Element { } }, [ - activePullRequestGenerationKey, - activeRepo, - activeWorktreeId, allocatePullRequestGenerationRequestId, - branchName, + createPullRequestGenerationTarget, refreshGitStatusAfterPullRequestGeneration, setPullRequestGenerationRecord, - updatePullRequestGenerationRecord, - worktreePath + updatePullRequestGenerationRecord ] ) @@ -2029,7 +2271,7 @@ function SourceControlInner(): React.JSX.Element { return resolvePullRequestGenerationCancel(current) }) void cancelRuntimeGeneratePullRequestFields({ - settings: useAppStore.getState().settings, + settings: record.context.runtimeTargetSettings, worktreeId: record.context.worktreeId, worktreePath: record.context.worktreePath, connectionId: record.context.connectionId @@ -2058,6 +2300,7 @@ function SourceControlInner(): React.JSX.Element { setBody: setPrBody, draft: prDraft, setDraft: setPrDraft, + fieldRevisions: prFieldRevisions, baseQuery: prBaseQuery, setBaseQuery: setPrBaseQuery, baseResults: prBaseResults, @@ -2103,10 +2346,36 @@ function SourceControlInner(): React.JSX.Element { void handleGeneratePullRequestFields() return } + const target = createPullRequestGenerationTarget( + { base: prBase, title: prTitle, body: prBody, draft: prDraft }, + prFieldRevisions + ) + if (!target) { + return + } + // Why: the agent-picker dialog can stay open while the user switches + // worktrees; keep Generate bound to the PR draft that opened it. + setPullRequestGenerationDialogTarget(target) openPullRequestGenerationDialog() - }, [activeRepo, handleGeneratePullRequestFields, openPullRequestGenerationDialog, settings]) + }, [ + activeRepo, + createPullRequestGenerationTarget, + handleGeneratePullRequestFields, + openPullRequestGenerationDialog, + prBase, + prBody, + prDraft, + prFieldRevisions, + prTitle, + settings + ]) useEffect(() => { + // Why: after a SourceControl remount, the PR composer first reseeds from + // eligibility; hydrate generated fields only after that seed exists. + if (hostedReviewCreation?.canCreate !== true) { + return + } if ( !activePullRequestGenerationKey || !activePullRequestGenerationRecord || @@ -2141,6 +2410,7 @@ function SourceControlInner(): React.JSX.Element { activePullRequestGenerationKey, activePullRequestGenerationRecord, applyGeneratedPullRequestFields, + hostedReviewCreation?.canCreate, updatePullRequestGenerationRecord ]) @@ -4476,7 +4746,7 @@ function SourceControlInner(): React.JSX.Element { /> { - void handleGenerate({ sourceControlAiResolvedParams: params }) + const target = commitGenerationDialogTarget ?? createCommitMessageGenerationTarget() + if (!target) { + return + } + void handleGenerate({ sourceControlAiResolvedParams: params, target }) }} onSaveDefaults={handleSaveCommitMessageGenerationDefaults} /> { - void handleGeneratePullRequestFields({ sourceControlAiResolvedParams: params }) + const target = + pullRequestGenerationDialogTarget ?? + createPullRequestGenerationTarget( + { base: prBase, title: prTitle, body: prBody, draft: prDraft }, + prFieldRevisions + ) + if (!target) { + return + } + void handleGeneratePullRequestFieldsForActive( + target.fields, + target.fieldRevisions, + { sourceControlAiResolvedParams: params }, + target + ) }} onSaveDefaults={handleSavePullRequestGenerationDefaults} /> diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index 9668bf7d566..8e430d6e5d1 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -30,6 +30,7 @@ import { createWorktreeNavHistorySlice } from './slices/worktree-nav-history' import { createDictationSlice } from './slices/dictation' import { createWorkspaceCleanupSlice } from './slices/workspace-cleanup' import { createPullRequestGenerationSlice } from './slices/pull-request-generation' +import { createCommitMessageGenerationSlice } from './slices/commit-message-generation' import { e2eConfig } from '@/lib/e2e-config' import { registerHttpLinkStoreAccessor } from '@/lib/http-link-routing' @@ -63,7 +64,8 @@ export const useAppStore = create()((...a) => ({ ...createWorktreeNavHistorySlice(...a), ...createDictationSlice(...a), ...createWorkspaceCleanupSlice(...a), - ...createPullRequestGenerationSlice(...a) + ...createPullRequestGenerationSlice(...a), + ...createCommitMessageGenerationSlice(...a) })) registerHttpLinkStoreAccessor(() => useAppStore.getState()) diff --git a/src/renderer/src/store/slices/commit-message-generation.ts b/src/renderer/src/store/slices/commit-message-generation.ts new file mode 100644 index 00000000000..77f271781cf --- /dev/null +++ b/src/renderer/src/store/slices/commit-message-generation.ts @@ -0,0 +1,189 @@ +import type { StateCreator } from 'zustand' +import type { GlobalSettings } from '../../../../shared/types' +import type { AppState } from '../types' + +export type CommitMessageGenerationRuntimeTargetSettings = Pick< + GlobalSettings, + 'activeRuntimeEnvironmentId' +> + +export type CommitMessageGenerationContext = { + worktreeId: string + worktreePath: string + connectionId?: string + requestId: number + runtimeTargetSettings?: CommitMessageGenerationRuntimeTargetSettings | null +} + +export type CommitMessageGenerationStatus = 'idle' | 'running' | 'canceled' | 'failed' | 'succeeded' + +export type CommitMessageGenerationRecord = { + context: CommitMessageGenerationContext + status: CommitMessageGenerationStatus + message: string | null + error: string | null + hydrated: boolean +} + +export type CommitMessageGenerationRecords = Record + +export type CommitMessageGenerationSlice = { + commitMessageGenerationRequestSeq: number + commitMessageGenerationRecords: CommitMessageGenerationRecords + allocateCommitMessageGenerationRequestId: () => number + setCommitMessageGenerationRecord: (key: string, record: CommitMessageGenerationRecord) => void + updateCommitMessageGenerationRecord: ( + key: string, + updater: (record: CommitMessageGenerationRecord | null) => CommitMessageGenerationRecord | null + ) => void + pruneCommitMessageGenerationRecords: (liveWorktreeKeys: ReadonlySet) => void +} + +export function getCommitMessageGenerationRecordKey( + worktreeId: string | null | undefined, + worktreePath: string | null | undefined +): string | null { + if (worktreeId) { + return worktreeId + } + return worktreePath?.trim() ? worktreePath : null +} + +export function createRunningCommitMessageGenerationRecord( + context: CommitMessageGenerationContext +): CommitMessageGenerationRecord { + return { + context, + status: 'running', + message: null, + error: null, + hydrated: false + } +} + +export function resolveCommitMessageGenerationSuccess({ + record, + requestId, + message +}: { + record: CommitMessageGenerationRecord | null | undefined + requestId: number + message: string +}): CommitMessageGenerationRecord | null { + if (!record || record.context.requestId !== requestId || record.status !== 'running') { + return null + } + return { + ...record, + status: 'succeeded', + message, + error: null, + hydrated: false + } +} + +export function resolveCommitMessageGenerationFailure({ + record, + requestId, + error, + canceled = false +}: { + record: CommitMessageGenerationRecord | null | undefined + requestId: number + error: string | null + canceled?: boolean +}): CommitMessageGenerationRecord | null { + if (!record || record.context.requestId !== requestId || record.status !== 'running') { + return null + } + return { + ...record, + status: canceled ? 'canceled' : 'failed', + message: null, + error: canceled ? null : error, + hydrated: false + } +} + +export function resolveCommitMessageGenerationCancel( + record: CommitMessageGenerationRecord | null | undefined +): CommitMessageGenerationRecord | null { + if (!record || record.status !== 'running') { + return null + } + return { + ...record, + status: 'canceled', + error: null, + hydrated: false + } +} + +export function markCommitMessageGenerationHydrated( + record: CommitMessageGenerationRecord | null | undefined +): CommitMessageGenerationRecord | null { + if (!record || record.status !== 'succeeded') { + return null + } + return { + ...record, + hydrated: true + } +} + +export const createCommitMessageGenerationSlice: StateCreator< + AppState, + [], + [], + CommitMessageGenerationSlice +> = (set) => ({ + commitMessageGenerationRequestSeq: 0, + commitMessageGenerationRecords: {}, + allocateCommitMessageGenerationRequestId: () => { + let nextRequestId = 0 + set((state) => { + nextRequestId = state.commitMessageGenerationRequestSeq + 1 + return { + commitMessageGenerationRequestSeq: nextRequestId + } + }) + return nextRequestId + }, + setCommitMessageGenerationRecord: (key, record) => + set((state) => ({ + commitMessageGenerationRecords: { + ...state.commitMessageGenerationRecords, + [key]: record + } + })), + updateCommitMessageGenerationRecord: (key, updater) => + set((state) => { + const nextRecord = updater(state.commitMessageGenerationRecords[key] ?? null) + if (!nextRecord) { + return {} + } + return { + commitMessageGenerationRecords: { + ...state.commitMessageGenerationRecords, + [key]: nextRecord + } + } + }), + pruneCommitMessageGenerationRecords: (liveWorktreeKeys) => + set((state) => { + let changed = false + const nextRecords: CommitMessageGenerationRecords = {} + for (const [key, record] of Object.entries(state.commitMessageGenerationRecords)) { + const worktreeKey = getCommitMessageGenerationRecordKey( + record.context.worktreeId, + record.context.worktreePath + ) + if (worktreeKey && liveWorktreeKeys.has(worktreeKey)) { + nextRecords[key] = record + } else { + changed = true + } + } + return changed ? { commitMessageGenerationRecords: nextRecords } : {} + }) +}) diff --git a/src/renderer/src/store/slices/diffComments.test.ts b/src/renderer/src/store/slices/diffComments.test.ts index 96d6e8f097f..d5672d751cb 100644 --- a/src/renderer/src/store/slices/diffComments.test.ts +++ b/src/renderer/src/store/slices/diffComments.test.ts @@ -137,6 +137,7 @@ import { createWorktreeNavHistorySlice } from './worktree-nav-history' import { createDictationSlice } from './dictation' import { createWorkspaceCleanupSlice } from './workspace-cleanup' import { createPullRequestGenerationSlice } from './pull-request-generation' +import { createCommitMessageGenerationSlice } from './commit-message-generation' function createTestStore() { return create()((...a) => ({ @@ -169,7 +170,8 @@ function createTestStore() { ...createWorktreeNavHistorySlice(...a), ...createDictationSlice(...a), ...createWorkspaceCleanupSlice(...a), - ...createPullRequestGenerationSlice(...a) + ...createPullRequestGenerationSlice(...a), + ...createCommitMessageGenerationSlice(...a) })) } diff --git a/src/renderer/src/store/slices/pull-request-generation.ts b/src/renderer/src/store/slices/pull-request-generation.ts index 6775a6fbb98..ed77d9f5169 100644 --- a/src/renderer/src/store/slices/pull-request-generation.ts +++ b/src/renderer/src/store/slices/pull-request-generation.ts @@ -1,4 +1,5 @@ import type { StateCreator } from 'zustand' +import type { GlobalSettings } from '../../../../shared/types' import type { AppState } from '../types' export type PullRequestFieldName = 'base' | 'title' | 'body' | 'draft' @@ -11,6 +12,11 @@ export type PullRequestGenerationFields = { draft: boolean } +export type PullRequestGenerationRuntimeTargetSettings = Pick< + GlobalSettings, + 'activeRuntimeEnvironmentId' +> + export type PullRequestGenerationContext = { worktreeId: string | null worktreePath: string @@ -18,6 +24,7 @@ export type PullRequestGenerationContext = { requestId: number repoId: string branch: string + runtimeTargetSettings?: PullRequestGenerationRuntimeTargetSettings | null } export type PullRequestGenerationStatus = 'idle' | 'running' | 'canceled' | 'failed' | 'succeeded' @@ -43,6 +50,7 @@ export type PullRequestGenerationSlice = { key: string, updater: (record: PullRequestGenerationRecord | null) => PullRequestGenerationRecord | null ) => void + prunePullRequestGenerationRecords: (liveWorktreeKeys: ReadonlySet) => void } export function getPullRequestGenerationWorktreeKey( @@ -214,5 +222,22 @@ export const createPullRequestGenerationSlice: StateCreator< [key]: nextRecord } } + }), + prunePullRequestGenerationRecords: (liveWorktreeKeys) => + set((state) => { + let changed = false + const nextRecords: PullRequestGenerationRecords = {} + for (const [key, record] of Object.entries(state.pullRequestGenerationRecords)) { + const worktreeKey = getPullRequestGenerationWorktreeKey( + record.context.worktreeId, + record.context.worktreePath + ) + if (worktreeKey && liveWorktreeKeys.has(worktreeKey)) { + nextRecords[key] = record + } else { + changed = true + } + } + return changed ? { pullRequestGenerationRecords: nextRecords } : {} }) }) diff --git a/src/renderer/src/store/slices/store-test-helpers.ts b/src/renderer/src/store/slices/store-test-helpers.ts index ce70ee77236..a65cb122a2c 100644 --- a/src/renderer/src/store/slices/store-test-helpers.ts +++ b/src/renderer/src/store/slices/store-test-helpers.ts @@ -38,6 +38,7 @@ import { createWorktreeNavHistorySlice } from './worktree-nav-history' import { createDictationSlice } from './dictation' import { createWorkspaceCleanupSlice } from './workspace-cleanup' import { createPullRequestGenerationSlice } from './pull-request-generation' +import { createCommitMessageGenerationSlice } from './commit-message-generation' import { translate } from '@/i18n/i18n' export const TEST_REPO = { @@ -79,7 +80,8 @@ export function createTestStore() { ...createWorktreeNavHistorySlice(...a), ...createDictationSlice(...a), ...createWorkspaceCleanupSlice(...a), - ...createPullRequestGenerationSlice(...a) + ...createPullRequestGenerationSlice(...a), + ...createCommitMessageGenerationSlice(...a) })) } diff --git a/src/renderer/src/store/types.ts b/src/renderer/src/store/types.ts index 7ef6ecec84d..e6152999f36 100644 --- a/src/renderer/src/store/types.ts +++ b/src/renderer/src/store/types.ts @@ -28,6 +28,7 @@ import type { WorktreeNavHistorySlice } from './slices/worktree-nav-history' import type { DictationSlice } from './slices/dictation' import type { WorkspaceCleanupSlice } from './slices/workspace-cleanup' import type { PullRequestGenerationSlice } from './slices/pull-request-generation' +import type { CommitMessageGenerationSlice } from './slices/commit-message-generation' export type AppState = RepoSlice & SparsePresetsSlice & @@ -58,4 +59,5 @@ export type AppState = RepoSlice & WorktreeNavHistorySlice & DictationSlice & WorkspaceCleanupSlice & - PullRequestGenerationSlice + PullRequestGenerationSlice & + CommitMessageGenerationSlice diff --git a/tests/e2e/source-control-pr-generation-switch.spec.ts b/tests/e2e/source-control-pr-generation-switch.spec.ts index 189338d7d0d..a41a53bf0d5 100644 --- a/tests/e2e/source-control-pr-generation-switch.spec.ts +++ b/tests/e2e/source-control-pr-generation-switch.spec.ts @@ -140,6 +140,67 @@ test.describe('Source Control AI PR generation worktree switching', () => { }) }) + test('hydrates pending PR generation after Source Control remounts', async ({ + orcaPage + }, testInfo) => { + await waitForSessionReady(orcaPage) + await waitForActiveWorktree(orcaPage) + const { prWorktreeId, prWorktreePath, primaryBranch } = await seedCreatePrComposer(orcaPage) + createBranchCommit(prWorktreePath) + + const screenshotDir = path.join( + process.cwd(), + 'validation-screenshots', + `pr-generation-remount-${Date.now()}` + ) + mkdirSync(screenshotDir, { recursive: true }) + await testInfo.attach('validation-screenshot-dir', { + body: screenshotDir, + contentType: 'text/plain' + }) + const generatorScriptPath = path.join(screenshotDir, 'delayed-pr-generator.cjs') + const callLogPath = path.join(screenshotDir, 'delayed-pr-generator.log') + await installDelayedPrGenerator(orcaPage, generatorScriptPath, callLogPath, primaryBranch) + + await openSourceControl(orcaPage, prWorktreeId) + const generate = orcaPage.getByRole('button', { + name: 'Generate pull request details with AI' + }) + await expect(generate).toBeVisible({ timeout: 10_000 }) + await expect(generate).toBeEnabled() + await generate.click() + await expect( + orcaPage.getByRole('button', { name: 'Stop generating pull request details' }) + ).toBeVisible() + await expect.poll(() => readLog(callLogPath)).toContain('start') + + await orcaPage.evaluate(() => { + window.__store?.getState().setRightSidebarTab('explorer') + }) + await expect( + orcaPage.getByRole('button', { name: 'Stop generating pull request details' }) + ).toHaveCount(0) + await expect + .poll(() => readFileSync(callLogPath, 'utf8'), { timeout: 10_000 }) + .toContain('finish') + + await openSourceControl(orcaPage, prWorktreeId) + await expect(orcaPage.getByRole('textbox', { name: 'Pull request title' })).toHaveValue( + 'Generated PR title after switch', + { timeout: 10_000 } + ) + await expect(orcaPage.getByRole('textbox', { name: 'Pull request description' })).toHaveValue( + 'Generated PR body after switch' + ) + await orcaPage.screenshot({ + path: path.join(screenshotDir, '01-remounted-source-control-hydrated-pr-fields.png') + }) + await writeEvidence(testInfo, screenshotDir, 'pr-generation-remount-evidence.json', { + expectedOriginalWorktreeId: prWorktreeId, + generatorLog: readLog(callLogPath) + }) + }) + test('keeps pending commit message generation attached to its original worktree', async ({ orcaPage }, testInfo) => { @@ -242,6 +303,73 @@ test.describe('Source Control AI PR generation worktree switching', () => { }) }) + test('hydrates pending commit message generation after Source Control remounts', async ({ + orcaPage + }, testInfo) => { + await waitForSessionReady(orcaPage) + await waitForActiveWorktree(orcaPage) + const { commitWorktreeId, commitWorktreePath } = await seedCommitMessageComposer(orcaPage) + createStagedCommitMessageChange(commitWorktreePath) + + const screenshotDir = path.join( + process.cwd(), + 'validation-screenshots', + `commit-message-generation-remount-${Date.now()}` + ) + mkdirSync(screenshotDir, { recursive: true }) + await testInfo.attach('validation-screenshot-dir', { + body: screenshotDir, + contentType: 'text/plain' + }) + const generatorScriptPath = path.join(screenshotDir, 'delayed-commit-generator.cjs') + const callLogPath = path.join(screenshotDir, 'delayed-commit-generator.log') + await installDelayedCommitMessageGenerator(orcaPage, generatorScriptPath, callLogPath) + + await openSourceControl(orcaPage, commitWorktreeId) + const generate = orcaPage.getByRole('button', { + name: 'Generate commit message with AI' + }) + await expect(generate).toBeVisible({ timeout: 10_000 }) + await expect(generate).toBeEnabled() + await generate.click() + await expect( + orcaPage.getByRole('button', { name: 'Stop generating commit message' }) + ).toBeVisible() + await expect.poll(() => readLog(callLogPath)).toContain('start') + + await orcaPage.evaluate(() => { + window.__store?.getState().setRightSidebarTab('explorer') + }) + await expect( + orcaPage.getByRole('button', { name: 'Stop generating commit message' }) + ).toHaveCount(0) + await expect + .poll(() => readFileSync(callLogPath, 'utf8'), { timeout: 10_000 }) + .toContain('finish') + + await openSourceControl(orcaPage, commitWorktreeId) + await expect(orcaPage.getByRole('textbox', { name: 'Commit message' })).toHaveValue( + [ + 'Generated commit message after switch', + '', + 'Generated from staged e2e-commit-message-generation.txt after switching worktrees' + ].join('\n'), + { timeout: 10_000 } + ) + await orcaPage.screenshot({ + path: path.join(screenshotDir, '01-remounted-source-control-hydrated-message.png') + }) + await writeEvidence( + testInfo, + screenshotDir, + 'commit-message-generation-remount-evidence.json', + { + expectedOriginalWorktreeId: commitWorktreeId, + generatorLog: readLog(callLogPath) + } + ) + }) + test('hides the commit AI composer on a clean branch empty state', async ({ orcaPage }, testInfo) => { From d27985e05b6867d2964b5f5b69ae4743a6e7240f Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Thu, 11 Jun 2026 23:50:25 -0700 Subject: [PATCH 03/15] Constrain right sidebar primary button widths to prevent layout jumps (#5248) Introduce min-width constraints, truncation, and content-width layouts for primary actions in the right sidebar and PR dialogs. This prevents buttons from shifting layout dramatically when labels morph dynamically as git and review state changes. --- .../src/components/GitHubItemDialog.tsx | 37 +++++++---- .../src/components/PullRequestPage.tsx | 33 ++++++---- .../review-animated-visual-ship-styles.tsx | 2 +- .../CreateHostedReviewComposer.tsx | 27 +++++--- .../right-sidebar/HostedReviewActions.tsx | 35 +++++++---- .../HostedReviewStateActions.tsx | 6 +- .../right-sidebar/SourceControl.tsx | 62 +++++++++++-------- .../right-sidebar-primary-action-layout.ts | 16 +++++ 8 files changed, 146 insertions(+), 72 deletions(-) create mode 100644 src/renderer/src/components/right-sidebar/right-sidebar-primary-action-layout.ts diff --git a/src/renderer/src/components/GitHubItemDialog.tsx b/src/renderer/src/components/GitHubItemDialog.tsx index 041ef30765f..2b12de6ee28 100644 --- a/src/renderer/src/components/GitHubItemDialog.tsx +++ b/src/renderer/src/components/GitHubItemDialog.tsx @@ -83,6 +83,11 @@ import { import type { DiffSection } from '@/components/editor/diff-section-types' import type { CombinedDiffFileTreeEntry } from '@/components/editor/combined-diff-file-tree-model' import { CHECK_COLOR, CHECK_ICON } from '@/components/right-sidebar/checks-panel-content' +import { + REVIEW_ACTION_MERGE_BUTTON_CLASS, + REVIEW_ACTION_STATE_BUTTON_CLASS, + RIGHT_SIDEBAR_PRIMARY_BUTTON_LABEL_CLASS +} from '@/components/right-sidebar/right-sidebar-primary-action-layout' import { createGitHubChecksTabState, resolveGitHubChecksTabState, @@ -3241,7 +3246,7 @@ function PRActionsPanel({ -
+
@@ -3250,7 +3255,8 @@ function PRActionsPanel({ type="button" size="sm" className={cn( - 'w-full justify-center gap-2 bg-green-600 text-white hover:bg-green-700', + REVIEW_ACTION_MERGE_BUTTON_CLASS, + 'gap-2 bg-green-600 text-white hover:bg-green-700', 'disabled:cursor-not-allowed disabled:opacity-50' )} > @@ -3259,11 +3265,13 @@ function PRActionsPanel({ ) : ( )} - {mergePresentation.autoMergeAction?.label ?? - (mergePresentation.directMergeAvailable - ? mergeMethods.defaultLabel - : mergePresentation.label)} - + + {mergePresentation.autoMergeAction?.label ?? + (mergePresentation.directMergeAvailable + ? mergeMethods.defaultLabel + : mergePresentation.label)} + + @@ -3309,7 +3317,8 @@ function PRActionsPanel({ variant={nextState === 'closed' ? 'outline' : 'secondary'} size="sm" className={cn( - 'w-full justify-center gap-2', + REVIEW_ACTION_STATE_BUTTON_CLASS, + 'gap-2', nextState === 'closed' && 'border-border bg-background text-foreground hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50' )} @@ -3323,9 +3332,11 @@ function PRActionsPanel({ ) : ( )} - {nextState === 'closed' - ? translate('auto.components.GitHubItemDialog.21860b58d0', 'Close pull request') - : translate('auto.components.GitHubItemDialog.ec5c4b3ab2', 'Reopen PR')} + + {nextState === 'closed' + ? translate('auto.components.GitHubItemDialog.21860b58d0', 'Close pull request') + : translate('auto.components.GitHubItemDialog.ec5c4b3ab2', 'Reopen PR')} +
@@ -4896,12 +4907,12 @@ function GHEditSection({ ) : null} {hasAttachedWorkspace ? ( - +
-
+
@@ -3492,7 +3497,8 @@ function PRActionsPanel({ type="button" size="sm" className={cn( - 'w-full justify-center gap-2 bg-green-600 text-white hover:bg-green-700', + REVIEW_ACTION_MERGE_BUTTON_CLASS, + 'gap-2 bg-green-600 text-white hover:bg-green-700', 'disabled:cursor-not-allowed disabled:opacity-50' )} > @@ -3501,11 +3507,13 @@ function PRActionsPanel({ ) : ( )} - {mergePresentation.autoMergeAction?.label ?? - (mergePresentation.directMergeAvailable - ? mergeMethods.defaultLabel - : mergePresentation.label)} - + + {mergePresentation.autoMergeAction?.label ?? + (mergePresentation.directMergeAvailable + ? mergeMethods.defaultLabel + : mergePresentation.label)} + + @@ -3551,7 +3559,8 @@ function PRActionsPanel({ variant={nextState === 'closed' ? 'outline' : 'secondary'} size="sm" className={cn( - 'w-full justify-center gap-2', + REVIEW_ACTION_STATE_BUTTON_CLASS, + 'gap-2', nextState === 'closed' && 'border-border bg-background text-foreground hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50' )} @@ -3565,9 +3574,11 @@ function PRActionsPanel({ ) : ( )} - {nextState === 'closed' - ? translate('auto.components.PullRequestPage.96d013ed28', 'Close pull request') - : translate('auto.components.PullRequestPage.9d5425918e', 'Reopen PR')} + + {nextState === 'closed' + ? translate('auto.components.PullRequestPage.96d013ed28', 'Close pull request') + : translate('auto.components.PullRequestPage.9d5425918e', 'Reopen PR')} +
diff --git a/src/renderer/src/components/feature-wall/review-animated-visual-ship-styles.tsx b/src/renderer/src/components/feature-wall/review-animated-visual-ship-styles.tsx index 00ed0a14c33..8284f0091af 100644 --- a/src/renderer/src/components/feature-wall/review-animated-visual-ship-styles.tsx +++ b/src/renderer/src/components/feature-wall/review-animated-visual-ship-styles.tsx @@ -14,7 +14,7 @@ export function ReviewShipVisualStyles(): JSX.Element { ) diff --git a/src/renderer/src/components/right-sidebar/CreateHostedReviewComposer.tsx b/src/renderer/src/components/right-sidebar/CreateHostedReviewComposer.tsx index 63e66c68d18..9656487a72d 100644 --- a/src/renderer/src/components/right-sidebar/CreateHostedReviewComposer.tsx +++ b/src/renderer/src/components/right-sidebar/CreateHostedReviewComposer.tsx @@ -25,6 +25,11 @@ import type { HostedReviewProvider } from '../../../../shared/hosted-review' import { stripBaseRef } from './useCreatePullRequestDialogFields' import type { DropdownActionKind, DropdownEntry } from './source-control-dropdown-items' import { CreateHostedReviewComposerFields } from './CreateHostedReviewComposerFields' +import { + RIGHT_SIDEBAR_MORPHING_PRIMARY_BUTTON_CLASS, + RIGHT_SIDEBAR_PRIMARY_BUTTON_LABEL_CLASS, + RIGHT_SIDEBAR_SPLIT_ACTION_ROW_CLASS +} from './right-sidebar-primary-action-layout' const EMPTY_DROPDOWN_ITEMS: DropdownEntry[] = [] @@ -245,13 +250,17 @@ export function CreateHostedReviewComposer({ baseSameAsBranch={baseSameAsBranch} /> -
+
{showDropdown ? ( diff --git a/src/renderer/src/components/right-sidebar/HostedReviewActions.tsx b/src/renderer/src/components/right-sidebar/HostedReviewActions.tsx index b82419fb61b..b61957030c9 100644 --- a/src/renderer/src/components/right-sidebar/HostedReviewActions.tsx +++ b/src/renderer/src/components/right-sidebar/HostedReviewActions.tsx @@ -22,6 +22,11 @@ import { MergedReviewActions } from './HostedReviewStateActions' import { useHostedReviewActions, type HostedReviewActionInfo } from './use-hosted-review-actions' +import { + RIGHT_SIDEBAR_MERGE_PRIMARY_BUTTON_CLASS, + RIGHT_SIDEBAR_PRIMARY_BUTTON_LABEL_CLASS, + RIGHT_SIDEBAR_SPLIT_ACTION_ROW_CLASS +} from './right-sidebar-primary-action-layout' import { translate } from '@/i18n/i18n' export default function HostedReviewActions({ @@ -100,17 +105,23 @@ export default function HostedReviewActions({ return (
-
+
{/* Why: wrapping in a so the tooltip trigger receives pointer events even when the merge button inside is disabled. */} - + diff --git a/src/renderer/src/components/right-sidebar/HostedReviewStateActions.tsx b/src/renderer/src/components/right-sidebar/HostedReviewStateActions.tsx index 5ac25e3d4bc..b1ad87dd1a5 100644 --- a/src/renderer/src/components/right-sidebar/HostedReviewStateActions.tsx +++ b/src/renderer/src/components/right-sidebar/HostedReviewStateActions.tsx @@ -22,12 +22,12 @@ export function ClosedReviewActions({ onReopenReview: () => void }): React.JSX.Element { return ( -
+
@@ -6025,12 +6035,12 @@ export function ConflictSummaryCard({
-
+
+
+ +
) : null}
) diff --git a/src/renderer/src/components/right-sidebar/right-sidebar-primary-action-layout.ts b/src/renderer/src/components/right-sidebar/right-sidebar-primary-action-layout.ts new file mode 100644 index 00000000000..e8dc4840439 --- /dev/null +++ b/src/renderer/src/components/right-sidebar/right-sidebar-primary-action-layout.ts @@ -0,0 +1,16 @@ +// Why: sidebar primaries rotate labels as git/review state changes. Content-width +// reads better than full-bleed; min-width reduces jump between states; max-w-full +// plus a truncating label span caps overflow in long i18n strings. +export const RIGHT_SIDEBAR_SPLIT_ACTION_ROW_CLASS = 'inline-flex max-w-full items-stretch' + +export const RIGHT_SIDEBAR_MORPHING_PRIMARY_BUTTON_CLASS = 'min-w-[10.5rem] max-w-full' + +// Covers "Squash and merge" and "Disable auto-merge". +export const RIGHT_SIDEBAR_MERGE_PRIMARY_BUTTON_CLASS = 'min-w-[11.5rem] max-w-full' + +export const RIGHT_SIDEBAR_PRIMARY_BUTTON_LABEL_CLASS = 'min-w-0 truncate' + +// PR full-page and item-dialog asides share the same merge/state labels. +export const REVIEW_ACTION_MERGE_BUTTON_CLASS = RIGHT_SIDEBAR_MERGE_PRIMARY_BUTTON_CLASS + +export const REVIEW_ACTION_STATE_BUTTON_CLASS = 'min-w-[11.5rem] max-w-full' From 4278fdc1e83e60617810c1419045a8d0c10fd7a8 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Fri, 12 Jun 2026 00:09:26 -0700 Subject: [PATCH 04/15] Constrain right sidebar primary action widths (#5249) * Constrain right sidebar primary button widths to prevent layout jumps Introduce min-width constraints, truncation, and content-width layouts for primary actions in the right sidebar and PR dialogs. This prevents buttons from shifting layout dramatically when labels morph dynamically as git and review state changes. * Fix right sidebar split action overflow --- .../right-sidebar/HostedReviewActions.tsx | 2 +- .../right-sidebar/SourceControl.tsx | 2 +- ...ight-sidebar-primary-action-layout.test.ts | 26 +++++++++++++++++++ .../right-sidebar-primary-action-layout.ts | 14 +++++----- 4 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 src/renderer/src/components/right-sidebar/right-sidebar-primary-action-layout.test.ts diff --git a/src/renderer/src/components/right-sidebar/HostedReviewActions.tsx b/src/renderer/src/components/right-sidebar/HostedReviewActions.tsx index b61957030c9..fd438a3ba85 100644 --- a/src/renderer/src/components/right-sidebar/HostedReviewActions.tsx +++ b/src/renderer/src/components/right-sidebar/HostedReviewActions.tsx @@ -112,7 +112,7 @@ export default function HostedReviewActions({ events even when the merge button inside is disabled. */} diff --git a/src/renderer/src/components/right-sidebar/SourceControl.tsx b/src/renderer/src/components/right-sidebar/SourceControl.tsx index 363723260cf..36fd64c3329 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControl.tsx @@ -5278,7 +5278,7 @@ export function CommitArea({ pair read as one split button instead of two detached buttons. */} - +
) : !checksExpanded ? null : ( diff --git a/src/renderer/src/components/right-sidebar/checks-panel-polling.test.ts b/src/renderer/src/components/right-sidebar/checks-panel-polling.test.ts new file mode 100644 index 00000000000..55e453ded19 --- /dev/null +++ b/src/renderer/src/components/right-sidebar/checks-panel-polling.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from 'vitest' + +import type { PRCheckDetail } from '../../../../shared/types' +import { + CHECKS_PANEL_BASE_POLL_INTERVAL_MS, + CHECKS_PANEL_MAX_POLL_INTERVAL_MS, + nextChecksPanelPollInterval +} from './checks-panel-polling' + +describe('nextChecksPanelPollInterval', () => { + it('keeps repeated empty results at the baseline poll interval', () => { + expect( + nextChecksPanelPollInterval({ + checks: [], + previousSignature: '[]', + currentIntervalMs: CHECKS_PANEL_MAX_POLL_INTERVAL_MS + }) + ).toEqual({ intervalMs: CHECKS_PANEL_BASE_POLL_INTERVAL_MS, signature: '[]' }) + }) + + it('backs off repeated non-empty results up to the maximum interval', () => { + const checks: PRCheckDetail[] = [ + { name: 'build', status: 'completed', conclusion: 'success', url: null } + ] + const { signature } = nextChecksPanelPollInterval({ + checks, + previousSignature: '', + currentIntervalMs: CHECKS_PANEL_BASE_POLL_INTERVAL_MS + }) + + expect( + nextChecksPanelPollInterval({ + checks, + previousSignature: signature, + currentIntervalMs: CHECKS_PANEL_BASE_POLL_INTERVAL_MS + }).intervalMs + ).toBe(CHECKS_PANEL_BASE_POLL_INTERVAL_MS * 2) + expect( + nextChecksPanelPollInterval({ + checks, + previousSignature: signature, + currentIntervalMs: CHECKS_PANEL_MAX_POLL_INTERVAL_MS + }).intervalMs + ).toBe(CHECKS_PANEL_MAX_POLL_INTERVAL_MS) + }) + + it('resets changed non-empty results to the baseline poll interval', () => { + const previous: PRCheckDetail[] = [ + { name: 'build', status: 'queued', conclusion: null, url: null } + ] + const next: PRCheckDetail[] = [ + { name: 'build', status: 'completed', conclusion: 'success', url: null } + ] + const { signature } = nextChecksPanelPollInterval({ + checks: previous, + previousSignature: '', + currentIntervalMs: CHECKS_PANEL_BASE_POLL_INTERVAL_MS + }) + + expect( + nextChecksPanelPollInterval({ + checks: next, + previousSignature: signature, + currentIntervalMs: CHECKS_PANEL_MAX_POLL_INTERVAL_MS + }).intervalMs + ).toBe(CHECKS_PANEL_BASE_POLL_INTERVAL_MS) + }) +}) diff --git a/src/renderer/src/components/right-sidebar/checks-panel-polling.ts b/src/renderer/src/components/right-sidebar/checks-panel-polling.ts new file mode 100644 index 00000000000..42573079584 --- /dev/null +++ b/src/renderer/src/components/right-sidebar/checks-panel-polling.ts @@ -0,0 +1,26 @@ +import type { PRCheckDetail } from '../../../../shared/types' + +export const CHECKS_PANEL_BASE_POLL_INTERVAL_MS = 30_000 +export const CHECKS_PANEL_MAX_POLL_INTERVAL_MS = 120_000 + +export function nextChecksPanelPollInterval(input: { + checks: PRCheckDetail[] + previousSignature: string + currentIntervalMs: number +}): { intervalMs: number; signature: string } { + const signature = JSON.stringify( + input.checks.map((check) => `${check.name}:${check.status}:${check.conclusion}`) + ) + + if (input.checks.length === 0) { + return { intervalMs: CHECKS_PANEL_BASE_POLL_INTERVAL_MS, signature } + } + + return { + intervalMs: + signature === input.previousSignature + ? Math.min(input.currentIntervalMs * 2, CHECKS_PANEL_MAX_POLL_INTERVAL_MS) + : CHECKS_PANEL_BASE_POLL_INTERVAL_MS, + signature + } +} diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 6b2e7498b04..e12518b199d 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -8070,7 +8070,7 @@ "74c6885b8a": "More comment actions", "cbcc4ab3db": "Showing first 100 checks", "0dca6bfab5": "Open check details", - "991f50c7e4": "No checks configured", + "991f50c7e4": "No checks reported yet", "9ad98f2a17": "pending", "5e52f4ef7f": "failing", "02ca4f9074": "passing", diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index e6f3a59db1e..3b33da37a6e 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -8070,7 +8070,7 @@ "74c6885b8a": "Más acciones de comentarios", "cbcc4ab3db": "Mostrando los primeros 100 cheques", "0dca6bfab5": "Abrir detalles del cheque", - "991f50c7e4": "No hay controles configurados", + "991f50c7e4": "Aún no se han reportado controles", "9ad98f2a17": "pendiente", "5e52f4ef7f": "defecto", "02ca4f9074": "paso", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 809a3162f51..f44cf3ae512 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -8070,7 +8070,7 @@ "74c6885b8a": "その他のコメント操作", "cbcc4ab3db": "最初の 100 件のチェックを表示しています", "0dca6bfab5": "オープンチェックの詳細", - "991f50c7e4": "チェックが設定されていません", + "991f50c7e4": "まだチェックは報告されていません", "9ad98f2a17": "保留中", "5e52f4ef7f": "失敗した", "02ca4f9074": "合格", diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 843af4c677a..5ce1070f7a4 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -8070,7 +8070,7 @@ "74c6885b8a": "추가 댓글 작업", "cbcc4ab3db": "처음 100개 검사 표시", "0dca6bfab5": "검사 세부 정보 열기", - "991f50c7e4": "구성된 검사가 없습니다.", + "991f50c7e4": "아직 보고된 체크가 없습니다.", "9ad98f2a17": "보류 중", "5e52f4ef7f": "실패", "02ca4f9074": "통과", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 43beb2959f2..36c6a9f3b7d 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -8070,7 +8070,7 @@ "74c6885b8a": "更多评论动作", "cbcc4ab3db": "显示前 100 项检查", "0dca6bfab5": "打开检查详细信息", - "991f50c7e4": "未配置检查", + "991f50c7e4": "尚未报告任何检查", "9ad98f2a17": "待办的", "5e52f4ef7f": "失败", "02ca4f9074": "通过", diff --git a/src/renderer/src/store/slices/github-checks-cache.test.ts b/src/renderer/src/store/slices/github-checks-cache.test.ts new file mode 100644 index 00000000000..2474ac521e3 --- /dev/null +++ b/src/renderer/src/store/slices/github-checks-cache.test.ts @@ -0,0 +1,390 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { create } from 'zustand' + +import { createGitHubSlice, prChecksCacheSuffix } from './github' +import { createHostedReviewSlice } from './hosted-review' +import { getHostedReviewCacheKey } from './hosted-review-cache-identity' +import type { AppState } from '../types' +import type { PRCheckDetail, PRInfo } from '../../../../shared/types' + +const mockApi = { + gh: { + prChecks: vi.fn() + }, + cache: { + setGitHub: vi.fn() + } +} + +// @ts-expect-error test window mock +globalThis.window = { api: mockApi } + +type Deferred = { + promise: Promise + resolve: (value: T) => void +} + +function deferred(): Deferred { + let resolve: (value: T) => void = () => {} + const promise = new Promise((promiseResolve) => { + resolve = promiseResolve + }) + return { promise, resolve } +} + +function createTestStore() { + return create()( + (...a) => + ({ + ...createGitHubSlice(...a), + ...createHostedReviewSlice(...a) + }) as AppState + ) +} + +function makePR(overrides: Partial = {}): PRInfo { + return { + number: 12, + title: 'Test PR', + state: 'open', + url: 'https://example.com/pr/12', + checksStatus: 'pending', + updatedAt: '2026-03-28T00:00:00Z', + mergeable: 'UNKNOWN', + headSha: 'head-oid', + ...overrides + } +} + +beforeEach(() => { + mockApi.gh.prChecks.mockReset() + mockApi.gh.prChecks.mockResolvedValue([]) + mockApi.cache.setGitHub.mockReset() + mockApi.cache.setGitHub.mockResolvedValue(undefined) +}) + +afterEach(() => { + vi.useRealTimers() +}) + +describe('createGitHubSlice.fetchPRChecks checks cache freshness', () => { + it('expires empty checks cache entries after the shorter empty TTL', async () => { + vi.useFakeTimers() + vi.setSystemTime(1_000) + const store = createTestStore() + const repoPath = '/repo' + const repoId = 'repo-id' + const branch = 'feature/test' + + mockApi.gh.prChecks.mockResolvedValue([]) + + await store.getState().fetchPRChecks(repoPath, 12, branch, undefined, null, { repoId }) + vi.setSystemTime(11_001) + await store.getState().fetchPRChecks(repoPath, 12, branch, undefined, null, { repoId }) + + expect(mockApi.gh.prChecks).toHaveBeenCalledTimes(2) + }) + + it('keeps repeated automatic empty checks refreshes cacheable', async () => { + vi.useFakeTimers() + vi.setSystemTime(1_000) + const store = createTestStore() + const repoPath = '/repo' + const repoId = 'repo-id' + const branch = 'feature/test' + + mockApi.gh.prChecks.mockResolvedValue([]) + + await store.getState().fetchPRChecks(repoPath, 12, branch, undefined, null, { repoId }) + vi.setSystemTime(11_001) + await store.getState().fetchPRChecks(repoPath, 12, branch, undefined, null, { repoId }) + + expect(mockApi.gh.prChecks).toHaveBeenCalledTimes(2) + expect(mockApi.gh.prChecks).toHaveBeenNthCalledWith(1, { + repoPath, + repoId, + prNumber: 12, + headSha: undefined, + prRepo: null, + noCache: false + }) + expect(mockApi.gh.prChecks).toHaveBeenNthCalledWith(2, { + repoPath, + repoId, + prNumber: 12, + headSha: undefined, + prRepo: null, + noCache: false + }) + }) + + it('keeps non-empty checks cache entries fresh for the normal checks TTL', async () => { + vi.useFakeTimers() + vi.setSystemTime(1_000) + const store = createTestStore() + const repoPath = '/repo' + const repoId = 'repo-id' + const branch = 'feature/test' + + mockApi.gh.prChecks.mockResolvedValue([ + { name: 'build', status: 'completed', conclusion: 'success', url: null } + ]) + + await store.getState().fetchPRChecks(repoPath, 12, branch, undefined, null, { repoId }) + vi.setSystemTime(11_001) + await store.getState().fetchPRChecks(repoPath, 12, branch, undefined, null, { repoId }) + + expect(mockApi.gh.prChecks).toHaveBeenCalledTimes(1) + }) + + it('dedupes simultaneous cacheable empty checks requests', async () => { + const store = createTestStore() + const repoPath = '/repo' + const repoId = 'repo-id' + const branch = 'feature/test' + const request = deferred() + mockApi.gh.prChecks.mockReturnValueOnce(request.promise) + + const first = store.getState().fetchPRChecks(repoPath, 12, branch, undefined, null, { repoId }) + const second = store.getState().fetchPRChecks(repoPath, 12, branch, undefined, null, { repoId }) + + expect(mockApi.gh.prChecks).toHaveBeenCalledTimes(1) + request.resolve([]) + + await expect(first).resolves.toEqual([]) + await expect(second).resolves.toEqual([]) + }) + + it('does not dedupe forced checks onto an in-flight cacheable request', async () => { + const store = createTestStore() + const repoPath = '/repo' + const repoId = 'repo-id' + const branch = 'feature/test' + const cacheableRequest = deferred() + const forcedChecks = [ + { name: 'build', status: 'completed', conclusion: 'success', url: null } as const + ] + mockApi.gh.prChecks + .mockReturnValueOnce(cacheableRequest.promise) + .mockResolvedValueOnce(forcedChecks) + + const cacheable = store + .getState() + .fetchPRChecks(repoPath, 12, branch, undefined, null, { repoId }) + const forced = store + .getState() + .fetchPRChecks(repoPath, 12, branch, undefined, null, { force: true, repoId }) + + expect(mockApi.gh.prChecks).toHaveBeenCalledTimes(1) + cacheableRequest.resolve([]) + + await expect(cacheable).resolves.toEqual([]) + await expect(forced).resolves.toEqual(forcedChecks) + expect(mockApi.gh.prChecks).toHaveBeenCalledTimes(2) + expect(mockApi.gh.prChecks).toHaveBeenNthCalledWith(2, { + repoPath, + repoId, + prNumber: 12, + headSha: undefined, + prRepo: null, + noCache: true + }) + }) + + it('dedupes simultaneous forced checks requests', async () => { + const store = createTestStore() + const repoPath = '/repo' + const repoId = 'repo-id' + const branch = 'feature/test' + const request = deferred() + const checks = [ + { name: 'build', status: 'completed', conclusion: 'success', url: null } as const + ] + mockApi.gh.prChecks.mockReturnValueOnce(request.promise) + + const first = store + .getState() + .fetchPRChecks(repoPath, 12, branch, undefined, null, { force: true, repoId }) + const second = store + .getState() + .fetchPRChecks(repoPath, 12, branch, undefined, null, { force: true, repoId }) + + expect(mockApi.gh.prChecks).toHaveBeenCalledTimes(1) + request.resolve(checks) + + await expect(first).resolves.toEqual(checks) + await expect(second).resolves.toEqual(checks) + expect(mockApi.gh.prChecks).toHaveBeenCalledWith({ + repoPath, + repoId, + prNumber: 12, + headSha: undefined, + prRepo: null, + noCache: true + }) + }) + + it('treats explicit noCache checks requests as fresh requests', async () => { + const store = createTestStore() + const repoPath = '/repo' + const repoId = 'repo-id' + const branch = 'feature/test' + + mockApi.gh.prChecks.mockResolvedValue([ + { name: 'build', status: 'completed', conclusion: 'success', url: null } + ]) + + await store.getState().fetchPRChecks(repoPath, 12, branch, undefined, null, { repoId }) + await store + .getState() + .fetchPRChecks(repoPath, 12, branch, undefined, null, { noCache: true, repoId }) + + expect(mockApi.gh.prChecks).toHaveBeenCalledTimes(2) + expect(mockApi.gh.prChecks).toHaveBeenNthCalledWith(2, { + repoPath, + repoId, + prNumber: 12, + headSha: undefined, + prRepo: null, + noCache: true + }) + }) + + it('preserves cached checks when the checks IPC fails', async () => { + const store = createTestStore() + const repoPath = '/repo' + const branch = 'feature/test' + const checksCacheKey = `${repoPath}::pr-checks::12` + const cachedChecks = [ + { name: 'build', status: 'completed', conclusion: 'failure', url: null } as const + ] + + store.setState({ + checksCache: { + [checksCacheKey]: { + data: cachedChecks, + fetchedAt: 1, + headSha: 'abc123head' + } + } + } as unknown as Partial) + mockApi.gh.prChecks.mockRejectedValueOnce(new Error('rate limited')) + + await expect( + store.getState().fetchPRChecks(repoPath, 12, branch, 'abc123head', null, { force: true }) + ).resolves.toEqual(cachedChecks) + + expect(store.getState().checksCache[checksCacheKey]?.data).toEqual(cachedChecks) + expect(store.getState().checksCache[checksCacheKey]?.fetchedAt).toBe(1) + }) + + it('does not return cached checks for a different requested head SHA after IPC failure', async () => { + const store = createTestStore() + const repoPath = '/repo' + const branch = 'feature/test' + const checksCacheKey = `${repoPath}::pr-checks::12` + const oldHeadChecks = [ + { name: 'build', status: 'completed', conclusion: 'success', url: null } as const + ] + + store.setState({ + checksCache: { + [checksCacheKey]: { + data: oldHeadChecks, + fetchedAt: 1, + headSha: 'old-head' + } + } + } as unknown as Partial) + mockApi.gh.prChecks.mockRejectedValueOnce(new Error('rate limited')) + + await expect( + store.getState().fetchPRChecks(repoPath, 12, branch, 'new-head', null, { force: true }) + ).resolves.toEqual([]) + + expect(store.getState().checksCache[checksCacheKey]?.data).toEqual(oldHeadChecks) + expect(store.getState().checksCache[checksCacheKey]?.headSha).toBe('old-head') + }) +}) + +describe('createGitHubSlice.applyGitHubPRRefreshEvent checks cache reuse', () => { + it('does not derive neutral PR status from stale empty checks during refresh events', () => { + const store = createTestStore() + const repoPath = '/repo' + const repoId = 'repo-1' + const branch = 'feature/stale-empty-checks' + const cacheKey = `${repoId}::${branch}` + const hostedReviewCacheKey = getHostedReviewCacheKey(repoPath, branch, null, repoId) + const checksCacheKey = `${repoId}::${prChecksCacheSuffix(12, null, 'head-oid')}` + + store.setState({ + checksCache: { + [checksCacheKey]: { + data: [], + fetchedAt: 1_000, + headSha: 'head-oid' + } + } + } as unknown as Partial) + + store.getState().applyGitHubPRRefreshEvent({ + sequence: 1, + aliases: [{ cacheKey, repoId, repoPath, branch }], + reason: 'visible', + outcome: { + kind: 'found', + pr: makePR({ number: 12, checksStatus: 'pending', headSha: 'head-oid' }), + fetchedAt: 21_000 + } + }) + + expect(store.getState().prCache[cacheKey]).toMatchObject({ + data: expect.objectContaining({ checksStatus: 'pending' }), + fetchedAt: 21_000 + }) + expect(store.getState().hostedReviewCache[hostedReviewCacheKey]).toMatchObject({ + data: expect.objectContaining({ provider: 'github', status: 'pending' }), + fetchedAt: 21_000 + }) + }) + + it('reuses fresh head-specific checks cache entries during refresh events', () => { + const store = createTestStore() + const repoPath = '/repo' + const repoId = 'repo-1' + const branch = 'feature/fresh-head-checks' + const cacheKey = `${repoId}::${branch}` + const hostedReviewCacheKey = getHostedReviewCacheKey(repoPath, branch, null, repoId) + const checksCacheKey = `${repoId}::${prChecksCacheSuffix(12, null, 'head-oid')}` + + store.setState({ + checksCache: { + [checksCacheKey]: { + data: [{ name: 'build', status: 'completed', conclusion: 'success', url: null }], + fetchedAt: 20_000, + headSha: 'head-oid' + } + } + } as unknown as Partial) + + store.getState().applyGitHubPRRefreshEvent({ + sequence: 1, + aliases: [{ cacheKey, repoId, repoPath, branch }], + reason: 'visible', + outcome: { + kind: 'found', + pr: makePR({ number: 12, checksStatus: 'pending', headSha: 'head-oid' }), + fetchedAt: 21_000 + } + }) + + expect(store.getState().prCache[cacheKey]).toMatchObject({ + data: expect.objectContaining({ checksStatus: 'success' }), + fetchedAt: 21_000 + }) + expect(store.getState().hostedReviewCache[hostedReviewCacheKey]).toMatchObject({ + data: expect.objectContaining({ provider: 'github', status: 'success' }), + fetchedAt: 21_000 + }) + }) +}) diff --git a/src/renderer/src/store/slices/github.test.ts b/src/renderer/src/store/slices/github.test.ts index 10d9bf92fce..c45ecf55801 100644 --- a/src/renderer/src/store/slices/github.test.ts +++ b/src/renderer/src/store/slices/github.test.ts @@ -847,62 +847,6 @@ describe('createGitHubSlice.fetchPRComments', () => { vi.useRealTimers() } }) - - it('preserves cached checks when the checks IPC fails', async () => { - const store = createTestStore() - const repoPath = '/repo' - const branch = 'feature/test' - const checksCacheKey = `${repoPath}::pr-checks::12` - const cachedChecks = [ - { name: 'build', status: 'completed', conclusion: 'failure', url: null } as const - ] - - store.setState({ - checksCache: { - [checksCacheKey]: { - data: cachedChecks, - fetchedAt: 1, - headSha: 'abc123head' - } - } - } as unknown as Partial) - mockApi.gh.prChecks.mockRejectedValueOnce(new Error('rate limited')) - - await expect( - store.getState().fetchPRChecks(repoPath, 12, branch, 'abc123head', null, { force: true }) - ).resolves.toEqual(cachedChecks) - - expect(store.getState().checksCache[checksCacheKey]?.data).toEqual(cachedChecks) - expect(store.getState().checksCache[checksCacheKey]?.fetchedAt).toBe(1) - }) - - it('does not return cached checks for a different requested head SHA after IPC failure', async () => { - const store = createTestStore() - const repoPath = '/repo' - const branch = 'feature/test' - const checksCacheKey = `${repoPath}::pr-checks::12` - const oldHeadChecks = [ - { name: 'build', status: 'completed', conclusion: 'success', url: null } as const - ] - - store.setState({ - checksCache: { - [checksCacheKey]: { - data: oldHeadChecks, - fetchedAt: 1, - headSha: 'old-head' - } - } - } as unknown as Partial) - mockApi.gh.prChecks.mockRejectedValueOnce(new Error('rate limited')) - - await expect( - store.getState().fetchPRChecks(repoPath, 12, branch, 'new-head', null, { force: true }) - ).resolves.toEqual([]) - - expect(store.getState().checksCache[checksCacheKey]?.data).toEqual(oldHeadChecks) - expect(store.getState().checksCache[checksCacheKey]?.headSha).toBe('old-head') - }) }) describe('createGitHubSlice.fetchPRCheckDetails', () => { diff --git a/src/renderer/src/store/slices/github.ts b/src/renderer/src/store/slices/github.ts index add36b6cbfc..7aaf6a21e07 100644 --- a/src/renderer/src/store/slices/github.ts +++ b/src/renderer/src/store/slices/github.ts @@ -427,6 +427,7 @@ function bypassesGitHubPRRefreshFreshness(reason: GitHubPRRefreshReason): boolea const CACHE_TTL = 300_000 // 5 minutes (stale data shown instantly, then refreshed) const CHECKS_CACHE_TTL = 60_000 // 1 minute — checks change more frequently +const EMPTY_CHECKS_CACHE_TTL = 10_000 // Why: the NewWorkspace page's work-item list is a browse surface, not a // source of truth, so 60s staleness is fine — stale data renders instantly // while a background refresh keeps it current. @@ -441,7 +442,11 @@ const inflightPRRequests = new Map< { promise: Promise; force: boolean; generation: number; lookupHintKey: string } >() const inflightIssueRequests = new Map>() -const inflightChecksRequests = new Map>() +type InflightChecksRequest = { + promise: Promise + noCache: boolean +} +const inflightChecksRequests = new Map() const inflightCommentsRequests = new Map>() type InflightWorkItems = { promise: Promise @@ -654,6 +659,10 @@ function isFresh(entry: CacheEntry | undefined, ttl = CACHE_TTL): entry is return entry !== undefined && Date.now() - entry.fetchedAt < ttl } +function checksCacheTtl(entry: CacheEntry | undefined): number { + return entry?.data?.length === 0 ? EMPTY_CHECKS_CACHE_TTL : CHECKS_CACHE_TTL +} + function findWorktreeById(state: AppState, worktreeId: string): Worktree | null { for (const worktrees of Object.values(state.worktreesByRepo)) { const worktree = worktrees.find((w) => w.id === worktreeId) @@ -2411,10 +2420,11 @@ export const createGitHubSlice: StateCreator = (s ) : cacheKey const inflightKey = cacheKey + const requestNoCache = options?.noCache === true || options?.force === true const cached = get().checksCache[cacheKey] ?? get().checksCache[legacyCacheKey] if ( - !options?.force && - isFresh(cached, CHECKS_CACHE_TTL) && + !requestNoCache && + isFresh(cached, checksCacheTtl(cached)) && (!headSha || cached.headSha === headSha) ) { const cachedChecks = cached.data ?? [] @@ -2438,7 +2448,16 @@ export const createGitHubSlice: StateCreator = (s const inflightRequest = inflightChecksRequests.get(inflightKey) if (inflightRequest) { - return inflightRequest + if (!requestNoCache || inflightRequest.noCache) { + return inflightRequest.promise + } + // Why: manual refreshes must not inherit an automatic request that may + // still be served from the GitHub CLI cache. Wait, then issue fresh. + await inflightRequest.promise.catch(() => {}) + const latestInflightRequest = inflightChecksRequests.get(inflightKey) + if (latestInflightRequest?.noCache) { + return latestInflightRequest.promise + } } const request = (async () => { @@ -2453,7 +2472,7 @@ export const createGitHubSlice: StateCreator = (s prNumber, headSha, prRepo: prRepo ?? null, - noCache: options?.force + noCache: requestNoCache }, { timeoutMs: 30_000 } ) @@ -2463,7 +2482,7 @@ export const createGitHubSlice: StateCreator = (s prNumber, headSha, prRepo: prRepo ?? null, - noCache: options?.force + noCache: requestNoCache })) as PRCheckDetail[]) set((s) => { const nextState: Partial = { @@ -2505,7 +2524,7 @@ export const createGitHubSlice: StateCreator = (s } })() - inflightChecksRequests.set(inflightKey, request) + inflightChecksRequests.set(inflightKey, { promise: request, noCache: requestNoCache }) return request }, @@ -2924,6 +2943,17 @@ export const createGitHubSlice: StateCreator = (s const checksCacheKeys = [ ...(alias.repoId ? [ + ...(pr.headSha + ? [ + runtimeScopedRepoCacheKey( + alias.repoPath, + alias.repoId, + prChecksCacheSuffix(pr.number, pr.prRepo, pr.headSha), + s.settings, + alias.connectionId + ) + ] + : []), runtimeScopedRepoCacheKey( alias.repoPath, alias.repoId, @@ -2933,6 +2963,17 @@ export const createGitHubSlice: StateCreator = (s ) ] : []), + ...(pr.headSha + ? [ + runtimeScopedRepoCacheKey( + alias.repoPath, + undefined, + prChecksCacheSuffix(pr.number, pr.prRepo, pr.headSha), + s.settings, + alias.connectionId + ) + ] + : []), runtimeScopedRepoCacheKey( alias.repoPath, undefined, @@ -2944,14 +2985,15 @@ export const createGitHubSlice: StateCreator = (s ] const checksEntry = checksCacheKeys .map((key) => s.checksCache[key]) - .find((entry) => entry?.data) - if ( - checksEntry?.data && - checksEntry.headSha && - pr.headSha && - checksEntry.headSha === pr.headSha && - event.outcome.fetchedAt - checksEntry.fetchedAt < CHECKS_CACHE_TTL - ) { + .find( + (entry) => + entry?.data && + entry.headSha && + pr.headSha && + entry.headSha === pr.headSha && + event.outcome.fetchedAt - entry.fetchedAt < checksCacheTtl(entry) + ) + if (checksEntry?.data) { return { ...pr, checksStatus: deriveCheckStatusFromChecks(checksEntry.data) } } return pr From 030a35a2dfa904f1c59acd80f46f455df03b362d Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Fri, 12 Jun 2026 00:18:51 -0700 Subject: [PATCH 06/15] Update manual project order notice wording and add translations (#5253) Correct a minor grammatical issue in the English notice by adding "the". Additionally, localize FolderWorkspaceComposerDialog and ProjectOrderManualDefaultNotice in Spanish, Japanese, Korean, and Chinese to replace the English fallback strings. --- .../ProjectOrderManualDefaultNotice.tsx | 2 +- src/renderer/src/i18n/locales/en.json | 2 +- src/renderer/src/i18n/locales/es.json | 20 +++++++++---------- src/renderer/src/i18n/locales/ja.json | 20 +++++++++---------- src/renderer/src/i18n/locales/ko.json | 18 ++++++++--------- src/renderer/src/i18n/locales/zh.json | 18 ++++++++--------- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/renderer/src/components/sidebar/ProjectOrderManualDefaultNotice.tsx b/src/renderer/src/components/sidebar/ProjectOrderManualDefaultNotice.tsx index 64abeff0c2d..4ccc2764d44 100644 --- a/src/renderer/src/components/sidebar/ProjectOrderManualDefaultNotice.tsx +++ b/src/renderer/src/components/sidebar/ProjectOrderManualDefaultNotice.tsx @@ -41,7 +41,7 @@ function ProjectOrderManualDefaultNotice(): React.JSX.Element | null {

{translate( 'auto.components.sidebar.ProjectOrderManualDefaultNotice.a1f4c2d8e0', - 'Manual project order is now default' + 'Manual project order is now the default' )}

diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index e12518b199d..15a7c166414 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -3942,7 +3942,7 @@ "chooseSourceProject": "Choose task source" }, "ProjectOrderManualDefaultNotice": { - "a1f4c2d8e0": "Manual project order is now default", + "a1f4c2d8e0": "Manual project order is now the default", "822ff300ad": "Dismiss", "b7e3a91c4f": "Drag project headers to reorder, or switch to", "e8c1f4a2b9": "in workspace options." diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 3b33da37a6e..97ad926127f 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -3934,18 +3934,18 @@ "b826a98b6f": "ocupado" }, "FolderWorkspaceComposerDialog": { - "connectFailed": "Failed to connect to project.", - "noRepos": "Add a Git project under this folder to attach GitHub or GitLab tasks.", - "title": "Create Folder Workspace", - "create": "Create workspace", - "sourceProject": "Task Source", - "chooseSourceProject": "Choose task source" + "connectFailed": "No se pudo conectar al proyecto.", + "noRepos": "Agrega un proyecto Git en esta carpeta para adjuntar tareas de GitHub o GitLab.", + "title": "Crear espacio de trabajo de carpeta", + "create": "Crear espacio de trabajo", + "sourceProject": "Origen de tareas", + "chooseSourceProject": "Elige el origen de tareas" }, "ProjectOrderManualDefaultNotice": { - "a1f4c2d8e0": "Manual project order is now default", - "822ff300ad": "Dismiss", - "b7e3a91c4f": "Drag project headers to reorder, or switch to", - "e8c1f4a2b9": "in workspace options." + "a1f4c2d8e0": "El orden manual de proyectos ahora es el predeterminado", + "822ff300ad": "Descartar", + "b7e3a91c4f": "Arrastra los encabezados de proyecto para reordenarlos, o cambia a", + "e8c1f4a2b9": "en las opciones del espacio de trabajo." } }, "shared": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index f44cf3ae512..5d8c348592c 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -3934,18 +3934,18 @@ } }, "FolderWorkspaceComposerDialog": { - "connectFailed": "Failed to connect to project.", - "noRepos": "Add a Git project under this folder to attach GitHub or GitLab tasks.", - "title": "Create Folder Workspace", - "create": "Create workspace", - "sourceProject": "Task Source", - "chooseSourceProject": "Choose task source" + "connectFailed": "プロジェクトに接続できませんでした。", + "noRepos": "GitHub または GitLab のタスクを関連付けるには、このフォルダー内に Git プロジェクトを追加してください。", + "title": "フォルダーワークスペースを作成", + "create": "ワークスペースを作成", + "sourceProject": "タスクソース", + "chooseSourceProject": "タスクソースを選択" }, "ProjectOrderManualDefaultNotice": { - "a1f4c2d8e0": "Manual project order is now default", - "822ff300ad": "Dismiss", - "b7e3a91c4f": "Drag project headers to reorder, or switch to", - "e8c1f4a2b9": "in workspace options." + "a1f4c2d8e0": "手動のプロジェクト順序がデフォルトになりました", + "822ff300ad": "閉じる", + "b7e3a91c4f": "プロジェクトヘッダーをドラッグして並べ替えるか、", + "e8c1f4a2b9": "をワークスペースオプションで選択してください。" } }, "shared": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 5ce1070f7a4..900ff72de2f 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -3934,18 +3934,18 @@ } }, "FolderWorkspaceComposerDialog": { - "connectFailed": "Failed to connect to project.", - "noRepos": "Add a Git project under this folder to attach GitHub or GitLab tasks.", - "title": "Create Folder Workspace", + "connectFailed": "프로젝트에 연결하지 못했습니다.", + "noRepos": "GitHub 또는 GitLab 작업을 연결하려면 이 폴더 아래에 Git 프로젝트를 추가하세요.", + "title": "폴더 워크스페이스 만들기", "create": "워크스페이스 만들기", - "sourceProject": "Task Source", - "chooseSourceProject": "Choose task source" + "sourceProject": "작업 소스", + "chooseSourceProject": "작업 소스 선택" }, "ProjectOrderManualDefaultNotice": { - "a1f4c2d8e0": "Manual project order is now default", - "822ff300ad": "Dismiss", - "b7e3a91c4f": "Drag project headers to reorder, or switch to", - "e8c1f4a2b9": "in workspace options." + "a1f4c2d8e0": "수동 프로젝트 순서가 이제 기본값입니다", + "822ff300ad": "닫기", + "b7e3a91c4f": "프로젝트 헤더를 드래그하여 순서를 바꾸거나", + "e8c1f4a2b9": "정렬을 워크스페이스 옵션에서 선택하세요." } }, "shared": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 36c6a9f3b7d..31fac7636e0 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -3934,18 +3934,18 @@ } }, "FolderWorkspaceComposerDialog": { - "connectFailed": "Failed to connect to project.", - "noRepos": "Add a Git project under this folder to attach GitHub or GitLab tasks.", - "title": "Create Folder Workspace", + "connectFailed": "无法连接到项目。", + "noRepos": "在此文件夹下添加 Git 项目,以关联 GitHub 或 GitLab 任务。", + "title": "创建文件夹工作区", "create": "创建工作区", - "sourceProject": "Task Source", - "chooseSourceProject": "Choose task source" + "sourceProject": "任务来源", + "chooseSourceProject": "选择任务来源" }, "ProjectOrderManualDefaultNotice": { - "a1f4c2d8e0": "Manual project order is now default", - "822ff300ad": "Dismiss", - "b7e3a91c4f": "Drag project headers to reorder, or switch to", - "e8c1f4a2b9": "in workspace options." + "a1f4c2d8e0": "手动项目排序现在是默认设置", + "822ff300ad": "关闭", + "b7e3a91c4f": "拖动项目标题即可重新排序,或在工作区选项中切换到", + "e8c1f4a2b9": "。" } }, "shared": { From 290ffc7494fbc2c443a4b3c3f7da2b8f629f9a76 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 07:40:36 +0000 Subject: [PATCH 07/15] release: v1.4.65-rc.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 931cb6b01a1..351b530e3c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "orca", - "version": "1.4.64-rc.2", + "version": "1.4.65-rc.0", "description": "Next-gen IDE for parallel agentic development", "homepage": "https://github.com/stablyai/orca", "author": "stablyai", From f31c40cb183864a9dcf8e338897b2161d8119747 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Fri, 12 Jun 2026 01:38:56 -0700 Subject: [PATCH 08/15] Add Grok sessions to AI Vault (#5258) --- .../ai-vault/session-scanner-grok-parser.ts | 110 ++++++++++++++++++ src/main/ai-vault/session-scanner-types.ts | 1 + src/main/ai-vault/session-scanner.test.ts | 38 ++++++ src/main/ai-vault/session-scanner.ts | 21 +++- .../right-sidebar/SourceControl.tsx | 5 +- src/renderer/src/i18n/locales/en.json | 3 +- src/renderer/src/i18n/locales/es.json | 3 +- src/renderer/src/i18n/locales/ja.json | 3 +- src/renderer/src/i18n/locales/ko.json | 3 +- src/renderer/src/i18n/locales/zh.json | 3 +- src/shared/ai-vault-types.ts | 3 + 11 files changed, 183 insertions(+), 10 deletions(-) create mode 100644 src/main/ai-vault/session-scanner-grok-parser.ts diff --git a/src/main/ai-vault/session-scanner-grok-parser.ts b/src/main/ai-vault/session-scanner-grok-parser.ts new file mode 100644 index 00000000000..53dca524860 --- /dev/null +++ b/src/main/ai-vault/session-scanner-grok-parser.ts @@ -0,0 +1,110 @@ +import { createReadStream } from 'fs' +import { readFile } from 'fs/promises' +import { dirname, join } from 'path' +import { createInterface } from 'readline' +import type { AiVaultSession } from '../../shared/ai-vault-types' +import type { FileWithMtime, SessionAccumulator } from './session-scanner-types' +import { + addPreviewMessage, + createAccumulator, + finalizeSession, + sessionIdFromFileName, + updateTimeline +} from './session-scanner-accumulator' +import { + asRecord, + extractString, + normalizeTitleText, + numberValue, + parseJsonObject +} from './session-scanner-values' + +export async function parseGrokSessionFile( + file: FileWithMtime, + platform: NodeJS.Platform = process.platform +): Promise { + const record = asRecord(JSON.parse(await readFile(file.path, 'utf-8')) as unknown) + if (!record) { + return null + } + const info = asRecord(record.info) + const sessionId = extractString(info?.id) ?? sessionIdFromFileName(dirname(file.path)) + const accumulator = createAccumulator({ agent: 'grok', file, sessionId }) + accumulator.cwd = extractString(info?.cwd) + accumulator.title = + normalizeTitleText(extractString(record.generated_title) ?? '') ?? + normalizeTitleText(extractString(record.session_summary) ?? '') + accumulator.model = extractString(record.current_model_id) + accumulator.branch = extractString(record.head_branch) + accumulator.messageCount = + numberValue(record.num_chat_messages) || numberValue(record.num_messages) + updateTimeline(accumulator, extractString(record.created_at)) + updateTimeline(accumulator, extractString(record.updated_at)) + updateTimeline(accumulator, extractString(record.last_active_at)) + await consumeGrokChatHistory(accumulator, dirname(file.path)) + return finalizeSession(accumulator, platform) +} + +async function consumeGrokChatHistory( + accumulator: SessionAccumulator, + sessionDir: string +): Promise { + try { + const lines = createInterface({ + input: createReadStream(join(sessionDir, 'chat_history.jsonl'), { encoding: 'utf-8' }), + crlfDelay: Infinity + }) + + for await (const line of lines) { + const record = parseJsonObject(line) + if (!record) { + continue + } + const role = extractString(record.type) + if (role !== 'user' && role !== 'assistant') { + continue + } + const text = extractGrokContentText(record.content) + if (role === 'user') { + accumulator.title ??= normalizeTitleText(text ?? '') + } + addPreviewMessage(accumulator, { + role, + text, + timestamp: extractString(record.timestamp) + }) + } + } catch { + // Summary-only sessions still provide enough metadata for the Vault list. + } +} + +function extractGrokContentText(value: unknown): string | null { + const text = extractGrokRawContentText(value) + if (!text) { + return null + } + return text.match(/\s*([\s\S]*?)\s*<\/user_query>/i)?.[1]?.trim() || text +} + +function extractGrokRawContentText(value: unknown): string | null { + if (typeof value === 'string') { + return extractString(value) + } + if (!Array.isArray(value)) { + return null + } + const parts: string[] = [] + for (const item of value) { + if (typeof item === 'string') { + parts.push(item) + continue + } + const record = asRecord(item) + const text = extractString(record?.text) || extractString(record?.content) + if (text) { + parts.push(text) + } + } + return extractString(parts.join(' ')) +} diff --git a/src/main/ai-vault/session-scanner-types.ts b/src/main/ai-vault/session-scanner-types.ts index 47beebcfbc4..da494568adb 100644 --- a/src/main/ai-vault/session-scanner-types.ts +++ b/src/main/ai-vault/session-scanner-types.ts @@ -13,6 +13,7 @@ export type AiVaultScanOptions = { copilotSessionsDir?: string cursorProjectsDir?: string opencodeStorageDir?: string + grokSessionsDir?: string hermesSessionsDir?: string rovoSessionsDir?: string openclawStateDir?: string diff --git a/src/main/ai-vault/session-scanner.test.ts b/src/main/ai-vault/session-scanner.test.ts index 7c7bd43b508..1efaacdacdb 100644 --- a/src/main/ai-vault/session-scanner.test.ts +++ b/src/main/ai-vault/session-scanner.test.ts @@ -20,6 +20,7 @@ function isolatedScanRoots(root: string) { copilotSessionsDir: join(root, 'copilot-sessions'), cursorProjectsDir: join(root, 'cursor-projects'), opencodeStorageDir: join(root, 'opencode-storage'), + grokSessionsDir: join(root, 'grok-sessions'), hermesSessionsDir: join(root, 'hermes-sessions'), rovoSessionsDir: join(root, 'rovo-sessions'), openclawStateDir: join(root, 'openclaw-state'), @@ -422,6 +423,42 @@ describe('scanAiVaultSessions', () => { }) ) + await mkdir(join(roots.grokSessionsDir, encodeURIComponent('/tmp/grok'), 'grok-session'), { + recursive: true + }) + await writeFile( + join(roots.grokSessionsDir, encodeURIComponent('/tmp/grok'), 'grok-session', 'summary.json'), + JSON.stringify({ + info: { id: 'grok-session', cwd: '/tmp/grok' }, + session_summary: '', + created_at: '2026-05-01T10:04:00.000Z', + updated_at: '2026-05-01T10:04:01.000Z', + num_chat_messages: 2, + current_model_id: 'grok-build', + head_branch: 'feature/grok-vault' + }) + ) + await writeFile( + join( + roots.grokSessionsDir, + encodeURIComponent('/tmp/grok'), + 'grok-session', + 'chat_history.jsonl' + ), + jsonLines([ + { + type: 'user', + content: [ + { + type: 'text', + text: 'contextGrok title' + } + ] + }, + { type: 'assistant', content: 'Done' } + ]) + ) + await mkdir(roots.hermesSessionsDir, { recursive: true }) await writeFile( join(roots.hermesSessionsDir, 'session_hermes-session.json'), @@ -544,6 +581,7 @@ describe('scanAiVaultSessions', () => { expect(commandByAgent.get('opencode')).toBe( "cd '/tmp/opencode' && opencode --session 'opencode-session'" ) + expect(commandByAgent.get('grok')).toBe("cd '/tmp/grok' && grok --resume 'grok-session'") expect(commandByAgent.get('hermes')).toBe( "cd '/tmp/hermes' && hermes --resume 'hermes-session'" ) diff --git a/src/main/ai-vault/session-scanner.ts b/src/main/ai-vault/session-scanner.ts index 5e31ff966c2..b3f612cea70 100644 --- a/src/main/ai-vault/session-scanner.ts +++ b/src/main/ai-vault/session-scanner.ts @@ -8,6 +8,7 @@ import type { import { sessionSortTime } from './session-scanner-accumulator' import { codexHomeForSessionsDir, uniqueCodexSessionsDirs } from './session-scanner-codex-paths' import { discoverFiles, discoverOpenClawFiles } from './session-scanner-discovery' +import { parseGrokSessionFile } from './session-scanner-grok-parser' import { parseDroidSessionFile, parseMessageGraphSessionFile, @@ -53,15 +54,17 @@ const OPENCODE_STORAGE_DIR = join( process.env.OPENCODE_CONFIG_DIR?.trim() || join(homedir(), '.local', 'share', 'opencode'), 'storage' ) +const GROK_SESSIONS_DIR = join( + process.env.GROK_HOME?.trim() || join(homedir(), '.grok'), + 'sessions' +) const HERMES_SESSIONS_DIR = join(homedir(), '.hermes', 'sessions') const ROVO_SESSIONS_DIR = join(homedir(), '.rovodev', 'sessions') const OPENCLAW_STATE_DIR = process.env.OPENCLAW_STATE_DIR?.trim() || join(homedir(), '.openclaw') -const OPENCLAW_LEGACY_STATE_DIR = join(homedir(), '.clawdbot') const PI_SESSIONS_DIR = normalizePiSessionsDir( process.env.PI_CODING_AGENT_DIR?.trim() || join(homedir(), '.pi', 'agent', 'sessions') ) const DROID_SESSIONS_DIR = join(homedir(), '.factory', 'sessions') -const DROID_PROJECTS_DIR = join(homedir(), '.factory', 'projects') export async function scanAiVaultSessions( options: AiVaultScanOptions = {} @@ -121,6 +124,14 @@ export async function scanAiVaultSessions( issues, extensions: ['.json'] }), + discoverFiles({ + rootDir: options.grokSessionsDir ?? GROK_SESSIONS_DIR, + limit: limitPerAgent, + agent: 'grok', + issues, + extensions: ['.json'], + filePredicate: (path) => basename(path) === 'summary.json' + }), discoverFiles({ rootDir: options.hermesSessionsDir ?? HERMES_SESSIONS_DIR, limit: limitPerAgent, @@ -140,7 +151,7 @@ export async function scanAiVaultSessions( discoverOpenClawFiles({ rootDirs: [ options.openclawStateDir ?? OPENCLAW_STATE_DIR, - options.openclawLegacyStateDir ?? OPENCLAW_LEGACY_STATE_DIR + options.openclawLegacyStateDir ?? join(homedir(), '.clawdbot') ], limit: limitPerAgent, issues @@ -160,7 +171,7 @@ export async function scanAiVaultSessions( extensions: ['.jsonl'] }), discoverFiles({ - rootDir: options.droidProjectsDir ?? DROID_PROJECTS_DIR, + rootDir: options.droidProjectsDir ?? join(homedir(), '.factory', 'projects'), limit: limitPerAgent, agent: 'droid', issues, @@ -274,6 +285,8 @@ async function parseAgentSessionFile( return parseCursorSessionFile(candidate.file, platform) case 'opencode': return parseOpenCodeSessionFile(candidate.file, platform) + case 'grok': + return parseGrokSessionFile(candidate.file, platform) case 'hermes': return parseHermesSessionFile(candidate.file, platform) case 'rovo': diff --git a/src/renderer/src/components/right-sidebar/SourceControl.tsx b/src/renderer/src/components/right-sidebar/SourceControl.tsx index 36fd64c3329..25ef6c1468c 100644 --- a/src/renderer/src/components/right-sidebar/SourceControl.tsx +++ b/src/renderer/src/components/right-sidebar/SourceControl.tsx @@ -1619,7 +1619,10 @@ function SourceControlInner(): React.JSX.Element { } }), requestId, - error: 'Custom command is empty. Add one in Settings -> Git -> Source Control AI.' + error: translate( + 'auto.components.right.sidebar.SourceControl.e9e238b260', + 'Custom command is empty. Add one in Settings -> Git -> Source Control AI.' + ) }) if (failedRecord) { setCommitMessageGenerationRecord(generationKey, failedRecord) diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 15a7c166414..efc86aa7866 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -7990,7 +7990,8 @@ "31f6d46278": "Unresolved", "2c417432b7": "Resolved locally", "f3a8b2c1d0e5": "Enter a {{value0}} title.", - "e2b7a1c0d9f4": "Failed to create {{value0}}" + "e2b7a1c0d9f4": "Failed to create {{value0}}", + "e9e238b260": "Custom command is empty. Add one in Settings -> Git -> Source Control AI." }, "SourceControlAgentActionDialog": { "8e856842d1": "Could not start the selected agent.", diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 97ad926127f..ee917b3c649 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -7990,7 +7990,8 @@ "31f6d46278": "Irresoluto", "2c417432b7": "Resuelto localmente", "f3a8b2c1d0e5": "Ingrese un título {{value0}}.", - "e2b7a1c0d9f4": "No se pudo crear {{value0}}" + "e2b7a1c0d9f4": "No se pudo crear {{value0}}", + "e9e238b260": "Custom command is empty. Add one in Settings -> Git -> Source Control AI." }, "SourceControlAgentActionDialog": { "8e856842d1": "No se pudo iniciar el agente seleccionado.", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 5d8c348592c..4ce65aeab9f 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -7990,7 +7990,8 @@ "2c417432b7": "ローカルで解決済み", "d206117f90": "{{value0}} コンフリクト ({{value1}})", "f3a8b2c1d0e5": "{{value0}} のタイトルを入力してください。", - "e2b7a1c0d9f4": "{{value0}} の作成に失敗しました" + "e2b7a1c0d9f4": "{{value0}} の作成に失敗しました", + "e9e238b260": "Custom command is empty. Add one in Settings -> Git -> Source Control AI." }, "SourceControlAgentActionDialog": { "8e856842d1": "選択した agent を開始できませんでした。", diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 900ff72de2f..c9c13d6ac09 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -7990,7 +7990,8 @@ "2c417432b7": "로컬에서 해결됨", "d206117f90": "{{value0}} 충돌 ({{value1}})", "f3a8b2c1d0e5": "{{value0}} 제목을 입력하세요.", - "e2b7a1c0d9f4": "{{value0}} 생성에 실패했습니다" + "e2b7a1c0d9f4": "{{value0}} 생성에 실패했습니다", + "e9e238b260": "Custom command is empty. Add one in Settings -> Git -> Source Control AI." }, "SourceControlAgentActionDialog": { "8e856842d1": "선택한 agent를 시작할 수 없습니다.", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 31fac7636e0..ec0449bff41 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -7990,7 +7990,8 @@ "2c417432b7": "已在本地解决", "d206117f90": "{{value0}} 冲突 ({{value1}})", "f3a8b2c1d0e5": "请输入 {{value0}} 标题。", - "e2b7a1c0d9f4": "创建 {{value0}} 失败" + "e2b7a1c0d9f4": "创建 {{value0}} 失败", + "e9e238b260": "Custom command is empty. Add one in Settings -> Git -> Source Control AI." }, "SourceControlAgentActionDialog": { "8e856842d1": "无法启动选定的 Agent。", diff --git a/src/shared/ai-vault-types.ts b/src/shared/ai-vault-types.ts index c8229dd3935..05d2a1740f1 100644 --- a/src/shared/ai-vault-types.ts +++ b/src/shared/ai-vault-types.ts @@ -11,6 +11,7 @@ export const AI_VAULT_AGENTS = [ 'rovo', 'copilot', 'opencode', + 'grok', 'openclaw', 'droid' ] as const satisfies readonly TuiAgent[] @@ -30,6 +31,7 @@ export const AI_VAULT_AGENT_LABELS = { rovo: 'Rovo Dev', copilot: 'GitHub Copilot', opencode: 'OpenCode', + grok: 'Grok', openclaw: 'OpenClaw', droid: 'Droid' } as const satisfies Record @@ -140,6 +142,7 @@ function buildAgentResumeInvocation( case 'claude': case 'cursor': case 'gemini': + case 'grok': case 'hermes': case 'openclaw': case 'droid': From d93de0935f94f9338113bd666e963e0c592f05bd Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Fri, 12 Jun 2026 10:14:49 -0700 Subject: [PATCH 09/15] Add effectiveness telemetry for in-app GitHub stars (#5245) Co-authored-by: Orca --- src/main/ipc/telemetry.test.ts | 8 + src/main/ipc/telemetry.ts | 1 + src/main/star-nag/service.test.ts | 272 ++++++++++++++++++- src/main/star-nag/service.ts | 135 ++++++++- src/main/telemetry/validator.test.ts | 37 +++ src/preload/api-types.ts | 2 + src/preload/index.ts | 2 + src/renderer/src/components/StarNagCard.tsx | 42 ++- src/renderer/src/web/web-preload-api.test.ts | 36 +++ src/renderer/src/web/web-preload-api.ts | 14 + src/shared/star-nag-telemetry.ts | 42 +++ src/shared/telemetry-events.test.ts | 77 ++++++ src/shared/telemetry-events.ts | 25 ++ 13 files changed, 664 insertions(+), 29 deletions(-) create mode 100644 src/shared/star-nag-telemetry.ts diff --git a/src/main/ipc/telemetry.test.ts b/src/main/ipc/telemetry.test.ts index 162ee86ba7a..27c7d0cd15d 100644 --- a/src/main/ipc/telemetry.test.ts +++ b/src/main/ipc/telemetry.test.ts @@ -160,6 +160,14 @@ describe('telemetry IPC handlers', () => { registerWith({ installId: 'x', existedBeforeTelemetryRelease: false, optedIn: true }) const handler = handlers.get('telemetry:track')! handler({}, 'app_starred_orca', { source: 'settings' }) + handler({}, 'star_nag_outcome', { + outcome: 'shown', + source: 'threshold', + mode: 'gh', + threshold: 35, + agents_since_baseline: 35, + agents_since_baseline_bucket: '35-69' + }) handler({}, 'feature_interaction_usage_bucket_reached', { feature_id: 'tasks', feature_category: 'task_management', diff --git a/src/main/ipc/telemetry.ts b/src/main/ipc/telemetry.ts index a4faa9d7927..03cc50e7026 100644 --- a/src/main/ipc/telemetry.ts +++ b/src/main/ipc/telemetry.ts @@ -55,6 +55,7 @@ let storeRef: Store | null = null const MAIN_OWNED_TELEMETRY_EVENTS = new Set([ 'app_starred_orca', + 'star_nag_outcome', 'feature_interaction_usage_bucket_reached' ]) diff --git a/src/main/star-nag/service.test.ts b/src/main/star-nag/service.test.ts index 5028667d25c..9dacb3d9dca 100644 --- a/src/main/star-nag/service.test.ts +++ b/src/main/star-nag/service.test.ts @@ -13,7 +13,15 @@ type TestWindow = { webContents: { send: ReturnType } } -const { appMock, browserWindowMock, checkOrcaStarredMock, ipcMainHandleMock } = vi.hoisted(() => ({ +const { + appMock, + browserWindowMock, + checkOrcaStarredMock, + starOrcaMock, + trackMock, + getCohortAtEmitMock, + ipcMainHandleMock +} = vi.hoisted(() => ({ appMock: { getVersion: vi.fn(() => '1.2.3') }, @@ -21,6 +29,9 @@ const { appMock, browserWindowMock, checkOrcaStarredMock, ipcMainHandleMock } = getAllWindows: vi.fn<() => TestWindow[]>(() => []) }, checkOrcaStarredMock: vi.fn(), + starOrcaMock: vi.fn(), + trackMock: vi.fn(), + getCohortAtEmitMock: vi.fn(() => ({ nth_repo_added: 3 })), ipcMainHandleMock: vi.fn() })) @@ -33,7 +44,16 @@ vi.mock('electron', () => ({ })) vi.mock('../github/client', () => ({ - checkOrcaStarred: checkOrcaStarredMock + checkOrcaStarred: checkOrcaStarredMock, + starOrca: starOrcaMock +})) + +vi.mock('../telemetry/client', () => ({ + track: trackMock +})) + +vi.mock('../telemetry/cohort-classifier', () => ({ + getCohortAtEmit: getCohortAtEmitMock })) type AgentStartedListener = (totalAgentsSpawned: number) => void @@ -131,6 +151,11 @@ describe('StarNagService', () => { browserWindowMock.getAllWindows.mockReturnValue([]) checkOrcaStarredMock.mockReset() checkOrcaStarredMock.mockResolvedValue(false) + starOrcaMock.mockReset() + starOrcaMock.mockResolvedValue(true) + trackMock.mockReset() + getCohortAtEmitMock.mockReset() + getCohortAtEmitMock.mockReturnValue({ nth_repo_added: 3 }) ipcMainHandleMock.mockReset() consoleInfoMock = vi.spyOn(console, 'info').mockImplementation(() => undefined) }) @@ -150,7 +175,9 @@ describe('StarNagService', () => { emitAgentStarted(46) expect(window.webContents.send).toHaveBeenCalledTimes(1) - expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { mode: 'gh' }) + expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { + mode: 'gh' + }) expect(consoleInfoMock).toHaveBeenCalledTimes(1) expect(consoleInfoMock).toHaveBeenCalledWith({ event: 'star_nag_shown', @@ -171,7 +198,18 @@ describe('StarNagService', () => { emitAgentStarted(45) await flushAsyncWork() - expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { mode: 'web' }) + expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { + mode: 'web' + }) + expect(trackMock).toHaveBeenCalledWith('star_nag_outcome', { + outcome: 'shown', + source: 'threshold', + mode: 'web', + threshold: STAR_NAG_INITIAL_THRESHOLD, + agents_since_baseline: 35, + agents_since_baseline_bucket: '35-69', + nth_repo_added: 3 + }) expect(consoleInfoMock).toHaveBeenCalledWith({ event: 'star_nag_shown', app_version: '1.2.3', @@ -203,13 +241,16 @@ describe('StarNagService', () => { await flushAsyncWork() expect(consoleInfoMock).not.toHaveBeenCalled() + expect(trackMock).not.toHaveBeenCalled() const window = createWindow() browserWindowMock.getAllWindows.mockReturnValue([window]) emitAgentStarted(46) await flushAsyncWork() - expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { mode: 'gh' }) + expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { + mode: 'gh' + }) expect(consoleInfoMock).toHaveBeenCalledWith({ event: 'star_nag_shown', app_version: '1.2.3', @@ -282,7 +323,9 @@ describe('StarNagService', () => { browserWindowMock.getAllWindows.mockReturnValue([window]) forceShow() - expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { mode: 'gh' }) + expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { + mode: 'gh' + }) expect(consoleInfoMock).toHaveBeenCalledWith({ event: 'star_nag_shown', app_version: '1.2.3', @@ -405,7 +448,9 @@ describe('StarNagService', () => { expect(window.webContents.send).toHaveBeenCalledTimes(1) expect(consoleInfoMock).toHaveBeenCalledTimes(1) - expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { mode: 'web' }) + expect(window.webContents.send).toHaveBeenCalledWith('star-nag:show', { + mode: 'web' + }) expect(consoleInfoMock).toHaveBeenCalledWith({ event: 'star_nag_shown', app_version: '1.2.3', @@ -458,4 +503,217 @@ describe('StarNagService', () => { source: 'force_show' }) }) + + it('emits shown and already_starred_suppressed outcomes with cohort context', async () => { + const window = createWindow() + browserWindowMock.getAllWindows.mockReturnValue([window]) + const { service, emitAgentStarted } = createHarness() + + service.start() + emitAgentStarted(45) + await flushAsyncWork() + + expect(trackMock).toHaveBeenCalledWith('star_nag_outcome', { + outcome: 'shown', + source: 'threshold', + mode: 'gh', + threshold: STAR_NAG_INITIAL_THRESHOLD, + agents_since_baseline: 35, + agents_since_baseline_bucket: '35-69', + nth_repo_added: 3 + }) + + trackMock.mockClear() + checkOrcaStarredMock.mockResolvedValue(true) + const next = createHarness() + next.service.start() + next.emitAgentStarted(45) + await flushAsyncWork() + + expect(trackMock).toHaveBeenCalledWith('star_nag_outcome', { + outcome: 'already_starred_suppressed', + source: 'threshold', + mode: 'gh', + threshold: STAR_NAG_INITIAL_THRESHOLD, + agents_since_baseline: 35, + agents_since_baseline_bucket: '35-69', + nth_repo_added: 3 + }) + }) + + it('emits dismissed, disabled, and opened_web as distinct main-owned outcomes', () => { + const window = createWindow() + browserWindowMock.getAllWindows.mockReturnValue([window]) + const dismissed = createHarness() + + dismissed.service.registerIpcHandlers() + getIpcHandler('star-nag:forceShow')() + getIpcHandler('star-nag:dismiss')() + + expect(trackMock).toHaveBeenCalledWith('star_nag_outcome', { + outcome: 'dismissed', + source: 'force_show', + mode: 'gh', + threshold: STAR_NAG_INITIAL_THRESHOLD, + agents_since_baseline: 35, + agents_since_baseline_bucket: '35-69', + nth_repo_added: 3, + next_threshold: STAR_NAG_INITIAL_THRESHOLD * 2 + }) + + trackMock.mockClear() + ipcMainHandleMock.mockClear() + const disabled = createHarness() + disabled.service.registerIpcHandlers() + getIpcHandler('star-nag:forceShow')() + getIpcHandler('star-nag:disable')() + + expect(trackMock).toHaveBeenCalledWith( + 'star_nag_outcome', + expect.objectContaining({ outcome: 'disabled', mode: 'gh' }) + ) + + trackMock.mockClear() + ipcMainHandleMock.mockClear() + const opened = createHarness() + opened.service.registerIpcHandlers() + getIpcHandler('star-nag:forceShow')() + getIpcHandler('star-nag:openWeb')() + + expect(trackMock).toHaveBeenCalledWith( + 'star_nag_outcome', + expect.objectContaining({ outcome: 'opened_web', mode: 'web' }) + ) + }) + + it('emits direct-star attempted and succeeded outcomes plus app_starred_orca', async () => { + const window = createWindow() + browserWindowMock.getAllWindows.mockReturnValue([window]) + const { service, ui } = createHarness() + + service.registerIpcHandlers() + getIpcHandler('star-nag:forceShow')() + const ok = await getIpcHandler('star-nag:starOrca')() + + expect(ok).toBe(true) + expect(ui.starNagCompleted).toBe(true) + expect(trackMock).toHaveBeenCalledWith( + 'star_nag_outcome', + expect.objectContaining({ outcome: 'star_attempted', mode: 'gh' }) + ) + expect(trackMock).toHaveBeenCalledWith( + 'star_nag_outcome', + expect.objectContaining({ outcome: 'star_succeeded', mode: 'gh' }) + ) + expect(trackMock).toHaveBeenCalledWith('app_starred_orca', { + source: 'star_nag', + nth_repo_added: 3 + }) + }) + + it('uses fresh cohort context for canonical app_starred_orca success telemetry', async () => { + const window = createWindow() + browserWindowMock.getAllWindows.mockReturnValue([window]) + getCohortAtEmitMock + .mockReturnValueOnce({ nth_repo_added: 2 }) + .mockReturnValueOnce({ nth_repo_added: 4 }) + const { service } = createHarness() + + service.registerIpcHandlers() + getIpcHandler('star-nag:forceShow')() + await getIpcHandler('star-nag:starOrca')() + + expect(trackMock).toHaveBeenCalledWith( + 'star_nag_outcome', + expect.objectContaining({ outcome: 'shown', nth_repo_added: 2 }) + ) + expect(trackMock).toHaveBeenCalledWith( + 'star_nag_outcome', + expect.objectContaining({ outcome: 'star_succeeded', nth_repo_added: 2 }) + ) + expect(trackMock).toHaveBeenCalledWith('app_starred_orca', { + source: 'star_nag', + nth_repo_added: 4 + }) + }) + + it('records success and completion when direct star resolves after dismissal cleared the visible session', async () => { + const window = createWindow() + browserWindowMock.getAllWindows.mockReturnValue([window]) + const deferredStar = createDeferred() + starOrcaMock.mockReturnValue(deferredStar.promise) + const { service, ui } = createHarness() + + service.registerIpcHandlers() + getIpcHandler('star-nag:forceShow')() + const starPromise = getIpcHandler('star-nag:starOrca')() + getIpcHandler('star-nag:dismiss')() + + deferredStar.resolve(true) + await expect(starPromise).resolves.toBe(true) + + expect(trackMock).toHaveBeenCalledWith( + 'star_nag_outcome', + expect.objectContaining({ outcome: 'star_succeeded', mode: 'gh' }) + ) + expect(trackMock).toHaveBeenCalledWith('app_starred_orca', { + source: 'star_nag', + nth_repo_added: 3 + }) + expect(ui.starNagCompleted).toBe(true) + }) + + it('clears the in-flight direct-star guard after thrown attempts so the user can retry', async () => { + const window = createWindow() + browserWindowMock.getAllWindows.mockReturnValue([window]) + starOrcaMock.mockRejectedValueOnce(new Error('gh failed')).mockResolvedValueOnce(true) + const { service, ui } = createHarness() + + service.registerIpcHandlers() + getIpcHandler('star-nag:forceShow')() + const starFromNag = getIpcHandler('star-nag:starOrca') + + await expect(starFromNag()).rejects.toThrow('gh failed') + await expect(starFromNag()).resolves.toBe(true) + + expect(starOrcaMock).toHaveBeenCalledTimes(2) + expect(ui.starNagCompleted).toBe(true) + }) + + it('records failed direct star before web fallback and guards duplicate in-flight attempts', async () => { + const window = createWindow() + browserWindowMock.getAllWindows.mockReturnValue([window]) + const deferredStar = createDeferred() + starOrcaMock.mockReturnValue(deferredStar.promise) + const { service, ui } = createHarness() + + service.registerIpcHandlers() + getIpcHandler('star-nag:forceShow')() + const starFromNag = getIpcHandler('star-nag:starOrca') + const first = starFromNag() + const second = starFromNag() + + deferredStar.resolve(false) + await expect(first).resolves.toBe(false) + await expect(second).resolves.toBe(false) + + const starAttempts = trackMock.mock.calls.filter( + ([name, payload]) => + name === 'star_nag_outcome' && + (payload as { outcome?: string }).outcome === 'star_attempted' + ) + expect(starAttempts).toHaveLength(1) + expect(trackMock).toHaveBeenCalledWith( + 'star_nag_outcome', + expect.objectContaining({ outcome: 'star_failed', mode: 'gh' }) + ) + + getIpcHandler('star-nag:openWeb')() + + expect(trackMock).toHaveBeenCalledWith( + 'star_nag_outcome', + expect.objectContaining({ outcome: 'opened_web', mode: 'web' }) + ) + expect(ui.starNagCompleted).toBe(true) + }) }) diff --git a/src/main/star-nag/service.ts b/src/main/star-nag/service.ts index 76afffd8d83..fd8b211a1a0 100644 --- a/src/main/star-nag/service.ts +++ b/src/main/star-nag/service.ts @@ -1,14 +1,22 @@ import { app, BrowserWindow, ipcMain } from 'electron' import { STAR_NAG_INITIAL_THRESHOLD } from '../../shared/constants' -import { checkOrcaStarred } from '../github/client' +import { checkOrcaStarred, starOrca } from '../github/client' import type { Store } from '../persistence' import type { StatsCollector } from '../stats/collector' +import { track } from '../telemetry/client' +import { getCohortAtEmit } from '../telemetry/cohort-classifier' +import { + bucketStarNagAgentsSinceBaseline, + type StarNagOutcome, + type StarNagPromptMode, + type StarNagPromptSource +} from '../../shared/star-nag-telemetry' +import type { EventProps } from '../../shared/telemetry-events' -type StarNagPromptSource = 'threshold' | 'force_show' -type StarNagPromptMode = 'gh' | 'web' +type StarNagPromptContext = Omit, 'outcome' | 'next_threshold'> -type StarNagPromptSession = { - source: StarNagPromptSource +type StarNagPromptSession = StarNagPromptContext & { + starAttemptPromise?: Promise } /** @@ -36,8 +44,9 @@ export class StarNagService { // resolving. private evaluating = false private pendingForceShow = false - // Why: dismissal backoff should only apply to a prompt that was actually - // delivered, and the dismissal payload needs the delivered prompt source. + // Why: dismissal backoff and action telemetry must use the prompt context + // that was delivered, not whatever threshold/source happens to be current + // when the renderer later reports a user action. private promptSession: StarNagPromptSession | null = null constructor(store: Store, stats: StatsCollector) { @@ -65,7 +74,9 @@ export class StarNagService { registerIpcHandlers(): void { ipcMain.handle('star-nag:dismiss', () => this.dismiss()) ipcMain.handle('star-nag:complete', () => this.markCompleted()) - ipcMain.handle('star-nag:disable', () => this.markCompleted()) + ipcMain.handle('star-nag:disable', () => this.disable()) + ipcMain.handle('star-nag:openWeb', () => this.openWeb()) + ipcMain.handle('star-nag:starOrca', () => this.starOrcaFromNag()) ipcMain.handle('star-nag:forceShow', () => this.forceShow()) } @@ -134,6 +145,7 @@ export class StarNagService { return } if (starred) { + this.trackAlreadyStarredSuppressed(source) // Already starred somewhere — lock in the permanent suppression so we // stop recomputing thresholds on every spawn. this.markCompleted() @@ -167,13 +179,67 @@ export class StarNagService { this.promptSession = null return false } + const context = this.createPromptContext(source, mode) win.webContents.send('star-nag:show', { mode }) this.promptVisible = true - this.promptSession = { source } + this.promptSession = context + this.trackOutcome('shown') this.logConsoleEvent('star_nag_shown', source) return true } + private createPromptContext( + source: StarNagPromptSource, + mode: StarNagPromptMode + ): StarNagPromptContext { + const ui = this.store.getUI() + const threshold = ui.starNagNextThreshold ?? STAR_NAG_INITIAL_THRESHOLD + const agentsSinceBaseline = Math.max( + 0, + this.stats.getTotalAgentsSpawned() - (ui.starNagBaselineAgents ?? 0) + ) + return { + source, + mode, + threshold, + agents_since_baseline: agentsSinceBaseline, + agents_since_baseline_bucket: bucketStarNagAgentsSinceBaseline(agentsSinceBaseline), + ...getCohortAtEmit() + } + } + + private trackOutcome( + outcome: StarNagOutcome, + options: { mode?: StarNagPromptMode; nextThreshold?: number } = {} + ): void { + const session = this.promptSession + if (!session) { + return + } + this.trackSessionOutcome(session, outcome, options) + } + + private trackSessionOutcome( + session: StarNagPromptSession, + outcome: StarNagOutcome, + options: { mode?: StarNagPromptMode; nextThreshold?: number } = {} + ): void { + const { starAttemptPromise: _starAttemptPromise, ...context } = session + track('star_nag_outcome', { + ...context, + outcome, + ...(options.mode === undefined ? {} : { mode: options.mode }), + ...(options.nextThreshold === undefined ? {} : { next_threshold: options.nextThreshold }) + }) + } + + private trackAlreadyStarredSuppressed(source: StarNagPromptSource): void { + track('star_nag_outcome', { + ...this.createPromptContext(source, 'gh'), + outcome: 'already_starred_suppressed' + }) + } + private logConsoleEvent( event: 'star_nag_shown' | 'star_nag_dismissed', source: StarNagPromptSource, @@ -211,6 +277,7 @@ export class StarNagService { const ui = this.store.getUI() const threshold = ui.starNagNextThreshold ?? STAR_NAG_INITIAL_THRESHOLD const nextThreshold = threshold * 2 + this.trackOutcome('dismissed', { nextThreshold }) this.logConsoleEvent('star_nag_dismissed', session.source, nextThreshold) this.store.updateUI({ starNagNextThreshold: nextThreshold, @@ -220,6 +287,56 @@ export class StarNagService { this.promptSession = null } + private disable(): void { + this.trackOutcome('disabled') + this.markCompleted() + } + + private openWeb(): void { + this.trackOutcome('opened_web', { mode: 'web' }) + this.markCompleted() + } + + private async starOrcaFromNag(): Promise { + const session = this.promptSession + if (!session) { + return false + } + if (session.starAttemptPromise) { + return session.starAttemptPromise + } + const attempt = this.runStarOrcaAttempt(session) + session.starAttemptPromise = attempt + try { + return await attempt + } finally { + if (this.promptSession === session) { + delete session.starAttemptPromise + } + } + } + + private async runStarOrcaAttempt(session: StarNagPromptSession): Promise { + this.trackSessionOutcome(session, 'star_attempted', { mode: 'gh' }) + const starred = await starOrca() + if (!starred) { + if (this.promptSession === session) { + this.trackSessionOutcome(session, 'star_failed', { mode: 'gh' }) + session.mode = 'web' + } + return false + } + this.trackSessionOutcome(session, 'star_succeeded', { mode: 'gh' }) + // Why: app_starred_orca remains the canonical cross-surface success event; + // star_nag_outcome is only the nag-funnel companion. + track('app_starred_orca', { + source: 'star_nag', + ...getCohortAtEmit() + }) + this.markCompleted() + return true + } + /** User successfully starred or opted out → never nag again. */ private markCompleted(): void { this.store.updateUI({ starNagCompleted: true }) diff --git a/src/main/telemetry/validator.test.ts b/src/main/telemetry/validator.test.ts index bb93bdad1b1..573b6907b32 100644 --- a/src/main/telemetry/validator.test.ts +++ b/src/main/telemetry/validator.test.ts @@ -41,6 +41,43 @@ describe('validate', () => { expect(result.ok).toBe(true) }) + it('accepts a well-formed star_nag_outcome payload with cohort context', () => { + const result = validate('star_nag_outcome', { + outcome: 'opened_web', + source: 'force_show', + mode: 'web', + threshold: 35, + agents_since_baseline: 42, + agents_since_baseline_bucket: '35-69', + nth_repo_added: 4 + }) + expect(result.ok).toBe(true) + }) + + it('rejects malformed star_nag_outcome payloads', () => { + expect( + validate('star_nag_outcome', { + outcome: 'opened_web', + source: 'force_show', + mode: 'web', + threshold: 35, + agents_since_baseline: 42, + agents_since_baseline_bucket: '35-69', + raw_error: 'nope' + } as never).ok + ).toBe(false) + expect( + validate('star_nag_outcome', { + outcome: 'opened_web', + source: 'force_show', + mode: 'web', + threshold: 0, + agents_since_baseline: 42, + agents_since_baseline_bucket: '35-69' + } as never).ok + ).toBe(false) + }) + it('drops unknown event names', () => { const result = validate('not_a_real_event' as never, {}) expect(result.ok).toBe(false) diff --git a/src/preload/api-types.ts b/src/preload/api-types.ts index 549a266b227..75a51fad6ea 100644 --- a/src/preload/api-types.ts +++ b/src/preload/api-types.ts @@ -1627,6 +1627,8 @@ export type PreloadApi = { dismiss: () => Promise complete: () => Promise disable: () => Promise + openWeb: () => Promise + starOrca: () => Promise forceShow: () => Promise } /** Fire-and-forget track. Loose typing at the IPC boundary on purpose — diff --git a/src/preload/index.ts b/src/preload/index.ts index 48a05a4bd92..3d2ce8b610e 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1475,6 +1475,8 @@ const api = { dismiss: (): Promise => ipcRenderer.invoke('star-nag:dismiss'), complete: (): Promise => ipcRenderer.invoke('star-nag:complete'), disable: (): Promise => ipcRenderer.invoke('star-nag:disable'), + openWeb: (): Promise => ipcRenderer.invoke('star-nag:openWeb'), + starOrca: (): Promise => ipcRenderer.invoke('star-nag:starOrca'), forceShow: (): Promise => ipcRenderer.invoke('star-nag:forceShow') }, diff --git a/src/renderer/src/components/StarNagCard.tsx b/src/renderer/src/components/StarNagCard.tsx index 4f6a4f9eaf4..f127caf344d 100644 --- a/src/renderer/src/components/StarNagCard.tsx +++ b/src/renderer/src/components/StarNagCard.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { ExternalLink, Star, X } from 'lucide-react' import { Card } from './ui/card' import { Button } from './ui/button' @@ -39,18 +39,24 @@ export function StarNagCard(): React.JSX.Element | null { }) }, []) - const handleClose = (): void => { + const handleClose = useCallback((): void => { + if (busy) { + return + } setVisible(false) // Why: fire-and-forget. If persisting the dismissal fails the worst case // is we re-fire the same threshold on next launch — not worth blocking // the close animation on. void window.api.starNag.dismiss() - } + }, [busy]) - const handleDisable = (): void => { + const handleDisable = useCallback((): void => { + if (busy) { + return + } setVisible(false) void window.api.starNag.disable() - } + }, [busy]) useEffect(() => { if (!visible) { @@ -63,9 +69,7 @@ export function StarNagCard(): React.JSX.Element | null { } window.addEventListener('keydown', onKeyDown) return () => window.removeEventListener('keydown', onKeyDown) - // eslint-disable-next-line react-hooks/exhaustive-deps -- handleClose closes - // over stable refs; re-binding on each render is unnecessary. - }, [visible]) + }, [handleClose, visible]) if (!visible) { return null @@ -78,7 +82,7 @@ export function StarNagCard(): React.JSX.Element | null { if (mode === 'web') { setBusy(true) await window.api.shell.openUrl(ORCA_STARGAZERS_URL) - await window.api.starNag.disable() + await window.api.starNag.openWeb() if (mountedRef.current) { setBusy(false) setVisible(false) @@ -86,7 +90,7 @@ export function StarNagCard(): React.JSX.Element | null { return } setBusy(true) - const ok = await window.api.gh.starOrca('star_nag') + const ok = await window.api.starNag.starOrca() if (mountedRef.current) { setBusy(false) } @@ -96,7 +100,6 @@ export function StarNagCard(): React.JSX.Element | null { } return } - await window.api.starNag.complete() if (mountedRef.current) { setVisible(false) } @@ -126,6 +129,7 @@ export function StarNagCard(): React.JSX.Element | null { size="icon" className="size-7 shrink-0" onClick={handleClose} + disabled={busy} aria-label={translate('auto.components.StarNagCard.b5e685e4d9', 'Dismiss')} > @@ -156,10 +160,22 @@ export function StarNagCard(): React.JSX.Element | null { : translate('auto.components.StarNagCard.2d67b6c849', 'Star on GitHub')}
- -
diff --git a/src/renderer/src/web/web-preload-api.test.ts b/src/renderer/src/web/web-preload-api.test.ts index 340dd3147b9..541393b26b4 100644 --- a/src/renderer/src/web/web-preload-api.test.ts +++ b/src/renderer/src/web/web-preload-api.test.ts @@ -1215,6 +1215,42 @@ describe('web file preload API', () => { }) }) +describe('web star nag preload API', () => { + beforeEach(() => { + vi.resetModules() + }) + + afterEach(() => { + vi.unstubAllGlobals() + }) + + it('keeps the browser-paired star nag API safe and in parity with the preload contract', async () => { + const { api } = await installApi('Linux') + + expect(Object.keys(api.starNag).sort()).toEqual([ + 'complete', + 'disable', + 'dismiss', + 'forceShow', + 'onShow', + 'openWeb', + 'starOrca' + ]) + + const listener = vi.fn() + const unsubscribe = api.starNag.onShow(listener) + unsubscribe() + + await expect(api.starNag.dismiss()).resolves.toBeUndefined() + await expect(api.starNag.complete()).resolves.toBeUndefined() + await expect(api.starNag.disable()).resolves.toBeUndefined() + await expect(api.starNag.openWeb()).resolves.toBeUndefined() + await expect(api.starNag.forceShow()).resolves.toBeUndefined() + await expect(api.starNag.starOrca()).resolves.toBe(false) + expect(listener).not.toHaveBeenCalled() + }) +}) + describe('web GitHub preload API', () => { beforeEach(() => { vi.resetModules() diff --git a/src/renderer/src/web/web-preload-api.ts b/src/renderer/src/web/web-preload-api.ts index d9b23fdb064..d7d422f6b59 100644 --- a/src/renderer/src/web/web-preload-api.ts +++ b/src/renderer/src/web/web-preload-api.ts @@ -170,6 +170,7 @@ function invalidateRuntimeWorktreeCaches(): void { type WebSettingsApi = NonNullable type WebKeybindingsApi = NonNullable type WebGitHubApi = NonNullable +type WebStarNagApi = NonNullable type WebGitHubResult = Awaited> type WebGitHubRouteKey = | 'repoSlug' @@ -555,6 +556,7 @@ function createWebPreloadApi(): Partial { browser: createBrowserApi(), emulator: createEmulatorApi(), gh: createGitHubApi(), + starNag: createStarNagApi(), gl: createGitLabApi(), hostedReview: createRuntimeNamespaceApi('hostedReview'), linear: createRuntimeNamespaceApi('linear'), @@ -1573,6 +1575,18 @@ function createEmulatorApi(): NonNullable['emulator']> { } as unknown as NonNullable['emulator']> } +function createStarNagApi(): WebStarNagApi { + return { + onShow: () => noopUnsubscribe, + dismiss: () => Promise.resolve(), + complete: () => Promise.resolve(), + disable: () => Promise.resolve(), + openWeb: () => Promise.resolve(), + starOrca: () => Promise.resolve(false), + forceShow: () => Promise.resolve() + } +} + function createGitHubApi(): WebGitHubApi { const route = (method: WebGitHubRuntimeMethod, args?: unknown): Promise => callRuntimeResult(method, mapRepoPathArg(args)) diff --git a/src/shared/star-nag-telemetry.ts b/src/shared/star-nag-telemetry.ts new file mode 100644 index 00000000000..7f995d0c6f9 --- /dev/null +++ b/src/shared/star-nag-telemetry.ts @@ -0,0 +1,42 @@ +import { z } from 'zod' + +export const STAR_NAG_OUTCOMES = [ + 'shown', + 'dismissed', + 'disabled', + 'star_attempted', + 'star_succeeded', + 'star_failed', + 'opened_web', + 'already_starred_suppressed' +] as const + +export const STAR_NAG_PROMPT_SOURCES = ['threshold', 'force_show'] as const +export const STAR_NAG_PROMPT_MODES = ['gh', 'web'] as const +export const STAR_NAG_AGENT_BUCKETS = ['0-34', '35-69', '70-139', '140-279', '280+'] as const + +export const starNagOutcomeSchema = z.enum(STAR_NAG_OUTCOMES) +export const starNagPromptSourceSchema = z.enum(STAR_NAG_PROMPT_SOURCES) +export const starNagPromptModeSchema = z.enum(STAR_NAG_PROMPT_MODES) +export const starNagAgentBucketSchema = z.enum(STAR_NAG_AGENT_BUCKETS) + +export type StarNagOutcome = z.infer +export type StarNagPromptSource = z.infer +export type StarNagPromptMode = z.infer +export type StarNagAgentBucket = z.infer + +export function bucketStarNagAgentsSinceBaseline(agentsSinceBaseline: number): StarNagAgentBucket { + if (agentsSinceBaseline < 35) { + return '0-34' + } + if (agentsSinceBaseline < 70) { + return '35-69' + } + if (agentsSinceBaseline < 140) { + return '70-139' + } + if (agentsSinceBaseline < 280) { + return '140-279' + } + return '280+' +} diff --git a/src/shared/telemetry-events.test.ts b/src/shared/telemetry-events.test.ts index 9d32a1bff30..f895ab957e3 100644 --- a/src/shared/telemetry-events.test.ts +++ b/src/shared/telemetry-events.test.ts @@ -158,6 +158,83 @@ describe('app_starred_orca schema', () => { }) }) +describe('star_nag_outcome schema', () => { + const valid = { + outcome: 'shown', + source: 'threshold', + mode: 'gh', + threshold: 35, + agents_since_baseline: 35, + agents_since_baseline_bucket: '35-69', + nth_repo_added: 2 + } + + it('accepts a strict valid payload with cohort context', () => { + expect(eventSchemas.star_nag_outcome.safeParse(valid).success).toBe(true) + }) + + it('is in the runtime cohort-injection roster', () => { + expect(isCohortExtendedEvent('star_nag_outcome')).toBe(true) + }) + + it('accepts next_threshold only as a positive integer', () => { + expect( + eventSchemas.star_nag_outcome.safeParse({ + ...valid, + outcome: 'dismissed', + next_threshold: 70 + }).success + ).toBe(true) + expect(eventSchemas.star_nag_outcome.safeParse({ ...valid, next_threshold: 0 }).success).toBe( + false + ) + expect(eventSchemas.star_nag_outcome.safeParse({ ...valid, next_threshold: 1.5 }).success).toBe( + false + ) + expect( + eventSchemas.star_nag_outcome.safeParse({ ...valid, outcome: 'shown', next_threshold: 70 }) + .success + ).toBe(false) + }) + + it('rejects unknown outcome source mode and bucket values', () => { + expect(eventSchemas.star_nag_outcome.safeParse({ ...valid, outcome: 'ignored' }).success).toBe( + false + ) + expect(eventSchemas.star_nag_outcome.safeParse({ ...valid, source: 'renderer' }).success).toBe( + false + ) + expect(eventSchemas.star_nag_outcome.safeParse({ ...valid, mode: 'desktop' }).success).toBe( + false + ) + expect( + eventSchemas.star_nag_outcome.safeParse({ + ...valid, + agents_since_baseline_bucket: '35+' + }).success + ).toBe(false) + }) + + it('rejects malformed numeric fields and raw extra fields', () => { + expect(eventSchemas.star_nag_outcome.safeParse({ ...valid, threshold: -1 }).success).toBe(false) + expect(eventSchemas.star_nag_outcome.safeParse({ ...valid, threshold: 1.5 }).success).toBe( + false + ) + expect( + eventSchemas.star_nag_outcome.safeParse({ ...valid, agents_since_baseline: -1 }).success + ).toBe(false) + expect(eventSchemas.star_nag_outcome.safeParse({ ...valid, nth_repo_added: -1 }).success).toBe( + false + ) + expect(eventSchemas.star_nag_outcome.safeParse({ ...valid, error: 'gh failed' }).success).toBe( + false + ) + expect( + eventSchemas.star_nag_outcome.safeParse({ ...valid, url: 'https://github.com' }).success + ).toBe(false) + }) +}) + describe('agent_error schema', () => { it('round-trips a minimal {error_class, agent_kind} payload', () => { const parsed = eventSchemas.agent_error.safeParse({ diff --git a/src/shared/telemetry-events.ts b/src/shared/telemetry-events.ts index 1203e21d357..9ebc26229d0 100644 --- a/src/shared/telemetry-events.ts +++ b/src/shared/telemetry-events.ts @@ -34,6 +34,12 @@ import { import { SETUP_SCRIPT_IMPORT_PROVIDERS } from './setup-script-import-providers' import { WORKSPACE_SOURCE_VALUES, type WorkspaceSource } from './workspace-source' import { appStarSourceSchema } from './gh-star-source' +import { + starNagAgentBucketSchema, + starNagOutcomeSchema, + starNagPromptModeSchema, + starNagPromptSourceSchema +} from './star-nag-telemetry' import { NESTED_REPO_COUNT_BUCKETS, NESTED_REPO_IMPORT_ACTIONS, @@ -349,6 +355,23 @@ const appStarredOrcaSchema = z }) .strict() +const starNagOutcomeEventSchema = z + .object({ + outcome: starNagOutcomeSchema, + source: starNagPromptSourceSchema, + mode: starNagPromptModeSchema, + threshold: z.number().int().positive(), + agents_since_baseline: z.number().int().nonnegative(), + agents_since_baseline_bucket: starNagAgentBucketSchema, + nth_repo_added: nthRepoAddedSchema, + next_threshold: z.number().int().positive().optional() + }) + .strict() + .refine((payload) => payload.next_threshold === undefined || payload.outcome === 'dismissed', { + message: 'next_threshold is only valid for dismissed outcomes', + path: ['next_threshold'] + }) + const workspaceCreatedSchema = z .object({ source: workspaceSourceSchema, @@ -1282,6 +1305,7 @@ const terminalPaneSplitSchema = z export const eventSchemas = { app_opened: appOpenedSchema, app_starred_orca: appStarredOrcaSchema, + star_nag_outcome: starNagOutcomeEventSchema, feature_interaction_usage_bucket_reached: featureInteractionUsageBucketReachedSchema, repo_added: repoAddedSchema, @@ -1408,6 +1432,7 @@ export const COHORT_EXTENDED: readonly EventName[] = Array.from(COHORT_EXTENDED_ type _CohortExtendedRoster = | 'app_opened' | 'app_starred_orca' + | 'star_nag_outcome' | 'feature_interaction_usage_bucket_reached' | 'repo_added' | 'add_repo_setup_step_action' From 0021d957963f22251bff4dc697658c356a467db2 Mon Sep 17 00:00:00 2001 From: Lesley Murfin Date: Fri, 12 Jun 2026 11:47:55 -0600 Subject: [PATCH 10/15] fix(serve): persist active serve session tab so PTY output doesn't snap focus to the last tab (#5131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(serve): persist active mobile-session tab so PTY output doesn't snap focus In `orca serve` (headless host + remote/web client), running a command in a terminal snapped the active tab back to the last-created tab. The renderer reconcile forces the client's active tab to the host's reported active (activeTabId / activeTabType / per-tab isActive), but `activateMobileSessionTab` only called `focusTerminal` — a no-op for serve tabs no renderer has adopted — and never persisted the chosen tab into the host's in-memory snapshot. So every `onPtyData` -> `touchMobileSessionSnapshotsForPty` republish re-emitted the stale "active = last tab" and the client snapped back on each command. Persist the activation into the headless snapshot (activeTabId, activeTabType, exclusive per-tab isActive, rebuilt tabGroups) after the focus dispatch, gated on `!getAvailableAuthoritativeWindow() && isHeadlessMobileSessionPublication(...)` — the same guard the hydrate path uses, so renderer-owned snapshots (including `:headless-merge:`) stay authoritative. No-op when the tab is already active; adds nothing to the `onPtyData` hot path. Adds 3 regression tests covering: chosen tab survives a PTY republish, no version bump on redundant activation, and no server-side persist when a renderer is authoritative. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(serve): exclude :headless-merge: snapshots from headless active-tab persist After a renderer detaches there is no authoritative window, so the !getAvailableAuthoritativeWindow() guard alone let merged (:headless-merge:) snapshots through this path. A merged snapshot still carries renderer-owned tabs/groups, so a server-side active-tab rewrite could collapse that mixed group state. Exclude merged epochs from the persist gate; only pure `headless:` / `headless-hydrated:` publications are persisted server-side. Adds a regression test: renderer-detach + `:headless-merge:` snapshot -> the headless persist is suppressed (no snapshot mutation / event). Addresses the CodeRabbit review on #5131. * review: clarify headless active-tab persistence guard Co-authored-by: Orca --------- Co-authored-by: Claude Opus 4.8 (1M context) Co-authored-by: Jinwoo-H Co-authored-by: Orca --- src/main/runtime/orca-runtime.test.ts | 145 ++++++++++++++++++++++++++ src/main/runtime/orca-runtime.ts | 59 ++++++++++- 2 files changed, 202 insertions(+), 2 deletions(-) diff --git a/src/main/runtime/orca-runtime.test.ts b/src/main/runtime/orca-runtime.test.ts index d78f56d2bd0..5460a02efe9 100644 --- a/src/main/runtime/orca-runtime.test.ts +++ b/src/main/runtime/orca-runtime.test.ts @@ -9443,6 +9443,151 @@ describe('OrcaRuntimeService', () => { expect(spawn.mock.calls[1]?.[0]).not.toHaveProperty('sessionId') }) + it('keeps the activated headless tab active across PTY republishes (serve focus-jump regression)', async () => { + // Why: in `orca serve`, focusTerminal has no renderer to persist the remote + // client's tab choice before PTY republishes. + let nextPty = 0 + const spawn = vi.fn().mockImplementation(async () => ({ id: `headless-pty-${++nextPty}` })) + const runtime = new OrcaRuntimeService(store) + runtime.setPtyController({ + spawn, + write: () => true, + kill: () => true, + getForegroundProcess: async () => null + }) + + const FIRST_LEAF = '22222222-2222-4222-8222-222222222222' + const SECOND_LEAF = '33333333-3333-4333-8333-333333333333' + // The first-created headless terminal is the one the snapshot marks active. + await runtime.createTerminal(`id:${TEST_WORKTREE_ID}`, { + tabId: 'tab-first', + leafId: FIRST_LEAF + }) + await runtime.createTerminal(`id:${TEST_WORKTREE_ID}`, { + tabId: 'tab-other', + leafId: SECOND_LEAF + }) + + const events: RuntimeMobileSessionTabsResult[] = [] + runtime.onMobileSessionTabsChanged((snapshot) => events.push(snapshot)) + + // The remote client switches to the other (non-active) tab. + await runtime.activateMobileSessionTab(`id:${TEST_WORKTREE_ID}`, 'tab-other') + + const afterActivate = events.at(-1) + expect(afterActivate?.activeTabId).toBe(`tab-other::${SECOND_LEAF}`) + expect(afterActivate?.activeTabType).toBe('terminal') + expect(afterActivate?.tabGroups?.[0]?.activeTabId).toBe('tab-other') + expect( + afterActivate?.tabs.find((tab) => tab.id === `tab-other::${SECOND_LEAF}`)?.isActive + ).toBe(true) + expect(afterActivate?.tabs.find((tab) => tab.id === `tab-first::${FIRST_LEAF}`)?.isActive).toBe( + false + ) + + // PTY title updates republish snapshots, so the client's chosen tab must + // survive after activation. + events.length = 0 + runtime.onPtyData('headless-pty-2', '\x1b]0;tab-other running\x07', 200) + + const afterPtyData = events.at(-1) + expect(afterPtyData?.activeTabId).toBe(`tab-other::${SECOND_LEAF}`) + expect(afterPtyData?.activeTabType).toBe('terminal') + expect(afterPtyData?.tabGroups?.[0]?.activeTabId).toBe('tab-other') + }) + + it('does not bump the snapshot version when re-activating the already-active headless tab', async () => { + // Why: redundant activations of the current tab must not force a remote re-render. + const spawn = vi.fn().mockResolvedValue({ id: 'headless-pty-solo' }) + const runtime = new OrcaRuntimeService(store) + runtime.setPtyController({ + spawn, + write: () => true, + kill: () => true, + getForegroundProcess: async () => null + }) + + const LEAF = '44444444-4444-4444-8444-444444444444' + await runtime.createTerminal(`id:${TEST_WORKTREE_ID}`, { tabId: 'tab-solo', leafId: LEAF }) + + const events: RuntimeMobileSessionTabsResult[] = [] + runtime.onMobileSessionTabsChanged((snapshot) => events.push(snapshot)) + + await runtime.activateMobileSessionTab(`id:${TEST_WORKTREE_ID}`, 'tab-solo') + + expect(events).toHaveLength(0) + }) + + it('does not persist active server-side when an authoritative renderer is attached', async () => { + // Why: when a renderer window is authoritative it re-syncs the snapshot itself, + // so the headless persist must NOT fire — the renderer stays the source of truth. + let nextPty = 0 + const spawn = vi.fn().mockImplementation(async () => ({ id: `attached-pty-${++nextPty}` })) + const runtime = new OrcaRuntimeService(store) + runtime.setPtyController({ + spawn, + write: () => true, + kill: () => true, + getForegroundProcess: async () => null + }) + + const LEAF_A = '55555555-5555-4555-8555-555555555555' + const LEAF_B = '66666666-6666-4666-8666-666666666666' + await runtime.createTerminal(`id:${TEST_WORKTREE_ID}`, { tabId: 'tab-a', leafId: LEAF_A }) + await runtime.createTerminal(`id:${TEST_WORKTREE_ID}`, { tabId: 'tab-b', leafId: LEAF_B }) + + // Make an authoritative renderer window present. + runtime.attachWindow(1) + runtime.markGraphReady(1) + electronMocks.BrowserWindow.fromId.mockReturnValue({ + isDestroyed: () => false, + webContents: { send: vi.fn() } + }) + + const events: RuntimeMobileSessionTabsResult[] = [] + runtime.onMobileSessionTabsChanged((snapshot) => events.push(snapshot)) + + await runtime.activateMobileSessionTab(`id:${TEST_WORKTREE_ID}`, 'tab-b') + + // The headless persist is gated off by the authoritative window — nothing emitted. + expect(events).toHaveLength(0) + }) + + it('does not persist active server-side for a `:headless-merge:` snapshot after renderer detach', async () => { + // Why: after renderer detach, merged snapshots have no authoritative window + // but still carry renderer-owned group state. + let nextPty = 0 + const spawn = vi.fn().mockImplementation(async () => ({ id: `merge-pty-${++nextPty}` })) + const runtime = new OrcaRuntimeService(store) + runtime.setPtyController({ + spawn, + write: () => true, + kill: () => true, + getForegroundProcess: async () => null + }) + + const LEAF_A = '77777777-7777-4777-8777-777777777777' + const LEAF_B = '88888888-8888-4888-8888-888888888888' + // tab-a (first-created) is the snapshot's active tab. + await runtime.createTerminal(`id:${TEST_WORKTREE_ID}`, { tabId: 'tab-a', leafId: LEAF_A }) + await runtime.createTerminal(`id:${TEST_WORKTREE_ID}`, { tabId: 'tab-b', leafId: LEAF_B }) + + // Simulate a post-detach merged snapshot with no authoritative window. + const current = runtime['mobileSessionTabsByWorktree'].get(TEST_WORKTREE_ID)! + runtime['mobileSessionTabsByWorktree'].set(TEST_WORKTREE_ID, { + ...current, + publicationEpoch: `renderer:headless-merge:${current.publicationEpoch}` + }) + + const events: RuntimeMobileSessionTabsResult[] = [] + runtime.onMobileSessionTabsChanged((snapshot) => events.push(snapshot)) + + // The merge exclusion must suppress server-side active rewrites. + await runtime.activateMobileSessionTab(`id:${TEST_WORKTREE_ID}`, 'tab-b') + + expect(events).toHaveLength(0) + }) + it('spawns fresh SSH terminals when hydrated persistence has no relay identity', async () => { const { runtimeStore } = makeRuntimeStoreWithWorkspaceSession( makeWorkspaceSessionWithHeadlessTerminal({ diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index 6f481983e9f..55cc02d6d4a 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -2770,6 +2770,8 @@ export class OrcaRuntimeService { throw new Error('tab_not_found') } + let activatedTab: RuntimeMobileSessionSnapshotTab = tab + if (tab.type === 'terminal') { const publicTab = this.toMobileSessionTabsResult(snapshot!).tabs.find( (candidate) => candidate.type === 'terminal' && candidate.id === tab.id @@ -2810,6 +2812,7 @@ export class OrcaRuntimeService { ) const targetTab = activeSibling ?? tab this.notifier?.focusTerminal(targetTab.parentTabId, worktreeId, targetTab.leafId) + activatedTab = targetTab } else if (tab.type === 'browser') { // Why: browser mobile tabs are renderer-owned unified tabs; focusing the // session tab keeps desktop tab order/group state authoritative. @@ -2817,9 +2820,56 @@ export class OrcaRuntimeService { } else { this.notifier?.focusEditorTab?.(tab.id, worktreeId) } + + // Why: serve/headless snapshots have no renderer to re-publish focus, but + // merged epochs can still contain renderer-owned group state. + if ( + !this.getAvailableAuthoritativeWindow() && + this.isPureHeadlessMobileSessionPublication(snapshot!.publicationEpoch) + ) { + this.persistHeadlessMobileSessionActiveTab(worktreeId, snapshot!, activatedTab) + } return this.getMobileSessionTabsForWorktree(worktreeId) } + private persistHeadlessMobileSessionActiveTab( + worktreeId: string, + snapshot: RuntimeMobileSessionTabsSnapshot, + activeTab: RuntimeMobileSessionSnapshotTab + ): void { + const alreadyActive = + snapshot.activeTabId === activeTab.id && + snapshot.activeTabType === activeTab.type && + snapshot.tabs.every((candidate) => candidate.isActive === (candidate.id === activeTab.id)) + if (alreadyActive) { + // Why: re-activating the already-active tab must not bump snapshotVersion, + // or every redundant activation would force a remote re-render. + return + } + const tabs = snapshot.tabs.map((candidate) => ({ + ...candidate, + isActive: candidate.id === activeTab.id + })) + const terminalTabs = tabs.filter( + (candidate): candidate is RuntimeMobileSessionTerminalTab => candidate.type === 'terminal' + ) + const next: RuntimeMobileSessionTabsSnapshot = { + ...snapshot, + snapshotVersion: snapshot.snapshotVersion + 1, + activeTabId: activeTab.id, + activeTabType: activeTab.type, + tabGroups: this.buildHeadlessMobileSessionTabGroups( + worktreeId, + terminalTabs, + activeTab.type === 'terminal' ? activeTab : null, + snapshot.tabGroups + ), + tabs + } + this.mobileSessionTabsByWorktree.set(worktreeId, next) + this.notifyMobileSessionTabsChanged(worktreeId) + } + private shouldMaterializeHeadlessMobileSessionTab( snapshot: RuntimeMobileSessionTabsSnapshot, tab: RuntimeMobileSessionTerminalTab @@ -13313,10 +13363,15 @@ export class OrcaRuntimeService { ) } + private isPureHeadlessMobileSessionPublication(publicationEpoch: string): boolean { + return ( + publicationEpoch.startsWith('headless:') || publicationEpoch.startsWith('headless-hydrated:') + ) + } + private isHeadlessMobileSessionPublication(publicationEpoch: string): boolean { return ( - publicationEpoch.startsWith('headless:') || - publicationEpoch.startsWith('headless-hydrated:') || + this.isPureHeadlessMobileSessionPublication(publicationEpoch) || publicationEpoch.includes(':headless-merge:') ) } From f027a1cb7b0435b7a6ed6b66776e588188f201ba Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Fri, 12 Jun 2026 10:56:49 -0700 Subject: [PATCH 11/15] Move settings out of help dropdown into dedicated sidebar button (#5272) - Pulls the Settings option out of the Help dropdown menu and renders it as a standalone button directly in the sidebar. - Integrates the `useShortcutKeys` hook and `ShortcutKeyCombo` component to display the settings keyboard shortcut in the button's tooltip. - Updates associated tests to verify the new button's placement, ARIA attributes, and tooltip content. --- .../sidebar/SidebarSettingsHelpMenu.test.tsx | 29 +- .../sidebar/SidebarSettingsHelpMenu.tsx | 289 ++++++++++-------- 2 files changed, 179 insertions(+), 139 deletions(-) diff --git a/src/renderer/src/components/sidebar/SidebarSettingsHelpMenu.test.tsx b/src/renderer/src/components/sidebar/SidebarSettingsHelpMenu.test.tsx index 97218602f21..d63a1555346 100644 --- a/src/renderer/src/components/sidebar/SidebarSettingsHelpMenu.test.tsx +++ b/src/renderer/src/components/sidebar/SidebarSettingsHelpMenu.test.tsx @@ -10,7 +10,7 @@ const mocks = vi.hoisted(() => ({ appRestart: vi.fn(), updaterCheck: vi.fn(), shellOpenUrl: vi.fn(), - useShortcutLabel: vi.fn(), + useShortcutKeys: vi.fn(), setupProgress: { ready: true, coreDoneCount: 2, @@ -32,7 +32,7 @@ vi.mock('@/store', () => ({ })) vi.mock('@/hooks/useShortcutLabel', () => ({ - useShortcutLabel: mocks.useShortcutLabel + useShortcutKeys: mocks.useShortcutKeys })) vi.mock('@/hooks/useMountedRef', () => ({ @@ -72,12 +72,14 @@ vi.mock('@/components/ui/tooltip', () => ({ vi.mock('@/components/ui/button', () => ({ Button: ({ children, - onClick + onClick, + 'aria-label': ariaLabel }: { children: ReactNode onClick?: (event: React.MouseEvent) => void + 'aria-label'?: string }) => ( - ) @@ -97,7 +99,7 @@ vi.mock('./SidebarFeedbackDialog', () => ({ describe('SidebarSettingsHelpMenu', () => { beforeEach(() => { vi.clearAllMocks() - mocks.useShortcutLabel.mockReturnValue('⌘,') + mocks.useShortcutKeys.mockReturnValue(['⌘', ',']) updateStatus = { state: 'idle' } mocks.setupProgress = { ready: true, @@ -112,9 +114,17 @@ describe('SidebarSettingsHelpMenu', () => { expect(html).toContain('Help') }) - it('renders Settings menu item', () => { + it('renders the settings button with correct aria-label', () => { const html = renderToStaticMarkup() - expect(html).toContain('Settings') + expect(html).toContain('aria-label="Settings"') + }) + + it('renders the settings button before the help button', () => { + const html = renderToStaticMarkup() + const settingsIndex = html.indexOf('lucide-settings') + const helpIndex = html.indexOf('lucide-circle-question-mark') + expect(settingsIndex).toBeGreaterThanOrEqual(0) + expect(helpIndex).toBeGreaterThan(settingsIndex) }) it('renders Send Feedback menu item', () => { @@ -179,8 +189,9 @@ describe('SidebarSettingsHelpMenu', () => { expect(html).toContain('Check for Updates') }) - it('renders shortcut label next to Settings', () => { + it('renders shortcut keys in the settings tooltip', () => { const html = renderToStaticMarkup() - expect(html).toContain('⌘,') + expect(html).toContain('⌘') + expect(html).toContain('>,') }) }) diff --git a/src/renderer/src/components/sidebar/SidebarSettingsHelpMenu.tsx b/src/renderer/src/components/sidebar/SidebarSettingsHelpMenu.tsx index a56c8ba7092..0da82554dfd 100644 --- a/src/renderer/src/components/sidebar/SidebarSettingsHelpMenu.tsx +++ b/src/renderer/src/components/sidebar/SidebarSettingsHelpMenu.tsx @@ -26,7 +26,8 @@ import { DropdownMenuTrigger } from '@/components/ui/dropdown-menu' import { useMountedRef } from '@/hooks/useMountedRef' -import { useShortcutLabel } from '@/hooks/useShortcutLabel' +import { useShortcutKeys } from '@/hooks/useShortcutLabel' +import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo' import { showOnboardingFromRenderer } from '../onboarding/show-onboarding-event' import { SetupGuideProgressRing } from '../setup-guide/SetupGuideProgressRing' import { useSetupGuideProgress } from '../setup-guide/use-setup-guide-progress' @@ -84,7 +85,7 @@ export function SidebarSettingsHelpMenu(): React.JSX.Element { const updateStatus = useAppStore((s) => s.updateStatus) const setupProgress = useSetupGuideProgress(true, false, false) - const settingsShortcut = useShortcutLabel('app.settings') + const settingsShortcutKeys = useShortcutKeys('app.settings') const [menuOpen, setMenuOpen] = useState(false) const [feedbackOpen, setFeedbackOpen] = useState(false) const [showAdminOptions, setShowAdminOptions] = useState(false) @@ -157,145 +158,173 @@ export function SidebarSettingsHelpMenu(): React.JSX.Element { return ( <> - +
- - - + - - {translate('auto.components.sidebar.SidebarSettingsHelpMenu.2991a0106c', 'Help')} + + {translate('auto.components.sidebar.SidebarSettingsHelpMenu.a428c25998', 'Settings')} + {settingsShortcutKeys.length > 0 ? ( + + ) : null} - - - - {translate('auto.components.sidebar.SidebarSettingsHelpMenu.a428c25998', 'Settings')} - {settingsShortcut} - - - - {translate( - 'auto.components.sidebar.SidebarSettingsHelpMenu.e565171a7c', - 'Keyboard Shortcuts' - )} - - - setFeedbackOpen(true)}> - - {translate( - 'auto.components.sidebar.SidebarSettingsHelpMenu.4cf5b868d7', - 'Send Feedback' - )} - - {showMilestones ? ( - - + + + + + + + + + {translate('auto.components.sidebar.SidebarSettingsHelpMenu.2991a0106c', 'Help')} + + + + + {translate( - 'auto.components.sidebar.SidebarSettingsHelpMenu.f8a2c91d4e', - 'Milestones' - )} - - - ) : null} - {showAdminOptions ? ( - - - {translate( - 'auto.components.sidebar.SidebarSettingsHelpMenu.b7e4d2a19c', - 'Onboarding' + 'auto.components.sidebar.SidebarSettingsHelpMenu.e565171a7c', + 'Keyboard Shortcuts' )} - ) : null} - } - /> - } - /> - - } - /> - openExternalUrl(DISCORD_URL)}> - - {translate('auto.components.sidebar.SidebarSettingsHelpMenu.eb9884e55b', 'Discord')} - - - openExternalUrl(X_URL)}> - - {translate('auto.components.sidebar.SidebarSettingsHelpMenu.c4f8e1b72a', 'X')} - - - - - {updateStatus.state === 'checking' ? ( - - ) : ( - - )} - {translate( - 'auto.components.sidebar.SidebarSettingsHelpMenu.29c56f30ee', - 'Check for Updates' - )} - - {showAdminOptions ? ( - <> - - - + + setFeedbackOpen(true)}> + + {translate( + 'auto.components.sidebar.SidebarSettingsHelpMenu.4cf5b868d7', + 'Send Feedback' + )} + + {showMilestones ? ( + + {translate( - 'auto.components.sidebar.SidebarSettingsHelpMenu.ad3d3ed7f1', - 'Restart Orca' + 'auto.components.sidebar.SidebarSettingsHelpMenu.f8a2c91d4e', + 'Milestones' + )} + + + ) : null} + {showAdminOptions ? ( + + + {translate( + 'auto.components.sidebar.SidebarSettingsHelpMenu.b7e4d2a19c', + 'Onboarding' )} - - ) : null} - - + ) : null} + } + /> + } + /> + + } + /> + openExternalUrl(DISCORD_URL)}> + + {translate('auto.components.sidebar.SidebarSettingsHelpMenu.eb9884e55b', 'Discord')} + + + openExternalUrl(X_URL)}> + + {translate('auto.components.sidebar.SidebarSettingsHelpMenu.c4f8e1b72a', 'X')} + + + + + {updateStatus.state === 'checking' ? ( + + ) : ( + + )} + {translate( + 'auto.components.sidebar.SidebarSettingsHelpMenu.29c56f30ee', + 'Check for Updates' + )} + + {showAdminOptions ? ( + <> + + + + {translate( + 'auto.components.sidebar.SidebarSettingsHelpMenu.ad3d3ed7f1', + 'Restart Orca' + )} + + + ) : null} + + +
) From dccea75b0e57b93c99cfe9f1f772abf0d1283466 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Fri, 12 Jun 2026 11:09:08 -0700 Subject: [PATCH 12/15] Fix active tab scrolling and editor keyboard layout on mobile (#5271) * Improve mobile session tab scrolling and editor keyboard layout - Auto-scroll the active session tab into view within the horizontal tab strip, using a new layout-aware utility with minimal scrolling. - Use visual viewport measurements in the editor WebView to accurately report keyboard insets and lift floating controls above the keyboard, as native events can under-report covered areas. * Fix active tab scrolling and normalize editor keyboard inset - Ensure the active session tab is scrolled into view immediately when its layout finishes to prevent it from remaining hidden off-screen. - Round, clamp, and validate WebView visualViewport bottom measurements to avoid rendering inconsistencies from fractional or invalid insets. - Trigger an immediate keyboard inset report on script initialization. * Update tabStripOffsetRef when scrolling mobile tab strip Ensure the cached offset is updated to match the next scroll position. Without this update, subsequent scroll calculations would use stale offset values. --- .../app/h/[hostId]/session/[worktreeId].tsx | 63 +++++++++++++++++- .../components/MobileRichMarkdownEditor.tsx | 25 ++++++- .../mobile-rich-markdown-editor-html.ts | 10 +-- ...kdown-editor-keyboard-inset-script.test.ts | 17 +++++ ...h-markdown-editor-keyboard-inset-script.ts | 28 ++++++++ mobile/src/session/tab-strip-scroll.test.ts | 66 +++++++++++++++++++ mobile/src/session/tab-strip-scroll.ts | 40 +++++++++++ 7 files changed, 239 insertions(+), 10 deletions(-) create mode 100644 mobile/src/components/mobile-rich-markdown-editor-keyboard-inset-script.test.ts create mode 100644 mobile/src/components/mobile-rich-markdown-editor-keyboard-inset-script.ts create mode 100644 mobile/src/session/tab-strip-scroll.test.ts create mode 100644 mobile/src/session/tab-strip-scroll.ts diff --git a/mobile/app/h/[hostId]/session/[worktreeId].tsx b/mobile/app/h/[hostId]/session/[worktreeId].tsx index 9046fcbf0c3..e4b2a03ccc3 100644 --- a/mobile/app/h/[hostId]/session/[worktreeId].tsx +++ b/mobile/app/h/[hostId]/session/[worktreeId].tsx @@ -131,6 +131,7 @@ import { type MobileNewTabAgentSettings } from '../../../../src/session/mobile-new-tab-agent-options' import { resolveMarkdownFloatingActionsBottom } from '../../../../src/session/markdown-floating-actions-layout' +import { resolveTabStripScrollOffset } from '../../../../src/session/tab-strip-scroll' import { createMobileSessionCreateWarningState, dismissMobileSessionCreateWarningState, @@ -431,6 +432,10 @@ function MarkdownReader({ onDiscard: () => void keyboardLift: number }) { + // The editor lives in a WebView; native Keyboard events under-report its + // covered area, so prefer the inset measured inside the WebView when larger. + const [webviewKeyboardInset, setWebviewKeyboardInset] = useState(0) + const effectiveKeyboardLift = Math.max(keyboardLift, webviewKeyboardInset) if (!doc || doc.status === 'loading') { return ( @@ -469,6 +474,7 @@ function MarkdownReader({ content={doc.localContent} editable={doc.editable && !doc.saving} onChange={onChange} + onKeyboardInsetChange={setWebviewKeyboardInset} /> {showFloatingActions ? ( (null) const [activeSessionTabId, setActiveSessionTabId] = useState(null) const activeSessionTabIdRef = useRef(null) + // Auto-scroll the tab strip so the active tab (synced from desktop on + // worktree entry) is revealed without a manual scroll. + const tabStripRef = useRef(null) + const tabStripOffsetRef = useRef(0) + const tabStripViewportWidthRef = useRef(0) + const tabStripContentWidthRef = useRef(0) + const tabLayoutsRef = useRef>(new Map()) const [markdownDocs, setMarkdownDocs] = useState>(new Map()) const markdownDocsRef = useRef>(new Map()) const [fileDocs, setFileDocs] = useState>(new Map()) @@ -2295,6 +2308,34 @@ export default function SessionScreen() { } }, []) + const scrollActiveTabIntoView = useCallback((tabId: string | null, animated: boolean) => { + if (!tabId) { + return + } + const layout = tabLayoutsRef.current.get(tabId) + if (!layout) { + return + } + const nextOffset = resolveTabStripScrollOffset({ + tabX: layout.x, + tabWidth: layout.width, + viewportWidth: tabStripViewportWidthRef.current, + contentWidth: tabStripContentWidthRef.current, + currentOffset: tabStripOffsetRef.current + }) + if (nextOffset !== tabStripOffsetRef.current) { + tabStripOffsetRef.current = nextOffset + tabStripRef.current?.scrollTo({ x: nextOffset, animated }) + } + }, []) + + // Reveal the active tab whenever it changes (e.g. desktop's open tab synced on + // worktree entry). Defer one frame so freshly mounted tab layouts are recorded. + useEffect(() => { + const id = requestAnimationFrame(() => scrollActiveTabIntoView(activeSessionTabId, true)) + return () => cancelAnimationFrame(id) + }, [activeSessionTabId, scrollActiveTabIntoView]) + useEffect(() => { if (hostId && worktreeId) { void AsyncStorage.setItem( @@ -3867,16 +3908,36 @@ export default function SessionScreen() { (#5106); leaving a non-live tab still closes the keyboard because the live input unmounts. */} { + tabStripOffsetRef.current = e.nativeEvent.contentOffset.x + }} + onLayout={(e) => { + tabStripViewportWidthRef.current = e.nativeEvent.layout.width + scrollActiveTabIntoView(activeSessionTabIdRef.current, false) + }} + onContentSizeChange={(width) => { + tabStripContentWidthRef.current = width + scrollActiveTabIntoView(activeSessionTabIdRef.current, false) + }} > {visibleTabs.map((t) => ( { + const { x, width } = e.nativeEvent.layout + tabLayoutsRef.current.set(t.id, { x, width }) + if (t.id === activeSessionTabIdRef.current) { + scrollActiveTabIntoView(t.id, false) + } + }} onPress={() => switchSessionTab(t)} onLongPress={() => { triggerMediumImpact() diff --git a/mobile/src/components/MobileRichMarkdownEditor.tsx b/mobile/src/components/MobileRichMarkdownEditor.tsx index e50bc2e19ca..c9e0b44e082 100644 --- a/mobile/src/components/MobileRichMarkdownEditor.tsx +++ b/mobile/src/components/MobileRichMarkdownEditor.tsx @@ -19,6 +19,7 @@ import { } from 'lucide-react-native' import WebView, { type WebViewMessageEvent } from 'react-native-webview' import { colors, radii, spacing } from '../theme/mobile-theme' +import { normalizeMobileRichMarkdownKeyboardInset } from './mobile-rich-markdown-editor-keyboard-inset-script' import { buildMobileRichMarkdownEditorHtml, escapeInjectedJavaScriptString @@ -73,12 +74,14 @@ type Props = { content: string editable: boolean onChange: (content: string) => void + onKeyboardInsetChange?: (bottom: number) => void } type EditorWebViewMessage = | { type: 'ready' } | { type: 'change'; markdown: string; generation: number } | { type: 'openLink'; url: string } + | { type: 'keyboardInset'; bottom: number } type ToolbarItem = { command: RichMarkdownCommand @@ -104,7 +107,12 @@ const TOOLBAR_ITEMS: ToolbarItem[] = [ { command: 'codeBlock', label: 'Code block', icon: FileCode2 } ] -function MobileRichMarkdownEditorInner({ content, editable, onChange }: Props) { +function MobileRichMarkdownEditorInner({ + content, + editable, + onChange, + onKeyboardInsetChange +}: Props) { const webViewRef = useRef(null) const readyRef = useRef(false) const documentGenerationRef = useRef(0) @@ -150,6 +158,12 @@ function MobileRichMarkdownEditorInner({ content, editable, onChange }: Props) { } }, [applyEditable, editable]) + // Clear any reported keyboard inset when the editor unmounts so a lifted + // Save/Discard bar settles back once the tab closes. + useEffect(() => { + return () => onKeyboardInsetChange?.(0) + }, [onKeyboardInsetChange]) + const handleMessage = useCallback( (event: WebViewMessageEvent) => { let message: unknown @@ -182,9 +196,16 @@ function MobileRichMarkdownEditorInner({ content, editable, onChange }: Props) { if (url) { void Linking.openURL(url).catch(() => {}) } + return + } + if (editorMessage.type === 'keyboardInset' && typeof editorMessage.bottom === 'number') { + const bottom = normalizeMobileRichMarkdownKeyboardInset(editorMessage.bottom) + if (bottom !== null) { + onKeyboardInsetChange?.(bottom) + } } }, - [applyContent, applyEditable, content, editable, onChange] + [applyContent, applyEditable, content, editable, onChange, onKeyboardInsetChange] ) const handleShouldStartLoadWithRequest = useCallback((request: { url?: string }) => { diff --git a/mobile/src/components/mobile-rich-markdown-editor-html.ts b/mobile/src/components/mobile-rich-markdown-editor-html.ts index 826bd1693d0..32fc12b59b2 100644 --- a/mobile/src/components/mobile-rich-markdown-editor-html.ts +++ b/mobile/src/components/mobile-rich-markdown-editor-html.ts @@ -1,4 +1,5 @@ import { colors } from '../theme/mobile-theme' +import { MOBILE_RICH_MARKDOWN_KEYBOARD_INSET_SCRIPT } from './mobile-rich-markdown-editor-keyboard-inset-script' export function escapeInjectedJavaScriptString(value: string): string { return JSON.stringify(value).replace(/<\/script/gi, '<\\/script') @@ -669,13 +670,8 @@ export function buildMobileRichMarkdownEditorHtml(): string { } }); - window.__orcaRichMarkdown = { - setMarkdown: setMarkdown, - setEditable: setEditable, - runCommand: runCommand, - currentMarkdown: currentMarkdown - }; - + window.__orcaRichMarkdown = { setMarkdown: setMarkdown, setEditable: setEditable, runCommand: runCommand, currentMarkdown: currentMarkdown }; +${MOBILE_RICH_MARKDOWN_KEYBOARD_INSET_SCRIPT} post({ type: 'ready' }); })(); diff --git a/mobile/src/components/mobile-rich-markdown-editor-keyboard-inset-script.test.ts b/mobile/src/components/mobile-rich-markdown-editor-keyboard-inset-script.test.ts new file mode 100644 index 00000000000..c29b6ab5ba9 --- /dev/null +++ b/mobile/src/components/mobile-rich-markdown-editor-keyboard-inset-script.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from 'vitest' +import { normalizeMobileRichMarkdownKeyboardInset } from './mobile-rich-markdown-editor-keyboard-inset-script' + +describe('normalizeMobileRichMarkdownKeyboardInset', () => { + it('rounds finite inset measurements for native layout', () => { + expect(normalizeMobileRichMarkdownKeyboardInset(42.6)).toBe(43) + }) + + it('clamps negative inset measurements to zero', () => { + expect(normalizeMobileRichMarkdownKeyboardInset(-8)).toBe(0) + }) + + it('rejects non-finite inset measurements', () => { + expect(normalizeMobileRichMarkdownKeyboardInset(Number.NaN)).toBeNull() + expect(normalizeMobileRichMarkdownKeyboardInset(Number.POSITIVE_INFINITY)).toBeNull() + }) +}) diff --git a/mobile/src/components/mobile-rich-markdown-editor-keyboard-inset-script.ts b/mobile/src/components/mobile-rich-markdown-editor-keyboard-inset-script.ts new file mode 100644 index 00000000000..4182d133f00 --- /dev/null +++ b/mobile/src/components/mobile-rich-markdown-editor-keyboard-inset-script.ts @@ -0,0 +1,28 @@ +// In-page script that reports the height covered by the on-screen keyboard. +// Native Keyboard events are unreliable while focus lives in the editor +// WebView, so measure the covered region directly from visualViewport and let +// RN lift its native Save/Discard bar above it. +export function normalizeMobileRichMarkdownKeyboardInset(value: number): number | null { + if (!Number.isFinite(value)) { + return null + } + return Math.max(0, Math.round(value)) +} + +export const MOBILE_RICH_MARKDOWN_KEYBOARD_INSET_SCRIPT = ` + var lastInset = -1; + function reportKeyboardInset() { + var viewport = window.visualViewport; + var bottom = viewport + ? Math.max(0, window.innerHeight - viewport.height - viewport.offsetTop) + : 0; + var rounded = Math.round(bottom); + if (rounded === lastInset) return; + lastInset = rounded; + post({ type: 'keyboardInset', bottom: rounded }); + } + if (window.visualViewport) { + window.visualViewport.addEventListener('resize', reportKeyboardInset); + window.visualViewport.addEventListener('scroll', reportKeyboardInset); + reportKeyboardInset(); + }` diff --git a/mobile/src/session/tab-strip-scroll.test.ts b/mobile/src/session/tab-strip-scroll.test.ts new file mode 100644 index 00000000000..fab517a3155 --- /dev/null +++ b/mobile/src/session/tab-strip-scroll.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it } from 'vitest' +import { resolveTabStripScrollOffset } from './tab-strip-scroll' + +describe('resolveTabStripScrollOffset', () => { + it('keeps the offset when the active tab is already fully visible', () => { + expect( + resolveTabStripScrollOffset({ + tabX: 140, + tabWidth: 128, + viewportWidth: 360, + contentWidth: 800, + currentOffset: 100 + }) + ).toBe(100) + }) + + it('scrolls left to reveal a tab off the left edge', () => { + expect( + resolveTabStripScrollOffset({ + tabX: 50, + tabWidth: 128, + viewportWidth: 360, + contentWidth: 800, + currentOffset: 200, + margin: 12 + }) + ).toBe(38) + }) + + it('scrolls right to reveal a tab off the right edge', () => { + expect( + resolveTabStripScrollOffset({ + tabX: 640, + tabWidth: 128, + viewportWidth: 360, + contentWidth: 900, + currentOffset: 0, + margin: 12 + }) + ).toBe(420) + }) + + it('clamps the offset to the content bounds', () => { + expect( + resolveTabStripScrollOffset({ + tabX: 880, + tabWidth: 128, + viewportWidth: 360, + contentWidth: 900, + currentOffset: 0 + }) + ).toBe(540) + }) + + it('returns the current offset when the viewport has not been measured', () => { + expect( + resolveTabStripScrollOffset({ + tabX: 100, + tabWidth: 128, + viewportWidth: 0, + contentWidth: 0, + currentOffset: 0 + }) + ).toBe(0) + }) +}) diff --git a/mobile/src/session/tab-strip-scroll.ts b/mobile/src/session/tab-strip-scroll.ts new file mode 100644 index 00000000000..275b9ed5f57 --- /dev/null +++ b/mobile/src/session/tab-strip-scroll.ts @@ -0,0 +1,40 @@ +export type TabStripScrollInput = { + tabX: number + tabWidth: number + viewportWidth: number + contentWidth: number + currentOffset: number + margin?: number +} + +/** + * Keep active-tab reveal deterministic across async RN layout events without + * nudging the strip when the tab is already visible. + */ +export function resolveTabStripScrollOffset({ + tabX, + tabWidth, + viewportWidth, + contentWidth, + currentOffset, + margin = 12 +}: TabStripScrollInput): number { + const maxOffset = Math.max(0, contentWidth - viewportWidth) + if (viewportWidth <= 0) { + return currentOffset + } + + const visibleStart = currentOffset + const visibleEnd = currentOffset + viewportWidth + const tabStart = tabX + const tabEnd = tabX + tabWidth + + let nextOffset = currentOffset + if (tabStart < visibleStart + margin) { + nextOffset = tabStart - margin + } else if (tabEnd > visibleEnd - margin) { + nextOffset = tabEnd + margin - viewportWidth + } + + return Math.min(Math.max(0, nextOffset), maxOffset) +} From 5ca63c85d759f3185a59c198e88a39bac21737d0 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Fri, 12 Jun 2026 11:20:43 -0700 Subject: [PATCH 13/15] fix: address review findings (#5273) --- .../tab-bar/TabBarQuickCommandsButton.tsx | 235 +---------- .../tab-bar/TabBarQuickCommandsMenu.tsx | 388 ++++++++++++++++++ src/renderer/src/i18n/locales/en.json | 4 +- src/renderer/src/i18n/locales/es.json | 4 +- src/renderer/src/i18n/locales/ja.json | 4 +- src/renderer/src/i18n/locales/ko.json | 4 +- src/renderer/src/i18n/locales/zh.json | 4 +- .../lib/terminal-quick-command-search.test.ts | 71 ++++ .../src/lib/terminal-quick-command-search.ts | 90 ++++ 9 files changed, 585 insertions(+), 219 deletions(-) create mode 100644 src/renderer/src/components/tab-bar/TabBarQuickCommandsMenu.tsx create mode 100644 src/renderer/src/lib/terminal-quick-command-search.test.ts create mode 100644 src/renderer/src/lib/terminal-quick-command-search.ts diff --git a/src/renderer/src/components/tab-bar/TabBarQuickCommandsButton.tsx b/src/renderer/src/components/tab-bar/TabBarQuickCommandsButton.tsx index 173170a4330..a033bc01407 100644 --- a/src/renderer/src/components/tab-bar/TabBarQuickCommandsButton.tsx +++ b/src/renderer/src/components/tab-bar/TabBarQuickCommandsButton.tsx @@ -1,37 +1,22 @@ import { useMemo, useState } from 'react' -import { ChevronDown, Pencil, Play, Plus, Trash2 } from 'lucide-react' +import { Plus } from 'lucide-react' import { useAppStore } from '@/store' -import { - Command, - CommandEmpty, - CommandItem, - CommandList, - CommandSeparator -} from '@/components/ui/command' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuTrigger -} from '@/components/ui/dropdown-menu' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { createTerminalQuickCommandDraft, TerminalQuickCommandDialog } from '@/components/terminal-quick-commands/TerminalQuickCommandDialog' import { - getTerminalQuickCommandBody, getTerminalQuickCommandScope, - isTerminalAgentQuickCommand, isTerminalQuickCommandComplete } from '../../../../shared/terminal-quick-commands' import { getRepoIdFromWorktreeId } from '../../../../shared/worktree-id' import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants' import { runQuickCommandInNewTab } from '@/lib/run-quick-command-in-new-tab' import type { TerminalQuickCommand } from '../../../../shared/types' -import { cn } from '@/lib/utils' import { useConfirmationDialog } from '@/components/confirmation-dialog' -import { AgentIcon, getAgentLabel } from '@/lib/agent-catalog' import { translate } from '@/i18n/i18n' +import { TabBarQuickCommandsMenu } from './TabBarQuickCommandsMenu' type TabBarQuickCommandsButtonProps = { worktreeId: string @@ -92,8 +77,6 @@ export function TabBarQuickCommandsButton({ return repoCommands[0] ?? globalCommands[0] ?? null }, [repoCommands, globalCommands, recentId]) - const [menuOpen, setMenuOpen] = useState(false) - const [commandValue, setCommandValue] = useState('') const [editor, setEditor] = useState< | { mode: 'add'; command: TerminalQuickCommand } | { mode: 'edit'; command: TerminalQuickCommand } @@ -103,16 +86,11 @@ export function TabBarQuickCommandsButton({ const totalVisible = repoCommands.length + globalCommands.length const hasAnyCommands = totalVisible > 0 - const handleOpenChange = (next: boolean): void => { - setMenuOpen(next) - if (!next) { - setCommandValue('') - } - } - - const handleRun = (command: TerminalQuickCommand): void => { - setMenuOpen(false) - runQuickCommandInNewTab({ command, worktreeId, groupId }) + const addRepoCommand = (): void => { + setEditor({ + mode: 'add', + command: createTerminalQuickCommandDraft({ type: 'repo', repoId: repoId ?? '' }) + }) } const handleSaveCommand = (next: TerminalQuickCommand): void => { @@ -123,7 +101,6 @@ export function TabBarQuickCommandsButton({ } const handleDeleteCommand = async (command: TerminalQuickCommand): Promise => { - setMenuOpen(false) const confirmed = await confirm({ title: translate( 'auto.components.tab.bar.TabBarQuickCommandsButton.e8e1a52edb', @@ -147,6 +124,10 @@ export function TabBarQuickCommandsButton({ void updateSettings({ terminalQuickCommands: current.filter((c) => c.id !== command.id) }) } + const handleRun = (command: TerminalQuickCommand): void => { + runQuickCommandInNewTab({ command, worktreeId, groupId }) + } + // Why: hidden in folder-mode worktrees (no repoId) and floating terminals. // Without a repoId the button can't represent a repo-scoped run target, and // global-only mode would be confusing in a context that doesn't belong to a @@ -163,12 +144,7 @@ export function TabBarQuickCommandsButton({ - - - - ) - return ( <> -
- - - - - - {mostRecent - ? isTerminalAgentQuickCommand(mostRecent) - ? translate( - 'auto.components.tab.bar.TabBarQuickCommandsButton.77ac113df0', - 'Start {{value0}}: {{value1}}', - { - value0: getAgentLabel(mostRecent.agent), - value1: getTerminalQuickCommandBody(mostRecent) - } - ) - : translate( - 'auto.components.tab.bar.TabBarQuickCommandsButton.37e1bb90ce', - 'Run: {{value0}}', - { value0: getTerminalQuickCommandBody(mostRecent) } - ) - : translate( - 'auto.components.tab.bar.TabBarQuickCommandsButton.85482c57bc', - 'Run quick command' - )} - - - - - - - - - - {totalVisible === 0 ? ( - - {translate( - 'auto.components.tab.bar.TabBarQuickCommandsButton.20bbd75896', - 'No commands' - )} - - ) : null} - {repoCommands.map(renderItem)} - {repoCommands.length > 0 && globalCommands.length > 0 ? ( - - ) : null} - {globalCommands.map(renderItem)} - -
- -
-
-
-
-
+ setEditor({ mode: 'edit', command })} + onDeleteCommand={(command) => void handleDeleteCommand(command)} + onRunCommand={handleRun} + /> void + onDeleteCommand: (command: TerminalQuickCommand) => void + onEditCommand: (command: TerminalQuickCommand) => void + onRunCommand: (command: TerminalQuickCommand) => void +} +export function TabBarQuickCommandsMenu({ + repoCommands, + globalCommands, + mostRecent, + onAddCommand, + onDeleteCommand, + onEditCommand, + onRunCommand +}: TabBarQuickCommandsMenuProps): React.JSX.Element { + const [menuOpen, setMenuOpen] = useState(false) + const [query, setQuery] = useState('') + const [commandValueOverride, setCommandValueOverride] = useState(null) + const searchInputRef = useRef(null) + const commandListRef = useRef(null) + const focusFrameRef = useRef(null) + const totalVisible = repoCommands.length + globalCommands.length + const showSearch = totalVisible > 1 + const filteredRepoCommands = useMemo( + () => searchTerminalQuickCommands(repoCommands, query), + [repoCommands, query] + ) + const filteredGlobalCommands = useMemo( + () => searchTerminalQuickCommands(globalCommands, query), + [globalCommands, query] + ) + const filteredVisibleCommands = useMemo( + () => [...filteredRepoCommands, ...filteredGlobalCommands], + [filteredRepoCommands, filteredGlobalCommands] + ) + const commandValue = useMemo(() => { + const activeValue = getTerminalQuickCommandPickerValue({ + preferredCommandId: mostRecent?.id ?? null, + filteredCommands: filteredVisibleCommands, + rawQuery: query + }) + if ( + commandValueOverride && + filteredVisibleCommands.some((command) => command.id === commandValueOverride) + ) { + return commandValueOverride + } + return activeValue + }, [commandValueOverride, filteredVisibleCommands, mostRecent?.id, query]) + const selectedCommand = useMemo( + () => filteredVisibleCommands.find((command) => command.id === commandValue) ?? null, + [commandValue, filteredVisibleCommands] + ) + const cancelFocusFrame = useCallback((): void => { + if (focusFrameRef.current !== null) { + cancelAnimationFrame(focusFrameRef.current) + focusFrameRef.current = null + } + }, []) + const focusSearchInput = useCallback((): void => { + cancelFocusFrame() + focusFrameRef.current = requestAnimationFrame(() => { + focusFrameRef.current = null + const searchInput = searchInputRef.current + if (!searchInput) { + return + } + searchInput.focus() + const end = searchInput.value.length + searchInput.setSelectionRange(end, end) + }) + }, [cancelFocusFrame]) + const handleOpenChange = (next: boolean): void => { + setMenuOpen(next) + if (next) { + setCommandValueOverride(null) + return + } + cancelFocusFrame() + setQuery('') + setCommandValueOverride(null) + } + useEffect(() => { + if (!menuOpen || !showSearch) { + return + } + // Why: Radix focuses the menu surface by default; search-first UX needs + // the input ready so Enter can run the highlighted command. + focusSearchInput() + return cancelFocusFrame + }, [cancelFocusFrame, focusSearchInput, menuOpen, showSearch]) + const runAndClose = useCallback( + (command: TerminalQuickCommand): void => { + setMenuOpen(false) + onRunCommand(command) + }, + [onRunCommand] + ) + const handleSearchKeyDown = useCallback( + (event: React.KeyboardEvent) => { + if (event.key === 'Enter' && selectedCommand) { + // Why: cmdk does not submit the highlighted item from CommandInput + // inside a DropdownMenu — mirror other searchable menus and run it here. + event.preventDefault() + event.stopPropagation() + runAndClose(selectedCommand) + return + } + if ( + (event.key === 'ArrowDown' || event.key === 'ArrowUp') && + filteredVisibleCommands.length > 0 + ) { + event.preventDefault() + event.stopPropagation() + const currentIndex = filteredVisibleCommands.findIndex( + (command) => command.id === commandValue + ) + const startIndex = Math.max(currentIndex, 0) + const direction = event.key === 'ArrowDown' ? 1 : -1 + let nextIndex = startIndex + direction + if (nextIndex < 0) { + nextIndex = filteredVisibleCommands.length - 1 + } else if (nextIndex >= filteredVisibleCommands.length) { + nextIndex = 0 + } + setCommandValueOverride(filteredVisibleCommands[nextIndex].id) + requestAnimationFrame(() => { + commandListRef.current + ?.querySelector('[cmdk-item][data-selected="true"]') + ?.scrollIntoView({ block: 'nearest' }) + }) + return + } + if (event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey) { + // Why: keep printable keys in the search field instead of Radix typeahead, + // while letting Escape/Tab and system shortcuts keep their menu semantics. + event.stopPropagation() + } + }, + [commandValue, filteredVisibleCommands, runAndClose, selectedCommand] + ) + const splitButtonClass = + 'my-auto flex h-7 shrink-0 items-stretch overflow-hidden rounded-md border border-border/60 text-muted-foreground' + const innerButtonBase = + 'flex items-center bg-transparent leading-none text-muted-foreground hover:bg-accent/50 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent' + const renderItem = (command: TerminalQuickCommand): React.JSX.Element => ( + runAndClose(command)} + className="group/qc mx-1 my-0.5 items-center gap-2 rounded-[7px] px-2 py-1.5 text-[12px] leading-5 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground" + > + {isTerminalAgentQuickCommand(command) ? ( + + + + ) : ( + + )} + + {command.label} + + {isTerminalAgentQuickCommand(command) + ? `${getAgentLabel(command.agent)}: ${command.prompt}` + : command.command} + + + + + + + + ) + return ( +
+ + + + + + {mostRecent + ? isTerminalAgentQuickCommand(mostRecent) + ? translate( + 'auto.components.tab.bar.TabBarQuickCommandsButton.77ac113df0', + 'Start {{value0}}: {{value1}}', + { + value0: getAgentLabel(mostRecent.agent), + value1: getTerminalQuickCommandBody(mostRecent) + } + ) + : translate( + 'auto.components.tab.bar.TabBarQuickCommandsButton.37e1bb90ce', + 'Run: {{value0}}', + { value0: getTerminalQuickCommandBody(mostRecent) } + ) + : translate( + 'auto.components.tab.bar.TabBarQuickCommandsButton.85482c57bc', + 'Run quick command' + )} + + + + + + + { + if (event.key !== 'Enter' || showSearch || filteredVisibleCommands.length !== 1) { + return + } + event.preventDefault() + runAndClose(filteredVisibleCommands[0]) + }} + > + + {showSearch ? ( + { + // Why: a new query changes the filtered list, so keyboard + // selection should jump to the best match immediately. + setCommandValueOverride(null) + setQuery(nextQuery) + }} + onKeyDown={handleSearchKeyDown} + className="h-9 py-2 text-[12px]" + wrapperClassName="border-b border-border/50 px-2" + iconClassName="h-3.5 w-3.5" + /> + ) : null} + + {filteredVisibleCommands.length === 0 ? ( + + {query.trim() + ? translate( + 'auto.components.tab.bar.TabBarQuickCommandsButton.b4e7f9a2c1', + 'No commands match' + ) + : translate( + 'auto.components.tab.bar.TabBarQuickCommandsButton.20bbd75896', + 'No commands' + )} + + ) : null} + {filteredRepoCommands.map(renderItem)} + {filteredRepoCommands.length > 0 && filteredGlobalCommands.length > 0 ? ( + + ) : null} + {filteredGlobalCommands.map(renderItem)} + +
+ +
+
+
+
+
+ ) +} diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index efc86aa7866..00ea06c7e57 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -2443,7 +2443,9 @@ "77ac113df0": "Start {{value0}}: {{value1}}", "7b1c9d6ae1": "Run", "c781f992e4": "destructive", - "be8f0ff166": "Delete" + "be8f0ff166": "Delete", + "f3a8c2d1e7": "Search quick commands...", + "b4e7f9a2c1": "No commands match" }, "shell": { "icons": { diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index ee917b3c649..6cd9799d15b 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -2443,7 +2443,9 @@ "77ac113df0": "Inicio {{value0}}: {{value1}}", "7b1c9d6ae1": "Correr", "c781f992e4": "destructivo", - "be8f0ff166": "Borrar" + "be8f0ff166": "Borrar", + "f3a8c2d1e7": "Search quick commands...", + "b4e7f9a2c1": "No commands match" }, "shell": { "icons": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 4ce65aeab9f..502c01e88e8 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -2443,7 +2443,9 @@ "77ac113df0": "{{value0}} を開始: {{value1}}", "7b1c9d6ae1": "走る", "c781f992e4": "破壊的な", - "be8f0ff166": "削除" + "be8f0ff166": "削除", + "f3a8c2d1e7": "Search quick commands...", + "b4e7f9a2c1": "No commands match" }, "shell": { "icons": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index c9c13d6ac09..5eeadea4384 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -2443,7 +2443,9 @@ "77ac113df0": "시작 {{value0}}: {{value1}}", "7b1c9d6ae1": "달리다", "c781f992e4": "파괴적인", - "be8f0ff166": "삭제" + "be8f0ff166": "삭제", + "f3a8c2d1e7": "Search quick commands...", + "b4e7f9a2c1": "No commands match" }, "shell": { "icons": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index ec0449bff41..fdebed774b3 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -2443,7 +2443,9 @@ "77ac113df0": "开始 {{value0}}: {{value1}}", "7b1c9d6ae1": "跑步", "c781f992e4": "destructive", - "be8f0ff166": "删除" + "be8f0ff166": "删除", + "f3a8c2d1e7": "Search quick commands...", + "b4e7f9a2c1": "No commands match" }, "shell": { "icons": { diff --git a/src/renderer/src/lib/terminal-quick-command-search.test.ts b/src/renderer/src/lib/terminal-quick-command-search.test.ts new file mode 100644 index 00000000000..c22133f6998 --- /dev/null +++ b/src/renderer/src/lib/terminal-quick-command-search.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from 'vitest' +import { + getTerminalQuickCommandPickerValue, + searchTerminalQuickCommands +} from './terminal-quick-command-search' +import type { TerminalQuickCommand } from '../../../shared/types' + +const commands: TerminalQuickCommand[] = [ + { + id: 'dev', + label: 'dev', + action: 'terminal-command', + command: 'pnpm dev', + appendEnter: true + }, + { + id: 'review', + label: 'codex-code-review', + action: 'agent-prompt', + agent: 'codex', + prompt: 'Review all code changes' + }, + { + id: 'simulate', + label: 'simulate new user', + action: 'terminal-command', + command: 'pnpm simulate-new-user', + appendEnter: true + } +] + +describe('terminal quick command search', () => { + it('returns all commands for an empty query', () => { + expect(searchTerminalQuickCommands(commands, '')).toEqual(commands) + }) + + it('matches label, body, and agent text', () => { + expect(searchTerminalQuickCommands(commands, 'dev').map((command) => command.id)).toEqual([ + 'dev' + ]) + expect(searchTerminalQuickCommands(commands, 'codex').map((command) => command.id)).toEqual([ + 'review' + ]) + expect( + searchTerminalQuickCommands(commands, 'review all').map((command) => command.id) + ).toEqual(['review']) + expect(searchTerminalQuickCommands(commands, 'simulate').map((command) => command.id)).toEqual([ + 'simulate' + ]) + }) + + it('prefers the recent command when the query is empty', () => { + expect( + getTerminalQuickCommandPickerValue({ + preferredCommandId: 'simulate', + filteredCommands: commands, + rawQuery: '' + }) + ).toBe('simulate') + }) + + it('selects the first filtered match while searching', () => { + expect( + getTerminalQuickCommandPickerValue({ + preferredCommandId: 'dev', + filteredCommands: searchTerminalQuickCommands(commands, 'codex'), + rawQuery: 'codex' + }) + ).toBe('review') + }) +}) diff --git a/src/renderer/src/lib/terminal-quick-command-search.ts b/src/renderer/src/lib/terminal-quick-command-search.ts new file mode 100644 index 00000000000..958847c78b4 --- /dev/null +++ b/src/renderer/src/lib/terminal-quick-command-search.ts @@ -0,0 +1,90 @@ +import { + getTerminalQuickCommandBody, + isTerminalAgentQuickCommand +} from '../../../shared/terminal-quick-commands' +import type { TerminalQuickCommand } from '../../../shared/types' + +type RankedCommand = { + command: TerminalQuickCommand + score: number + index: number +} + +const NO_MATCH = Number.POSITIVE_INFINITY + +export function searchTerminalQuickCommands( + commands: readonly TerminalQuickCommand[], + rawQuery: string +): TerminalQuickCommand[] { + const query = normalizeSearchText(rawQuery) + if (!query) { + return [...commands] + } + + const matches: RankedCommand[] = [] + commands.forEach((command, index) => { + const score = scoreQuickCommand(command, query) + if (score !== NO_MATCH) { + matches.push({ command, score, index }) + } + }) + + matches.sort((a, b) => a.score - b.score || a.index - b.index) + return matches.map((match) => match.command) +} + +export function getTerminalQuickCommandPickerValue({ + preferredCommandId, + filteredCommands, + rawQuery +}: { + preferredCommandId: string | null + filteredCommands: readonly TerminalQuickCommand[] + rawQuery: string +}): string { + if (!normalizeSearchText(rawQuery)) { + if ( + preferredCommandId && + filteredCommands.some((command) => command.id === preferredCommandId) + ) { + return preferredCommandId + } + return filteredCommands[0]?.id ?? '' + } + return filteredCommands[0]?.id ?? '' +} + +function scoreQuickCommand(command: TerminalQuickCommand, query: string): number { + const body = getTerminalQuickCommandBody(command) + const scores = [scoreCandidate(query, command.label, 0), scoreCandidate(query, body, 400)] + if (isTerminalAgentQuickCommand(command)) { + scores.push(scoreCandidate(query, command.agent, 200)) + } + return Math.min(...scores) +} + +function scoreCandidate(query: string, rawCandidate: string, baseScore: number): number { + const candidate = normalizeSearchText(rawCandidate) + if (!candidate) { + return NO_MATCH + } + if (candidate === query) { + return baseScore + } + if (candidate.startsWith(query)) { + return baseScore + 50 + } + const wordIndex = candidate.indexOf(` ${query}`) + if (wordIndex >= 0) { + return baseScore + 100 + wordIndex + } + const index = candidate.indexOf(query) + if (index >= 0) { + return baseScore + 200 + index + } + return NO_MATCH +} + +function normalizeSearchText(value: string): string { + return value.trim().toLowerCase().replace(/\s+/g, ' ') +} From 528a9292aaec759be8b5563ce31a7f8be84493f0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 18:22:56 +0000 Subject: [PATCH 14/15] release: v1.4.65-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 351b530e3c8..d9ee8a2f415 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "orca", - "version": "1.4.65-rc.0", + "version": "1.4.65-rc.1", "description": "Next-gen IDE for parallel agentic development", "homepage": "https://github.com/stablyai/orca", "author": "stablyai", From d2a3f12baa82adfbb30389c3a2b2a300cab439ca Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Fri, 12 Jun 2026 11:43:39 -0700 Subject: [PATCH 15/15] fix: address review findings (#5275) --- .../runtime-home-service.test.ts | 1 + src/main/codex-accounts/service.test.ts | 1 + src/main/persistence.ts | 15 + src/renderer/src/App.tsx | 5 +- .../link-routing-preference-dialog.tsx | 260 ++++++++++++++++++ .../settings/BrowserLinkRoutingSetting.tsx | 7 +- .../components/terminal-pane/TerminalPane.tsx | 35 +++ .../terminal-link-handlers.test.ts | 83 +++++- .../terminal-osc-link-routing.ts | 14 +- .../terminal-url-link-hit-testing.ts | 41 ++- .../use-terminal-pane-lifecycle.ts | 21 +- src/renderer/src/i18n/locales/en.json | 38 +++ src/renderer/src/i18n/locales/es.json | 38 +++ src/renderer/src/i18n/locales/ja.json | 38 +++ src/renderer/src/i18n/locales/ko.json | 38 +++ src/renderer/src/i18n/locales/zh.json | 38 +++ .../src/lib/http-link-routing.test.ts | 12 +- src/renderer/src/lib/http-link-routing.ts | 2 +- .../src/lib/workspace-port-actions.ts | 2 +- src/shared/constants.ts | 3 +- src/shared/types.ts | 3 + 21 files changed, 667 insertions(+), 28 deletions(-) create mode 100644 src/renderer/src/components/link-routing-preference-dialog.tsx diff --git a/src/main/codex-accounts/runtime-home-service.test.ts b/src/main/codex-accounts/runtime-home-service.test.ts index 1096210f842..1630ada4d8a 100644 --- a/src/main/codex-accounts/runtime-home-service.test.ts +++ b/src/main/codex-accounts/runtime-home-service.test.ts @@ -82,6 +82,7 @@ function createSettings(overrides: Partial = {}): GlobalSettings localAccountRuntime: 'host', localAccountWslDistro: null, openLinksInApp: false, + openLinksInAppPreferencePrompted: false, rightSidebarOpenByDefault: true, sourceControlViewMode: 'list', showTitlebarAppName: true, diff --git a/src/main/codex-accounts/service.test.ts b/src/main/codex-accounts/service.test.ts index 21f1335010f..2a00e2a4856 100644 --- a/src/main/codex-accounts/service.test.ts +++ b/src/main/codex-accounts/service.test.ts @@ -86,6 +86,7 @@ function createSettings(overrides: Partial = {}): GlobalSettings localAccountRuntime: 'host', localAccountWslDistro: null, openLinksInApp: false, + openLinksInAppPreferencePrompted: false, rightSidebarOpenByDefault: true, sourceControlViewMode: 'list', showTitlebarAppName: true, diff --git a/src/main/persistence.ts b/src/main/persistence.ts index a1e7ea44acf..c2a044024e5 100644 --- a/src/main/persistence.ts +++ b/src/main/persistence.ts @@ -2229,6 +2229,14 @@ export class Store { parsed.settings?.disabledTuiAgents ) const migratedAgentYoloDefaults = migrateAgentYoloDefaults(parsed.settings) + const openLinksInAppWasPersisted = Object.prototype.hasOwnProperty.call( + parsed.settings ?? {}, + 'openLinksInApp' + ) + const migratedOpenLinksInAppPreferencePrompted = + typeof parsed.settings?.openLinksInAppPreferencePrompted === 'boolean' + ? parsed.settings.openLinksInAppPreferencePrompted + : openLinksInAppWasPersisted if ( parsed.settings?.agentYoloDefaultsMigrated !== true || hasUnsupportedTuiAgentArgs('opencode', parsed.settings?.agentDefaultArgs?.opencode) || @@ -2245,6 +2253,12 @@ export class Store { if (!autoRenameBranchFromWorkDefaultedOn) { this.loadNeedsSave = true } + if ( + parsed.settings?.openLinksInAppPreferencePrompted !== + migratedOpenLinksInAppPreferencePrompted + ) { + this.loadNeedsSave = true + } const normalizedOnboarding = normalizeLoadedOnboardingState( parsed.onboarding, defaults.onboarding @@ -2322,6 +2336,7 @@ export class Store { openInApplications: normalizeOpenInApplications(parsed.settings?.openInApplications, { seedDefaults: true }), + openLinksInAppPreferencePrompted: migratedOpenLinksInAppPreferencePrompted, notifications: normalizeNotificationSettings(parsed.settings?.notifications), sourceControlAi: migratedSourceControlAi, // Why: new builds read sourceControlAi, but rollback builds still diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index d4a2d1320a4..f797cf4fc64 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -61,6 +61,7 @@ import { WorkspacePortScanner } from './components/ports/WorkspacePortScanner' import { CrashReportDialog } from './components/crash-report/CrashReportDialog' import { RecoverableRenderErrorBoundary } from './components/error-boundaries/RecoverableRenderErrorBoundary' import { ConfirmationDialogProvider } from './components/confirmation-dialog' +import { LinkRoutingPreferenceDialogProvider } from './components/link-routing-preference-dialog' import RecentTabSwitcher from './components/tab-bar/RecentTabSwitcher' import { useGitStatusPolling } from './components/right-sidebar/useGitStatusPolling' import { useEditorExternalWatch } from './hooks/useEditorExternalWatch' @@ -1773,7 +1774,8 @@ function App(): React.JSX.Element { > - + + {/* Why: leaf-mounted retention sync keeps agent-status retention subscriptions from re-rendering the App tree. */} @@ -2287,6 +2289,7 @@ function App(): React.JSX.Element { > + diff --git a/src/renderer/src/components/link-routing-preference-dialog.tsx b/src/renderer/src/components/link-routing-preference-dialog.tsx new file mode 100644 index 00000000000..cdf07d62962 --- /dev/null +++ b/src/renderer/src/components/link-routing-preference-dialog.tsx @@ -0,0 +1,260 @@ +import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react' +import { ExternalLink, Settings } from 'lucide-react' + +import { Button } from '@/components/ui/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle +} from '@/components/ui/dialog' +import { Badge } from '@/components/ui/badge' +import { ShortcutKeyCombo } from '@/components/ShortcutKeyCombo' +import { useAppStore } from '@/store' +import { translate } from '@/i18n/i18n' + +type LinkRoutingPreferenceDialogOptions = { + url?: string + preview?: boolean + openLinksInAppDefault?: boolean +} + +type LinkRoutingPreferenceDialogRequest = { + id: number + options: LinkRoutingPreferenceDialogOptions + resolve: (openInOrca: boolean) => void +} + +type LinkRoutingPreferenceDialogContextValue = ( + options?: LinkRoutingPreferenceDialogOptions +) => Promise + +const PREVIEW_STORAGE_KEY = 'orca.previewLinkRoutingPreferenceDialog' +const PREVIEW_DEFAULT_STORAGE_KEY = `${PREVIEW_STORAGE_KEY}.default` +const LinkRoutingPreferenceDialogContext = + createContext(null) + +function displayHostForUrl(url: string | undefined): string | null { + if (!url) { + return null + } + try { + return new URL(url).host + } catch { + return null + } +} + +export function LinkRoutingPreferenceDialogProvider({ + children +}: { + children: React.ReactNode +}): React.JSX.Element { + const nextIdRef = useRef(0) + const [queue, setQueue] = useState([]) + const activeRequest = queue[0] ?? null + const activeRequestRef = useRef(activeRequest) + const setContextualToursBlockingSurfaceVisible = useAppStore( + (s) => s.setContextualToursBlockingSurfaceVisible + ) + const lastDisplayedRequestRef = useRef(activeRequest) + activeRequestRef.current = activeRequest + if (activeRequest) { + lastDisplayedRequestRef.current = activeRequest + } + // Why: Radix keeps dialog content mounted while closing; keep copy stable during exit animation. + const displayedRequest = activeRequest ?? lastDisplayedRequestRef.current + const displayHost = displayHostForUrl(displayedRequest?.options.url) + const openLinksInAppDefault = displayedRequest?.options.openLinksInAppDefault === true + const isMac = navigator.userAgent.includes('Mac') + const systemBrowserShortcutKeys = isMac ? ['⇧', '⌘'] : ['Shift', 'Ctrl'] + + useEffect(() => { + setContextualToursBlockingSurfaceVisible(activeRequest !== null) + return () => setContextualToursBlockingSurfaceVisible(false) + }, [activeRequest, setContextualToursBlockingSurfaceVisible]) + + const requestPreference = useCallback((options = {}) => { + return new Promise((resolve) => { + const request: LinkRoutingPreferenceDialogRequest = { + id: nextIdRef.current, + options, + resolve + } + nextIdRef.current += 1 + setQueue((currentQueue) => [...currentQueue, request]) + }) + }, []) + + useEffect(() => { + if (!import.meta.env.DEV || typeof window === 'undefined') { + return + } + if (window.sessionStorage.getItem(PREVIEW_STORAGE_KEY) !== '1') { + return + } + const previewDefault = window.sessionStorage.getItem(PREVIEW_DEFAULT_STORAGE_KEY) + window.sessionStorage.removeItem(PREVIEW_STORAGE_KEY) + window.sessionStorage.removeItem(PREVIEW_DEFAULT_STORAGE_KEY) + void requestPreference({ + openLinksInAppDefault: previewDefault === 'orca', + preview: true, + url: 'https://github.com/stablyai/orca/pull/1234' + }) + }, [requestPreference]) + + const settleActiveRequest = useCallback((openInOrca: boolean) => { + const request = activeRequestRef.current + if (!request) { + return + } + request.resolve(openInOrca) + setQueue((currentQueue) => { + if (currentQueue[0]?.id === request.id) { + return currentQueue.slice(1) + } + return currentQueue.filter((queuedRequest) => queuedRequest.id !== request.id) + }) + }, []) + + return ( + + {children} + !open && settleActiveRequest(false)} + > + +
+ +
+ + {translate( + 'auto.components.link.routing.preference.dialog.badge', + 'Terminal link' + )} + + {displayedRequest?.options.preview ? ( + + {translate('auto.components.link.routing.preference.dialog.preview', 'Preview')} + + ) : null} +
+
+ + {openLinksInAppDefault + ? translate( + 'auto.components.link.routing.preference.dialog.keep.title', + "Keep terminal links in Orca's browser?" + ) + : translate( + 'auto.components.link.routing.preference.dialog.title', + "Open terminal links in Orca's browser?" + )} + + + {openLinksInAppDefault + ? translate( + 'auto.components.link.routing.preference.dialog.keep.description', + 'Or use your system browser by default.' + ) + : translate( + 'auto.components.link.routing.preference.dialog.description', + "Use Orca's browser for terminal links, or keep your system browser." + )} + +
+
+
+ +
+ {displayHost ? ( +
+ + {translate('auto.components.link.routing.preference.dialog.link.label', 'Link')} + + + {displayHost} + +
+ ) : null} + +
+ +
+

+ {translate( + 'auto.components.link.routing.preference.dialog.orca.note', + 'Orca can use imported cookies for logged-in sites.' + )} +

+

+ {translate( + 'auto.components.link.routing.preference.dialog.settings.note', + 'Change this later in Settings → Browser.' + )} +

+

+ + {translate( + 'auto.components.link.routing.preference.dialog.shortcut.note.prefix', + 'When links open in Orca,' + )} + + + + {translate( + 'auto.components.link.routing.preference.dialog.shortcut.note.suffix', + 'click opens system browser once.' + )} + +

+
+
+
+ + + + + +
+
+
+ ) +} + +export function useLinkRoutingPreferenceDialog(): LinkRoutingPreferenceDialogContextValue { + const requestPreference = useContext(LinkRoutingPreferenceDialogContext) + if (!requestPreference) { + throw new Error( + 'useLinkRoutingPreferenceDialog must be used inside LinkRoutingPreferenceDialogProvider' + ) + } + return requestPreference +} diff --git a/src/renderer/src/components/settings/BrowserLinkRoutingSetting.tsx b/src/renderer/src/components/settings/BrowserLinkRoutingSetting.tsx index bc5fa79a76a..6b0885a86ef 100644 --- a/src/renderer/src/components/settings/BrowserLinkRoutingSetting.tsx +++ b/src/renderer/src/components/settings/BrowserLinkRoutingSetting.tsx @@ -42,7 +42,12 @@ export function BrowserLinkRoutingSetting({

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 literal 0 HcmV?d00001 diff --git a/docs/readme/README.es.md b/docs/readme/README.es.md index fedafc84f5f..414400b9569 100644 --- a/docs/readme/README.es.md +++ b/docs/readme/README.es.md @@ -3,137 +3,231 @@