feat(desktop): CodeMirrorDiffViewer統一、git blame、GitGraph、マージコンフリクト解消UIの追加#38
feat(desktop): CodeMirrorDiffViewer統一、git blame、GitGraph、マージコンフリクト解消UIの追加#38
Conversation
- FileViewerPane の diff を行数によらず常に CodeMirrorDiffViewer で表示 - MergeView の b 側を編集可能にし Mod+S で保存、revertControls で変更を元に戻せるように - テーマカラー(addition/deletion/modified)を diff ハイライトに適用 - 純追加/純削除行はインラインハイライトを抑制(VSCode 同様の挙動) - 空きスペースに斜め線パターンを表示 - diff-test-fixtures.ts と /diff-test ルートで全パターンを確認できるテスト環境を追加
- VscSourceControlアイコンをbase→currentブランチ表示に刷新 - base/currentそれぞれ独立したプルダウンで切り替え可能に - Generate commit message with AIボタンをテキストエリアから2行目に移動 - generateCommitMessage mutationとcommitMessage stateをChangesViewに引き上げ
- 未コミットの変更がある状態でcurrentブランチ切り替え時に確認ダイアログを表示 - gitエラー時にわかりやすいエラートーストを表示 - UIメッセージを英語に統一
- ChangesHeaderにGitGraphボタンを追加(新規タブとして開く) - SVGベースのブランチグラフをレーン計算アルゴリズムで描画 - コミット行クリックで詳細パネル(CommitDetailsPanel)を展開 - 詳細パネルのファイル名クリックでdiffを表示 - カラム幅のドラッグリサイズ対応 - ブランチバッジのツールチップ表示 - git-graph PaneTypeをMosaicレイアウトに統合
- CodeMirrorDiffViewer・CodeEditor の両エディタに対応 - カーソル行の行末に著者・時刻・コミットメッセージを薄く表示 - ホバーで VS Code 風の詳細ポップアップを表示(アバター・ハッシュ・コピーボタン) - テーマの CSS 変数に準拠したカラーリング
- ChangesViewにConflictedセクションを追加しコンフリクトファイルを表示 - CodeMirror EditorView + ViewPlugin + Decorationでインラインビューアを実装 - 各コンフリクトブロック上にAccept Current/Incoming/Bothリンクを挿入 - parseConflictMarkersでマーカーをパースしConflictRegion[]を返す - filesystem.writeFileで解消後のファイルを保存、Cmd+S対応 - ChangeCategory/"conflicted"とFileViewerMode/"conflict"を型定義に追加
- containerWidthをResizeObserverで計測し詳細パネルのmaxWidthに適用 - 900px以上で横並び、未満で縦積みのレスポンシブレイアウトに変更
- eq() を常に false にして DOM 再利用によるhasLeftリセット漏れを修正 - カーソル行変更時に既存トリップを即座に閉じるよう修正 - ホバー滞在時間を1秒に変更
- FileViewerContentにconflict viewMode分岐を追加 - RightSidebarでconflictedカテゴリ時にviewMode: conflictを明示 - ui-state ZodスキーマにconflictとconflictedのEnum値を追加 - ConflictViewerのhandleResolve/handleSaveをrefで保持しエディタ再作成ループを修正 - encoding・maxBytes指定・エラー表示オーバーレイを追加 - conflict-themeをVSCode風の落ち着いた配色に変更 - AcceptボタンにCurrent/Incoming別のhoverカラーを追加
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 3 minutes and 32 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughAdds inline git-blame support, an inline conflict-resolution editor, and a GitGraph commit browser with commit detail panel; extends status parsing and task handlers to surface conflicted files and commit-graph data; integrates blame and conflict UI into CodeMirror-based viewers and the tab/pane system. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant FileViewer
participant TRPC
participant CodeEditor
participant BlamePlugin
User->>FileViewer: Open file (worktreePath + path)
FileViewer->>TRPC: getGitBlame(worktreePath, absolutePath)
TRPC-->>FileViewer: BlameEntry[]
FileViewer->>CodeEditor: render (blameEntries)
CodeEditor->>BlamePlugin: createBlamePlugin(blameEntries)
User->>CodeEditor: Move cursor to line
BlamePlugin-->>CodeEditor: Render inline blame widget & tooltip on hover
sequenceDiagram
participant User
participant ChangesView
participant TRPC
participant GraphLayout
participant GitGraphView
User->>ChangesView: Click Git Graph toggle
ChangesView->>TRPC: getCommitGraph(worktreePath, maxCount)
TRPC-->>ChangesView: CommitGraphData (nodes)
ChangesView->>GraphLayout: computeGraphLanes(nodes)
GraphLayout-->>GitGraphView: GraphNodeLayout[]
GitGraphView->>User: Render scrollable commit graph
User->>GitGraphView: Expand commit row
GitGraphView->>TRPC: getCommitFiles(worktreePath, commitHash)
TRPC-->>GitGraphView: Commit files
GitGraphView->>User: Show CommitDetailsPanel with file list
sequenceDiagram
participant User
participant FileViewer
participant ConflictViewer
participant FS
User->>FileViewer: Open conflicted file (mode "conflict")
FileViewer->>ConflictViewer: mount with absoluteFilePath
ConflictViewer->>FS: readFile(absoluteFilePath, limit)
FS-->>ConflictViewer: file content
ConflictViewer->>ConflictViewer: parseConflictMarkers(content)
ConflictViewer->>User: Render action widgets per region
User->>ConflictViewer: Click "Accept Current"
ConflictViewer->>FS: writeFile(updated content)
FS-->>ConflictViewer: write success
ConflictViewer->>User: onSave callback / confirmation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 14
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/lib/trpc/routers/ui-state/index.ts (1)
43-47:⚠️ Potential issue | 🟠 MajorAdd
git-graphtopaneSchema.typeto avoid persistence failures.With the new pane type enabled in tab rendering,
tabs.setvalidation can reject state snapshots that includegit-graphpanes.💡 Proposed fix
const paneSchema = z.object({ id: z.string(), tabId: z.string(), - type: z.enum(["terminal", "webview", "file-viewer", "chat", "devtools"]), + type: z.enum([ + "terminal", + "webview", + "file-viewer", + "chat", + "devtools", + "git-graph", + ]), name: z.string(),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/lib/trpc/routers/ui-state/index.ts` around lines 43 - 47, The paneSchema zod enum for pane types currently excludes "git-graph", causing validation failures; update the z.enum([...]) in the paneSchema declaration (the const paneSchema) to include "git-graph" among the allowed values so persisted state containing git-graph panes passes tabs.set validation.
🧹 Nitpick comments (10)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/useOrderedSections/useOrderedSections.test.tsx (1)
18-40: Consider adding a test case for the conflicted section.The fixtures are updated to include the
"conflicted"category, but there's no test case that verifies the conflicted section behavior (e.g., count calculation whenconflictedFilesis non-empty). Adding such a test would ensure the new section is handled correctly.📝 Example test case
test("counts conflicted files correctly", () => { const sections = useOrderedSections({ ...emptyArgs, conflictedFiles: [emptyFile(), emptyFile()], }); const conflictedSection = sections.find( (section) => section.id === "conflicted", ); expect(conflictedSection).toBeDefined(); expect(conflictedSection?.count).toBe(2); });🤖 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/hooks/useOrderedSections/useOrderedSections.test.tsx` around lines 18 - 40, Add a unit test in useOrderedSections.test.tsx that verifies the "conflicted" section is present and its count reflects conflictedFiles; call useOrderedSections with the existing emptyArgs but set conflictedFiles to an array of fixtures (e.g., two emptyFile() items), find the section by section.id === "conflicted", and assert the section is defined and its count equals the number of conflictedFiles provided (2).apps/desktop/src/renderer/routes/_authenticated/_dashboard/diff-test/page.tsx (1)
1-9: Consider gating this test route for development only.This diff-test route and its TopBar button appear to be for development/testing purposes. Consider conditionally registering this route or hiding the button in production builds to avoid exposing internal tooling to end users.
🤖 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/diff-test/page.tsx` around lines 1 - 9, The diff-test route (Route created by createFileRoute with component DiffTestPage) should only be registered in non-production builds: guard the route export/registration with an environment check (e.g. import.meta.env.DEV or process.env.NODE_ENV !== "production") so Route is not created or exported in production, and likewise hide or conditionally render the TopBar button that links to this route (referencing the same DiffTestPage/Route symbol) behind the same check to prevent exposing dev tooling to end users.apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/ConflictViewer/conflict-theme.ts (1)
43-51: Prefer theme tokens over hard-coded hover RGB values.Using fixed
rgb(...)values for conflict action hover colors can drift from the rest of the theme system. Consider switching these to shared CSS variables/tokens for consistent theming.🤖 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/ConflictViewer/conflict-theme.ts` around lines 43 - 51, Replace the hard-coded hover RGB values in the conflict-theme styles with the theme's shared tokens/CSS variables so the hover colors follow the app's design system; specifically update the ".cm-conflict-action-btn-current" and ".cm-conflict-action-btn-incoming" hover rules in conflict-theme.ts to use the appropriate theme token or CSS variable (e.g., a success/positive hover token for current and an info/primary hover token for incoming such as var(--color-success-hover) and var(--color-info-hover) or the equivalent theme token accessor) instead of "rgb(35, 197, 94)" and "rgb(56, 154, 230)".apps/desktop/src/renderer/stores/tabs/store.ts (1)
1666-1701: Consider telemetry parity for Git Graph tab opens.
addGitGraphTabupdates state correctly, but unlike otheradd*Tabactions it doesn’t emit apanel_openedevent. If Git Graph usage should be tracked, add the same capture pattern here.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/renderer/stores/tabs/store.ts` around lines 1666 - 1701, The addGitGraphTab action correctly creates and sets the new tab/pane but misses emitting telemetry; after creating the tab/pane (use createGitGraphTabWithPane) and after the set(...) call but before returning, call the same telemetry capture used by other add*Tab actions (e.g., capture('panel_opened', { workspaceId, tabId: tab.id, paneId: pane.id, panel: 'git_graph' })) so Git Graph opens are tracked consistently; ensure you import/use the same capture function/name as other tabs and keep the payload keys consistent with existing panel_opened events.apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/constants.ts (1)
7-8: Consider addingas consttoMIN_COLUMN_WIDTHSfor consistency.
DEFAULT_COLUMN_WIDTHSusesas constfor a readonly tuple, whileMIN_COLUMN_WIDTHSis a mutablenumber[]. Usingas conston both would ensure consistency and prevent accidental mutations.✨ Proposed fix
export const DEFAULT_COLUMN_WIDTHS = [72, 200, 550, 120, 90] as const; -export const MIN_COLUMN_WIDTHS: number[] = [60, 80, 100, 60, 80]; +export const MIN_COLUMN_WIDTHS = [60, 80, 100, 60, 80] as const;🤖 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/GitGraphView/constants.ts` around lines 7 - 8, MIN_COLUMN_WIDTHS is declared as a mutable number[] while DEFAULT_COLUMN_WIDTHS is a readonly tuple; change MIN_COLUMN_WIDTHS to be a readonly tuple by adding "as const" (or remove the explicit number[] type so TS infers the readonly tuple) so it becomes immutable like DEFAULT_COLUMN_WIDTHS; update any code that expects a mutable array to accept a readonly tuple if necessary and reference MIN_COLUMN_WIDTHS and DEFAULT_COLUMN_WIDTHS when making the change.apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/GitGraphView.tsx (1)
140-143: Potential undefined access ifMIN_COLUMN_WIDTHS[index]is out of bounds.If
indexexceeds the array length (unlikely given current usage but possible if columns change),MIN_COLUMN_WIDTHS[index]returnsundefined, causingMath.max(undefined, ...)to produceNaN. Consider adding a fallback.🛡️ Defensive fallback
const handleMouseMove = (event: MouseEvent) => { + const minWidth = MIN_COLUMN_WIDTHS[index] ?? 60; const nextWidth = Math.max( - MIN_COLUMN_WIDTHS[index], + minWidth, startWidth + event.clientX - startX, );🤖 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/GitGraphView/GitGraphView.tsx` around lines 140 - 143, The expression computing nextWidth uses MIN_COLUMN_WIDTHS[index] directly which can be undefined if index is out of bounds; fix by deriving a safe minimum first (e.g., const safeMin = (index >= 0 && index < MIN_COLUMN_WIDTHS.length) ? MIN_COLUMN_WIDTHS[index] : (MIN_COLUMN_WIDTHS[MIN_COLUMN_WIDTHS.length - 1] ?? 0)) and then compute nextWidth with Math.max(safeMin, startWidth + event.clientX - startX); update the code around the nextWidth calculation in GitGraphView.tsx to use safeMin so Math.max never receives undefined.apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/CommitDetailsPanel.tsx (1)
26-37: Hardcoded Japanese locale and timezone may limit international usability.The date formatting uses
ja-JPlocale andAsia/Tokyotimezone. If the app targets international users, consider using the user's system locale or making this configurable.🌐 Consider using system locale
function formatCommitDate(date: Date): string { - return new Date(date).toLocaleString("ja-JP", { - timeZone: "Asia/Tokyo", + return new Date(date).toLocaleString(undefined, { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: 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/RightSidebar/ChangesView/components/GitGraphView/CommitDetailsPanel.tsx` around lines 26 - 37, The formatCommitDate function currently hardcodes "ja-JP" and "Asia/Tokyo"; change it to use the user's system locale/timezone (or make them configurable) by either: 1) removing the hardcoded locale and passing undefined or navigator.language to toLocaleString/Intl.DateTimeFormat, and 2) obtaining the user's timezone from Intl.DateTimeFormat().resolvedOptions().timeZone or accepting an optional timezone parameter on formatCommitDate(date, locale?, timeZone?) so callers can override; update the function signature (formatCommitDate) and its call sites accordingly to default to system settings when no overrides are provided.apps/desktop/src/lib/trpc/routers/changes/git-blame.ts (1)
108-109: Consider logging the error before returning empty entries.Silently returning empty entries on failure may hide underlying issues (permission errors, invalid paths, etc.). Adding a debug log would aid troubleshooting without changing the graceful degradation behavior.
📝 Proposed enhancement
- } catch { + } catch (error) { + console.debug("[git-blame] failed to get blame:", error); return { entries: [] }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/lib/trpc/routers/changes/git-blame.ts` around lines 108 - 109, The catch block that currently does "return { entries: [] }" swallows errors; update that catch to log the caught error before returning empty entries. Locate the catch that returns "{ entries: [] }" and add a debug/error log (e.g., console.debug/console.error or the local logger if available) that includes the caught error and context (path/commit/file) so failures are recorded while preserving the graceful return of empty entries.apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/GraphRow.tsx (1)
53-57: Hardcoded Japanese locale for date formatting.The date is formatted with a hardcoded
"ja-JP"locale. Consider usingundefinedto respect the user's system locale, or implementing a locale preference system.const formattedDate = new Date(node.date).toLocaleDateString("ja-JP", { + const formattedDate = new Date(node.date).toLocaleDateString(undefined, { year: "numeric", month: "2-digit", day: "2-digit", });🤖 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/GitGraphView/GraphRow.tsx` around lines 53 - 57, The date formatting in GraphRow.tsx uses a hardcoded "ja-JP" locale for formattedDate which forces Japanese formatting; update the code that constructs formattedDate (the new Date(node.date).toLocaleDateString(...) call) to use undefined (or a supplied user locale preference) instead of "ja-JP" so it respects the system/user locale or wiring a locale prop/settings value through the component if you need an app-specific preference.apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createBlamePlugin.ts (1)
19-41: Hardcoded Japanese locale strings limit internationalization.The time formatting functions use hardcoded Japanese strings (
分前,時間前,朝,午後, etc.). This will display Japanese text for all users regardless of their locale preference. Consider using theIntl.RelativeTimeFormatandIntl.DateTimeFormatAPIs for proper localization.♻️ Example using Intl APIs
function formatTimeAgo(timestamp: number): string { const now = Date.now() / 1000; const diff = now - timestamp; - if (diff < 60) return "just now"; - if (diff < 3600) return `${Math.floor(diff / 60)}分前`; - if (diff < 86400) return `${Math.floor(diff / 3600)}時間前`; - if (diff < 86400 * 30) return `${Math.floor(diff / 86400)}日前`; - if (diff < 86400 * 365) return `${Math.floor(diff / (86400 * 30))}ヶ月前`; - return `${Math.floor(diff / (86400 * 365))}年前`; + const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: "auto" }); + if (diff < 60) return rtf.format(0, "second"); + if (diff < 3600) return rtf.format(-Math.floor(diff / 60), "minute"); + if (diff < 86400) return rtf.format(-Math.floor(diff / 3600), "hour"); + if (diff < 86400 * 30) return rtf.format(-Math.floor(diff / 86400), "day"); + if (diff < 86400 * 365) return rtf.format(-Math.floor(diff / (86400 * 30)), "month"); + return rtf.format(-Math.floor(diff / (86400 * 365)), "year"); } function formatFullDate(timestamp: number): string { const d = new Date(timestamp * 1000); - const month = d.getMonth() + 1; - const day = d.getDate(); - const year = d.getFullYear(); - const hours = d.getHours(); - const minutes = String(d.getMinutes()).padStart(2, "0"); - const ampm = hours < 12 ? "朝" : "午後"; - const hour12 = hours % 12 || 12; - return `${month} ${day}th, ${year} ${hour12}:${minutes} ${ampm}`; + return new Intl.DateTimeFormat(undefined, { + year: "numeric", + month: "long", + day: "numeric", + hour: "numeric", + minute: "2-digit", + }).format(d); }🤖 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/createBlamePlugin.ts` around lines 19 - 41, formatTimeAgo and formatFullDate currently hardcode Japanese strings; replace them with locale-aware formatting using Intl.RelativeTimeFormat and Intl.DateTimeFormat. In formatTimeAgo(timestamp: number) compute the largest appropriate unit (seconds, minutes, hours, days, months, years), create an Intl.RelativeTimeFormat with a locale parameter (accept locale or use navigator.language / a passed-in app locale as fallback) and return rtf.format(value, unit). In formatFullDate(timestamp: number) create an Intl.DateTimeFormat with the same locale and options { year: "numeric", month: "long" or "numeric" as desired, day: "numeric", hour: "numeric", minute: "2-digit", hour12: false/true based on locale } and return formatter.format(new Date(timestamp * 1000)); ensure functions accept or derive locale rather than emitting hardcoded Japanese text.
🤖 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/changes/status.ts`:
- Around line 137-139: The dedupe key used when calling runGitTask (currently
`graph:${input.worktreePath}`) doesn't include the requested graph size, so
different getCommitGraph(maxCount) calls collide; update the dedupeKey to
incorporate the requested maxCount (e.g., include `input.maxCount` or the
argument passed to getCommitGraph) so the key becomes unique per size (for
example `graph:${input.worktreePath}:max:${input.maxCount}`) and handle any
undefined/default maxCount consistently so coalescing only merges identical
requests.
In `@apps/desktop/src/lib/trpc/routers/changes/utils/parse-status.ts`:
- Around line 31-39: CONFLICT_PAIRS currently includes both marker-producing
states and non-marker states; update parse-status.ts so only content-conflict
states (AA, UU) remain in CONFLICT_PAIRS and route non-marker states (DD, AU,
UD, UA, DU) into a separate resolution flow used by ConflictViewer—either remove
those non-marker codes from CONFLICT_PAIRS or implement a bespoke handler that
triggers delete/add-specific actions (e.g., present git rm for DD or staging
actions for AU/DU/UD/UA) so the UI shows appropriate resolution buttons instead
of an empty editor.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/TopBar.tsx`:
- Around line 33-34: The matchRoute call in TopBar.tsx uses the shorthand
"/diff-test" which doesn't match the actual route; update the string literal to
the full absolute path "/_authenticated/_dashboard/diff-test" where used (e.g.,
in the isDiffTestOpen assignment and any navigate or route checks in TopBar.tsx)
so it matches the route defined in page.tsx and follows the project's
absolute-path convention.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/diff-test/DiffTestPage.tsx`:
- Around line 6-8: The code currently reads diffFixtureList[selectedIndex]
without guards, which will crash if diffFixtureList is empty or selectedIndex is
out of range; update the DiffTestPage state and render logic to handle this:
when initializing, derive a safe index (e.g., Math.max(0,
Math.min(selectedIndex, diffFixtureList.length - 1))) or set selectedIndex to 0
only if diffFixtureList.length > 0, add a useEffect that clamps selectedIndex
whenever diffFixtureList changes, and in render gate on a valid fixture (e.g.,
if (!fixture) return null or a placeholder) before dereferencing fixture;
reference selectedIndex, setSelectedIndex, diffFixtureList, and fixture to
locate where to add these checks.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createBlamePlugin.ts`:
- Line 97: The code in createBlamePlugin.ts builds a fake parent hash via const
parentHash = `${entry.commitHash.substring(0, 6)}0`, which misleads users;
replace this by either retrieving the real parent commit hash from the available
git/commit metadata (use the commit's parent field or call the repo API where
entry or commit object is available) and display that, or if that data is not
present, remove the parentHash display entirely (do not fabricate values).
Locate the parentHash usage in createBlamePlugin (and any UI rendering that
references entry.commitHash / parentHash) and update the logic to show the real
parent when available or hide the parent element when not.
- Around line 104-127: The tooltip currently assigns unescaped HTML via
tooltip.innerHTML in createBlamePlugin.ts using fields like entry.author and
entry.summary (and optionally initials/parentHash), which risks XSS from commit
data; replace the innerHTML construction with DOM-safe code that creates
elements (e.g., divs, spans, buttons) and sets their text via textContent (or
use a vetted sanitizer) for any user-controllable strings, then append those
nodes to tooltip—ensure ICON_* values that are trusted remain inserted as HTML
only where safe and keep copy button markup created via createElement to avoid
injecting raw commit text.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.ts`:
- Around line 89-105: The theme code is appending hex alpha suffixes to OKLCH
color strings (using editorTheme.colors.deletion/addition) which produces
invalid CSS; update createCodeMirrorTheme to avoid string-concatenating alpha
hexes—either convert the color tokens to hex/hex8 in getEditorTheme (ensuring
editorTheme.colors.deletion and .addition are hex/hex8) or replace the
concatenations for the selectors that set backgroundColor/background (the rules
for ".cm-deletedChunk", "&.cm-merge-a .cm-changedText, .cm-deletedChunk
.cm-deletedText", "&.cm-merge-b .cm-changedText", "&.cm-merge-b
.cm-deletedText", and the .cm-changedLine rules) with format-agnostic mixing
like CSS color-mix(in srgb, ${editorTheme.colors.deletion} 8%) / color-mix(in
srgb, ${editorTheme.colors.addition} 8%) so OKLCH values remain valid.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/ConflictViewer/ConflictViewer.tsx`:
- Around line 215-301: The effect currently destroys and recreates the
EditorView on absoluteFilePath or activeTheme changes which discards unsaved
edits; instead preserve the editor instance and document by: 1) move theme and
syntax-related extensions (editorTheme, syntaxHighlighting, conflictTheme) into
their own Compartment(s) (e.g., themeCompartment, syntaxCompartment) so you can
call themeCompartment.reconfigure(...) and syntaxCompartment.reconfigure(...)
via editorRef.current.dispatch without destroying the view; 2) only recreate the
EditorView when absoluteFilePath truly requires a new doc (and before replacing
the view, read view.state.doc.toString() to detect unsaved edits and prompt the
user via your existing save/confirm flow); and 3) keep langCompartment behavior
for language loads but use reconfigure on that compartment instead of recreating
the whole EditorView.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/ConflictViewer/parseConflictMarkers.ts`:
- Around line 44-65: The parser must recognize diff3 base markers so
base-section lines aren't misclassified as "current": in
parseConflictMarkers.ts, when line.startsWith("|||||||") and state === "current"
set state = "base", record separatorLine for the base start, and start
collecting baseLines in a new array (like currentLines and incomingLines); when
you later hit "=======" transition from "base" to "incoming" (instead of from
"current"), and when building the region pushed into regions include baseLines
(and currentLabel/incomingLabel as before) so consumers (Accept Current/Both)
can ignore baseLines; also reset baseLines and state same as other branches.
Ensure you add the new baseLines variable and the "base" state handling
alongside existing state transitions (state, currentLines, incomingLines,
separatorLine, startLine, regions).
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx`:
- Around line 197-203: The blame overlay is fetched via
electronTrpc.changes.getGitBlame.useQuery (blameData) which only accepts
worktree/absolute paths and thus shows current-worktree authors even for
historical diffs; change the query's enabled condition to also require that
commitHash is not set (e.g., enabled: Boolean(worktreePath && absoluteFilePath
&& !commitHash)) so blame is skipped for committed diffs, and consider updating
getGitBlame to accept a revision/commitHash in the future to make it
revision-aware.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx`:
- Around line 266-279: The mutation handler generateCommitMessageMutation
unconditionally calls setCommitMessage in onSuccess which can overwrite a user
draft typed during generation; capture the current commit message when starting
the request (e.g., store a snapshot in a ref or local variable in the function
that triggers electronTrpc.changes.generateCommitMessage.useMutation) and in
onSuccess only call setCommitMessage(data.message) if the current commit message
still equals that snapshot (i.e., unchanged by the user). Update the trigger
code that calls generateCommitMessageMutation.mutate to save the snapshot and
pass or reference it so onSuccess can compare before updating the commit
message.
- Around line 819-823: The hasUncommittedChanges prop currently checks
stagedFiles, unstagedFiles, and untrackedFiles but omits conflictedFiles,
causing conflicted files to be treated as clean; update the
hasUncommittedChanges expression (the prop passed where hasUncommittedChanges is
set) to also consider conflictedFiles (e.g., include conflictedFiles.length > 0)
so unresolved merge conflicts are treated as uncommitted changes alongside
staged/unstaged/untracked files.
In `@apps/desktop/src/shared/tabs-types.ts`:
- Line 94: Update the documentation/comments that describe FileViewer modes and
diff-category to include the new "conflict" mode: locate the FileViewerMode type
and any doc comments referencing FileViewerState.viewMode or the diff-category
descriptions and add "conflict" to the listed modes and explain its semantics
(when it is used) alongside "rendered", "raw", and "diff" so the docs match the
type definition. Ensure any examples or guidance for diff-category handling
mention how "conflict" should be displayed/treated.
---
Outside diff comments:
In `@apps/desktop/src/lib/trpc/routers/ui-state/index.ts`:
- Around line 43-47: The paneSchema zod enum for pane types currently excludes
"git-graph", causing validation failures; update the z.enum([...]) in the
paneSchema declaration (the const paneSchema) to include "git-graph" among the
allowed values so persisted state containing git-graph panes passes tabs.set
validation.
---
Nitpick comments:
In `@apps/desktop/src/lib/trpc/routers/changes/git-blame.ts`:
- Around line 108-109: The catch block that currently does "return { entries: []
}" swallows errors; update that catch to log the caught error before returning
empty entries. Locate the catch that returns "{ entries: [] }" and add a
debug/error log (e.g., console.debug/console.error or the local logger if
available) that includes the caught error and context (path/commit/file) so
failures are recorded while preserving the graceful return of empty entries.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/diff-test/page.tsx`:
- Around line 1-9: The diff-test route (Route created by createFileRoute with
component DiffTestPage) should only be registered in non-production builds:
guard the route export/registration with an environment check (e.g.
import.meta.env.DEV or process.env.NODE_ENV !== "production") so Route is not
created or exported in production, and likewise hide or conditionally render the
TopBar button that links to this route (referencing the same DiffTestPage/Route
symbol) behind the same check to prevent exposing dev tooling to end users.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createBlamePlugin.ts`:
- Around line 19-41: formatTimeAgo and formatFullDate currently hardcode
Japanese strings; replace them with locale-aware formatting using
Intl.RelativeTimeFormat and Intl.DateTimeFormat. In formatTimeAgo(timestamp:
number) compute the largest appropriate unit (seconds, minutes, hours, days,
months, years), create an Intl.RelativeTimeFormat with a locale parameter
(accept locale or use navigator.language / a passed-in app locale as fallback)
and return rtf.format(value, unit). In formatFullDate(timestamp: number) create
an Intl.DateTimeFormat with the same locale and options { year: "numeric",
month: "long" or "numeric" as desired, day: "numeric", hour: "numeric", minute:
"2-digit", hour12: false/true based on locale } and return formatter.format(new
Date(timestamp * 1000)); ensure functions accept or derive locale rather than
emitting hardcoded Japanese text.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/ConflictViewer/conflict-theme.ts`:
- Around line 43-51: Replace the hard-coded hover RGB values in the
conflict-theme styles with the theme's shared tokens/CSS variables so the hover
colors follow the app's design system; specifically update the
".cm-conflict-action-btn-current" and ".cm-conflict-action-btn-incoming" hover
rules in conflict-theme.ts to use the appropriate theme token or CSS variable
(e.g., a success/positive hover token for current and an info/primary hover
token for incoming such as var(--color-success-hover) and
var(--color-info-hover) or the equivalent theme token accessor) instead of
"rgb(35, 197, 94)" and "rgb(56, 154, 230)".
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/CommitDetailsPanel.tsx`:
- Around line 26-37: The formatCommitDate function currently hardcodes "ja-JP"
and "Asia/Tokyo"; change it to use the user's system locale/timezone (or make
them configurable) by either: 1) removing the hardcoded locale and passing
undefined or navigator.language to toLocaleString/Intl.DateTimeFormat, and 2)
obtaining the user's timezone from
Intl.DateTimeFormat().resolvedOptions().timeZone or accepting an optional
timezone parameter on formatCommitDate(date, locale?, timeZone?) so callers can
override; update the function signature (formatCommitDate) and its call sites
accordingly to default to system settings when no overrides are provided.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/constants.ts`:
- Around line 7-8: MIN_COLUMN_WIDTHS is declared as a mutable number[] while
DEFAULT_COLUMN_WIDTHS is a readonly tuple; change MIN_COLUMN_WIDTHS to be a
readonly tuple by adding "as const" (or remove the explicit number[] type so TS
infers the readonly tuple) so it becomes immutable like DEFAULT_COLUMN_WIDTHS;
update any code that expects a mutable array to accept a readonly tuple if
necessary and reference MIN_COLUMN_WIDTHS and DEFAULT_COLUMN_WIDTHS when making
the change.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/GitGraphView.tsx`:
- Around line 140-143: The expression computing nextWidth uses
MIN_COLUMN_WIDTHS[index] directly which can be undefined if index is out of
bounds; fix by deriving a safe minimum first (e.g., const safeMin = (index >= 0
&& index < MIN_COLUMN_WIDTHS.length) ? MIN_COLUMN_WIDTHS[index] :
(MIN_COLUMN_WIDTHS[MIN_COLUMN_WIDTHS.length - 1] ?? 0)) and then compute
nextWidth with Math.max(safeMin, startWidth + event.clientX - startX); update
the code around the nextWidth calculation in GitGraphView.tsx to use safeMin so
Math.max never receives undefined.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/GraphRow.tsx`:
- Around line 53-57: The date formatting in GraphRow.tsx uses a hardcoded
"ja-JP" locale for formattedDate which forces Japanese formatting; update the
code that constructs formattedDate (the new
Date(node.date).toLocaleDateString(...) call) to use undefined (or a supplied
user locale preference) instead of "ja-JP" so it respects the system/user locale
or wiring a locale prop/settings value through the component if you need an
app-specific preference.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/useOrderedSections/useOrderedSections.test.tsx`:
- Around line 18-40: Add a unit test in useOrderedSections.test.tsx that
verifies the "conflicted" section is present and its count reflects
conflictedFiles; call useOrderedSections with the existing emptyArgs but set
conflictedFiles to an array of fixtures (e.g., two emptyFile() items), find the
section by section.id === "conflicted", and assert the section is defined and
its count equals the number of conflictedFiles provided (2).
In `@apps/desktop/src/renderer/stores/tabs/store.ts`:
- Around line 1666-1701: The addGitGraphTab action correctly creates and sets
the new tab/pane but misses emitting telemetry; after creating the tab/pane (use
createGitGraphTabWithPane) and after the set(...) call but before returning,
call the same telemetry capture used by other add*Tab actions (e.g.,
capture('panel_opened', { workspaceId, tabId: tab.id, paneId: pane.id, panel:
'git_graph' })) so Git Graph opens are tracked consistently; ensure you
import/use the same capture function/name as other tabs and keep the payload
keys consistent with existing panel_opened events.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: b809e98a-6fb5-4116-be7f-c30c0a599ecd
📒 Files selected for processing (48)
README.mdapps/desktop/src/lib/trpc/routers/changes/git-blame.tsapps/desktop/src/lib/trpc/routers/changes/index.tsapps/desktop/src/lib/trpc/routers/changes/status.tsapps/desktop/src/lib/trpc/routers/changes/utils/parse-status.tsapps/desktop/src/lib/trpc/routers/changes/workers/git-task-handlers.tsapps/desktop/src/lib/trpc/routers/changes/workers/git-task-types.tsapps/desktop/src/lib/trpc/routers/ui-state/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/TopBar/TopBar.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/diff-test/DiffTestPage.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/diff-test/page.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InfiniteScrollView/hooks/useOrderedSections/useOrderedSections.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/FileViewerPane.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/diff-test-fixtures.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/ConflictViewer/ConflictActionWidget.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/ConflictViewer/ConflictViewer.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/ConflictViewer/conflict-theme.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/ConflictViewer/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/ConflictViewer/parseConflictMarkers.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/GitGraphPane/GitGraphPane.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/GitGraphPane/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ChangesHeader/ChangesHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/CommitInput/CommitInput.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/CommitDetailsPanel.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/GitGraphView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/GraphRow.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/constants.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/ReviewPanel.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/useOrderedSections/useOrderedSections.test.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/hooks/useOrderedSections/useOrderedSections.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/utils/computeGraphLanes.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/index.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/CodeEditor.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createBlamePlugin.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createCodeMirrorTheme.tsapps/desktop/src/renderer/stores/changes/section-order.tsapps/desktop/src/renderer/stores/changes/store.tsapps/desktop/src/renderer/stores/editor-state/types.tsapps/desktop/src/renderer/stores/tabs/store.tsapps/desktop/src/renderer/stores/tabs/types.tsapps/desktop/src/renderer/stores/tabs/utils.tsapps/desktop/src/shared/changes-types.tsapps/desktop/src/shared/tabs-types.ts
- createBlamePlugin: innerHTML → DOM構築でXSS対策、偽parentHash表示を削除 - TopBar/diff-test: 開発用テストページを本番コードから削除 - FileViewerContent: onContextMenuCapture を復元し右クリック位置情報を再取得 - ChangesView: isGitGraphOpen をタブストアから動的に取得 - useOrderedSections (ChangesContent): conflictedFiles を接続、count/content を修正 - parseConflictMarkers: ======= 判定を trimEnd() === に変更し誤検出を防止 - ChangesHeader: gitエラーメッセージに日本語キーワードを追加 - CommitDetailsPanel: ja-JP/Asia/Tokyo のハードコードを削除しシステムロケールを使用
- ui-state/index.ts: paneSchema.type に git-graph を追加(永続化失敗の防止) - status.ts: getCommitGraph の dedupeKey に maxCount を含める - parse-status.ts: CONFLICT_PAIRS をマーカー生成状態(AA/UU)のみに絞り込み - constants.ts: MIN_COLUMN_WIDTHS に as const を追加 - GitGraphView.tsx: MIN_COLUMN_WIDTHS[index] の undefined フォールバック追加
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (3)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx (2)
283-296:⚠️ Potential issue | 🟠 MajorAI-generated message can overwrite user's draft.
The
onSuccesshandler unconditionally callssetCommitMessage(data.message). If the user types while generation is in-flight, their draft will be overwritten when the response arrives.Consider capturing the commit message value when the mutation is triggered and only applying the result if the field hasn't changed:
💡 Proposed approach
+const commitMessageAtRequestRef = useRef<string | null>(null); + const generateCommitMessageMutation = electronTrpc.changes.generateCommitMessage.useMutation({ onSuccess: (data) => { - if (data.message) { + // Only apply if user hasn't edited since request started + if (data.message && commitMessage === commitMessageAtRequestRef.current) { setCommitMessage(data.message); + commitMessageAtRequestRef.current = null; } else { toast.error( "Failed to generate commit message. Check your AI provider settings.", ); } },Then when triggering the mutation:
onGenerateCommitMessage={() => { + commitMessageAtRequestRef.current = commitMessage; generateCommitMessageMutation.mutate({ worktreePath }) }}🤖 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/ChangesView.tsx` around lines 283 - 296, The onSuccess handler for electronTrpc.changes.generateCommitMessage.useMutation currently always calls setCommitMessage(data.message), which can clobber a user's in-progress draft; fix by capturing the commit message field value when the mutation is triggered (e.g., store current commitMessage in a local ref or variable inside the handler that calls generateCommitMessageMutation.mutate) and in onSuccess only call setCommitMessage(data.message) if the field value still matches that captured snapshot (or is empty/unchanged), otherwise ignore the AI result; update references around generateCommitMessageMutation, setCommitMessage, and the mutation trigger so the response only applies when the user hasn't modified the draft.
836-840:⚠️ Potential issue | 🟠 Major
hasUncommittedChangesshould include conflicted files.The
hasUncommittedChangesprop checks staged, unstaged, and untracked files but omitsconflictedFiles. This means unresolved merge conflicts won't trigger the branch-switch warning dialog inChangesHeader.💡 Proposed fix
hasUncommittedChanges={ stagedFiles.length > 0 || unstagedFiles.length > 0 || - untrackedFiles.length > 0 + untrackedFiles.length > 0 || + conflictedFiles.length > 0 }🤖 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/ChangesView.tsx` around lines 836 - 840, The hasUncommittedChanges prop currently only checks stagedFiles, unstagedFiles, and untrackedFiles; include conflictedFiles in that condition so unresolved merge conflicts also count as uncommitted changes. Locate the JSX where hasUncommittedChanges is set (the prop on ChangesHeader / ChangesView) and update the boolean expression to OR in conflictedFiles.length > 0 (or check conflictedFiles.length && > 0) alongside stagedFiles, unstagedFiles, and untrackedFiles to ensure the branch-switch warning triggers for conflicts.apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx (1)
199-205:⚠️ Potential issue | 🟠 MajorDon't reuse current-worktree blame for historical tabs.
commitHashnever participates inelectronTrpc.changes.getGitBlame.useQuery, but the returnedblameDatanow feeds bothCodeMirrorDiffViewerandCodeEditor. Opening the samefilePathfor a historical revision therefore overlays current-worktree authors onto historical content. Disable this query whencommitHashis set until the procedure becomes revision-aware.Proposed fix
const { data: blameData } = electronTrpc.changes.getGitBlame.useQuery( { worktreePath: worktreePath ?? "", absolutePath: absoluteFilePath }, { - enabled: Boolean(worktreePath && absoluteFilePath), + enabled: Boolean(worktreePath && absoluteFilePath && !commitHash), staleTime: 60_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/FileViewerContent/FileViewerContent.tsx` around lines 199 - 205, The current use of electronTrpc.changes.getGitBlame.useQuery returns blameData for the live worktree and is being used by CodeMirrorDiffViewer and CodeEditor even for historical tabs; modify the query call (electronTrpc.changes.getGitBlame.useQuery) so it is disabled when commitHash is set (e.g., include !commitHash in the enabled condition) to prevent current-worktree blame from being fetched/used for historical revisions (affects blameData fed into CodeMirrorDiffViewer and CodeEditor that use worktreePath and absoluteFilePath).
🧹 Nitpick comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createBlamePlugin.ts (1)
66-67: Unused constant.
_ICON_ARROWis defined but never used. If it's not planned for future use, consider removing it to reduce clutter.🤖 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/createBlamePlugin.ts` around lines 66 - 67, The constant _ICON_ARROW is defined but never used in createBlamePlugin.ts; either remove the unused constant declaration (_ICON_ARROW) to eliminate dead code or, if the arrow SVG is intended for UI, replace existing inline SVG usages with this constant and reference _ICON_ARROW where needed (e.g., in the plugin's render/toolbar/template code such as any renderBlameIcon or toolbar item that expects an SVG). Ensure imports/exports are updated accordingly and that no lint errors remain after removal or usage.apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx (1)
97-113: Consider simplifying theisGitGraphOpenselector.The current implementation uses multiple inline type assertions which reduce readability. Consider extracting a helper or using a more type-safe approach.
♻️ Suggested refactor
const isGitGraphOpen = useTabsStore((s) => worktreePath - ? s.tabs.some( - (t) => - t.workspaceId === workspaceId && - Object.values(s.panes).some( - (p) => - p.tabId === t.id && - "type" in p && - (p as { type: string }).type === "git-graph" && - "gitGraph" in p && - (p as { gitGraph?: { worktreePath: string } }).gitGraph - ?.worktreePath === worktreePath, - ), - ) + ? s.tabs.some((t) => { + if (t.workspaceId !== workspaceId) return false; + return Object.values(s.panes).some((p) => { + if (p.tabId !== t.id) return false; + if (!("type" in p) || p.type !== "git-graph") return false; + if (!("gitGraph" in p)) return false; + const gitGraphPane = p as { gitGraph?: { worktreePath: string } }; + return gitGraphPane.gitGraph?.worktreePath === worktreePath; + }); + }) : 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/RightSidebar/ChangesView/ChangesView.tsx` around lines 97 - 113, The isGitGraphOpen selector is hard to read due to repeated inline type assertions; refactor by extracting a type-guard/helper function (e.g., isGitGraphPane(p): p is { type: "git-graph"; gitGraph?: { worktreePath: string } }) and then rewrite the useTabsStore callback to use that guard and clearer checks against tabs, panes and gitGraph.worktreePath; update the selector logic in isGitGraphOpen to call isGitGraphPane(p) and directly compare p.gitGraph?.worktreePath === worktreePath for readability and type-safety while preserving the existing semantics.
🤖 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/createBlamePlugin.ts`:
- Around line 19-29: The UI strings are hardcoded in Japanese in formatTimeAgo
and formatFullDate and the copy button title (copyBtn.title); extract all
user-facing strings ("分前","時間前","日前","ヶ月前","年前","朝","午後","コピー", etc.) into the
app's localization layer or a translation file and replace literal strings with
locale lookups (e.g., t('time.minutes_ago')), and update formatFullDate to
compute the correct English ordinal suffix for day numbers (1st, 2nd, 3rd,
4th...21st, 22nd, 23rd) instead of always appending "th". Ensure formatTimeAgo
uses localized pluralization/units via the same i18n API so it renders
appropriately for different locales and wire copyBtn.title to the translation
key.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx`:
- Around line 385-394: The code currently only updates
lastDiffLocationRef.current when getDiffLocationFromEvent(nativeEvent) returns a
location, leaving stale values when it returns null; update the handler so that
after calling getDiffLocationFromEvent(nativeEvent) you explicitly clear
lastDiffLocationRef.current (set to null/undefined) when location is falsy,
otherwise compute column via getColumnFromDiffPoint and assign
lastDiffLocationRef.current = { ...location, column } so subsequent handlers
like onEditAtLocation don't use stale coordinates.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/CommitDetailsPanel.tsx`:
- Around line 66-250: The CommitDetailsPanel component needs to be relocated
into its own folder-per-component layout: create a CommitDetailsPanel directory,
move the current CommitDetailsPanel.tsx into
CommitDetailsPanel/CommitDetailsPanel.tsx, add a CommitDetailsPanel/index.ts
that re-exports the component (export { CommitDetailsPanel } from
'./CommitDetailsPanel'), and update any imports across the codebase that
referenced the old path to import from the new barrel; ensure the component's
named export remains CommitDetailsPanel and run the project to fix any broken
import paths.
- Around line 212-223: The click handler that calls addFileViewerPane from
CommitDetailsPanel.tsx currently builds absolutePath via
toAbsoluteWorkspacePath(worktreePath, file.path) but doesn't pass the original
path for renamed files; update the payload passed to addFileViewerPane so that
when file.status === "renamed" you include an oldPath field (e.g. compute
toAbsoluteWorkspacePath(worktreePath, file.oldPath) or use file.oldPath)
alongside filePath, diffCategory, fileStatus, and commitHash so the file viewer
can render rename diffs correctly.
---
Duplicate comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx`:
- Around line 199-205: The current use of
electronTrpc.changes.getGitBlame.useQuery returns blameData for the live
worktree and is being used by CodeMirrorDiffViewer and CodeEditor even for
historical tabs; modify the query call
(electronTrpc.changes.getGitBlame.useQuery) so it is disabled when commitHash is
set (e.g., include !commitHash in the enabled condition) to prevent
current-worktree blame from being fetched/used for historical revisions (affects
blameData fed into CodeMirrorDiffViewer and CodeEditor that use worktreePath and
absoluteFilePath).
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx`:
- Around line 283-296: The onSuccess handler for
electronTrpc.changes.generateCommitMessage.useMutation currently always calls
setCommitMessage(data.message), which can clobber a user's in-progress draft;
fix by capturing the commit message field value when the mutation is triggered
(e.g., store current commitMessage in a local ref or variable inside the handler
that calls generateCommitMessageMutation.mutate) and in onSuccess only call
setCommitMessage(data.message) if the field value still matches that captured
snapshot (or is empty/unchanged), otherwise ignore the AI result; update
references around generateCommitMessageMutation, setCommitMessage, and the
mutation trigger so the response only applies when the user hasn't modified the
draft.
- Around line 836-840: The hasUncommittedChanges prop currently only checks
stagedFiles, unstagedFiles, and untrackedFiles; include conflictedFiles in that
condition so unresolved merge conflicts also count as uncommitted changes.
Locate the JSX where hasUncommittedChanges is set (the prop on ChangesHeader /
ChangesView) and update the boolean expression to OR in conflictedFiles.length >
0 (or check conflictedFiles.length && > 0) alongside stagedFiles, unstagedFiles,
and untrackedFiles to ensure the branch-switch warning triggers for conflicts.
---
Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createBlamePlugin.ts`:
- Around line 66-67: The constant _ICON_ARROW is defined but never used in
createBlamePlugin.ts; either remove the unused constant declaration
(_ICON_ARROW) to eliminate dead code or, if the arrow SVG is intended for UI,
replace existing inline SVG usages with this constant and reference _ICON_ARROW
where needed (e.g., in the plugin's render/toolbar/template code such as any
renderBlameIcon or toolbar item that expects an SVG). Ensure imports/exports are
updated accordingly and that no lint errors remain after removal or usage.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx`:
- Around line 97-113: The isGitGraphOpen selector is hard to read due to
repeated inline type assertions; refactor by extracting a type-guard/helper
function (e.g., isGitGraphPane(p): p is { type: "git-graph"; gitGraph?: {
worktreePath: string } }) and then rewrite the useTabsStore callback to use that
guard and clearer checks against tabs, panes and gitGraph.worktreePath; update
the selector logic in isGitGraphOpen to call isGitGraphPane(p) and directly
compare p.gitGraph?.worktreePath === worktreePath for readability and
type-safety while preserving the existing semantics.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: b15931da-90c7-43ed-817e-ad11c5097ba1
📒 Files selected for processing (8)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InfiniteScrollView/InfiniteScrollView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InfiniteScrollView/hooks/useOrderedSections/useOrderedSections.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/ConflictViewer/parseConflictMarkers.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ChangesHeader/ChangesHeader.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/CommitDetailsPanel.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/components/CodeEditor/createBlamePlugin.ts
✅ Files skipped from review due to trivial changes (1)
- apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/ConflictViewer/parseConflictMarkers.ts
- createBlamePlugin: 日付の序数サフィックスを正しく計算(1st/2nd/3rd/th) - FileViewerContent: diff外の右クリック時にlastDiffLocationRefをnullクリア - CommitDetailsPanel: フォルダ構成に移動(CommitDetailsPanel/index.ts追加) - CommitDetailsPanel: リネームファイルのoldPathをaddFileViewerPaneに渡す
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/changes/status.ts`:
- Around line 121-124: The schema for the router input currently allows any
number for maxCount; update the zod schema where maxCount is defined (the
z.object with worktreePath and maxCount) to validate it as a positive integer by
using .int().min(1).optional() (following the pattern used for retryCount). This
ensures the maxCount field passed into the handler (which forwards to git
--max-count) is a valid positive integer and prevents negative/fractional values
or zero from reaching the git call.
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/GitGraphView.tsx`:
- Around line 93-102: The ResizeObserver setup effect currently runs only once
and returns early if scrollContainerRef.current is null, so when the loading UI
is replaced by the real content the observer is never attached; change the
effect that creates the ResizeObserver (the useEffect that references
scrollContainerRef, setContainerWidth) to re-run when the container element
appears by depending on the ref (e.g., include scrollContainerRef.current or a
stateful callback ref value in the dependency array) so the observer is attached
once the DOM node exists; ensure you still call
setContainerWidth(el.clientWidth) after observing and properly disconnect the
observer in the cleanup.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 33e67a06-f31f-4974-86a6-d52f4d6924b4
📒 Files selected for processing (6)
.gitignoreapps/desktop/src/lib/trpc/routers/changes/status.tsapps/desktop/src/lib/trpc/routers/changes/utils/parse-status.tsapps/desktop/src/lib/trpc/routers/ui-state/index.tsapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/GitGraphView.tsxapps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/constants.ts
✅ Files skipped from review due to trivial changes (1)
- .gitignore
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/GitGraphView/constants.ts
- status.ts: maxCountのバリデーションをint().min(1).max(5_000)に強化し、デフォルト500を明示 - GitGraphView: ResizeObserverをローディング完了後に設定されるよう依存配列を修正
概要
close #36
close #37
desktopアプリにおける差分表示・コード閲覧体験を全面的に強化するための機能追加および関連バグ修正。CodeMirrorDiffViewerへの統一、git blameインライン表示、GitGraphビューア、VSCodeスタイルのマージコンフリクト解消UIを実装した。
変更内容
createBlamePlugin.tsを新規追加)viewMode: "conflict"時にFileViewerPaneがrawデータを上書きしないよう修正テスト方法
Summary by CodeRabbit
New Features
Bug Fixes