Skip to content

feat(desktop): UX improvements batch (clone progress, diff search, keep-alive, multi-select, findInPage)#154

Merged
MocA-Love merged 18 commits intomainfrom
feat/desktop-ux-improvements
Apr 13, 2026
Merged

feat(desktop): UX improvements batch (clone progress, diff search, keep-alive, multi-select, findInPage)#154
MocA-Love merged 18 commits intomainfrom
feat/desktop-ux-improvements

Conversation

@MocA-Love
Copy link
Copy Markdown
Owner

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

Summary

デスクトップの UX 改善を 5 トピックまとめて対応。

1. Clone 進捗ストリーミング (New Project → Clone)

  • projects.cloneRepocloneId を受け取れる形に拡張し、simpleGit({ progress, abort }) で進捗イベントと中断制御を付与
  • tRPC observable projects.cloneProgressprojects.cancelClone mutation を新設
  • CloneRepoTab に phase / progress bar / streaming log / Cancel ボタンの UI を追加。モックは clone.html 参照

2. Diff Viewer の検索を Files タブと統一 (#2 + #4)

  • CodeMirrorDiffViewersearch({ createPanel: createHiddenSearchPanel }) + CodeEditorSearchOverlay を組み込み、右上 top-1 right-1 の overlay に統一
  • Mod-f / F3 / Mod-g / Escape を a・b 両 editor に適用、フォーカス中 editor をターゲット
  • runFindNext / runFindPrevious 直後に EditorView.scrollIntoView(head, { y: \"center\", yMargin: 48 }) を重ねて dispatch し 検索結果を画面中央にスクロール

3. タブ切替で editor state がリセットされる問題

  • 根本原因: PersistentTabRendererwebview / vscode-extension / reference-graph の 3 タイプのみ keep-alive していた → file-viewer タブは非アクティブ時に unmount されていた
  • 1 行 fix: file-viewer も persistent 判定に追加。これで非アクティブタブが position: fixed; left: -9999px で keep-alive され、scroll / search / cursor / ハイライトが全て保持される

4. Git サイドバーの複数選択 stage/unstage

  • MultiSelectContext を新設: セクション単位で選択状態を保持、Shift+Click (range) / Cmd+Click (toggle) / 通常 click (clear) を処理
  • BulkActionBar を選択中に表示 (Stage / Unstage / × clear)
  • Unstaged / Staged セクションの FileList を <MultiSelectProvider> でラップ。既存の stageFiles / unstageFiles mutation (バックエンド対応済み) をそのまま利用
  • 破壊的変更: FileItem の Cmd+Click の挙動を "open in editor" から multi-select に変更 (VS Code 準拠)。open in editor は double-click / context menu / hover action から利用可能

5. 内蔵ブラウザの Cmd+F (find in page)

  • Main: browser-managerfindInPage / stopFindInPage メソッドと setupFindInPage helper を追加。webview の before-input-event で Cmd+F を検知し renderer に通知、found-in-page イベントを購読してマッチ数/ordinal を emit
  • tRPC: browser.findInPage / stopFindInPage mutation、onFoundInPage / onFindRequested subscription を新設
  • Renderer: 新規 BrowserFindOverlay (CodeEditorSearchOverlay と同じ右上配置・Match Case トグル・prev/next/close)、BrowserPane 内で購読と keydown capture を wire

6. drag autoscroll 診断ログ (#3 調査用)

  • CodeEditor の mousedown で CM6 の scrollableParents walk を再現してコンソールに出力 (scroller rect / scrollHeight / clientHeight / 親チェーン / pointer との距離)
  • ユーザーが spurious drag scroll を再現した際にログを貼ってもらい、scrollParents.y が意図しない祖先に解決されているかを確認するのが目的。修正自体はログ結果を踏まえて別 PR

Test plan

  • Clone: 大きめの repo を clone して phase / progress / log が流れ、完了時にプロジェクトが開くこと
  • Clone Cancel: 途中で Cancel を押して状態が "canceled" に遷移し、error ダイアログが出ないこと
  • Diff Viewer: Cmd+F で右上に overlay が出て、next/prev で検索結果が画面中央にスクロールすること、Escape で閉じること
  • Diff Viewer: a (左) / b (右) 両方で検索ハイライトが出ること
  • タブ切替: ファイルを開いた状態で別タブに切り替え → 戻ってきたとき scroll / search / cursor / 選択が保持されていること
  • ワークスペース切替では従来通りリセットされる (今回の fix の対象外)
  • Git 複数選択: Unstaged でファイルを Shift+Click / Cmd+Click して複数選択、BulkActionBar から Stage できること
  • Staged でも同様に Unstage できること
  • 通常 click は従来通りファイル選択 (multi-selection は clear される)
  • ブラウザ Cmd+F: webview にフォーカスがある状態で Cmd+F → overlay が出現、タイプしてマッチ数表示、next/prev 動作、Escape で閉じてハイライトが消えること
  • ブラウザ URL バー等 chrome にフォーカスがある状態でも Cmd+F が動作すること
  • CodeEditor でドラッグ選択時に DevTools コンソールで [CodeEditor drag-diagnostic] が出ること (修正用)

Summary by CodeRabbit

リリースノート

  • 新機能

    • ブラウザペインのページ内検索(Cmd/Ctrl+Fで開く、検索オーバーレイ、前後移動、Match-case、Escapeで閉じる、リアルタイム検索要求/結果の更新)
    • クローン進捗のストリーミング表示、詳細ログ、経過時間、キャンセル機能、ログのクリア
    • 変更ビューのマルチセレクトと一括操作パネル(複数選択でのステージ/アンステージ/破棄)
    • 差分エディタの検索オーバーレイとキーボードショートカット
    • 検索オーバーレイ共通コンポーネントの追加
  • スタイル

    • クローン進捗用のインジケーターアニメーションを追加
  • ドキュメント

    • タブ永続化の説明を更新(スクロール位置・検索状態・カーソル等の保持)

…ep-alive, multi-select, findInPage)

- Clone: stream git progress via tRPC observable, add cancel + UI with phase/bar/log
- Diff viewer: reuse CodeEditorSearchOverlay at top-right with center-scroll on navigate
- Tabs: keep file-viewer tabs mounted when inactive so scroll/search/cursor survive switching
- Git sidebar: Shift/Cmd click multi-select + BulkActionBar for bulk stage/unstage
- Browser: Cmd+F find-in-page via webContents.findInPage with right-top overlay
- CodeEditor: diagnostic mousedown logging for drag-autoscroll investigation
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

ブラウザペインのページ内検索(UI・IPC・tRPC)、クローン進捗のストリーミングとキャンセル、複数選択と一括操作UI、CodeMirror の検索オーバーレイ統合、タブ永続化対象の拡張、およびエディタ向けドラッグ診断ロガーが追加されました。

Changes

Cohort / File(s) Summary
ページ内検索
apps/desktop/src/lib/trpc/routers/browser/browser.ts, apps/desktop/src/main/lib/browser/browser-manager.ts, apps/desktop/src/renderer/.../BrowserPane/BrowserPane.tsx, apps/desktop/src/renderer/.../BrowserPane/components/BrowserFindOverlay/BrowserFindOverlay.tsx, apps/desktop/src/renderer/.../BrowserPane/components/BrowserFindOverlay/index.ts
tRPC に findInPage/stopFindInPage ミューテーションと onFoundInPage/onFindRequested サブスクリプションを追加。BrowserManager が webContents イベントを仲介し、BrowserPane に検索オーバーレイ(BrowserFindOverlay)を導入してキー操作とサブスクライブで状態を同期。
クローン進捗・キャンセル
apps/desktop/src/lib/trpc/routers/projects/projects.ts, apps/desktop/src/renderer/.../CloneRepoTab/CloneRepoTab.tsx, apps/desktop/src/renderer/globals.css
CloneProgressEvent 型、per-clone イベントバッファ、AbortController レジストリを導入。cloneProgress サブスクリプションと cancelClone ミューテーションを追加。cloneRepocloneId モードで simple-git の進捗/abort を利用しログ・進捗を送出。資格情報の赤字化と失敗時のパスクリアを実装。CSS に進行アニメーションを追加。
複数選択 / バルク操作
apps/desktop/src/renderer/.../MultiSelectContext/MultiSelectContext.tsx, apps/desktop/src/renderer/.../MultiSelectContext/index.ts, apps/desktop/src/renderer/.../BulkActionBar/BulkActionBar.tsx, apps/desktop/src/renderer/.../BulkActionBar/index.ts, apps/desktop/src/renderer/.../FileItem/FileItem.tsx, apps/desktop/src/renderer/.../useOrderedSections/useOrderedSections.tsx
MultiSelectProvider / useMultiSelect を追加。Shift/Cmd/Ctrl による範囲選択・トグル選択と選択維持ロジックを実装。BulkActionBar を追加しステージ/アンステージ/破棄の一括操作を統合、FileItem とリストを連携。
CodeMirror 検索オーバーレイ(差分ビュー)
apps/desktop/src/renderer/.../CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx
@codemirror/search のパネルをプログラム制御に切替え、検索状態の追跡、マッチ列挙、キーボードナビゲーション、隠しパネルファクトリ、MergeView 解体時のリセット処理を追加。
ビュー永続化・エディタ診断
apps/desktop/src/renderer/.../PersistentTabRenderer.tsx, apps/desktop/src/renderer/.../CodeEditor/CodeEditor.tsx
PersistentTabRenderer の永続化対象に file-viewer を追加。CodeEditor にドラッグオートスクロール診断ロガーと mousedown ハンドラを追加してデバッグ情報を出力。

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as BrowserPane
    participant TRPCClient as tRPC Client
    participant TRPCServer as tRPC Server
    participant Manager as BrowserManager
    participant Web as WebContents

    User->>UI: Cmd/Ctrl+F 押下
    UI->>TRPCClient: サブスクライブ (onFindRequested)
    TRPCServer->>Manager: emit find-requested:paneId
    Manager->>Web: preventDefault / emit find-requested
    User->>UI: 検索語入力 -> UI: findInPage mutation
    TRPCClient->>TRPCServer: findInPage({paneId,text,...})
    TRPCServer->>Manager: RPC -> Manager.findInPage -> Web.findInPage
    Web->>Manager: found-in-page イベント (matches)
    Manager->>TRPCServer: emit found-in-page:paneId
    TRPCServer->>TRPCClient: onFoundInPage イベント配信
    TRPCClient->>UI: UI 更新 (matchCount/activeOrdinal)
Loading
sequenceDiagram
    actor User
    participant UI as CloneRepoTab
    participant TRPCClient as tRPC Client
    participant TRPCServer as tRPC Server
    participant Git as simple-git

    User->>UI: Clone ボタン(生成 cloneId)
    UI->>TRPCClient: cloneRepo.mutate({ cloneId })
    TRPCServer->>Git: start clone with progress & abort hooks
    Git->>TRPCServer: progress/log callbacks
    TRPCServer->>TRPCClient: cloneProgress サブスクリプションでイベント送出
    TRPCClient->>UI: 進捗/ログ到着 -> UI 更新
    User->>UI: Cancel -> cancelClone({ cloneId })
    TRPCClient->>TRPCServer: cancelClone -> abort controller -> emit "canceled"
    TRPCClient->>UI: terminal event 到着(done/error/canceled)
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

Poem

🐇 ぼくウサギ、キーで呼ぶよ「検索!」
マッチがぴょんと見つかり、ログはさらさら流れる。
クローンは進み、必要ならば止められる。
選択揃えて一括で動かす、作業は軽やか。
ぴょん、とまた次のフィーチャーへ!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning PRの説明は、提供されたテンプレートに対して不完全です。Description、Related Issues、Type of Change、Testing、Screenshots、Additional Notesセクションが未記入です。 テンプレートの全セクション(説明、関連Issue、変更タイプ、テスト、スクリーンショット、追記)を埋めるか、既存のサマリー形式が適切な場合はテンプレート要件を明確にしてください。
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed タイトルは、clone進捗、diff検索、keep-alive、複数選択、findInPageという5つの主要な変更を網羅した正確で具体的なPRの内容を反映しています。

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/desktop-ux-improvements

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.

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: 00314640c5

ℹ️ 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".

Comment thread apps/desktop/src/lib/trpc/routers/projects/projects.ts Outdated
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: 7

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/main/lib/browser/browser-manager.ts (1)

55-60: ⚠️ Potential issue | 🟠 Major

再登録時に find リスナーが掃除されません。

prevId !== webContentsId の掃除対象に this.findListeners が入っていないので、pane が新しい webContents に張り替わると古い before-input-event / found-in-page ハンドラが残ります。結果として find 系イベントが二重発火し得ます。

🩹 最小修正案
 			for (const map of [
 				this.consoleListeners,
 				this.contextMenuListeners,
 				this.fullscreenListeners,
 				this.popupListeners,
+				this.findListeners,
 			]) {

Also applies to: 107-107

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

In `@apps/desktop/src/main/lib/browser/browser-manager.ts` around lines 55 - 60,
修正対象は再登録時のリスナー掃除ループで、現在 this.findListeners が含まれておらず古い before-input-event /
found-in-page ハンドラが残るため、for ループの配列(this.consoleListeners,
this.contextMenuListeners, this.fullscreenListeners, this.popupListeners)に
this.findListeners を追加して、prevId !== webContentsId の分岐で find リスナーも正しく
remove/cleanup されるようにする(該当箇所は browser-manager.ts の該当ループと同様の別箇所(行
~107)にも同じ変更を適用すること)。
🧹 Nitpick comments (4)
apps/desktop/src/renderer/globals.css (1)

432-434: prefers-reduced-motion 対応を追加してください

実装は問題ありませんが、無限アニメーションなので OS の reduced motion 設定時に停止できるようにしておくとアクセシビリティが向上します。

差分案
 .animate-clone-indeterminate {
 	animation: clone-indeterminate 1.4s ease-in-out infinite;
 }
+
+@media (prefers-reduced-motion: reduce) {
+	.animate-clone-indeterminate {
+		animation: none;
+	}
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/globals.css` around lines 432 - 434, The infinite
animation defined by the CSS rule .animate-clone-indeterminate (animation:
clone-indeterminate 1.4s ease-in-out infinite) must respect users' OS reduced
motion setting; add a prefers-reduced-motion: reduce media query that targets
.animate-clone-indeterminate and disables the animation (e.g., set animation:
none and animation-duration: 0 or similar, optionally adding !important to
override) so the animation is turned off for users who prefer reduced motion.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/MultiSelectContext/MultiSelectContext.tsx (1)

66-77: Shift+クリック時にアンカーがない場合の動作を検討

anchorPathnull の状態で Shift+クリックすると、条件をスキップして Cmd/Ctrl の分岐もスキップし、最終的に通常クリックとして処理されます。結果として anchorPath が設定され、次回の Shift+クリックで範囲選択が機能しますが、初回の Shift+クリックでは単一選択ではなくクリアされる動作になります。

VS Code の動作と同様にするなら、アンカーがない状態での Shift+クリックはクリックした項目を選択しつつアンカーを設定する方が直感的かもしれません。

♻️ 提案: アンカーがない場合の Shift+クリック処理
 const handleClick = useCallback<MultiSelectApi["handleClick"]>(
   (path, event) => {
-    if (event.shiftKey && anchorPath) {
+    if (event.shiftKey) {
+      if (!anchorPath) {
+        // No anchor yet: select clicked item and set as anchor
+        setSelectedPaths(new Set([path]));
+        setAnchorPath(path);
+        return "multi";
+      }
       const paths = filesRef.current.map((f) => f.path);
       const fromIdx = paths.indexOf(anchorPath);
       const toIdx = paths.indexOf(path);
🤖 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/RightSidebar/ChangesView/components/MultiSelectContext/MultiSelectContext.tsx`
around lines 66 - 77, When Shift+click occurs but anchorPath is null, modify the
handleClick (MultiSelectApi["handleClick"]) logic so it doesn't fall through to
clearing selection; instead, set the clicked item as selected and initialize the
anchor. Concretely, inside the shiftKey branch detect if anchorPath is falsy and
then call setSelectedPaths(new Set([path])) and setAnchorPath(path) (or the
component's equivalent) and return the appropriate action string (e.g., "single"
or "multi") so subsequent Shift+clicks will perform range selection; keep the
existing range-selection behavior when anchorPath is present.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/BulkActionBar/BulkActionBar.tsx (1)

33-46: アクセシビリティ: アクションボタンに aria-label の追加を検討

クリア選択ボタン(Line 82)には aria-label がありますが、Stage/Unstage/Discard ボタンには title 属性のみです。スクリーンリーダーの一貫性のために aria-label の追加を検討してください。

♻️ 例: Stage ボタン
 <button
   type="button"
   onClick={() => {
     onStageSelected(files);
     ctx.clear();
   }}
   disabled={isActioning}
   title="Stage selected"
+  aria-label="Stage selected"
   className="..."
 >
🤖 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/RightSidebar/ChangesView/components/BulkActionBar/BulkActionBar.tsx`
around lines 33 - 46, The Stage button (JSX using VscAdd and calling
onStageSelected(files) then ctx.clear()) only has a title attribute but lacks an
aria-label; add an aria-label attribute (e.g., aria-label="Stage selected") to
this button and do the same for the Unstage and Discard action buttons in the
BulkActionBar component so screen readers get consistent labels—ensure the
aria-label text matches the visible title/tooltips and update the corresponding
JSX for each action button.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/BrowserFindOverlay.tsx (1)

29-37: Match Case トグルに押下状態を公開してください。

見た目の active 状態はありますが、支援技術には現在状態が伝わりません。aria-pressed と明示的なラベルを付けておくと操作性が上がります。

♿ 例
 		<button
 			type="button"
 			onClick={onClick}
 			title={title}
+			aria-label={title}
+			aria-pressed={active}
 			className={`inline-flex h-5 w-5 items-center justify-center rounded text-[11px] font-medium leading-none transition-colors ${

Also applies to: 101-107

🤖 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/TabView/BrowserPane/components/BrowserFindOverlay/BrowserFindOverlay.tsx`
around lines 29 - 37, The Match Case toggle in BrowserFindOverlay renders a
button with visual active styling but doesn't expose its pressed state to
assistive tech; update the button (in BrowserFindOverlay) to include
aria-pressed={active} and an explicit aria-label (e.g., `${title}, ${active ?
"pressed" : "not pressed"}` or a localized equivalent) so screen readers receive
state + purpose, and apply the same change to the other similar toggle buttons
referenced around lines 101-107 (use the relevant title/label and their active
state props).
🤖 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/lib/trpc/routers/projects/projects.ts`:
- Around line 353-359: 現在の cloneEventBus/emitCloneEvent 実装は購読開始前に発生した
log/progress/即時 error を取りこぼすため、cloneId ごとに最新の progress と terminal
event(error/complete)をバッファして購読開始時に再送するか、購読確立ハンドシェイクを導入してから clone
を開始するよう修正してください。具体的には cloneEventBus と emitCloneEvent の振る舞いを変更して(1)内部 Map で
cloneId → {lastProgress?, terminalEvent?} を保持し(2)購読用の subscribe/onceSubscribe
API を追加してサブスクライブ時にバッファを即時再送し、さらに必要なら clone を開始する cloneRepo 呼び出し側(例: CloneRepoTab
の cloneId
設定フロー)を購読確立後にトリガーするよう調整してください。同様の修正をファイル内の該当箇所(コメントで指摘の別ブロック)にも適用してください。
- Around line 1401-1453: The code leaves a partial clone directory at clonePath
on abort/failure which causes subsequent attempts to be skipped by the "existing
folder" check; update the streaming branch (the try/catch around
gitWithProgress.clone) to remove the created clonePath directory on error/abort
before emitting the error and rethrowing (use cloneId, abortController,
gitWithProgress, emitCloneEvent to locate where to add cleanup), and similarly
wrap the non-streaming branch (where getSimpleGitWithShellPath().clone is
called) in a try/catch that deletes clonePath on failure before rethrowing;
ensure the cleanup only deletes the intended directory (check existence) and
that cloneAbortControllers.delete(cloneId) remains in finally.

In
`@apps/desktop/src/renderer/routes/_authenticated/_onboarding/new-project/components/CloneRepoTab/CloneRepoTab.tsx`:
- Around line 212-217: handleCancel currently sets wasCanceledRef.current = true
and setStatus("canceled") immediately before calling cancelClone.mutate, causing
the UI to show canceled even if the cancel RPC fails; change handleCancel so it
only calls cancelClone.mutate({ cloneId }, { onSuccess: () => {
wasCanceledRef.current = true; setStatus("canceled"); }, onError: () => { /*
keep status "cloning" and surface an error if needed */ } }) and remove the
premature assignments from the top of handleCancel so the state is updated only
when cancelClone succeeds.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx`:
- Around line 795-806: The dragDiagnosticHandler currently enables
logDragAutoscrollDiagnostics on every left mousedown (registered via
EditorView.domEventHandlers) which causes ancestor walks and layout reads in
production; change it so logDragAutoscrollDiagnostics is only attached when a
debug flag or dev build is active (e.g., guard creation of dragDiagnosticHandler
with a runtime DEBUG or process.env.NODE_ENV !== 'production' check, or add a
boolean prop like enableDragDiagnostics) and ensure EditorState.create uses the
conditional extension only when enabled; reference the symbols
dragDiagnosticHandler, logDragAutoscrollDiagnostics,
EditorView.domEventHandlers, and EditorState.create when making the change.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/BrowserFindOverlay.tsx`:
- Around line 58-63: The current useEffect in BrowserFindOverlay only
focuses/selects inputRef when isOpen changes, so pressing Cmd/Ctrl+F again while
the overlay is already open does not refocus the input; add a keydown listener
(registered when isOpen is true and removed on cleanup) inside the
BrowserFindOverlay component that listens for the Cmd/Ctrl+F key combo and calls
inputRef.current?.focus() and inputRef.current?.select(), or extract that logic
into a named handler (e.g., handleFocusFindInput) and invoke it both from the
existing useEffect and from the keydown listener to ensure repeated Cmd/Ctrl+F
always returns focus to the input.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx`:
- Around line 467-470: Focus ハンドラで activeEditorRef.current
のみ更新しているためペイン切替時にオーバーレイの matchCount / activeMatchIndex
が更新されません。EditorView.domEventHandlers の focus ハンドラ(focusTracker の定義箇所)内で
activeEditorRef.current = view の直後に syncSearchOverlayStateRef.current?.()
を呼び出して検索オーバーレイの状態を同期するようにしてください(関数が存在するかを optional call で確認すること)。
- Around line 274-284: MergeView rebuilds leave stale search state and overlays
pointing at the old EditorView; update the teardown/create logic so that when
MergeView is destroyed or recreated you reset search-related refs/state (clear
activeEditorRef.current, set isSearchOpenRef.current = false, set
syncSearchOverlayStateRef.current = null, reset
searchMatchCount/activeSearchMatchIndex) and/or immediately reapply the current
search query into the newly created EditorView by invoking the search overlay
sync (use the existing syncSearchOverlayStateRef callback or a new
applySearchToEditor function right after MergeView creation) to ensure overlays
and F3/Mod-g target the new EditorView (affects activeEditorRef,
isSearchOpenRef, syncSearchOverlayStateRef and search state handling around
MergeView construction/destruction).

---

Outside diff comments:
In `@apps/desktop/src/main/lib/browser/browser-manager.ts`:
- Around line 55-60: 修正対象は再登録時のリスナー掃除ループで、現在 this.findListeners が含まれておらず古い
before-input-event / found-in-page ハンドラが残るため、for ループの配列(this.consoleListeners,
this.contextMenuListeners, this.fullscreenListeners, this.popupListeners)に
this.findListeners を追加して、prevId !== webContentsId の分岐で find リスナーも正しく
remove/cleanup されるようにする(該当箇所は browser-manager.ts の該当ループと同様の別箇所(行
~107)にも同じ変更を適用すること)。

---

Nitpick comments:
In `@apps/desktop/src/renderer/globals.css`:
- Around line 432-434: The infinite animation defined by the CSS rule
.animate-clone-indeterminate (animation: clone-indeterminate 1.4s ease-in-out
infinite) must respect users' OS reduced motion setting; add a
prefers-reduced-motion: reduce media query that targets
.animate-clone-indeterminate and disables the animation (e.g., set animation:
none and animation-duration: 0 or similar, optionally adding !important to
override) so the animation is turned off for users who prefer reduced motion.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/BrowserFindOverlay.tsx`:
- Around line 29-37: The Match Case toggle in BrowserFindOverlay renders a
button with visual active styling but doesn't expose its pressed state to
assistive tech; update the button (in BrowserFindOverlay) to include
aria-pressed={active} and an explicit aria-label (e.g., `${title}, ${active ?
"pressed" : "not pressed"}` or a localized equivalent) so screen readers receive
state + purpose, and apply the same change to the other similar toggle buttons
referenced around lines 101-107 (use the relevant title/label and their active
state props).

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/BulkActionBar/BulkActionBar.tsx`:
- Around line 33-46: The Stage button (JSX using VscAdd and calling
onStageSelected(files) then ctx.clear()) only has a title attribute but lacks an
aria-label; add an aria-label attribute (e.g., aria-label="Stage selected") to
this button and do the same for the Unstage and Discard action buttons in the
BulkActionBar component so screen readers get consistent labels—ensure the
aria-label text matches the visible title/tooltips and update the corresponding
JSX for each action button.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/MultiSelectContext/MultiSelectContext.tsx`:
- Around line 66-77: When Shift+click occurs but anchorPath is null, modify the
handleClick (MultiSelectApi["handleClick"]) logic so it doesn't fall through to
clearing selection; instead, set the clicked item as selected and initialize the
anchor. Concretely, inside the shiftKey branch detect if anchorPath is falsy and
then call setSelectedPaths(new Set([path])) and setAnchorPath(path) (or the
component's equivalent) and return the appropriate action string (e.g., "single"
or "multi") so subsequent Shift+clicks will perform range selection; keep the
existing range-selection behavior when anchorPath is present.
🪄 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: a01a6471-c1fa-4668-a91e-6f6cae53adb8

📥 Commits

Reviewing files that changed from the base of the PR and between 252a926 and 0031464.

📒 Files selected for processing (17)
  • apps/desktop/src/lib/trpc/routers/browser/browser.ts
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/main/lib/browser/browser-manager.ts
  • apps/desktop/src/renderer/globals.css
  • apps/desktop/src/renderer/routes/_authenticated/_onboarding/new-project/components/CloneRepoTab/CloneRepoTab.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/PersistentTabRenderer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/BrowserFindOverlay.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/BulkActionBar/BulkActionBar.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/BulkActionBar/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileItem/FileItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/MultiSelectContext/MultiSelectContext.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/MultiSelectContext/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/useOrderedSections/useOrderedSections.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx

Comment thread apps/desktop/src/lib/trpc/routers/projects/projects.ts
Comment thread apps/desktop/src/lib/trpc/routers/projects/projects.ts
P1:
- projects: cleanup partial clonePath on git clone abort/failure so retry
  is not blocked by the existing-folder guard
- projects: buffer clone progress events per cloneId with monotonic seq
  so subscribers established after the mutation starts do not miss
  early events; dedupe replay via seq
- CodeMirrorDiffViewer: reset search state/refs on MergeView teardown to
  avoid pointing at a destroyed EditorView after original/modified/language
  change
- CodeEditor: gate drag-autoscroll diagnostic logging behind
  localStorage.debug:code-editor-drag flag

P2:
- BrowserPane: hoist Cmd+F keydown capture to the pane root so the shortcut
  also fires when focus is in the toolbar / URL bar
- BrowserFindOverlay: expose imperative focusInput handle; re-Cmd+F now
  refocuses the input even while overlay is already open
- CloneRepoTab: only transition to "canceled" after cancelClone mutation
  confirms, otherwise leave status as cloning
- CodeMirrorDiffViewer: sync overlay match count when focus moves between
  a/b sides
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: 2

🧹 Nitpick comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx (2)

421-421: biome-ignore コメントの説明が実際の動作と若干異なります。

コメントでは「MergeView is created once and destroyed on unmount」と記載されていますが、依存配列 [original, modified, language, viewMode] により、これらの値が変更されると MergeView は再作成されます。lint 抑制自体は正当ですが、コメントを「MergeView is recreated when content or view mode changes」などに更新すると、コードの意図がより明確になります。

📝 コメントの修正案
-	// biome-ignore lint/correctness/useExhaustiveDependencies: MergeView is created once and destroyed on unmount
+	// biome-ignore lint/correctness/useExhaustiveDependencies: Helper functions use refs to avoid stale closures; MergeView is recreated when deps change
🤖 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/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx`
at line 421, Update the biome-ignore comment above the MergeView creation to
accurately reflect behavior: change the message from "MergeView is created once
and destroyed on unmount" to something like "MergeView is recreated when
original, modified, language, or viewMode change (cleanup on unmount)" so it
matches the dependency array [original, modified, language, viewMode] and the
actual lifecycle of the MergeView instance in CodeMirrorDiffViewer (look for the
MergeView initialization and the lint suppression comment).

321-343: 検索マッチの列挙処理は許容範囲内のパフォーマンスです。

syncSearchOverlayStateSEARCH_MATCH_LIMIT (10,000件) で上限が設けられており、大規模ドキュメントでも無限ループにはなりません。ただし、選択変更・ドキュメント変更のたびにマッチを再列挙するため、非常に多くのマッチがある場合はわずかな遅延が生じる可能性があります。現状の実装で問題が発生した場合は、デバウンスやキャッシュの導入を検討してください。

🤖 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/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx`
around lines 321 - 343, syncSearchOverlayState currently re-enumerates all
matches on every selection/document change (inside syncSearchOverlayState using
getActiveEditor(), getSearchQuery(), and the cursor loop up to
SEARCH_MATCH_LIMIT), which can cause UI jank with many matches; debounce calls
to syncSearchOverlayState (e.g., wrap invocations in a short debounce) and
implement a simple cache keyed by the editor document version + query (store
computed matches, match count, and active index) so you only re-enumerate when
the document version or query changes; ensure you still compute active match
index from cached matches using getActiveSearchMatchIndex and preserve behavior
when query.valid is false.
🤖 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/lib/trpc/routers/projects/projects.ts`:
- Around line 1495-1521: The code currently emits and logs input.url (which can
include credentials) via emitCloneLog and emitCloneEvent; change it to parse
input.url and produce a sanitizedUrl with the userinfo removed/ redacted, use
sanitizedUrl in all calls to emitCloneLog and emitCloneEvent (and any error
messages) while still passing the original input.url only to
gitWithProgress.clone; update the catch block where cloneError and message are
emitted to use the same sanitizedUrl; apply the same sanitization and
replacement in the other similar block around the emitCloneEvent usage (the one
at the second location noted) so no renderer-bound logs include userinfo.

In
`@apps/desktop/src/renderer/routes/_authenticated/_onboarding/new-project/components/CloneRepoTab/CloneRepoTab.tsx`:
- Around line 63-68: The subscription is being disabled based on status which
can flip before final events arrive, losing terminal log entries; change the
electronTrpc.projects.cloneProgress.useSubscription calls (e.g., the one
currently using isActive = status === "cloning" and its enabled arg) to base the
subscription lifetime on cloneId presence instead (enabled: cloneId !== null) so
the subscription stays open until cloneId is cleared, and ensure you still pass
a valid param shape (use { cloneId } when cloneId exists, otherwise undefined)
for all occurrences (also update the other two useSubscription sites referenced
around the later blocks).

---

Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx`:
- Line 421: Update the biome-ignore comment above the MergeView creation to
accurately reflect behavior: change the message from "MergeView is created once
and destroyed on unmount" to something like "MergeView is recreated when
original, modified, language, or viewMode change (cleanup on unmount)" so it
matches the dependency array [original, modified, language, viewMode] and the
actual lifecycle of the MergeView instance in CodeMirrorDiffViewer (look for the
MergeView initialization and the lint suppression comment).
- Around line 321-343: syncSearchOverlayState currently re-enumerates all
matches on every selection/document change (inside syncSearchOverlayState using
getActiveEditor(), getSearchQuery(), and the cursor loop up to
SEARCH_MATCH_LIMIT), which can cause UI jank with many matches; debounce calls
to syncSearchOverlayState (e.g., wrap invocations in a short debounce) and
implement a simple cache keyed by the editor document version + query (store
computed matches, match count, and active index) so you only re-enumerate when
the document version or query changes; ensure you still compute active match
index from cached matches using getActiveSearchMatchIndex and preserve behavior
when query.valid is false.
🪄 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: f8c5d733-7553-4747-9271-68a981b479f2

📥 Commits

Reviewing files that changed from the base of the PR and between 0031464 and 5a57251.

📒 Files selected for processing (7)
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/renderer/routes/_authenticated/_onboarding/new-project/components/CloneRepoTab/CloneRepoTab.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/BrowserFindOverlay.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx
✅ Files skipped from review due to trivial changes (2)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/BrowserPane.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/BrowserPane/components/BrowserFindOverlay/BrowserFindOverlay.tsx

Comment thread apps/desktop/src/lib/trpc/routers/projects/projects.ts Outdated
- projects: redact credentials from git URLs before logging so PATs /
  basic auth tokens embedded in the clone URL never reach the renderer
  via progress logs or error messages
- CloneRepoTab: base the cloneProgress subscription lifetime on cloneId
  presence instead of status, so terminal log entries arriving after
  status flips in cloneRepo/cancelClone callbacks are still delivered
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: 1

🤖 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/lib/trpc/routers/projects/projects.ts`:
- Around line 1510-1514: emitCloneEvent({type: "done", cloneId, time:
Date.now()}) を現行の位置から移動して、プロジェクト作成とワークスペース初期化が確実に成功した直後にのみ送るようにしてください: 具体的には
getDefaultBranch の取得、DB insert(プロジェクト保存処理)、ensureProjectGitHubOwner と
ensureMainWorkspace のすべてが成功した後に emitCloneEvent
を呼び出すように順序を変更し、失敗時はエラーイベントを送るか例外を伝搬させて renderer に誤って「Clone
complete」を記録させないようにしてください(参照シンボル: emitCloneEvent, getDefaultBranch,
ensureProjectGitHubOwner, ensureMainWorkspace, cloneId)。
🪄 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: 2974defa-0f6b-4c7b-abc8-8d6f1620fcaf

📥 Commits

Reviewing files that changed from the base of the PR and between 5a57251 and c251f3d.

📒 Files selected for processing (2)
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/renderer/routes/_authenticated/_onboarding/new-project/components/CloneRepoTab/CloneRepoTab.tsx

Comment thread apps/desktop/src/lib/trpc/routers/projects/projects.ts Outdated
Previously `{type: "done"}` was emitted right after `git clone` returned,
so the renderer logged "Clone complete" even if getDefaultBranch / DB
insert / ensureProjectGitHubOwner / ensureMainWorkspace subsequently
failed. Move the terminal success event to after the project row and
workspace are fully created, and have the outer catch emit an error
event (when no terminal event has been produced yet) so post-clone
failures are still reported through the streaming channel.
- projects: use distributive Omit so CloneEventInput preserves the
  discriminated union shape; the previous Omit<Union, 'seq'> collapsed
  into the common intersection and broke emitCloneEvent callers that
  passed type-specific fields (message/stage/etc)
- MultiSelectContext: re-order barrel exports per biome organizeImports
- CodeEditor: unconditionally log drag-autoscroll scrollableParents walk
  on mousedown (removed localStorage gate) so the race is visible in any
  session without setup
- CodeEditor: add scrollSearchMatchToCenter to the overlay find
  handlers — the file viewer was missing center-scroll behaviour
  entirely (only the DiffViewer had it). Log head / scrollTop /
  matchCoordsY before + after the dispatch so we can see whether the
  effect is applied and where it lands
- CodeMirrorDiffViewer: same before/after logging around the existing
  scrollIntoView center dispatch to investigate why navigation is not
  visibly centering
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: 2

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/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx (1)

858-922: ⚠️ Potential issue | 🟠 Major

searchMode 切り替え時に CodeMirror の検索実装が再構成されていません。

search() 拡張と overlay 用の keymap は EditorState.create() で固定されており、searchMode 変更時に reconfigure されていません。他の機能(theme、editable、language など)は Compartment パターンで適切に再構成されていますが、search 機能はこのパターンに従っていません。結果として、native-paneloverlay を切り替えても UI の表示のみ更新され、CodeMirror 側のプラグインと keymap は元の状態のまま残ります。

searchCompartment を作成して search 拡張と keymap を Compartment でラップし、useEffect で searchMode 変更時に reconfigure するか、あるいは editor 全体を作り直す必要があります。

🤖 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/components/CodeEditor/CodeEditor.tsx`
around lines 858 - 922, The search extension and overlay-specific keymap are
currently created statically inside the editor init and aren’t reconfigured when
searchMode changes; create a new Compartment (e.g., searchCompartment) and move
the search(...) extension plus the conditional overlay keymap entries into that
compartment, then in a useEffect that depends on searchMode call
editorView.dispatch({ effects: searchCompartment.reconfigure(...) }) (or
editor.reconfigure if using EditorState) to swap between the overlay config and
the native-panel config; ensure you reference the existing symbols search(),
searchMode,
ensureOverlaySearchOpen/handleOverlayFindNext/handleOverlaySearchClose, and the
keymap block so the overlay keys are added/removed when reconfiguring.
♻️ Duplicate comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx (1)

831-846: ⚠️ Potential issue | 🟠 Major

ドラッグ診断ハンドラを常時登録しないでください。

ここは全ユーザーの左クリックごとに ancestor walk、getComputedStylegetBoundingClientRect()console.log を実行します。テキスト選択のホットパスなので、調査用なら dev build か明示フラグでのみ extension を追加する形に戻した方が安全です。

🤖 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/components/CodeEditor/CodeEditor.tsx`
around lines 831 - 846, The dragDiagnosticHandler (created via
EditorView.domEventHandlers and calling logDragAutoscrollDiagnostics) is always
added into the EditorState.create extensions, causing expensive ancestor walks
and geometry/style reads on every left-click; only add this extension when
running under a dev/debug mode or an explicit diagnostic flag. Modify the code
that builds the extensions array passed to EditorState.create to conditionally
push dragDiagnosticHandler (or call a helper like isDragDiagnosticsEnabled())
only when a dev build or feature-flag evaluates true (e.g., NODE_ENV ===
'development' or a dedicated runtime flag), leaving production builds to omit
the handler entirely.
🤖 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/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx`:
- Around line 723-756: The debug console.logs and expensive measurements inside
scrollSearchMatchToCenter are always executed on every search; guard them behind
a debug flag (e.g., isDebugSearch or a logger.debug check) so normal F3/Mod-g
searches don't call coordsAtPos/getBoundingClientRect or log. In function
scrollSearchMatchToCenter, wrap the initial coordsAtPos/getBoundingClientRect
and the two console.log blocks (the "before scrollIntoView" and the
queueMicrotask "after scrollIntoView") in a conditional that checks the debug
flag (or use an existing debug logger method) so these calculations and logs run
only when debugging is enabled. Ensure the scrollIntoView dispatch remains
unconditional.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx`:
- Around line 383-417: The search-navigation diagnostic logs and expensive
layout measurements inside scrollActiveSelectionToCenter (calls to
view.coordsAtPos, scroller.getBoundingClientRect(), and the two console.log
statements) must be removed or gated behind a debug flag; update the function so
these measurements and console.logs only run when a debug/dev mode is enabled
(e.g. check a DEBUG or isDev boolean) or use the existing logging facility
instead of console.log; keep the actual scrollIntoView dispatch always, but
avoid calling coordsAtPos/getBoundingClientRect and avoid logging in
production—reference scrollActiveSelectionToCenter, view.coordsAtPos,
scroller.getBoundingClientRect, and mergeViewRef to locate and guard/remove the
diagnostic code.

---

Outside diff comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx`:
- Around line 858-922: The search extension and overlay-specific keymap are
currently created statically inside the editor init and aren’t reconfigured when
searchMode changes; create a new Compartment (e.g., searchCompartment) and move
the search(...) extension plus the conditional overlay keymap entries into that
compartment, then in a useEffect that depends on searchMode call
editorView.dispatch({ effects: searchCompartment.reconfigure(...) }) (or
editor.reconfigure if using EditorState) to swap between the overlay config and
the native-panel config; ensure you reference the existing symbols search(),
searchMode,
ensureOverlaySearchOpen/handleOverlayFindNext/handleOverlaySearchClose, and the
keymap block so the overlay keys are added/removed when reconfiguring.

---

Duplicate comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx`:
- Around line 831-846: The dragDiagnosticHandler (created via
EditorView.domEventHandlers and calling logDragAutoscrollDiagnostics) is always
added into the EditorState.create extensions, causing expensive ancestor walks
and geometry/style reads on every left-click; only add this extension when
running under a dev/debug mode or an explicit diagnostic flag. Modify the code
that builds the extensions array passed to EditorState.create to conditionally
push dragDiagnosticHandler (or call a helper like isDragDiagnosticsEnabled())
only when a dev build or feature-flag evaluates true (e.g., NODE_ENV ===
'development' or a dedicated runtime flag), leaving production builds to omit
the handler entirely.
🪄 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: ebde9dfb-a6be-4781-99f5-50ee047d3e13

📥 Commits

Reviewing files that changed from the base of the PR and between e433cf4 and 7c3a39a.

📒 Files selected for processing (2)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsx

CM6's find commands dispatch `scrollIntoView: true` (y: "nearest")
synchronously as part of the selection update. Dispatching a second
`y: "center"` effect in the same tick causes CM to coalesce them and
the nearest scroll wins — which is exactly what the diagnostic logs
(matchCoordsY unchanged, small scrollTop deltas) showed for both the
CodeEditor and CodeMirrorDiffViewer navigation paths.

Schedule the center scroll in requestAnimationFrame so runFindNext's
scroll has settled first, then dispatch `y: "center"` from the updated
head position. Log scrollTop and coordsAtPos before + after in both
frames so we can verify the effect applied and whether centering lands
at the expected coordinate.

Also widen the drag-autoscroll diagnostic: mousedown now attaches a
scoped mousemove + scrollDOM scroll listener (both released on mouseup)
so we can capture spurious scrolls that happen while dragging — the
initial mousedown position consistently reports `nearEdgeAtStart: false`
so the trigger must be in a subsequent mousemove inside the 6px edge
band.
## drag-autoscroll spurious triggering

Diagnostic logs showed CM firing drag-select autoscroll at +8px/tick
(dragScrollSpeed's baseline) even while the pointer was hundreds of
pixels from any edge. Tracked the cause to the hidden search panel:

- `@codemirror/search` defaults `top: false`, so the hidden search panel
  lives in the BOTTOM PanelGroup
- `PanelGroup.scrollMargin()` for a bottom group is
  `min(innerHeight, scrollDOM.bottom) - panelDom.top`
- The previous `createHiddenSearchPanel` hid the container with
  `display: none`, which makes `getBoundingClientRect()` return
  `{top: 0, bottom: 0, ...}`
- That leaves `scrollMargin = scrollDOM.bottom - 0 ≈ scroller height`,
  i.e. the drag autoscroll's "bottom edge" effectively collapses to
  y=0 and every mousemove inside the viewport satisfies the edge check

Replace `display: none` with a zero-height in-flow collapse (height:0,
visibility:hidden, overflow:hidden). The panel stays at scroller.bottom
so scrollMargin resolves to ~0, and drag autoscroll only fires when
the pointer is genuinely within 6px of the scroller edge.

Same fix mirrored in CodeMirrorDiffViewer's local copy of
createHiddenSearchPanel.

## search center scroll

Follow-up `scrollIntoView({y: "center"})` dispatches remained flaky on
huge virtualized files — logs showed `delta: 43` and the match still at
clientY=-63 after the dispatch. `view.coordsAtPos(head)` returns
estimated coords for un-rendered lines, and CM's internal scroll math
uses those estimates, producing a near-noop instead of the large jump
needed to center a line tens of thousands of pixels away.

Replace the `scrollIntoView` effect with a manual `scrollDOM.scrollTop`
write computed from `view.lineBlockAt(head)`, which is backed by CM's
own doc-relative line-height cache (maintained by the measure cycle and
always accurate for `scrollTop` math). Same refactor in DiffViewer.
The root causes are now fixed (hidden search panel no longer uses
display:none so PanelGroup.scrollMargin resolves correctly, and search
navigation uses view.lineBlockAt + manual scrollTop to sidestep CM's
virtualized-content coordsAtPos estimation). Strip the mousedown/
mousemove/scroll telemetry and before/after scrollIntoView logging —
the functional fixes stay in place.
Each FileList variant re-sorts files internally — compact sorts by
filename, grouped sorts by folder+name, tree builds a DFS-ordered tree
with folders first. The MultiSelectProvider, however, was wrapping the
list with the raw unordered `files` array, so `indexOf(anchor)` /
`indexOf(clicked)` for shift-click range selection sliced the wrong
positions and produced the "sparse / flickering" selections the user
reported.

Extract `orderFilesForViewMode` that mirrors each variant's sort (and
flattens tree DFS) and thread the ordered arrays through
`useOrderedSections` to the provider. FileList variants keep their own
internal sort; the helper is the single source of truth for the
visual order used by range selection.

Tree mode approximates "all folders expanded" DFS order — collapsed
folders still contribute their files to the range, which is acceptable
until expand/collapse state is threaded through the context.
1. Hover / context-menu actions on a selected file apply to the whole
   multi-selection.
   - MultiSelectProvider now accepts onStageSelected / onUnstageSelected
     / onDiscardSelected and exposes them through the context.
   - FileItem reads the context at action-invocation time: if the file
     is part of a selection of 2+, it runs the bulk handler with
     `ctx.selectedFiles` and clears the selection; otherwise it runs
     the single-file handler. Clicking an action on a non-selected
     file still targets only that file, matching VS Code.
   - Context menu and hover labels reflect the bulk count
     ("Stage 5 files" etc.) when applicable.
   - The standalone BulkActionBar component is removed — the
     VS Code-native hover / context flow replaces it.

2. Shift-click without a prior anchor picks the first visible file as
   the anchor so the range extends from the top of the list down to
   the clicked item (VS Code fallback).

3. Range selection order is DOM-driven: FileItem carries a
   `data-multi-select-path` attribute, and MultiSelectProvider wraps
   children in a `display: contents` div that scopes
   `querySelectorAll('[data-multi-select-path]')` to its subtree.
   DOM order naturally reflects what the user sees — including tree
   view's collapsed-folder filtering, which the previous static
   `orderFilesForViewMode` helper couldn't handle. For virtualized
   lists where the anchor has scrolled out of the DOM the resolver
   falls back to the precomputed flat order so shift-click still
   works across large change sets.
Adds the first batch of VS Code Git settings — `git.enableSmartCommit`
and `git.smartCommitChanges` — under Settings → Git & Worktrees.

When enabled and the staging area is empty, the Commit button auto-
stages all unstaged changes (`git add -A`) or tracked-only changes
(`git add -u`) before the commit runs. The button label flips to
`Commit All (N)` and the tooltip explains which files will be staged,
so the auto-stage path is visually distinct from a regular commit.
No confirmation dialog is shown — the setting itself is opt-in.

Changes:
- schema: `enableSmartCommit`, `smartCommitChanges` columns on settings
  (migration 0045)
- zod: `SMART_COMMIT_CHANGES_MODES = ["all", "tracked"]` + type (the
  VS Code `"none"` option is dropped since it is redundant with the
  top-level toggle)
- security/git-commands: new `gitStageTracked` helper (`git add -u`)
- staging router: `stageTracked` mutation
- settings router: `getSmartCommit` / `setSmartCommit`
- CommitInput: reads setting, relaxes `canCommit`, runs stage mutation
  before commit mutation in the smart path, overrides primary button
  label + tooltip, includes stage mutations in `isPending`
- GitSettings: Switch + Select UI; settings-search entry for discovery
Adds `git.autoStash` to Settings → Git & Worktrees. When enabled, the
pull and sync actions in CommitInput orchestrate:

  stashIncludeUntracked → pull/sync → stashPop

around the network op whenever the working tree has local changes
(staged, unstaged, or untracked). A clean tree skips the stash step.

Failure handling favours data preservation:

- If the network op fails, the stash is left intact on the stack and
  the user sees a `auto-stash-pull-failed` Japanese info dialog
  telling them to run `git stash pop` manually once they're ready.
- If stash pop fails (usually a conflict), an `auto-stash-pop-conflict`
  Japanese dialog appears with the same guidance.

The default pull/sync error handlers bail out via an `autoStashInFlight`
ref so the built-in pull error dialog doesn't fire on top of the
auto-stash dialogs.

Also:
- schema: `autoStash` boolean column on settings (migration 0046)
- settings router: `getAutoStash` / `setAutoStash`
- GitSettings: Switch with description
- settings-search: `GIT_AUTO_STASH` entry for in-settings search
- `isPending` now includes stashIncludeUntracked / stashPop so the
  primary button correctly disables during the orchestration

The existing manual `stashAndRetry` action on the pull error dialog is
kept for the auto-stash=off case.
Adds `git.branchSortOrder` + `git.pinDefaultBranch` settings under
Settings → Git & Worktrees. The base branch picker now orders
branches by committer date (newest first) or alphabetically (case
insensitive), and optionally pins the repo's default branch at the
top regardless of sort order.

Remote-only branches are included too — `host-service.git.listBranches`
reads `git branch -r --sort=...` in addition to local refs, dedupes
remote entries whose name already exists locally, and flags the rest
with `isRemote: true`. BaseBranchSelector displays them as
`origin/<name>` with a muted "remote" tag so they're distinguishable
from locals while `branch.name` stays in short-name form (the same
shape `git.listCommits` / `git.getStatus` already consume as a
baseBranch input).

Changes:
- schema: `branchSortOrder` + `pinDefaultBranch` columns on settings
  (migration 0047)
- zod: `BRANCH_SORT_ORDERS = ["committerdate", "alphabetical"]`
- settings router: `getBranchSortOrder` / `setBranchSortOrder`
- host-service: `listBranches` accepts `sortOrder` + `pinDefault`,
  returns local + remote-only branches each with `isRemote`; uses
  `git branch --sort=-committerdate` for the committerdate case and a
  JS `localeCompare({ sensitivity: "base" })` pass for alphabetical;
  pin step moves the default branch to the head of the list
- Branch type: new `isRemote: boolean` field; buildBranch accepts an
  `isRemote` option and skips the upstream probe for remote-tracking
  entries, reading commit metadata via `origin/<name>`
- useChangesTab: reads the setting via electronTrpc and threads it
  into the listBranches query key
- BaseBranchSelector: displays `origin/<name>` for remote entries and
  labels them; dedup key includes the local/remote flag
- GitSettings: Select (committerdate / alphabetical) + Switch for pin
- settings-search: `GIT_BRANCH_SORT_ORDER` entry
Adds `git.postCommitCommand` to Settings → Git & Worktrees. When set
to `push` or `sync`, a successful commit automatically chains into
the corresponding network op.

Chaining goes through the existing push / handleSync paths so all the
established UX pieces stay intact:

- pushMutation's onSuccess → toast + warning dialog + PR follow-ups
- pushMutation's onError → retry / pullRebaseAndRetryPush dialog
- handleSync routes through the auto-stash orchestrator, so
  `postCommitCommand: "sync"` composes correctly with `git.autoStash`
  (commit → stash → sync → pop)
- Smart Commit's stage → commit path also triggers post-commit because
  the chain is wired on the commit mutation's mutate-side onSuccess
  (so it fires after both the smart and plain commit flows)

Changes:
- schema: `postCommitCommand` text column (migration 0048)
- zod: `POST_COMMIT_COMMANDS = ["none", "push", "sync"]`
- settings router: `getPostCommitCommand` / `setPostCommitCommand`
- CommitInput: query the setting, mirror into a ref, and invoke push /
  handleSync from a new `runPostCommitCommand` helper inside the
  commit mutation's onSuccess options
- GitSettings: Select (None / Push / Sync) with description
- settings-search: `GIT_POST_COMMIT_COMMAND` entry
The `commit-identity-missing` dialog that PR #153 introduced told the
user to run `git config` in a terminal, which meant leaving the app
mid-commit. Replace that instruction with an inline form inside the
existing unified GitOperationDialog so the user can set user.name /
user.email and retry the commit without context switching.

- `getGitAuthorEmail` helper added next to the existing
  `getGitAuthorName`; `settings.getGitInfo` now returns it too.
- `settings.setGlobalGitUserConfig` mutation: writes `user.name` and
  `user.email` to the global git config via `simple-git.addConfig(...,
  "global")`.
- `MissingGitUserConfigForm` component: two inputs + a "保存して再試行"
  button. On success it fires the caller-provided `onSaved` callback
  so the commit retry runs immediately with the new identity.
- `gitErrorDialog` is now a `.tsx` so it can render the form as the
  dialog's `extraContent`. The old "設定後に再試行" primary button is
  removed — the form's own save button owns the retry flow to avoid
  a stale retry that would hit the same error again.
- Dialog is closed before firing retry so that any subsequent failure
  (`commit-identity-missing` classifier hit a second time, network
  error, etc) can open a fresh dialog from its own onError.
@MocA-Love MocA-Love marked this pull request as draft April 13, 2026 08:59
@MocA-Love MocA-Love marked this pull request as ready for review April 13, 2026 08:59
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: 7058fb7a2d

ℹ️ 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".

Comment thread apps/desktop/src/main/lib/browser/browser-manager.ts
B (修正必須) CommitInput: handleCommitAndPush / handleCommitPushAndCreatePR
  Extract the Smart Commit staging branch into runCommitWithCallback so
  that Commit+Push and Commit+Push+PR use the same smart-commit staging
  path as the primary Commit button. Previously willSmartCommit=true with
  an empty index caused nothing-to-commit errors from the variant buttons.
  handleCommit is now a one-liner calling runCommitWithCallback.

C (修正必須) CommitInput: handleFetchAndPull bypassed Auto Stash
  Thread Fetch & Pull through runPullOrSyncWithAutoStash("pull") so the
  auto-stash orchestration runs on the pull step, matching the plain Pull
  button behaviour.

D (修正推奨) browser-manager: findListeners missing from re-registration cleanup
  Add this.findListeners to the cleanup loop in register() so that when a
  pane is re-registered with a new webContentsId (e.g. after a page
  reload) the previous before-input-event and found-in-page listeners are
  torn down before new ones are added, preventing listener accumulation
  and double-fire of find-requested events.

A (修正推奨) CloneRepoTab: remove `undefined as unknown as` type cast
  Pass { cloneId: cloneId ?? "" } so the required input field is always
  a string; the enabled:false guard ensures the subscription never fires
  with an empty string.
@MocA-Love MocA-Love marked this pull request as draft April 13, 2026 09:30
@MocA-Love MocA-Love marked this pull request as ready for review April 13, 2026 09:30
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: dcf967791f

ℹ️ 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".

Comment thread packages/host-service/src/trpc/router/git/utils/git-helpers.ts
1. host-service listBranches: filter non-origin remotes
   git branch -r includes refs from all configured remotes (upstream/*,
   fork/*, etc). The readRefs helper was stripping the "origin/" prefix
   from matching lines but leaving non-origin lines unchanged, so
   "upstream/main" survived as-is and then buildBranch assembled the
   invalid ref "origin/upstream/main" for metadata lookups. Add an
   explicit filter so only entries that start with the given prefix are
   kept, dropping all non-origin remote-tracking refs.

2. CommitInput: guard "tracked" smart-commit against untracked-only changes
   smartCommitMode "tracked" uses `git add -u`, which ignores untracked
   files. If the only changes were new/untracked files, the Commit button
   was enabled by willSmartCommit but the commit would always fail with
   "nothing to commit". Pass unstagedTrackedCount (status.unstaged.length,
   excludes untracked) from ChangesView and require at least one tracked
   unstaged change in "tracked" mode; "all" mode still only needs any
   unstagedChangeCount > 0.
@MocA-Love MocA-Love marked this pull request as draft April 13, 2026 09:58
@MocA-Love MocA-Love marked this pull request as ready for review April 13, 2026 09:58
@MocA-Love MocA-Love merged commit d5336af into main Apr 13, 2026
11 of 14 checks passed
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.

1 participant