Skip to content

upstream 取り込み PR #9: 計画後前進分 12 commits + bun.lock regen#411

Merged
MocA-Love merged 16 commits intomainfrom
upstream/batch-9-post-plan
Apr 24, 2026
Merged

upstream 取り込み PR #9: 計画後前進分 12 commits + bun.lock regen#411
MocA-Love merged 16 commits intomainfrom
upstream/batch-9-post-plan

Conversation

@MocA-Love
Copy link
Copy Markdown
Owner

@MocA-Love MocA-Love commented Apr 24, 2026

Summary

計画書以降に upstream が前進した 12 commits を取り込み。目標は GitHub UI の "behind" 数値減少。

計画完遂 (PR #2c #408 まで) 後、upstream で新規に追加された non-cherry-picked commits を Codex 事前調査で特定:

# SHA Title
1 43131f6fd fix(desktop): show v1 uncommitted-changes banner instead of second delete prompt (superset-sh#3688)
2 a8b29fa25 fix(desktop): fail closed when adopted host-service has no version (superset-sh#3679)
3 be3b5fcc5 fix(desktop): keep v2 terminals and browsers stable across workspace switches (superset-sh#3687)
4 083a518fe fix(host-service): place v2 worktrees under ~/.superset/worktrees/ (superset-sh#3669)
5 9038e32ea feat(marketing): simplify product menu and show yearly discount (superset-sh#3691)
6 e306eb20b feat(desktop): v2 AI workspace rename generates title + branch together (superset-sh#3692)
7 4163c8d08 feat(desktop): show PR state as sidebar workspace icon (superset-sh#3694)
8 678832461 fix(desktop): use Alerter for automation detail delete confirm (superset-sh#3695)
9 b3b70cd5c fix(desktop): honor agent selection in new-workspace modal (superset-sh#3699)
10 2bd46ecc0 fix(host-service): count untracked file lines in getStatus (superset-sh#3701)
11 5697c8a2d fix(desktop): reap stale notify.sh paths from in-repo dev worktrees (superset-sh#3698)
12 847bc2c38 fix(desktop): adopt Ghostty keyboard model in v2 terminal (superset-sh#3700)

支援 commits

  • 556dfad75 style: lint 自動修正 (agent-wrappers.test.ts フォーマット)
  • b91b1c418 fix(deps): bun.lock 完全再生成 (@mastra/core 1.26.0-alpha.3 resolution 脱落を修復)

Conflict 解消方針(Codex 事前分析に基づく)

#3688 (useDestroyDialogState.ts):

  • fork の UX(toast なし・楽観削除・deleteBranch 初期 false)を維持
  • upstream の electronTrpc.workspaces.canDelete + preflight warning + force retry を統合

#3687 (terminal-runtime-registry.ts, layout.tsx):

  • fork の terminalRendererDebug + backoff リセットロジックを維持
  • upstream の connect/reconnect 分割を統合
  • GlobalBrowserLifecycleMainWindowEffects と共存させて追加

#3692 (workspace-creation.ts, v2-workspace.ts):

  • applyAiWorkspaceRename + AI rename ロジック + workspaceNameWasAutoGenerated フラグ追加
  • fork の PR checkout / baseBranchSource 維持
  • updateNameFromHost 手続きを v2-workspace に追加

#3699 (PromptGroup.tsx):

  • useSubmitWorkspace(projectId, selectedAgent) 変更
  • fork の DevicePicker 二重表示 / branch preview / agent picker UX 維持

判断委譲事項は全て「採用」方針

Fork 固有機能ヘルスチェック

baseline 比較で 全項目一致:

  • 19 tRPC プロシージャ (github-extended.ts 配下)
  • ansi_up / @vscode/ripgrep / @xyflow/react 依存
  • TERMINAL_OPTIONS (5) / SUPERSET_WORKSPACE_NAME (24) / moonshot-ai.kimi-code (5)
  • v1MigrationState, TiptapPromptEditor
  • desktop version 1.5.10, dmg.size="4g"
  • listBranches sortOrder/pinDefault
  • terminalRendererDebug instrumentation / MainWindowEffects singleton / 楽観削除 UX / PR checkout+baseBranchSource+fork note+GitHub link command

Test plan

  • bun install 正常完了
  • bun run typecheck グリーン (27/27)
  • bun run lint グリーン
  • fork 固有機能 baseline 比較 regression ゼロ
  • desktop dev 起動確認
  • v1 uncommitted changes banner 動作
  • v2 worktree 作成が ~/.superset/worktrees// 下に
  • v2 AI workspace rename (title + branch 同時) 動作
  • PR state sidebar icon 表示
  • automation delete confirm (Alerter) 動作
  • Ghostty keyboard model の v2 terminal 動作

  • 本 PR は計画書作成後 (2026-04-24 以降) に upstream が前進した 12 commits を取り込むための継続タスク
  • メモリに標準フロー (project_upstream_batch_workflow.md) が記録済み、今後も同じパターンで取り込み継続可能

Summary by CodeRabbit

リリースノート

  • 新機能

    • ワークスペース切り替え時のターミナル・ブラウザペーン状態を保持するように改善
    • ワークスペース削除時に変更内容と未発行コミットの警告を追加
    • サイドバーでプルリクエスト情報を直接表示・操作可能に
    • AIによるワークスペース自動命名機能を追加
  • バグ修正

    • ホットキーバブリング時のターミナルキー入力を正確に処理
    • ワークスペース切り替え時の誤ったペーン破棄を防止
  • 改善

    • ファイルリネーム・コピー操作の検出精度を向上
    • 削除確認ダイアログのUI/UXを改善
    • Gitの変更ファイル追跡を強化

saddlepaddle and others added 14 commits April 24, 2026 15:17
…lete prompt (superset-sh#3688)

The v2 workspace delete flow showed two warnings: a generic confirm pane,
then a "Uncommitted changes in worktree" pane after destroy hit a conflict.
Surface the v1 yellow banner inline on the confirm pane via the existing
canDelete preflight, force when warnings are shown, and silently retry on
the rare race so the user only ever sees one prompt.
…uperset-sh#3679)

* fix(desktop): fail closed when adopted host-service has no version

The version gate only killed the host when fetchHostVersion returned a
string less than MIN_HOST_SERVICE_VERSION. If the host lacked the
host.info route entirely (older releases), the helper returned null and
we silently adopted it, producing 404s on project.findByPath and other
new routes.

Flip the guard to fail-closed: any host that can't prove it meets the
minimum version is killed and the manifest removed so the next spawn
brings up a compatible service.

* fix(desktop): compare host-service versions numerically

String comparison made "0.10.0" < "0.2.0" evaluate to true, which
would have killed any future double-digit minor version. Split the
version on dots and compare segments as integers.

Also split the adopt-killing log into two branches so the null case
reads "version unknown" instead of "version unknown < 0.2.0".

* refactor(desktop): use semver library for host-service version check

Replace the hand-rolled split-and-compare with `semver.satisfies`,
which already handles malformed input by returning false. The
`semver` package is already a dependency.

* refactor(desktop): inline host-service version check
…switches (superset-sh#3687)

* fix(desktop): keep v2 terminals stable across workspace switches

Problem: every v2-workspace switch yanked the xterm wrapper out of the DOM and
re-opened a WebSocket, producing a visible "switching and reattaching" flash
instead of VSCode-style hide/show. The v2-workspace layout's WorkspaceTrpcProvider
has a load-bearing key that unmounts the whole subtree on every switch, so the
React component for TerminalPane goes away — but the xterm instance, wrapper
div, and transport in terminalRuntimeRegistry should survive, and previously the
DOM node didn't.

Three entangled issues were fixed together:

1. Parking container. On detach, the wrapper used to be wrapper.remove()'d,
   taking the rendered canvases with it. Now it's appended to a hidden
   body-level div (#v2-terminal-parking) so xterm stays attached to the
   document. Re-mount in the new workspace is a DOM move back from parking
   to the live container — mirrors the existing v1 persistent-webview pattern
   at usePersistentWebview.ts:14-27 and VSCode's TerminalInstance setVisible
   model.

2. DOM vs transport split. The previous single registry.attach() both
   mounted DOM and opened the WebSocket. That forced TerminalPane to gate
   attach on ensureSession, which made warm returns wait on a tRPC round-trip
   (visible delay) and made cold mounts race — opening a WS before the
   server session existed produced "Session not found. Call terminal.
   ensureSession first.". Split into mount() (synchronous, DOM-only, safe
   on every mount) and connect() (called only after ensureSession resolves).
   Matches VSCode's TerminalInstance.attachToElement + _createProcess and
   Tabby's XTermFrontend.attach + setSession.

3. Effect dep narrowing. TerminalPane's attach effect used to depend on
   [terminalId, websocketUrl, initialThemeType, workspaceId]; prop churn
   during the provider key remount (workspaceId flipping while pane data
   caught up) forced repeated detach/attach cycles. Narrowed to [terminalId]
   with the others read through refs. websocketUrl changes now go through
   registry.reconnect(), which is hard-gated on transport already being
   live so it never opens a WS before ensureSession has resolved.

Log walkthrough on a warm workspace switch:
  pane:effect-cleanup → registry:detach → runtime:detach (wrapper parks)
  pane:effect-mount   → registry:mount  → runtime:attach wasParked:true
  registry:reconnect-skip same-url
  pane:ensureSession-ok → registry:connect → transport:connect-skip idempotent

Instrumentation stays in this commit for the rollout check; a follow-up
strips the termLog calls once this is confirmed in production.

* chore(desktop): strip v2 terminal lifecycle instrumentation

Removes the temporary termLog tracing added to diagnose the workspace-switch
reattach bug. The behavior fix (parking + mount/connect split) landed in the
previous commit and is confirmed stable in logs; no functional change here.

* fix(desktop): keep v2 browser panes stable across workspace switches

Browser webviews were being destroyed on workspace switch, discarding
guest-page state (URL, scroll, history) even though the registry was
designed to persist them.

Root cause: usePaneRegistry wired browser destruction through the Panes
library's onRemoved hook:

    onRemoved: (pane) => browserRuntimeRegistry.destroy(pane.id)

Under ideal conditions the v2 layout's `key={`${workspace.id}:${hostUrl}`}`
remounts the WorkspaceTrpcProvider subtree on every switch, so each
workspace gets its own Workspace component whose previous-panes diff
never observes a cross-workspace "removal". But the remount isn't
always prompt — layout.tsx's useLiveQuery can return stale WS-A data
for a tick while page.tsx's already flipped to WS-B. During that tick
the existing WorkspaceContent stays mounted, useV2WorkspacePaneLayout
calls store.replaceState(WS-B panes) on the same store instance, and
the Panes diff correctly sees "WS-A's browser is gone" → fires
onRemoved → destroys the webview. By the time the user returns,
attach() runs the cold createEntry path and the guest page is lost.

Terminals don't hit this because destruction goes through
useGlobalTerminalLifecycle, a global sweep against every workspace's
persisted paneLayout — cross-workspace "removal" isn't a removal from
the sweep's perspective.

Fix: mirror the terminal pattern exactly.
- Added useGlobalBrowserLifecycle under
  _authenticated/components/GlobalBrowserLifecycle/, following the
  same shape as useGlobalTerminalLifecycle (extract pane.ids from all
  workspace layouts, diff against previous, 500 ms grace delay
  destroy to tolerate cross-workspace pane moves).
- Mounted <GlobalBrowserLifecycle /> alongside <GlobalTerminalLifecycle />
  in _authenticated/layout.tsx.
- Removed the onRemoved wiring from usePaneRegistry.tsx — the sweep
  replaces it.

Verified with instrumentation that a workspace switch on a live browser
pane no longer reaches browserRuntimeRegistry.destroy, and that closing
a browser pane still destroys after the 500 ms grace.

Followup to PR superset-sh#3687 (terminal-side fix landed earlier on this branch).
Plan doc at apps/desktop/plans/20260423-1226-v2-pane-persistence-across-workspace-switch.md
captures the full root-cause analysis for both runtimes as a reference
for future pane persistence work.

Also removes a now-redundant biome-ignore comment in TerminalPane
that biome flagged as having no effect after the dep narrowing in the
earlier commit.

* fix(desktop): mark parked terminal container inert; clarify reconnect doc

Two follow-ups from PR superset-sh#3687 review:

- `inert` on #v2-terminal-parking. Parked terminals' internal <textarea>
  still had `tabindex=0`, so a keyboard user tabbing through the app
  could land in an off-screen terminal and have keystrokes silently go
  to the wrong pane. `inert` removes the subtree from the tab order
  and the accessibility tree, and moves focus out automatically, which
  also handles the "blur before park" concern for free. aria-hidden
  added for belt-and-suspenders on older engines.

- `reconnect` JSDoc. Clarify that the guard only skips `"disconnected"`
  (never-opened transport, caller should use ensureSession + connect
  path). `"connecting"`, `"open"`, `"closed"` are all intentionally
  allowed through — `connect()` aborts any in-flight or stale socket
  before opening the new one.

* chore(desktop): deslop v2 terminal pane effect and detach comment

- Collapse TerminalPane's .then()/.catch() into .catch()/.finally():
  one connect() call site instead of two identical guarded calls. Same
  semantics (connect after ensureSession settles, even on rejection),
  cancellation check consolidated.
- Merge the three connect-related comments into one block explaining
  "connect regardless of outcome" + idempotency. Drop the redundant
  "// DOM first" inline that the block comment above already covered.
- Trim detachFromContainer's 5-line comment down to 2 lines pointing
  at getParkingContainer — the helper's docstring already explains
  the parking rationale in full.

* perf(desktop): memoize v2 TerminalPane useSyncExternalStore args

The module-level `subscribeToState(terminalId)` built a fresh closure on
every render, so `useSyncExternalStore` saw a new subscribe function each
time and re-subscribed to `terminalRuntimeRegistry.onStateChange` on every
TerminalPane render — this is the anti-pattern React's useSyncExternalStore
docs explicitly warn about ("If you don't memoize the subscribe function,
React will resubscribe to your store every time your component re-renders").

Inline the helpers and wrap both subscribe + getSnapshot in useCallback
keyed on [terminalId]. Re-subscribe now only fires when the pane's
terminalId actually changes (cold create / destroy), not on every
keystroke-triggered re-render.
…ojectId> (superset-sh#3669)

* fix(host-service): place worktrees under ~/.superset/worktrees/<projectId>

Mirror v1 desktop's convention of keeping worktrees outside the primary
checkout tree, and match v2's existing ~/.superset/repos/<projectId>
layout for symmetry. Detection switches to the local `workspaces` table
(plus the new root for orphan adoption), so legacy worktrees at
<repo>/.worktrees/ keep working without a migration.

* chore(host-service): trim worktree-path comments

* lint
…rset-sh#3691)

Remove the feature card from the Product nav dropdown; show $20 struck
through next to $15 on the Pro pricing card when Yearly is selected to
make the discount explicit. Pin the price row height so toggling between
Monthly and Yearly doesn't cause a vertical shift.
…er (superset-sh#3692)

* feat(desktop): v2 AI workspace rename generates title + branch together

Post-create rename now makes a single structured-output call returning
{ title, branchName }, applies the title to v2_workspaces.name, and also
renames the git branch (git branch -m in the worktree + updates the
host-local workspaces.branch + cloud v2_workspaces.branch). Replaces the
naive 20-char slice that was producing mid-word truncations like
"New v2 workspaces na". Branch-prefix support for v2 tracked in SUPER-478.

* fix(desktop): roll back git rename if cloud write fails; coerce oversized AI names

Addresses two review findings on v2 AI rename:

- Partial-commit: previously `git branch -m` + host-local `workspaces.branch` were
  written before the cloud mutate, so a cloud failure left git + local in the new
  state and the cloud (and the web app) stuck on the old branch. Now we git-rename
  first, push name+branch to cloud, and only update host-local on cloud success;
  on cloud throw we git-rename back to the old name.
- `.max()` on the zod schema rejected any overlong model output, silently no-op'ing
  the whole rename. Replaced with `.transform()` pipes that trim the title and
  sanitize the branch so overshoots are coerced instead of dropped.

* fix(desktop): loosen AI workspace name bounds, fall back branch to title slug

- Title cap bumped 40 → 150 and drops `.min(1)` — basically trust the small
  model with `.describe()` guidance.
- Branch falls back to a slug of the title when the model's branchName
  sanitizes to nothing (e.g. all emoji), so branch rename stops being a
  failure point.

* chore(desktop): drop title-slug branch fallback in AI workspace names

Per-field gating in the caller already skips the branch rename when branchName
is empty; no need to salvage. If the model's output is bad, skip the rename.

* refactor(desktop): drop structured-generation wrapper, call Agent.generate directly

The wrapper in packages/chat was pure indirection — chat already has @mastra/core
as a direct dep. Removed it, added @mastra/core to host-service, and call
agent.generate({ structuredOutput }) directly from ai-workspace-names. Also tightened
getSmallModel's return type to MastraModelConfig | null so callers don't need casts.

* refactor(desktop): hoist AI rename orchestration out of create handler

Pulls the ~80-line post-create block out of workspaceCreation.create into
applyAiWorkspaceRename in ai-workspace-names.ts — same file as the generator
so naming + applying live together. listBranchNames moves to its own util so
both callers can share it.

Call site in the create handler is now a three-line fire-and-forget.
)

* feat(desktop): show PR state as sidebar workspace icon

Replace the host-type icon in the v2 dashboard sidebar with a PR state
icon (open/merged/closed/draft), colored by state, when a workspace has
an associated pull request. Clicking the icon opens the PR on GitHub.
Drop the now-redundant bottom-right PR badge, and make the diff stats
only colorize when the workspace is active.

* fix(desktop): address PR review comments on sidebar PR icon

- Stop keydown propagation on the PR icon button so keyboard activation
  (Enter/Space) does not also trigger the parent row's workspace click.
- Use hover:bg-foreground/10 on the PR icon button so hover is visible
  even when the row is active (which already has bg-muted).
- Humanize PR state in the icon tooltip (Open/Merged/Closed/Draft).
- Drop the stale rounded class from the diff-stats container now that
  its background is gone.
- Use LuGitPullRequestClosed instead of LuCircleDot for closed PRs.

* chore: lint
…set-sh#3695)

* fix(desktop): use Alerter for automation detail delete confirm

Closes SUPER-436. Replaces native window.confirm() on the automation
detail page with the shared alert() helper so the confirm matches the
rest of the app's UI.

* fix(desktop): surface delete errors via toast.promise

Addresses review feedback on superset-sh#3695: the Alerter would silently swallow
a failed delete. Wrap mutateAsync in toast.promise so the user sees
loading, success, and error states.
…sh#3699)

* fix(desktop): honor agent selection in new-workspace modal

The modal's agent picker was cosmetic — `selectedAgent` was persisted to
localStorage for the UI but never written to the pending row, so the
pending page's `buildForkAgentLaunch` always called `getFallbackAgentId`
(which hard-prefers claude). Selecting codex, cursor, etc. silently
launched claude instead.

Thread the selected agent through the pending row into the launch build;
"none" now genuinely skips the agent launch (no silent claude
substitution).

* test(desktop): update buildForkAgentLaunch tests for explicit agent selection

Tests were written against the old fallback-to-claude behavior — they
passed `agentId: null` and expected Claude to launch. Under the new
contract, null is treated as "no selection → no launch".

Pass explicit `agentId` in each case. Add coverage for the new null /
"none" paths.
…sh#3701)

* fix(host-service): count untracked file lines in getStatus (SUPER-472)

Untracked files were pushed into `unstaged` with `additions: 0`, so the
sidebar LOC delta missed every newly-added file in the working tree
(`useDiffStats` sums against-base + staged + unstaged).

* feat(host-service): detect renames/copies on untracked files

Run git's real rename/copy detection over the working tree by copying
.git/index to a temp file, marking untracked files intent-to-add against
that copy, and running `git diff -M -C`. Real index is never mutated.

Merges matched deleted+untracked pairs into single rename entries with
correct additions/deletions, and renders `old → new` in the v2 sidebar
file row.

Catches:
- mv tracked → untracked (rename)
- cp tracked → untracked (copy, sourced from the tracked blob)
- mv with edits (rename with non-zero stats)

Falls back silently to the unmerged deleted+untracked listing on any
error (missing index, copy failure, intent-to-add reject).

* fix(host-service): address review on getStatus rename/LOC

- Cap parallel file I/O at 64 workers in countUntrackedFileLines so
  workspaces with thousands of untracked files don't EMFILE
- Sniff first 8KB for NUL bytes before counting lines so binary files
  under the 1MB cap (PNGs, lockfiles, compiled artifacts) don't get a
  meaningless utf-8 line count
- Log temp-dir cleanup failures instead of silently swallowing
- Add -M -C to staged numstat and propagate file.from as oldPath so
  staged renames collapse to a single 0/0 entry (matches the working-
  tree rename detection added in the previous commit)
…uperset-sh#3698)

Dev setup writes SUPERSET_HOME_DIR=<worktree>/superset-dev-data (no leading
dot), which didn't match the managed-hook regex, so stale notify.sh entries
from deleted dev worktrees stuck around in ~/.codex/hooks.json. Widen the
pattern to also recognize `superset-dev-data/` and add a regression test.
…h#3700)

* fix(desktop): adopt Ghostty keyboard model in v2 terminal

v2's terminal runtime only filtered app hotkeys. With kitty keyboard
protocol enabled (needed for Shift+Enter disambiguation in claude-code,
modifier reporting in neovim/helix), every Mac Cmd chord xterm saw got
CSI-u encoded and leaked into TUIs as a literal char — and line-edit
niceties like Cmd+Left/Right/Backspace and Option+Left/Right that v1
handles never worked at all.

Mirror Ghostty's approach (src/input/key_encode.zig:534-545: "on macOS,
command+keys do not encode text"): bubble every Mac Cmd chord out to
the host before xterm's kitty encoder runs, then port v1's line-edit
chord translators so shell navigation works the same in both renderers.

Changes:
- Broaden shouldBubbleClipboardShortcut's Mac branch to bubble all Cmd
  chords (not just Cmd+C/V with selection gating). v1 benefits too.
- Port v1's line-edit translators into v2's custom key handler:
  Cmd+Left/Right/Backspace, Option+Left/Right, Windows Ctrl+Left/Right.
  Duplicates v1 for now; a follow-up can share the handler properly.
- Wire shouldSelectAllShortcut into v2 so Cmd+A selects terminal buffer.
- Use xterm.input(data, true) to inject translated sequences into the
  PTY (fires onData, forwarded by terminal-ws-transport).
- Restructure tests around the new Mac rule.

* fix(desktop): preventDefault on bubbled clipboard chords

Matches VS Code (terminalInstance.ts:1116-1175) and Tabby (xtermFrontend.ts:199-214):
call `event.preventDefault()` before returning false from the custom key
handler so the browser's default action can't double-fire alongside the
Electron `role: 'paste'`/`'copy'` accelerator registered in
src/main/lib/menu.ts. Paste still flows — webContents.paste() dispatches
the paste event on the focused textarea independently of the DOM keydown
default, and xterm's paste listener picks it up.

Keydown-only — preventDefault on keyup is a no-op and would suppress
kitty protocol release events we still want xterm to handle.

* refactor(desktop): collapse translateLineEditChord to a grouped form

Seven near-identical 8-line blocks collapsed to a platform+modifier
grouped structure with a tiny onlyMod helper. Same behavior, ~55 fewer
lines, adding a new chord is one line instead of ten.

* revert: don't preventDefault on bubbled clipboard chords

Broke Cmd+C/V. On Electron macOS, the browser's keydown → paste-command
pipeline is what dispatches the paste event on xterm's textarea, and
preventDefault blocks that pipeline. VS Code and Tabby can preventDefault
because they implement paste themselves (command system / ClipboardAddon);
we rely on xterm's built-in paste listener, so the default must run.

Revert of 36cf276.
… resolution

upstream superset-sh#3692 (aceb0fd v2 AI workspace rename) が
packages/host-service の @mastra/core を 1.26.0-alpha.3 に更新したが、
cherry-pick 時の lockfile conflict resolution で該当 resolution entry が
欠落し、electron-builder の production dependency traversal が
"Production dependency @mastra/core not found for package @superset/host-service"
で fail していた。

`rm bun.lock && bun install` で完全再生成して整合性担保。
fork 固有依存 (ansi_up, @vscode/ripgrep, @xyflow/react) は全て保持。
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

ターミナルペインのマウント/接続ロジックを分離し、ブラウザペインのライフサイクルを全体的に管理するシステムへ移行。ワークスペース作成時のAI駆動の命名機能を追加。Gitの差分検出ロジックをリネーム/コピー検出に対応。複数の依存関係を更新し、ワークスペース削除フローとUI要素を改善。

Changes

Cohort / File(s) Summary
依存関係更新
apps/desktop/package.json, packages/chat/package.json, packages/host-service/package.json
@mastra/core1.25.0 から 1.26.0-alpha.3 にアップグレード。ホストサービスに新しい依存関係を追加。
ターミナルランタイムリファクタリング
apps/desktop/src/renderer/lib/terminal/terminal-runtime-registry.ts, apps/desktop/src/renderer/lib/terminal/terminal-runtime.ts, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/TerminalPane.tsx, apps/desktop/plans/20260423-1226-v2-pane-persistence-across-workspace-switch.md
ターミナルの attach() メソッドを mount() に改名し、WebSocket処理を分離。新しく connect() および reconnect() メソッドを追加してトランスポートライフサイクルを管理。キーボードハンドラーを追加してホットキー処理を改善。DOMマウント状態の保持とセッション準備に基づく接続処理を実装。
ブラウザペングローバルライフサイクル
apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/GlobalBrowserLifecycle.tsx, apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/hooks/useGlobalBrowserLifecycle/useGlobalBrowserLifecycle.ts, apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/hooks/useGlobalBrowserLifecycle/index.ts, apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/index.ts, apps/desktop/src/renderer/routes/_authenticated/layout.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx
ワークスペース切り替え時のブラウザペインの破棄を防ぐため、グローバルライフサイクル管理を実装。500ms の猶予期間を設けて遅延破棄を行い、クロスワークスペース遷移中の予期しない削除を回避。
ワークスペース削除フロー
apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/$automationId/page.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/DashboardSidebarDeleteDialog.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/DestroyConfirmPane/DestroyConfirmPane.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/ConflictPane/ConflictPane.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/ConflictPane/index.ts, apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/UnknownErrorPane/UnknownErrorPane.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/UnknownErrorPane/index.ts, apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/hooks/useDestroyDialogState/useDestroyDialogState.ts
削除確認ダイアログをリファクタリング。ダーティ状態チェックを追加し、ダーティワークツリー検出時に警告バナーを表示。ConflictPane および UnknownErrorPane コンポーネントを削除し、エラー処理を簡略化。トースト通知により削除フィードバックを提供。
ワークスペース作成とAI命名
packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts, packages/host-service/src/trpc/router/workspace-creation/utils/list-branch-names.ts, packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts, apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx, apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useSubmitWorkspace/useSubmitWorkspace.ts, apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.ts, apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.test.ts, apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildIntentPayload.test.ts, apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/dispatchForkLaunch.ts, apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts
ユーザープロンプトからAIで自動生成されたワークスペース名とブランチ名を実装。Mastraエージェントを使用して命名を生成。ブランチ競合を検出し、クラウド更新時のロールバック機構を搭載。エージェント選択をペンディングワークスペース行に保持。
Git差分処理の改善
packages/host-service/src/trpc/router/git/git.ts, packages/host-service/src/trpc/router/git/utils/git-helpers.ts
--numstat -z -M -C フラグでリネーム/コピー検出を実装。ステージ済みおよび未ステージファイルの oldPath を設定。未追跡ファイルの行数をカウント。リネーム検出ロジックで一時インデックスを使用。
ホストサービス設定
apps/desktop/src/main/lib/host-service-coordinator.ts, apps/desktop/src/main/lib/agent-setup/agent-wrappers-common.ts, apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts
バージョン互換性チェックを semver パッケージに変更。Superset管理フック検出の正規表現を拡張し、/superset-dev-data/ パスに対応。Codex フック調整のテスト範囲を拡大。
TRPC ルーターの拡張
packages/trpc/src/router/v2-workspace/v2-workspace.ts
ワークスペース名とブランチをホストから更新する新しい updateNameFromHost ミューテーションを追加。オプティミスティック競合検出を実装。
UIコンポーネント更新
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceIcon/DashboardSidebarWorkspaceIcon.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDiffStats/DashboardSidebarWorkspaceDiffStats.tsx, apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/components/FileRow/FileRow.tsx
プルリクエスト状態アイコンとツールチップを追加。サイドバーアイコンをクリック可能にしてURLを開く機能を追加。ファイル行にリネーム/コピー表示用の oldPath を表示。
クリップボードと入力処理
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/clipboardShortcuts.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/clipboardShortcuts.test.ts
macOS のClipboard バブリング動作を Cmd キー全般に拡張。Windows/Linux の特定チョードのバブリング動作を調整。テストを高レベルルール検証に改訂。
マーケティングコンポーネント
apps/marketing/src/app/components/Header/components/DesktopNav/DesktopNav.tsx, apps/marketing/src/app/pricing/components/PricingTiers/components/PricingCard/PricingCard.tsx
ナビゲーションドロップダウンからカスタム機能カードを削除。価格設定カードに打消し価格表示を追加。
パッケージタイプ更新
packages/chat/src/server/shared/small-model/get-small-model.ts
getSmallModel の戻り値を型安全にして MastraModelConfig | null を返すように。

Sequence Diagram(s)

sequenceDiagram
    participant React
    participant TerminalPane
    participant TerminalRegistry
    participant Terminal
    participant WebSocket
    
    React->>TerminalPane: render (mount phase)
    TerminalPane->>TerminalRegistry: mount(terminalId, container, appearance)
    TerminalRegistry->>Terminal: create & attach to DOM
    TerminalRegistry-->>TerminalPane: mounted
    
    TerminalPane->>TerminalPane: ensure server session
    
    TerminalPane->>TerminalRegistry: connect(terminalId, wsUrl)
    TerminalRegistry->>WebSocket: open/reuse connection
    TerminalRegistry-->>TerminalPane: connected
    
    Note over TerminalPane,WebSocket: WebSocket URL changes
    TerminalPane->>TerminalRegistry: reconnect(terminalId, newWsUrl)
    TerminalRegistry->>WebSocket: swap endpoint
    TerminalRegistry-->>TerminalPane: reconnected
    
    React->>React: unmount workspace
    Terminal-->>Terminal: stay in parking container
Loading
sequenceDiagram
    participant CollectionsProvider
    participant GlobalBrowserLifecycle
    participant BrowserRegistry
    
    CollectionsProvider->>GlobalBrowserLifecycle: useLiveQuery all v2WorkspaceLocalState
    GlobalBrowserLifecycle->>GlobalBrowserLifecycle: extract browser pane IDs from paneLayout
    GlobalBrowserLifecycle->>GlobalBrowserLifecycle: diff against previous set
    
    alt Pane ID removed
        GlobalBrowserLifecycle->>GlobalBrowserLifecycle: schedule destroy(paneId) after 500ms
    else Pane ID reappears
        GlobalBrowserLifecycle->>GlobalBrowserLifecycle: clear pending timeout
    end
    
    Note over GlobalBrowserLifecycle,BrowserRegistry: Prevents premature destruction during workspace switch
    GlobalBrowserLifecycle->>BrowserRegistry: destroy(paneId) after grace window
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 ターミナルはそっと森の奥へ、
マウントして接続し、再接続の踊り。
ブラウザペインは500ミリ秒の猶予で息をする、
ワークスペース切り替えも恐れず、
AI が名付ける新しい枝たち。

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR タイトルは upstream からの 12 コミット取り込みと bun.lock 再生成という主要な変更内容を要約しており、レポジトリの指向性を反映している。
Description check ✅ Passed PR 説明は詳細な Summary、取り込み対象の 12 コミット、conflict 解消方針、fork 機能ヘルスチェック、テストプランを含んでおり、テンプレート要件を十分に満たしている。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch upstream/batch-9-post-plan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 24, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

Sherif の multiple-dependency-versions エラーを解消。upstream superset-sh#3692
(aceb0fd) が host-service の @mastra/core を 1.26.0-alpha.3 に
アップデートしたが、apps/desktop と packages/chat は 1.25.0 のまま
だった。upstream/main では 3 箇所全て 1.26.0-alpha.3 で統一されているため、
fork でも揃える。
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b91b1c4188

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Codex P1 指摘対応。updateNameFromHost mutation は expectedCurrentName
mismatch (ユーザーの並行 rename) 時に既存 row を返す no-op 動作だが、
fork 側の applyAiWorkspaceRename は戻り値を見ずに local DB / git branch
を無条件で新 name に書き換えていた。結果: cloud 側は old branch のまま、
local は new branch になり分岐が食い違う。

mutation 結果の branch フィールドを比較し、cloud が accept しなかった
場合は git branch rename を rollback + host-sqlite 更新を skip。
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.test.ts (1)

116-123: ⚠️ Potential issue | 🟡 Minor

テストのアサーションが agentId: null デフォルトによって無意味になっています。

pendingBase() のデフォルトが agentId: null になったため、このテストは「有効なエージェントが存在しない」ことではなく「agentIdnull」であることを理由に null を返しています (resolveAgentId が最初に null を返す)。agentConfigs: [] のパスを本来意図したとおりに検証するには、明示的に agentId を指定してください。

🧪 提案: 意図したパスを通す
 	test("returns null when there are no enabled agents", async () => {
 		const build = await buildForkAgentLaunch({
-			pending: pendingBase({ prompt: "hi" }),
+			pending: pendingBase({ prompt: "hi", agentId: "claude" }),
 			attachments: undefined,
 			agentConfigs: [],
 		});
 		expect(build).toBeNull();
 	});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/buildForkAgentLaunch.test.ts
around lines 116 - 123, The test currently relies on pendingBase() defaulting
agentId to null which causes resolveAgentId to return null and makes the
assertion meaningless; update the test that calls buildForkAgentLaunch so
pendingBase is given an explicit non-null agentId (e.g., "agent-1") to ensure
the code path where agentConfigs: [] is evaluated by resolveAgentId and the
function actually returns null due to no enabled agents rather than due to a
null agentId; keep references to buildForkAgentLaunch, pendingBase,
resolveAgentId and agentConfigs when making the change.
🧹 Nitpick comments (18)
apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts (1)

1189-1250: 冪等性アサーションの追加を推奨

同ファイルの他の stale-path 置換テスト(例: Lines 1045-1123 の "replaces stale Codex hook commands from old superset paths")では、1 回目の結果を書き戻してから 2 回目を実行し JSON.parse(content2)).toEqual(JSON.parse(content)) で冪等性を確認しています。本テストも同じ getCodexGlobalHooksJsonContent 経路を通すので、回帰検知の一貫性のためにここでも 2 回目実行と冪等性チェックを入れておくと、将来 reconcile ロジックが再適用時にエントリを重複させるような退行を拾えます。

♻️ 冪等性チェックの追加案
 		const content = getCodexGlobalHooksJsonContent(currentHookPath);
 		expect(content).not.toBeNull();
 		if (content === null) throw new Error("Expected content");
 
+		// Second run should be idempotent
+		writeFileSync(codexHooksPath, content);
+		const content2 = getCodexGlobalHooksJsonContent(currentHookPath);
+		expect(content2).not.toBeNull();
+
 		const parsed = JSON.parse(content) as {
 			hooks: Record<
 				string,
 				Array<{
 					matcher?: string;
 					hooks: Array<{ type: string; command: string }>;
 				}>
 			>;
 		};
@@
 			expect(
 				hooks.some((def) =>
 					def.hooks.some((hook) => hook.command === staleHookPath),
 				),
 			).toBe(false);
 		}
+
+		expect(JSON.parse(content2 as string)).toEqual(JSON.parse(content));
 	});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts` around lines
1189 - 1250, Add an idempotency assertion by calling
getCodexGlobalHooksJsonContent a second time and asserting the JSON output is
unchanged: after the existing first call/parse (content → parsed), call
getCodexGlobalHooksJsonContent again (content2), ensure content2 is not null,
parse it and assert JSON.parse(content2) equals JSON.parse(content) (or toEqual
on the parsed objects). This uses the existing getCodexGlobalHooksJsonContent
helper and the parsed variable names so the test will detect regressions where
reconcile logic might duplicate entries on re-application.
apps/marketing/src/app/pricing/components/PricingTiers/components/PricingCard/PricingCard.tsx (1)

83-107: resolvePrice に明示的な戻り値型を付与することを推奨します。

現状は戻り値型が推論に任されており、strikethroughstring | null としてユニオンで推論されますが、将来的にいずれかの分岐の戻り値が変わるとサイレントに型がぶれる可能性があります。明示的な interface を定義しておくと意図が明確になり、分岐の追加・変更時のリグレッションを防げます。コーディングガイドラインでも TypeScript でのオブジェクト形状には interface を優先するよう定められています。

♻️ 提案リファクタ
+interface ResolvedPrice {
+	display: string;
+	strikethrough: string | null;
+	note: string;
+	cadence: string;
+}
+
-function resolvePrice(tier: PricingTier, isYearly: boolean) {
+function resolvePrice(tier: PricingTier, isYearly: boolean): ResolvedPrice {

As per coding guidelines: "Prefer interface for defining object shapes in TypeScript".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/marketing/src/app/pricing/components/PricingTiers/components/PricingCard/PricingCard.tsx`
around lines 83 - 107, Add an explicit return type for resolvePrice by defining
a dedicated interface (e.g., ResolvedPrice) that describes the object shape
(fields: display: string, strikethrough: string | null, note: string, cadence?:
string | null as appropriate) and update the function signature to return that
interface; ensure all branches in resolvePrice (fixed, custom, and the
monthly/yearly path) return objects conforming to this interface so TypeScript
will catch future regressions and keep the shape consistent.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.ts (1)

129-176: resolveAgentId 呼び出し後の再解決が重複しています。

resolveAgentId は既に indexResolvedAgentConfigs(configs).get(...) を実行し enabled をチェックした上で AgentDefinitionId | null を返しています。にもかかわらず、呼び出し側 (Line 132-135) で再度 indexResolvedAgentConfigs を実行し enabled を再チェックしており、インデックス構築が 2 回走る上にロジックも重複します。resolveAgentId に設定オブジェクトを返させるか、ヘルパーに再整理することで冗長な再解決を削除できます。

♻️ 提案: 解決結果を一度だけ返す
-	const agentId = resolveAgentId(inputs.pending.agentId, inputs.agentConfigs);
-	if (!agentId) return null;
-
-	const agentConfig = indexResolvedAgentConfigs(inputs.agentConfigs).get(
-		agentId,
-	);
-	if (!agentConfig || !agentConfig.enabled) return null;
+	const agentConfig = resolveAgentConfig(
+		inputs.pending.agentId,
+		inputs.agentConfigs,
+	);
+	if (!agentConfig) return null;
+	const agentId = agentConfig.id;
@@
-function resolveAgentId(
-	selected: string | null,
-	configs: ResolvedAgentConfig[],
-): AgentDefinitionId | null {
-	if (!selected || selected === "none") return null;
-	const match = indexResolvedAgentConfigs(configs).get(
-		selected as AgentDefinitionId,
-	);
-	return match?.enabled ? match.id : null;
-}
+function resolveAgentConfig(
+	selected: string | null,
+	configs: ResolvedAgentConfig[],
+): ResolvedAgentConfig | null {
+	if (!selected || selected === "none") return null;
+	const match = indexResolvedAgentConfigs(configs).get(
+		selected as AgentDefinitionId,
+	);
+	return match?.enabled ? match : null;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/buildForkAgentLaunch.ts
around lines 129 - 176, The call to indexResolvedAgentConfigs is being done
twice: once inside resolveAgentId and again in the caller to re-fetch
agentConfig, causing redundant indexing and checks; change resolveAgentId to
return the selected ResolvedAgentConfig | null (not just AgentDefinitionId),
e.g. resolveAgentConfig(selected, configs) that performs
indexResolvedAgentConfigs(configs) and enabled check, then in the caller use the
returned ResolvedAgentConfig directly (replace agentId + agentConfig lookup with
the single returned config) so you remove the second indexResolvedAgentConfigs
call and duplicate enabled logic; update references to resolveAgentId,
agentConfig, and any code that expects an id to instead use the config or derive
the id from it.
apps/desktop/src/main/lib/host-service-coordinator.ts (1)

291-300: semver satisfies呼び出しのprererelease対応と失敗理由ログの改善を検討

semver.satisfies(version, ">=${MIN_HOST_SERVICE_VERSION}") はデフォルトで includePrerelease: false のため、将来 host-service が 0.2.0-beta などのプレリリース版を返した場合、実際には十分新しいバージョンでもチェックが失敗し、正常なサービスが kill されます。プロジェクトは auto-updater.ts でプレリリース版を明示的に扱っているため、ここでも統一的に対応するか、リリースポリシーとしてステーブル版のみに限定することを検討してください。

あわせて、ログメッセージの version ${version} < ${MIN_HOST_SERVICE_VERSION} は、実際には「範囲外(プレリリース、無効な形式など含む)」なので、より正確に incompatible と表記する方が無難です。

参考パッチ
-		!semver.satisfies(version, `>=${MIN_HOST_SERVICE_VERSION}`)
+		!semver.satisfies(version, `>=${MIN_HOST_SERVICE_VERSION}`, {
+			includePrerelease: true,
+		})
-			const reason = version
-				? `version ${version} < ${MIN_HOST_SERVICE_VERSION}`
-				: "version unknown";
+			const reason = version
+				? `version ${version} incompatible with >=${MIN_HOST_SERVICE_VERSION}`
+				: "version unknown";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/lib/host-service-coordinator.ts` around lines 291 -
300, The semver check in semver.satisfies(version,
`>=${MIN_HOST_SERVICE_VERSION}`) can incorrectly reject prerelease versions and
the log message is misleading; update the compatibility logic in the
host-service check (the block using semver.satisfies, MIN_HOST_SERVICE_VERSION
and organizationId) to explicitly handle prereleases (e.g., pass {
includePrerelease: true } to semver.satisfies or first check
semver.valid/semver.prerelease) and compute a clear reason string (e.g.,
"incompatible: prerelease X", "incompatible: invalid version", or "incompatible:
version X < MIN_HOST_SERVICE_VERSION") then include that reason and the raw
version in the console/processLogger output instead of "version X <
MIN_HOST_SERVICE_VERSION" so the log accurately reflects
prerelease/invalid-format failures.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/DestroyConfirmPane/DestroyConfirmPane.tsx (1)

88-96: isCheckingStatus 中の視覚的フィードバック不足(軽微な UX 改善)

プリフライトチェック中は Delete ボタンが無効化されますが、理由を示す表示(スピナーや "Checking…" ラベル等)がないため、ユーザーにはボタンが不活性に見えるだけになります。チェックは通常短時間ですが、遅い環境では戸惑いの原因になり得ます。

💡 任意の UX 改善案
 					<Button
 						variant="destructive"
 						size="sm"
 						className="h-7 px-3 text-xs"
 						onClick={onConfirm}
 						disabled={isCheckingStatus}
 					>
-						Delete
+						{isCheckingStatus ? "Checking…" : "Delete"}
 					</Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/DestroyConfirmPane/DestroyConfirmPane.tsx`
around lines 88 - 96, The Delete button becomes disabled during the preflight
check but lacks visual feedback; update DestroyConfirmPane to show a spinner
and/or change the button label when isCheckingStatus is true (e.g., replace
"Delete" with "Checking…" and render a small spinner icon inside the Button
while disabled) so users understand why the button is inactive; modify the
Button usage in DestroyConfirmPane to conditionally render the spinner and
alternate label based on the isCheckingStatus prop while keeping onConfirm and
the disabled behavior intact.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/DashboardSidebarDeleteDialog.tsx (1)

55-67: hasWarnings の重複(任意の軽微リファクタ)

同じ hasChanges || hasUnpushedCommits が本ファイル(Line 55)と DestroyConfirmPane.tsx(Line 38)の両方で計算されています。バナー表示用と onConfirmforce 引数決定用で用途は異なるため現状でも動作上の問題はありませんが、DestroyConfirmPane から hasWarnings を props として受け取る/あるいは親で計算して子に渡す、のいずれかに集約すると意図が 1 箇所に集中して追いやすくなります。急ぎの対応は不要です。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/DashboardSidebarDeleteDialog.tsx`
around lines 55 - 67, Compute the combined warning flag once in the parent and
pass it into the child instead of duplicating the logic: remove the local
calculation of hasWarnings in DestroyConfirmPane and add a new boolean prop
(e.g., hasWarnings) on DestroyConfirmPane; in the parent
(DashboardSidebarDeleteDialog) keep the existing hasWarnings = hasChanges ||
hasUnpushedCommits and pass hasWarnings to DestroyConfirmPane, then update
DestroyConfirmPane to use the received hasWarnings both for banner display and
for calling onConfirm/force logic (ensure run(hasWarnings) in the parent or
propagate the flag to where run is invoked).
packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts (1)

172-208: ロールバック処理の重複をヘルパー化できます。

git branch -m deduped oldBranchName + .catch でログを出すブロックが cloud 失敗時(L175-185)と no-op 検出時(L198-206)でほぼ同一です。将来どちらかだけ修正して差分が入るリスクがあるので、内部ヘルパーに切り出す余地があります。動作に問題は無いので対応は任意です。

♻️ 提案
+	const rollbackGitBranch = async (reason: string) => {
+		await ctx
+			.git(worktreePath)
+			.then((g) => g.raw(["branch", "-m", deduped, oldBranchName]))
+			.catch((rollbackErr) => {
+				console.warn(
+					`[applyAiWorkspaceRename] git branch rollback ${reason} (workspace ${workspaceId}, ${deduped} → ${oldBranchName})`,
+					rollbackErr,
+				);
+			});
+	};
+
 	let cloudResult: Awaited<
 		ReturnType<typeof ctx.api.v2Workspace.updateNameFromHost.mutate>
 	>;
 	try {
 		cloudResult = await ctx.api.v2Workspace.updateNameFromHost.mutate(patch);
 	} catch (err) {
-		if (gitRenamed) {
-			await ctx
-				.git(worktreePath)
-				.then((g) => g.raw(["branch", "-m", deduped, oldBranchName]))
-				.catch((rollbackErr) => {
-					console.warn(
-						`[applyAiWorkspaceRename] git branch rollback failed (workspace ${workspaceId}, ${deduped} → ${oldBranchName})`,
-						rollbackErr,
-					);
-				});
-		}
+		if (gitRenamed) await rollbackGitBranch("after cloud error");
 		throw err;
 	}
 	...
 	if (!cloudAcceptedBranch && gitRenamed) {
-		await ctx
-			.git(worktreePath)
-			.then((g) => g.raw(["branch", "-m", deduped, oldBranchName]))
-			.catch((rollbackErr) => {
-				console.warn(
-					`[applyAiWorkspaceRename] git branch rollback after cloud no-op failed (workspace ${workspaceId}, ${deduped} → ${oldBranchName})`,
-					rollbackErr,
-				);
-			});
+		await rollbackGitBranch("after cloud no-op");
 		return;
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts`
around lines 172 - 208, The two identical git rollback blocks in
ai-workspace-names.ts should be extracted into a small helper to avoid
duplication: create a helper (e.g., rollbackGitBranch or rollbackBranchRename)
that accepts ctx, worktreePath, workspaceId, fromBranch (deduped) and toBranch
(oldBranchName), calls ctx.git(worktreePath).then(g => g.raw(["branch","-m",
fromBranch, toBranch])) and catches errors to console.warn using the same
contextual message currently used in both places; then replace the two inline
rollback blocks (the try/catch rollback after updateNameFromHost failure and the
cloud no-op rollback) with calls to that helper to keep behavior identical and
centralize future changes.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/clipboardShortcuts.ts (2)

30-72: 関数名と実際のスコープの乖離(任意のリファクタ提案)

macOS ブランチが return event.metaKey に広がったことで、この関数は「クリップボードショートカット」を超えた汎用的な Cmd-chord バブリング判定を担うようになりました。Ghostty 由来の設計意図は JSDoc に明記されているので現状でも把握は可能ですが、将来の読み手のために関数名を shouldBubbleTerminalChord など実態を反映したものに変える/もしくは JSDoc の冒頭でエイリアス的に言及しておくと、呼び出し側(terminal-runtime.ts)での役割が明確になります。挙動は正しく、コメントが充実しているため急務ではありません。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/clipboardShortcuts.ts`
around lines 30 - 72, The function shouldBubbleClipboardShortcut has drifted to
a more general terminal chord-bubbling role; rename it to
shouldBubbleTerminalChord (and update its JSDoc to reflect the Ghostty-derived
Cmd-chord semantics) and update all call sites (e.g., the caller in
terminal-runtime.ts) to use the new name so the API matches behavior and intent.

49-51: Mac キーコード処理の実行順序依存性をドキュメント化する

shouldBubbleClipboardShortcut の JSDoc に、translateLineEditChord および shouldSelectAllShortcut の後に呼ばれることが必須である旨をより明確に記載してください。現在の実装は正しい順序を保っていますが、将来の変更時に無言の挙動破壊を防ぐため、この前提条件をより厳密に文書化することで保守性が向上します。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/clipboardShortcuts.ts`
around lines 49 - 51, Update the JSDoc for the function
shouldBubbleClipboardShortcut to explicitly state that it must be invoked after
translateLineEditChord and shouldSelectAllShortcut (i.e., it assumes those have
already been applied), documenting the Mac-specific keycode handling order
dependency so future changes don't break behavior; reference the function name
shouldBubbleClipboardShortcut and the dependent helpers translateLineEditChord
and shouldSelectAllShortcut in the comment and note the required call order on
macOS.
apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/hooks/useGlobalBrowserLifecycle/useGlobalBrowserLifecycle.ts (4)

4-4: browserRuntimeRegistry のモジュール配置がアーキテクチャ的に不自然

この hook はアプリ全体の副作用を担うグローバルライフサイクルにも関わらず、依存する browserRuntimeRegistry がワークスペース固有パスの深い位置(_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/BrowserPane/...)に置かれており、グローバルと特定ルートが逆方向に依存しています。対照的に terminalRuntimeRegistryrenderer/lib/terminal/ のニュートラルな位置にあります。

今回の PR スコープでは差分を増やす変更になるので必須ではありませんが、将来的には browserRuntimeRegistryrenderer/lib/browser/ などに移して BrowserPane 側から参照する形に逆転させると、依存方向がレイヤに沿って綺麗になります。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/hooks/useGlobalBrowserLifecycle/useGlobalBrowserLifecycle.ts`
at line 4, The import of browserRuntimeRegistry in useGlobalBrowserLifecycle
comes from a workspace-specific deep path, inverting dependency direction;
extract browserRuntimeRegistry into a neutral shared module (e.g., create
renderer/lib/browser/browserRuntimeRegistry.ts), export the same identifier,
update all callers (useGlobalBrowserLifecycle and BrowserPane and any other
consumers) to import from the new shared module instead of the _dashboard/...
path, and remove the original file or re-export from it to avoid duplicate
definitions; this mirrors how terminalRuntimeRegistry is placed and keeps global
lifecycle hooks independent of route-specific code.

94-101: cleanup effect は既存の effect と統合できる

行 94-101 の cleanup 専用 useEffect(() => () => {...}, []) は、既存の useEffect(..., [allWorkspaceRows, collections]) の return 部に inline cleanup を書けば十分です(依存配列の再実行時にも毎回走るのを避けたい場合は別 effect のままで可)。

ただし現状は依存配列が変わるたびに effect 本体が再実行されるので、cleanup を統合すると「diff 計算の副作用として設定したタイマーが、再実行のたびに全部クリアされる」挙動になり意味が変わります。そのため現在の分離は意図的に正しいです。補足として、その意図をコメントに一言残しておくと将来のリファクタで誤って統合されるのを防げます。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/hooks/useGlobalBrowserLifecycle/useGlobalBrowserLifecycle.ts`
around lines 94 - 101, The separate cleanup useEffect that clears
pendingDestruction.current timers (the useEffect with an empty dependency array
that iterates pendingDestruction.current.values() and calls clearTimeout) is
intentionally isolated from the other effect that depends on [allWorkspaceRows,
collections] because merging them would cause timers to be cleared on every
dependency change; add a short explanatory comment immediately above the
empty-deps useEffect stating that it must run only on unmount to avoid clearing
timers during dependency-driven re-runs and must not be merged with the
useEffect handling allWorkspaceRows/collections to preserve the intended
lifecycle semantics.

75-86: setTimeout 内でクロージャが古い collections を掴み続ける可能性

500ms のタイマーコールバックは effect が走った時点の collections クロージャを掴みます。collections の identity がその後変わった場合([allWorkspaceRows, collections] 依存から次の effect が走る)、古い collection の .state.values() を参照することになります。

実運用では collections が動的に差し替わるケースは稀なので実害の可能性は低いですが、useCollections() が Provider を跨いで切り替わる/テストで mock 差し替えをするようなシナリオでは destroy 判定がミスる可能性があります。collectionsRef = useRef(collections) を最新に更新して callback 内で ref を参照する、あるいは collection identity 変化時に pending を全クリアするなどの対処を検討してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/hooks/useGlobalBrowserLifecycle/useGlobalBrowserLifecycle.ts`
around lines 75 - 86, The timeout callback uses a stale closure over collections
causing incorrect destroy checks; update the effect so the callback reads the
latest collections via a ref (e.g., create collectionsRef = useRef(collections)
and keep it synced whenever collections changes) and inside the setTimeout
callback use collectionsRef.current.values() (or clear
pendingDestruction.current when collections identity changes) to ensure
extractBrowserPaneIds(freshRows) uses the current collection before calling
browserRuntimeRegistry.destroy(browserId); keep DESTROY_DELAY_MS and
pendingDestruction.current logic intact and ensure the effect dependency
[allWorkspaceRows, collections] still triggers the ref update.

13-27: paneLayout: unknown のキャストを型ガードに置き換えると安全

extractBrowserPaneIdsrow.paneLayout as WorkspaceState<unknown> | undefined とキャストしていますが、実際の shape 検証はしていないため、スキーマが変わった時や他の rows 型でも silently 通過します。useGlobalTerminalLifecycle と対称にするという設計方針は理解しましたが、少なくとも layout && typeof layout === "object" && "tabs" in layout のような最小限の runtime ガードを入れておくと、スキーマ差分時のデバッグが楽になります。

また pane.kind === "browser" の分岐でも pane.id が string である保証はキャスト任せなので、typeof pane.id === "string" を併せて確認するとさらに堅牢です。

♻️ 参考パッチ
 function extractBrowserPaneIds(rows: { paneLayout: unknown }[]): Set<string> {
 	const ids = new Set<string>();
 	for (const row of rows) {
 		const layout = row.paneLayout as WorkspaceState<unknown> | undefined;
 		if (!layout?.tabs) continue;
 		for (const tab of layout.tabs) {
 			for (const pane of Object.values(tab.panes)) {
-				if (pane.kind === "browser") {
+				if (pane.kind === "browser" && typeof pane.id === "string") {
 					ids.add(pane.id);
 				}
 			}
 		}
 	}
 	return ids;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/hooks/useGlobalBrowserLifecycle/useGlobalBrowserLifecycle.ts`
around lines 13 - 27, Replace the unsafe cast in extractBrowserPaneIds with
runtime type guards: check that row.paneLayout is an object and has a "tabs"
property (e.g., layout && typeof layout === "object" && "tabs" in layout) before
treating it as WorkspaceState, iterate tabs and panes as now, and additionally
verify pane.kind === "browser" && typeof pane.id === "string" before calling
ids.add(pane.id); mirror the minimal guards used in useGlobalTerminalLifecycle
to keep behavior consistent.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/clipboardShortcuts.test.ts (1)

119-143: Linux の Ctrl+C 挙動テストが欠けている

実装の Linux ブランチ(clipboardShortcuts.ts 行 67-71)では Windows と異なり Ctrl+ChasSelection に関わらず絶対にバブリングしない(=常に PTY に渡って SIGINT になる)仕様になっています。この差分は Windows vs Linux の差別化ポイントとしてレグレッション耐性の高いケースなので、テストに以下を追加しておくと安心です:

  • Linux Ctrl+C with selection stays with the PTY (SIGINT)
  • Linux Ctrl+C without selection stays with the PTY (SIGINT)
♻️ 参考パッチ
 			{
 				name: "Linux Ctrl+Insert stays with the PTY",
 				event: makeEvent({ code: "Insert", ctrlKey: true }),
 				options: { isMac: false, isWindows: false, hasSelection: false },
 				expected: false,
 			},
+			{
+				name: "Linux Ctrl+C with selection stays with PTY (Ctrl+Shift+C is the copy idiom)",
+				event: makeEvent({ code: "KeyC", ctrlKey: true }),
+				options: { isMac: false, isWindows: false, hasSelection: true },
+				expected: false,
+			},
+			{
+				name: "Linux Ctrl+C without selection stays with PTY (SIGINT)",
+				event: makeEvent({ code: "KeyC", ctrlKey: true }),
+				options: { isMac: false, isWindows: false, hasSelection: false },
+				expected: false,
+			},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/clipboardShortcuts.test.ts`
around lines 119 - 143, Add two Linux Ctrl+C test cases to the array in
clipboardShortcuts.test.ts to cover the Linux-specific behavior in
clipboardShortcuts.ts: add one named "Linux Ctrl+C with selection stays with the
PTY (SIGINT)" and one "Linux Ctrl+C without selection stays with the PTY
(SIGINT)"; both should use event = makeEvent({ code: "KeyC", ctrlKey: true })
with options { isMac: false, isWindows: false } and hasSelection true/false
respectively, and both expected = false to assert that Ctrl+C always does not
bubble to the UI but is sent to the PTY.
apps/desktop/src/renderer/lib/terminal/terminal-runtime.ts (1)

77-85: コメントとコードの齟齬(軽微)。

JSDoc は「mod is the only non-shift modifier held」(=Shift の有無は不問) と読めますが、実装は !event.shiftKey を強制しており Shift を押していると false を返します。実害はありませんが、現行ロジックに合わせてコメントを「no other modifier held (including Shift)」等に揃えるか、Shift を許容したいならコードを調整してください。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/lib/terminal/terminal-runtime.ts` around lines 77 -
85, The JSDoc for function onlyMod is out of sync with its implementation: the
comment says Shift is allowed but the code enforces !event.shiftKey. Either
update the JSDoc to state “no other modifier held (including Shift)” to reflect
the current behavior, or change the implementation in onlyMod to allow Shift
(remove the !event.shiftKey check and adjust logic accordingly) depending on the
intended semantics; use the onlyMod signature and usages to guide which variant
to keep.
apps/desktop/plans/20260423-1226-v2-pane-persistence-across-workspace-switch.md (1)

5-7: ネストしたバッククォートのレンダリング崩れ(軽微なドキュメント修正)。

Line 6 と Line 70 の `key={`${workspace.id}:${hostUrl}`}` はシングルバッククォートのコードスパン内に更にバッククォートが含まれているため、多くの Markdown レンダラで途中で code span が途切れて表示が崩れます。ダブルバッククォートで囲むと安全です。

🔧 修正案
-(`layout.tsx:79` uses `key={`${workspace.id}:${hostUrl}`}`). Every pane React
+(`layout.tsx:79` uses ``key={`${workspace.id}:${hostUrl}`}``). Every pane React
-the v2 layout's `key={`${workspace.id}:${hostUrl}`}` remounts on every
+the v2 layout's ``key={`${workspace.id}:${hostUrl}`}`` remounts on every

Also applies to: 68-72

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/plans/20260423-1226-v2-pane-persistence-across-workspace-switch.md`
around lines 5 - 7, ドキュメント内のコードスパン `` `key={`${workspace.id}:${hostUrl}`}` ``
がネストしたバッククォートを含んでいるため Markdown
レンダリングが崩れています。該当するコードスパンをシングルバッククォートからダブルバッククォートで囲む表記(例:
````key={`${workspace.id}:${hostUrl}`}```)に変更し、同じ表記が使われている他の箇所(コメントで指摘された同一スニペットの出現箇所)も同様に修正してください。
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/components/FileRow/FileRow.tsx (1)

46-75: ディレクトリをまたぐリネーム/コピー時に旧ディレクトリが表示されない

dir は新しいパス(file.path)から算出されるため、例えば src/a/old.ts → src/b/new.ts のようなケースでは src/b/ old.ts → new.ts と表示され、元の場所(src/a/)が一切見えなくなります。Git の標準的な短縮表記(src/{a/old.ts => b/new.ts})と比べるとユーザーが変更元を追えないのは残念なポイントです。

改善案:旧パスと新パスでディレクトリが異なる場合は oldPath のフルパス(またはディレクトリ)を表示する。

♻️ 提案差分
 	const { dir, basename } = splitPath(file.path);
-	const oldBasename =
-		file.oldPath && (file.status === "renamed" || file.status === "copied")
-			? splitPath(file.oldPath).basename
-			: null;
+	const oldDisplay =
+		file.oldPath && (file.status === "renamed" || file.status === "copied")
+			? (() => {
+					const old = splitPath(file.oldPath);
+					return old.dir === dir ? old.basename : `${old.dir}${old.basename}`;
+				})()
+			: null;
-				{oldBasename && (
+				{oldDisplay && (
 					<span className="truncate text-muted-foreground">
-						{oldBasename}
+						{oldDisplay}
 						<span className="px-1">→</span>
 					</span>
 				)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/components/FileRow/FileRow.tsx
around lines 46 - 75, The current UI computes dir from the new path only, so
renames/copied files that move directories lose the old directory; update the
render logic to derive oldDir (e.g., const oldDir = file.oldPath ?
splitPath(file.oldPath).dirname : null) and compare it to dir, and when oldDir
exists and oldDir !== dir show the oldDir (or the full oldPath dirname)
alongside oldBasename (before the arrow) so users can see the original
directory; adjust the JSX that currently renders {dir && ...} and {oldBasename
&& ...} to render oldDir when different from dir and fall back to existing
behavior otherwise (use symbols: file.oldPath, oldBasename, oldDir, dir,
splitPath, basename).
packages/host-service/src/trpc/router/git/git.ts (1)

230-241: 共有参照パターンの意図をコメントで補足したい

entryuntrackedFilesunstaged の両方に push して、後続の countUntrackedFileLines が mutate した additionsunstaged 側にも反映されるようにしている点は効率的ですが、読み手には分かりにくいです。将来の改変でうっかり片方だけ新オブジェクト化すると unstaged の additions が 0 のまま残る潜在的なバグにつながります。短いコメントで意図を明示しておくと安全です。

♻️ 提案差分
 				if (file.index === "?" && wd === "?") {
+					// Same object pushed to both lists so that
+					// countUntrackedFileLines' in-place additions mutation
+					// is visible through `unstaged` as well.
 					const entry: ChangedFile = {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/host-service/src/trpc/router/git/git.ts` around lines 230 - 241,
ここでは entry オブジェクトを untrackedFiles と unstaged の両方に push して、後続の
countUntrackedFileLines が entry.additions を mutate
した結果を両方に反映させる「共有参照パターン」を利用しています(つまり同一オブジェクトを参照させていることが意図)。untrackedFiles/unstaged
に同じオブジェクトを入れている理由と、将来片方だけコピーしてしまうと差分が生じるリスクがある点を、関数内の該当ブロック(変数名: entry,
untrackedFiles, unstaged, countUntrackedFileLines を参照)に短いコメントで明示してください。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx`:
- Around line 137-161: The PR button (the <button> that wraps
DashboardSidebarWorkspaceIcon when pullRequest is truthy) lacks a visible
keyboard focus style; add focus-visible utility classes to its className (e.g.,
focus-visible:outline-none plus a visible ring/ring-offset classes consistent
with the app theme) so Tab-key focus is clearly indicated without changing
behavior, keeping the existing onClick/onKeyDown handlers and aria-label intact.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceIcon/DashboardSidebarWorkspaceIcon.tsx`:
- Around line 62-71: The PR icon render currently short-circuits the offline
branch so offline remote devices (isRemoteDeviceOffline / LuCloudOff) aren't
visually distinguished when pullRequestState is set; modify renderPrimaryIcon
(or the parent that calls it) to apply an offline visual treatment when
isRemoteDeviceOffline is true even if pullRequestState exists—for example, wrap
the selected PrIcon (from PR_ICON_BY_STATE with PR_COLOR_BY_STATE) in a
container or pass a prop that reduces opacity or adds a dimming class when
isRemoteDeviceOffline is true so the PR state remains visible but the icon is
visually marked offline.

In
`@apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/hooks/useGlobalBrowserLifecycle/useGlobalBrowserLifecycle.ts`:
- Around line 56-92: The effect's initial run misses stale registry entries
because prevBrowserIdsRef is initialized to an empty Set; add a registry
enumeration and use it on first effect run: introduce
browserRuntimeRegistry.getAllPaneIds() (or similar) and on mount/useEffect when
prevBrowserIdsRef.current is empty, populate prevBrowserIdsRef (or merge) with
the set returned by getAllPaneIds() so the existing loop will schedule
destruction for pane IDs not present in currentBrowserIds; update useEffect in
useGlobalBrowserLifecycle (references: prevBrowserIdsRef, useEffect,
extractBrowserPaneIds, pendingDestruction, DESTROY_DELAY_MS, allWorkspaceRows,
collections) to call the new API and handle timers consistently.

In
`@apps/marketing/src/app/pricing/components/PricingTiers/components/PricingCard/PricingCard.tsx`:
- Around line 41-45: Replace the visual-only <span> used for the old price in
PricingCard with a semantic element (use <del> or <s>) for the strikethrough
value (the variable strikethrough) and add an appropriate aria-label (e.g.,
"通常価格" or "旧価格") so screen readers know it is the previous price; keep the
existing CSS classes for styling and ensure the active price element has a
contrasting aria-label (e.g., "現在の価格") or role to avoid both being read as
equal.

In `@packages/chat/package.json`:
- Line 39: Update the dependency entry for "@mastra/core" in package.json from
"1.26.0-alpha.3" to the stable release "1.27.0"; locate the "@mastra/core"
dependency key and replace its version string, then run your package manager
(npm/yarn/pnpm) to refresh lockfile and verify install/build.

---

Outside diff comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/buildForkAgentLaunch.test.ts:
- Around line 116-123: The test currently relies on pendingBase() defaulting
agentId to null which causes resolveAgentId to return null and makes the
assertion meaningless; update the test that calls buildForkAgentLaunch so
pendingBase is given an explicit non-null agentId (e.g., "agent-1") to ensure
the code path where agentConfigs: [] is evaluated by resolveAgentId and the
function actually returns null due to no enabled agents rather than due to a
null agentId; keep references to buildForkAgentLaunch, pendingBase,
resolveAgentId and agentConfigs when making the change.

---

Nitpick comments:
In
`@apps/desktop/plans/20260423-1226-v2-pane-persistence-across-workspace-switch.md`:
- Around line 5-7: ドキュメント内のコードスパン `` `key={`${workspace.id}:${hostUrl}`}` ``
がネストしたバッククォートを含んでいるため Markdown
レンダリングが崩れています。該当するコードスパンをシングルバッククォートからダブルバッククォートで囲む表記(例:
````key={`${workspace.id}:${hostUrl}`}```)に変更し、同じ表記が使われている他の箇所(コメントで指摘された同一スニペットの出現箇所)も同様に修正してください。

In `@apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts`:
- Around line 1189-1250: Add an idempotency assertion by calling
getCodexGlobalHooksJsonContent a second time and asserting the JSON output is
unchanged: after the existing first call/parse (content → parsed), call
getCodexGlobalHooksJsonContent again (content2), ensure content2 is not null,
parse it and assert JSON.parse(content2) equals JSON.parse(content) (or toEqual
on the parsed objects). This uses the existing getCodexGlobalHooksJsonContent
helper and the parsed variable names so the test will detect regressions where
reconcile logic might duplicate entries on re-application.

In `@apps/desktop/src/main/lib/host-service-coordinator.ts`:
- Around line 291-300: The semver check in semver.satisfies(version,
`>=${MIN_HOST_SERVICE_VERSION}`) can incorrectly reject prerelease versions and
the log message is misleading; update the compatibility logic in the
host-service check (the block using semver.satisfies, MIN_HOST_SERVICE_VERSION
and organizationId) to explicitly handle prereleases (e.g., pass {
includePrerelease: true } to semver.satisfies or first check
semver.valid/semver.prerelease) and compute a clear reason string (e.g.,
"incompatible: prerelease X", "incompatible: invalid version", or "incompatible:
version X < MIN_HOST_SERVICE_VERSION") then include that reason and the raw
version in the console/processLogger output instead of "version X <
MIN_HOST_SERVICE_VERSION" so the log accurately reflects
prerelease/invalid-format failures.

In `@apps/desktop/src/renderer/lib/terminal/terminal-runtime.ts`:
- Around line 77-85: The JSDoc for function onlyMod is out of sync with its
implementation: the comment says Shift is allowed but the code enforces
!event.shiftKey. Either update the JSDoc to state “no other modifier held
(including Shift)” to reflect the current behavior, or change the implementation
in onlyMod to allow Shift (remove the !event.shiftKey check and adjust logic
accordingly) depending on the intended semantics; use the onlyMod signature and
usages to guide which variant to keep.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/DestroyConfirmPane/DestroyConfirmPane.tsx`:
- Around line 88-96: The Delete button becomes disabled during the preflight
check but lacks visual feedback; update DestroyConfirmPane to show a spinner
and/or change the button label when isCheckingStatus is true (e.g., replace
"Delete" with "Checking…" and render a small spinner icon inside the Button
while disabled) so users understand why the button is inactive; modify the
Button usage in DestroyConfirmPane to conditionally render the spinner and
alternate label based on the isCheckingStatus prop while keeping onConfirm and
the disabled behavior intact.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/DashboardSidebarDeleteDialog.tsx`:
- Around line 55-67: Compute the combined warning flag once in the parent and
pass it into the child instead of duplicating the logic: remove the local
calculation of hasWarnings in DestroyConfirmPane and add a new boolean prop
(e.g., hasWarnings) on DestroyConfirmPane; in the parent
(DashboardSidebarDeleteDialog) keep the existing hasWarnings = hasChanges ||
hasUnpushedCommits and pass hasWarnings to DestroyConfirmPane, then update
DestroyConfirmPane to use the received hasWarnings both for banner display and
for calling onConfirm/force logic (ensure run(hasWarnings) in the parent or
propagate the flag to where run is invoked).

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/`$pendingId/buildForkAgentLaunch.ts:
- Around line 129-176: The call to indexResolvedAgentConfigs is being done
twice: once inside resolveAgentId and again in the caller to re-fetch
agentConfig, causing redundant indexing and checks; change resolveAgentId to
return the selected ResolvedAgentConfig | null (not just AgentDefinitionId),
e.g. resolveAgentConfig(selected, configs) that performs
indexResolvedAgentConfigs(configs) and enabled check, then in the caller use the
returned ResolvedAgentConfig directly (replace agentId + agentConfig lookup with
the single returned config) so you remove the second indexResolvedAgentConfigs
call and duplicate enabled logic; update references to resolveAgentId,
agentConfig, and any code that expects an id to instead use the config or derive
the id from it.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/`$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/components/FileRow/FileRow.tsx:
- Around line 46-75: The current UI computes dir from the new path only, so
renames/copied files that move directories lose the old directory; update the
render logic to derive oldDir (e.g., const oldDir = file.oldPath ?
splitPath(file.oldPath).dirname : null) and compare it to dir, and when oldDir
exists and oldDir !== dir show the oldDir (or the full oldPath dirname)
alongside oldBasename (before the arrow) so users can see the original
directory; adjust the JSX that currently renders {dir && ...} and {oldBasename
&& ...} to render oldDir when different from dir and fall back to existing
behavior otherwise (use symbols: file.oldPath, oldBasename, oldDir, dir,
splitPath, basename).

In
`@apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/hooks/useGlobalBrowserLifecycle/useGlobalBrowserLifecycle.ts`:
- Line 4: The import of browserRuntimeRegistry in useGlobalBrowserLifecycle
comes from a workspace-specific deep path, inverting dependency direction;
extract browserRuntimeRegistry into a neutral shared module (e.g., create
renderer/lib/browser/browserRuntimeRegistry.ts), export the same identifier,
update all callers (useGlobalBrowserLifecycle and BrowserPane and any other
consumers) to import from the new shared module instead of the _dashboard/...
path, and remove the original file or re-export from it to avoid duplicate
definitions; this mirrors how terminalRuntimeRegistry is placed and keeps global
lifecycle hooks independent of route-specific code.
- Around line 94-101: The separate cleanup useEffect that clears
pendingDestruction.current timers (the useEffect with an empty dependency array
that iterates pendingDestruction.current.values() and calls clearTimeout) is
intentionally isolated from the other effect that depends on [allWorkspaceRows,
collections] because merging them would cause timers to be cleared on every
dependency change; add a short explanatory comment immediately above the
empty-deps useEffect stating that it must run only on unmount to avoid clearing
timers during dependency-driven re-runs and must not be merged with the
useEffect handling allWorkspaceRows/collections to preserve the intended
lifecycle semantics.
- Around line 75-86: The timeout callback uses a stale closure over collections
causing incorrect destroy checks; update the effect so the callback reads the
latest collections via a ref (e.g., create collectionsRef = useRef(collections)
and keep it synced whenever collections changes) and inside the setTimeout
callback use collectionsRef.current.values() (or clear
pendingDestruction.current when collections identity changes) to ensure
extractBrowserPaneIds(freshRows) uses the current collection before calling
browserRuntimeRegistry.destroy(browserId); keep DESTROY_DELAY_MS and
pendingDestruction.current logic intact and ensure the effect dependency
[allWorkspaceRows, collections] still triggers the ref update.
- Around line 13-27: Replace the unsafe cast in extractBrowserPaneIds with
runtime type guards: check that row.paneLayout is an object and has a "tabs"
property (e.g., layout && typeof layout === "object" && "tabs" in layout) before
treating it as WorkspaceState, iterate tabs and panes as now, and additionally
verify pane.kind === "browser" && typeof pane.id === "string" before calling
ids.add(pane.id); mirror the minimal guards used in useGlobalTerminalLifecycle
to keep behavior consistent.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/clipboardShortcuts.test.ts`:
- Around line 119-143: Add two Linux Ctrl+C test cases to the array in
clipboardShortcuts.test.ts to cover the Linux-specific behavior in
clipboardShortcuts.ts: add one named "Linux Ctrl+C with selection stays with the
PTY (SIGINT)" and one "Linux Ctrl+C without selection stays with the PTY
(SIGINT)"; both should use event = makeEvent({ code: "KeyC", ctrlKey: true })
with options { isMac: false, isWindows: false } and hasSelection true/false
respectively, and both expected = false to assert that Ctrl+C always does not
bubble to the UI but is sent to the PTY.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/clipboardShortcuts.ts`:
- Around line 30-72: The function shouldBubbleClipboardShortcut has drifted to a
more general terminal chord-bubbling role; rename it to
shouldBubbleTerminalChord (and update its JSDoc to reflect the Ghostty-derived
Cmd-chord semantics) and update all call sites (e.g., the caller in
terminal-runtime.ts) to use the new name so the API matches behavior and intent.
- Around line 49-51: Update the JSDoc for the function
shouldBubbleClipboardShortcut to explicitly state that it must be invoked after
translateLineEditChord and shouldSelectAllShortcut (i.e., it assumes those have
already been applied), documenting the Mac-specific keycode handling order
dependency so future changes don't break behavior; reference the function name
shouldBubbleClipboardShortcut and the dependent helpers translateLineEditChord
and shouldSelectAllShortcut in the comment and note the required call order on
macOS.

In
`@apps/marketing/src/app/pricing/components/PricingTiers/components/PricingCard/PricingCard.tsx`:
- Around line 83-107: Add an explicit return type for resolvePrice by defining a
dedicated interface (e.g., ResolvedPrice) that describes the object shape
(fields: display: string, strikethrough: string | null, note: string, cadence?:
string | null as appropriate) and update the function signature to return that
interface; ensure all branches in resolvePrice (fixed, custom, and the
monthly/yearly path) return objects conforming to this interface so TypeScript
will catch future regressions and keep the shape consistent.

In `@packages/host-service/src/trpc/router/git/git.ts`:
- Around line 230-241: ここでは entry オブジェクトを untrackedFiles と unstaged の両方に push
して、後続の countUntrackedFileLines が entry.additions を mutate
した結果を両方に反映させる「共有参照パターン」を利用しています(つまり同一オブジェクトを参照させていることが意図)。untrackedFiles/unstaged
に同じオブジェクトを入れている理由と、将来片方だけコピーしてしまうと差分が生じるリスクがある点を、関数内の該当ブロック(変数名: entry,
untrackedFiles, unstaged, countUntrackedFileLines を参照)に短いコメントで明示してください。

In
`@packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts`:
- Around line 172-208: The two identical git rollback blocks in
ai-workspace-names.ts should be extracted into a small helper to avoid
duplication: create a helper (e.g., rollbackGitBranch or rollbackBranchRename)
that accepts ctx, worktreePath, workspaceId, fromBranch (deduped) and toBranch
(oldBranchName), calls ctx.git(worktreePath).then(g => g.raw(["branch","-m",
fromBranch, toBranch])) and catches errors to console.warn using the same
contextual message currently used in both places; then replace the two inline
rollback blocks (the try/catch rollback after updateNameFromHost failure and the
cloud no-op rollback) with calls to that helper to keep behavior identical and
centralize future changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e703f9b8-b4ef-4bcf-830a-f85664527b3a

📥 Commits

Reviewing files that changed from the base of the PR and between 7f535f0 and eb7ccc4.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (46)
  • apps/desktop/package.json
  • apps/desktop/plans/20260423-1226-v2-pane-persistence-across-workspace-switch.md
  • apps/desktop/src/main/lib/agent-setup/agent-wrappers-common.ts
  • apps/desktop/src/main/lib/agent-setup/agent-wrappers.test.ts
  • apps/desktop/src/main/lib/host-service-coordinator.ts
  • apps/desktop/src/renderer/lib/terminal/terminal-runtime-registry.ts
  • apps/desktop/src/renderer/lib/terminal/terminal-runtime.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/automations/$automationId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/DashboardSidebarDeleteDialog.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/ConflictPane/ConflictPane.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/ConflictPane/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/DestroyConfirmPane/DestroyConfirmPane.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/UnknownErrorPane/UnknownErrorPane.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/UnknownErrorPane/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/hooks/useDestroyDialogState/useDestroyDialogState.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDiffStats/DashboardSidebarWorkspaceDiffStats.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceIcon/DashboardSidebarWorkspaceIcon.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.test.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildForkAgentLaunch.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/buildIntentPayload.test.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/pending/$pendingId/dispatchForkLaunch.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/components/WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/components/FileRow/FileRow.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/TerminalPane/TerminalPane.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/usePaneRegistry.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/PromptGroup.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/DashboardNewWorkspaceModal/components/DashboardNewWorkspaceForm/PromptGroup/hooks/useSubmitWorkspace/useSubmitWorkspace.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/GlobalBrowserLifecycle.tsx
  • apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/hooks/useGlobalBrowserLifecycle/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/hooks/useGlobalBrowserLifecycle/useGlobalBrowserLifecycle.ts
  • apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/layout.tsx
  • apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/clipboardShortcuts.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/clipboardShortcuts.ts
  • apps/marketing/src/app/components/Header/components/DesktopNav/DesktopNav.tsx
  • apps/marketing/src/app/pricing/components/PricingTiers/components/PricingCard/PricingCard.tsx
  • packages/chat/package.json
  • packages/chat/src/server/shared/small-model/get-small-model.ts
  • packages/host-service/package.json
  • packages/host-service/src/trpc/router/git/git.ts
  • packages/host-service/src/trpc/router/git/utils/git-helpers.ts
  • packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts
  • packages/host-service/src/trpc/router/workspace-creation/utils/list-branch-names.ts
  • packages/host-service/src/trpc/router/workspace-creation/workspace-creation.ts
  • packages/trpc/src/router/v2-workspace/v2-workspace.ts
💤 Files with no reviewable changes (4)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/ConflictPane/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/UnknownErrorPane/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/UnknownErrorPane/UnknownErrorPane.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarDeleteDialog/components/ConflictPane/ConflictPane.tsx

Comment on lines +137 to +161
{pullRequest ? (
<button
type="button"
onClick={(event) => {
event.stopPropagation();
openUrl.mutate(pullRequest.url);
}}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") {
event.stopPropagation();
}
}}
aria-label={`Open pull request #${pullRequest.number}`}
className="relative mr-2.5 flex size-5 shrink-0 cursor-pointer items-center justify-center rounded hover:bg-foreground/10"
>
<DashboardSidebarWorkspaceIcon
hostType={hostType}
hostIsOnline={hostIsOnline}
isActive={isActive}
variant="expanded"
workspaceStatus={null}
creationStatus={creationStatus}
pullRequestState={pullRequest.state}
/>
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

PR ボタンにキーボードフォーカス可視化スタイルがない

hover:bg-foreground/10 は定義されていますが、focus-visible: 相当の視覚指示がないため、Tab でフォーカスを当てた際にユーザーが現在位置を認識しづらい状態です。このボタンは role="button" な親行の中にネストされているため、特にキーボード操作時のフォーカス遷移を明示することが重要です。

♿ 提案: focus-visible リング追加
-								className="relative mr-2.5 flex size-5 shrink-0 cursor-pointer items-center justify-center rounded hover:bg-foreground/10"
+								className="relative mr-2.5 flex size-5 shrink-0 cursor-pointer items-center justify-center rounded hover:bg-foreground/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{pullRequest ? (
<button
type="button"
onClick={(event) => {
event.stopPropagation();
openUrl.mutate(pullRequest.url);
}}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") {
event.stopPropagation();
}
}}
aria-label={`Open pull request #${pullRequest.number}`}
className="relative mr-2.5 flex size-5 shrink-0 cursor-pointer items-center justify-center rounded hover:bg-foreground/10"
>
<DashboardSidebarWorkspaceIcon
hostType={hostType}
hostIsOnline={hostIsOnline}
isActive={isActive}
variant="expanded"
workspaceStatus={null}
creationStatus={creationStatus}
pullRequestState={pullRequest.state}
/>
</button>
{pullRequest ? (
<button
type="button"
onClick={(event) => {
event.stopPropagation();
openUrl.mutate(pullRequest.url);
}}
onKeyDown={(event) => {
if (event.key === "Enter" || event.key === " ") {
event.stopPropagation();
}
}}
aria-label={`Open pull request #${pullRequest.number}`}
className="relative mr-2.5 flex size-5 shrink-0 cursor-pointer items-center justify-center rounded hover:bg-foreground/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<DashboardSidebarWorkspaceIcon
hostType={hostType}
hostIsOnline={hostIsOnline}
isActive={isActive}
variant="expanded"
workspaceStatus={null}
creationStatus={creationStatus}
pullRequestState={pullRequest.state}
/>
</button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsx`
around lines 137 - 161, The PR button (the <button> that wraps
DashboardSidebarWorkspaceIcon when pullRequest is truthy) lacks a visible
keyboard focus style; add focus-visible utility classes to its className (e.g.,
focus-visible:outline-none plus a visible ring/ring-offset classes consistent
with the app theme) so Tab-key focus is clearly indicated without changing
behavior, keeping the existing onClick/onKeyDown handlers and aria-label intact.

Comment on lines +62 to +71
const renderPrimaryIcon = () => {
if (pullRequestState) {
const PrIcon = PR_ICON_BY_STATE[pullRequestState];
return (
<PrIcon
className={cn("size-3.5", PR_COLOR_BY_STATE[pullRequestState])}
strokeWidth={1.75}
/>
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

PR アイコン表示時にリモートデバイスのオフライン状態が視覚化されない

pullRequestState が設定されると、isRemoteDeviceOffline 分岐(LuCloudOff)に到達しなくなります。PR 付きの remote-device ワークスペースがオフラインのとき、ユーザーはサイドバーのアイコンだけでは「このデバイスは到達不可」であることを判別できません。

意図した仕様(PR 状態を最優先で表示する)であれば問題ありませんが、オフライン時の可視性を残したい場合は、isRemoteDeviceOffline のときだけアイコンの opacity を落とすなどの合成表示を検討してください。

💡 参考: オフライン時に半透明化する差分例
 	const renderPrimaryIcon = () => {
 		if (pullRequestState) {
 			const PrIcon = PR_ICON_BY_STATE[pullRequestState];
 			return (
 				<PrIcon
-					className={cn("size-3.5", PR_COLOR_BY_STATE[pullRequestState])}
+					className={cn(
+						"size-3.5",
+						PR_COLOR_BY_STATE[pullRequestState],
+						isRemoteDeviceOffline && "opacity-60",
+					)}
 					strokeWidth={1.75}
 				/>
 			);
 		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceIcon/DashboardSidebarWorkspaceIcon.tsx`
around lines 62 - 71, The PR icon render currently short-circuits the offline
branch so offline remote devices (isRemoteDeviceOffline / LuCloudOff) aren't
visually distinguished when pullRequestState is set; modify renderPrimaryIcon
(or the parent that calls it) to apply an offline visual treatment when
isRemoteDeviceOffline is true even if pullRequestState exists—for example, wrap
the selected PrIcon (from PR_ICON_BY_STATE with PR_COLOR_BY_STATE) in a
container or pass a prop that reduces opacity or adds a dimming class when
isRemoteDeviceOffline is true so the PR state remains visible but the icon is
visually marked offline.

Comment on lines +56 to +92
useEffect(() => {
const currentBrowserIds = extractBrowserPaneIds(allWorkspaceRows);
const prevBrowserIds = prevBrowserIdsRef.current;

// Cancel any pending destruction for ids that reappeared (e.g. pane
// moved between workspaces, user undo, or the transient replaceState
// churn we were fighting in the first place).
for (const browserId of currentBrowserIds) {
const timer = pendingDestruction.current.get(browserId);
if (timer) {
clearTimeout(timer);
pendingDestruction.current.delete(browserId);
}
}

for (const browserId of prevBrowserIds) {
if (currentBrowserIds.has(browserId)) continue;
if (pendingDestruction.current.has(browserId)) continue;

const timer = setTimeout(() => {
pendingDestruction.current.delete(browserId);

const freshRows = Array.from(
collections.v2WorkspaceLocalState.state.values(),
);
const freshIds = extractBrowserPaneIds(freshRows);

if (!freshIds.has(browserId)) {
browserRuntimeRegistry.destroy(browserId);
}
}, DESTROY_DELAY_MS);

pendingDestruction.current.set(browserId, timer);
}

prevBrowserIdsRef.current = currentBrowserIds;
}, [allWorkspaceRows, collections]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# useGlobalTerminalLifecycle の実装と registry の公開 API を確認
fd -t f 'useGlobalTerminalLifecycle.ts' | head -20
fd -t f 'browserRuntimeRegistry.ts' | xargs -I {} rg -n --type=ts -C 2 '^\s*(public\s+)?(destroy|get|has|list|keys|ids)\s*[\(:]' {}

Repository: MocA-Love/superset

Length of output: 328


🏁 Script executed:

#!/bin/bash
# Find and inspect browserRuntimeRegistry implementation
fd -t f 'browserRuntimeRegistry.ts' -o 'browserRuntimeRegistry.js'

Repository: MocA-Love/superset

Length of output: 232


🏁 Script executed:

#!/bin/bash
# Find where browserRuntimeRegistry is exported or defined
rg -l 'browserRuntimeRegistry' --type ts --type tsx | head -5

Repository: MocA-Love/superset

Length of output: 90


🏁 Script executed:

#!/bin/bash
# Get the full useGlobalTerminalLifecycle implementation, especially around prevRef initialization
fd -t f 'useGlobalTerminalLifecycle.ts' -x cat -n {} | head -100

Repository: MocA-Love/superset

Length of output: 3302


🏁 Script executed:

#!/bin/bash
# Find and inspect registry implementations
fd -t f 'terminal-runtime-registry' --type f

Repository: MocA-Love/superset

Length of output: 130


🏁 Script executed:

#!/bin/bash
# Find browserRuntimeRegistry
fd -t f 'browserRuntimeRegistry' --type f

Repository: MocA-Love/superset

Length of output: 218


🏁 Script executed:

#!/bin/bash
# Search more broadly for registry files
fd -type f 'runtime-registry'

Repository: MocA-Love/superset

Length of output: 233


🏁 Script executed:

#!/bin/bash
# Read terminalRuntimeRegistry
cat -n apps/desktop/src/renderer/lib/terminal/terminal-runtime-registry.ts

Repository: MocA-Love/superset

Length of output: 11806


🏁 Script executed:

#!/bin/bash
# Read browserRuntimeRegistry
cat -n apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/\$workspaceId/hooks/usePaneRegistry/components/BrowserPane/browserRuntimeRegistry.ts

Repository: MocA-Love/superset

Length of output: 25211


🏁 Script executed:

#!/bin/bash
# Check browserRuntimeRegistry export and if groups is accessible
rg -n "export.*browserRuntimeRegistry" apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/\$workspaceId/hooks/usePaneRegistry/components/BrowserPane/browserRuntimeRegistry.ts -A 5

Repository: MocA-Love/superset

Length of output: 303


🏁 Script executed:

#!/bin/bash
# Check how browserRuntimeRegistry is used in useGlobalBrowserLifecycle
rg -n "browserRuntimeRegistry" apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/hooks/useGlobalBrowserLifecycle/useGlobalBrowserLifecycle.ts -B 2 -A 2

Repository: MocA-Love/superset

Length of output: 574


🏁 Script executed:

#!/bin/bash
# Check the full useGlobalBrowserLifecycle file for context about initialization
cat -n apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/hooks/useGlobalBrowserLifecycle/useGlobalBrowserLifecycle.ts | head -100

Repository: MocA-Love/superset

Length of output: 4179


初回マウント時に stale な registry エントリがクリーンアップされない

prevBrowserIdsRefnew Set() で初期化されるため、初回 effect 実行時には「以前存在した ID」が空集合です。その結果、アプリ起動直後に registry に残っている browser エントリがあっても、このループではスケジュール対象になりません。

通常起動シナリオではアプリ起動時に registry は空なので問題は顕在化しにくいですが、HMR 中や GlobalBrowserLifecycle が再マウントされた場合、前回セッションで生成された webview がリークし得ます。useGlobalTerminalLifecycle も同じ挙動ですが、そちらは terminalRuntimeRegistry.getAllTerminalIds() で enumeration API を持っています。

browserRuntimeRegistry には現在、公開された enumeration API がないため、初回実行時に registry に存在するすべての pane を列挙して差分を判定する実装に対応するには、新たに getAllPaneIds() のような API を追加するか、別の手段で初回実行を検出して既存 registry の内容を取得する必要があります。両者まとめて対処するのが望ましいです。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/components/GlobalBrowserLifecycle/hooks/useGlobalBrowserLifecycle/useGlobalBrowserLifecycle.ts`
around lines 56 - 92, The effect's initial run misses stale registry entries
because prevBrowserIdsRef is initialized to an empty Set; add a registry
enumeration and use it on first effect run: introduce
browserRuntimeRegistry.getAllPaneIds() (or similar) and on mount/useEffect when
prevBrowserIdsRef.current is empty, populate prevBrowserIdsRef (or merge) with
the set returned by getAllPaneIds() so the existing loop will schedule
destruction for pane IDs not present in currentBrowserIds; update useEffect in
useGlobalBrowserLifecycle (references: prevBrowserIdsRef, useEffect,
extractBrowserPaneIds, pendingDestruction, DESTROY_DELAY_MS, allWorkspaceRows,
collections) to call the new API and handle timers consistently.

Comment on lines +41 to +45
{strikethrough && (
<span className="text-2xl font-medium tracking-tight leading-none text-muted-foreground line-through">
{strikethrough}
</span>
)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

取り消し線価格のアクセシビリティを改善してください。

line-through は視覚的な装飾のみで、スクリーンリーダーには「旧価格」であることが伝わりません。<span> ではなく意味論的に <s> もしくは <del> を使用し、さらに aria-label で「通常価格」などのコンテキストを付与することを検討してください。現状だとスクリーンリーダー利用者には取り消し線のない元価格と割引後価格が並列で読み上げられ、どちらが有効な価格か判別できません。

♿ 提案されたアクセシビリティ修正
-					{strikethrough && (
-						<span className="text-2xl font-medium tracking-tight leading-none text-muted-foreground line-through">
-							{strikethrough}
-						</span>
-					)}
+					{strikethrough && (
+						<s
+							aria-label={`Regular price ${strikethrough}`}
+							className="text-2xl font-medium tracking-tight leading-none text-muted-foreground"
+						>
+							{strikethrough}
+						</s>
+					)}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{strikethrough && (
<span className="text-2xl font-medium tracking-tight leading-none text-muted-foreground line-through">
{strikethrough}
</span>
)}
{strikethrough && (
<s
aria-label={`Regular price ${strikethrough}`}
className="text-2xl font-medium tracking-tight leading-none text-muted-foreground"
>
{strikethrough}
</s>
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/marketing/src/app/pricing/components/PricingTiers/components/PricingCard/PricingCard.tsx`
around lines 41 - 45, Replace the visual-only <span> used for the old price in
PricingCard with a semantic element (use <del> or <s>) for the strikethrough
value (the variable strikethrough) and add an appropriate aria-label (e.g.,
"通常価格" or "旧価格") so screen readers know it is the previous price; keep the
existing CSS classes for styling and ensure the active price element has a
contrasting aria-label (e.g., "現在の価格") or role to avoid both being read as
equal.

"@ai-sdk/anthropic": "^3.0.43",
"@ai-sdk/openai": "3.0.36",
"@mastra/core": "1.25.0",
"@mastra/core": "1.26.0-alpha.3",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: `@mastra/core` の 1.26.0-alpha.3 バージョンの存在と安定性を確認

# npm レジストリで利用可能なバージョンを確認
npm view `@mastra/core` versions --json | jq -r '.[]' | grep "1.26.0-alpha.3"

# 最新の安定版を確認
npm view `@mastra/core` version

# パッケージの情報を取得
npm view `@mastra/core`@1.26.0-alpha.3 --json | jq '{version, deprecated, dist}'

Repository: MocA-Love/superset

Length of output: 907


🌐 Web query:

@mastra/core 1.26.0-alpha.3 changelog breaking changes

💡 Result:

No specific changelog or breaking changes found for @mastra/core 1.26.0-alpha.3. The package exists per Snyk security page (result 9), but detailed release notes for this exact alpha version are not publicly available in searched sources. The official CHANGELOG.md is at https://github.com/mastra-ai/mastra/blob/main/packages/core/CHANGELOG.md (results 21,2,19). Recent stable releases like 1.16.0 (result 3, published 2026-03-26) contain minor and patch changes only, no breaking changes listed. Earlier alphas like 1.14.0-alpha.3 (result 19) and 0.24.7-alpha.3 (result 62) show patch changes. For 1.x series breaking changes, see the 1.0.0 release blog post (result 10): subpath imports required, tool execution signature changed, context namespaces, tool output validation, API renames, pagination changes, observability config, Node 22.13+ minimum, DB migrations. Check the GitHub repo releases page https://github.com/mastra-ai/mastra/releases and packages/core CHANGELOG.md directly for the latest alpha details, as they may have been published after search index.

Citations:


@mastra/core を最新の安定版 1.27.0 にアップグレードしてください

@mastra/core のバージョンが 1.26.0-alpha.3(アルファ版)に設定されていますが、npm レジストリで確認したところ最新の安定版は 1.27.0 です。より新しい安定版が利用可能なため、本番環境での使用を考慮して 1.27.0 へのアップグレードをお勧めします。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chat/package.json` at line 39, Update the dependency entry for
"@mastra/core" in package.json from "1.26.0-alpha.3" to the stable release
"1.27.0"; locate the "@mastra/core" dependency key and replace its version
string, then run your package manager (npm/yarn/pnpm) to refresh lockfile and
verify install/build.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants