Skip to content

Conversation

@saddlepaddle
Copy link
Collaborator

@saddlepaddle saddlepaddle commented Nov 28, 2025

Implement a comprehensive diff viewing system for the Changes tab:

  • Add tRPC router for git diff operations (getChangedFiles, getAllDiffs, etc.)
  • Create Zustand store for diff UI state (mode, commit range, scroll-to)
  • Build sidebar components: DiffModeSelector, FileTree, FileNode, FolderNode
  • Build content components: AllDiffsViewer with react-window virtualization
  • Add theme utilities for diff colors derived from terminal ANSI colors
  • Add Shiki highlighter hook for syntax highlighting
  • Support three diff modes: Unstaged, All Changes (vs parent), and Range

Uses infinite scroll approach where all diffs are shown in a single
virtualized list, and clicking a file in the sidebar scrolls to it.

🤖 Generated with Claude Code

Co-Authored-By: Claude [email protected]

Description

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Additional Notes

Summary by CodeRabbit

New Features

  • Diff Viewer – View git changes with syntax-highlighted code, supporting unstaged and staged modifications
  • Changed Files Explorer – Browse modified files organized in a hierarchical tree with status badges and change counts
  • Commit History – Select specific commits to compare or view recent changes
  • Branch Comparison – Compare changes against parent branches
  • Multi-Mode Diffs – Switch between unstaged, staged, all-changes, and custom commit range views

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Nov 28, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
website Ready Ready Preview Comment Nov 28, 2025 6:21pm

@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

Walkthrough

This PR introduces a comprehensive diff viewer feature for the desktop application. It adds Git diff parsing utilities, a TRPC diff router, React hooks for syntax highlighting and theme management, a Zustand-based diff store for UI state, and multiple React components for displaying and navigating changes with virtualization support. Three npm dependencies are added (react-virtualized-auto-sizer, react-window, shiki).

Changes

Cohort / File(s) Change Summary
Dependencies
apps/desktop/package.json
Added runtime dependencies: react-virtualized-auto-sizer, react-window, shiki; added dev dependency @types/react-window
TRPC Diff Router
apps/desktop/src/lib/trpc/routers/diff/diff.ts, apps/desktop/src/lib/trpc/routers/diff/index.ts, apps/desktop/src/lib/trpc/routers/diff/types.ts
Created new TRPC diff router with procedures for fetching changed files, diffs, commit history, and branches; defined type system for git diff data structures (FileStatus, ChangedFile, DiffLine, DiffHunk, FileDiff, Commit) and DiffMode union type
Git Utilities
apps/desktop/src/lib/trpc/routers/diff/utils/parent-branch.ts, apps/desktop/src/lib/trpc/routers/diff/utils/parse-diff.ts
Added utilities to detect parent/current git branches and parse raw git diffs into structured FileDiff objects with language detection and hunk line-level detail
TRPC Router Integration
apps/desktop/src/lib/trpc/routers/index.ts
Integrated createDiffRouter into main app router as new diff property
Diff Rendering Hooks
apps/desktop/src/renderer/hooks/useDiffColors.ts, apps/desktop/src/renderer/hooks/useHighlighter.ts, apps/desktop/src/renderer/hooks/index.ts
Created useDiffColors hook deriving colors from theme; created useHighlighter hook providing lazy-loaded Shiki syntax highlighting with caching; added barrel exports for hooks
Diff Content View (Main)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/ChangesContent.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/index.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/types.ts
Removed placeholder "Coming soon" component; added new ChangesContent component fetching and displaying diffs via TRPC with loading/error states; added local type definitions for diff components
Diff Viewers (Virtualized)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/AllDiffsViewer.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/DiffViewer.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/.../*
Implemented virtualized diff viewers using react-window with components for rendering diff headers, hunk headers, and individual diff lines with syntax highlighting
Sidebar Changes View
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/index.ts
Removed placeholder "Coming soon" component; added new ChangesView component with file tree navigation, diff mode selector, commit history, and branch detection
File Tree Components
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/FileTree.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/FileNode.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/FolderNode.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/.../*
Created hierarchical file tree rendering with expandable folders and clickable file nodes showing diff status and change counts
Diff Mode Selector
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/DiffModeSelector.tsx
Added mode selector component for switching between unstaged, all-changes, and range (commit-based) diff views
File Tree Utilities
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/hooks/useFileTree.ts, apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/types.ts
Implemented useFileTree hook to build hierarchical tree structure from flat file list and getAllFolderPaths utility; defined tree node types
Diff Store (State Management)
apps/desktop/src/renderer/stores/diff/store.ts, apps/desktop/src/renderer/stores/diff/types.ts, apps/desktop/src/renderer/stores/diff/hooks.ts, apps/desktop/src/renderer/stores/diff/index.ts, apps/desktop/src/renderer/stores/index.ts
Created Zustand-based diff store managing mode, commit range, scroll targets, and expanded folders; exposed store via hooks for UI components; integrated into main stores module
Theme Utilities
apps/desktop/src/renderer/stores/theme/utils/diff-theme.ts, apps/desktop/src/renderer/stores/theme/utils/index.ts
Added theme utilities to derive DiffColors and create Shiki TextMate themes from terminal ANSI colors; exported new theme creation functions

Sequence Diagram

sequenceDiagram
    participant User as User
    participant SidebarCV as ChangesView<br/>(Sidebar)
    participant ContentCV as ChangesContent<br/>(Main)
    participant Store as Diff Store
    participant TRPC as TRPC Router
    participant Git as Git Utils
    participant AllDiffs as AllDiffsViewer<br/>(with virtualization)

    User->>SidebarCV: Selects workspace
    SidebarCV->>Store: Set mode (unstaged/all-changes/range)
    SidebarCV->>TRPC: getChangedFiles(worktreePath, mode)
    TRPC->>Git: Parse git status & numstat
    Git-->>TRPC: Changed files with additions/deletions
    TRPC-->>SidebarCV: File list
    SidebarCV->>SidebarCV: Build file tree
    SidebarCV-->>User: Display file tree

    rect rgb(200, 220, 240)
        Note over User,AllDiffs: User clicks file or changes mode
        User->>SidebarCV: Click file or select commit range
        SidebarCV->>Store: scrollToFile(path) / setCommitRange(range)
    end

    SidebarCV->>ContentCV: Trigger diffs fetch
    ContentCV->>TRPC: getAllDiffs(worktreePath, mode, range)
    TRPC->>Git: Get diffs for all files
    Git-->>TRPC: Raw diff content per file
    TRPC->>TRPC: parseGitDiff() for each file
    TRPC-->>ContentCV: FileDiff[] with hunks & lines
    ContentCV->>Store: Get scrollToFilePath
    ContentCV->>AllDiffs: Render with virtualization
    AllDiffs->>AllDiffs: Flatten hunks into items
    AllDiffs->>AllDiffs: Compute item heights (header/line/spacer)
    AllDiffs-->>User: Display virtualized diffs
    
    User->>AllDiffs: Scroll/interact
    AllDiffs->>AllDiffs: VariableSizeList renders visible items only
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • Git diff parsing logic (parse-diff.ts): Correctness of hunk header detection, line number tracking, binary file detection, and edge cases with renamed files
  • Git command execution (diff.ts, parent-branch.ts): Error handling robustness, git compatibility across versions, command invocation correctness
  • Shiki highlighter caching (useHighlighter.ts): Memory management with bounded cache, cache eviction policy, theme update handling, lazy initialization correctness
  • React-window virtualization (AllDiffsViewer.tsx, DiffViewer.tsx): Correctness of dynamic item height calculations, scroll position synchronization, and list ref management
  • Store state consistency (store.ts, hooks.ts): Proper state isolation, action atomicity, folder expansion tracking with Set mutations, scroll target lifecycle

Possibly Related PRs

  • desktop app ui rewrite  #104: Implements sidebar mode infrastructure and gating logic that this PR integrates with for the ChangesView component
  • diffs #73: Contains related git diff handling and parent-branch detection utilities that align with this PR's diff operations
  • Settings + Themes #158: Modifies renderer theme utilities and exports that this PR extends with new Shiki theme creation functions

Poem

🐰 A warren of diffs, now beautifully scrolled,
With virtualized hunks, a sight to behold!
React-window whispers through files so grand,
While Shiki highlights, with syntax so grand—
Our Changes view springs forth from coming soon,
A diff viewer's symphony, perfectly in tune!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.89% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add GitHub-style diff viewer with infinite scroll' directly and clearly describes the main feature being added—a diff viewer component with virtualized infinite scroll, which is the primary focus of this changeset.
Description check ✅ Passed The PR description provides a comprehensive overview of the implementation (tRPC router, Zustand store, components, theming, syntax highlighting) and explains the architectural approach (infinite scroll via virtualization). However, required template sections (Type of Change, Testing, and Related Issues) are not filled out, leaving structural gaps despite substantive content.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch violet-forest-64

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.

saddlepaddle and others added 2 commits November 27, 2025 23:47
Implement a comprehensive diff viewing system for the Changes tab:

- Add tRPC router for git diff operations (getChangedFiles, getAllDiffs, etc.)
- Create Zustand store for diff UI state (mode, commit range, scroll-to)
- Build sidebar components: DiffModeSelector, FileTree, FileNode, FolderNode
- Build content components: AllDiffsViewer with react-window virtualization
- Add theme utilities for diff colors derived from terminal ANSI colors
- Add Shiki highlighter hook for syntax highlighting
- Support three diff modes: Unstaged, All Changes (vs parent), and Range

Uses infinite scroll approach where all diffs are shown in a single
virtualized list, and clicking a file in the sidebar scrolls to it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Copy link

@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

🧹 Nitpick comments (12)
apps/desktop/src/lib/trpc/routers/diff/utils/parse-diff.ts (1)

144-152: Edge case: Empty lines at EOF may be misclassified.

The condition line === "" handles trailing empty strings from split("\n") at EOF, but these aren't actual diff content. They'll be added as context lines with both line counters incremented, which could cause line number drift if the diff has trailing newlines.

Consider filtering out trailing empty strings before processing:

 export function parseGitDiff(rawDiff: string, filePath: string): FileDiff {
-	const lines = rawDiff.split("\n");
+	const lines = rawDiff.split("\n");
+	// Remove trailing empty string from split
+	if (lines.length > 0 && lines[lines.length - 1] === "") {
+		lines.pop();
+	}
apps/desktop/src/renderer/hooks/useDiffColors.ts (1)

1-32: Hook implementation looks solid; consider alias imports for stores

The default colors and useMemo over theme correctly fall back when no theme is loaded and recompute only on theme changes. The mapping through createDiffColors matches the DiffColors contract.

To align with the desktop alias convention, consider switching the relative store imports to aliases, e.g. something along the lines of:

// Example sketch – adjust to your actual aliases
import { useTheme } from "renderer/stores/theme";
import { createDiffColors, type DiffColors } from "renderer/stores/theme/utils";

This keeps imports consistent across the renderer codebase.

As per coding guidelines.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/ChangesContent.tsx (1)

31-40: Guard diff query for range mode when no commit range is selected

If the diff router requires a range when mode === "range", calling diff.getAllDiffs with mode: "range" but range: undefined will surface as an avoidable error in the UI.

You can make the query enable condition stricter, e.g.:

const isRangeMode = mode === "range";
const hasRange = !!commitRange;

const {
  data: allDiffs,
  isLoading,
  error,
} = trpc.diff.getAllDiffs.useQuery(
  {
    worktreePath: worktreePath ?? "",
    mode,
    range: commitRange || undefined,
  },
  {
    enabled: !!worktreePath && (!isRangeMode || hasRange),
  },
);

so the range-mode query only fires once a valid range is configured.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/FileNode.tsx (2)

7-26: Hardcoded colors break theme consistency.

The modified (#d29922) and renamed/copied (#58a6ff) status colors are hardcoded and won't adapt to theme changes, unlike other status colors that use colors.addedIndicator, colors.deletedIndicator, etc. Consider extending the DiffColors interface to include colors for all status types.

As per coding guidelines, follow existing patterns and match the codebase style when writing new code.


69-69: Consider safer filename extraction.

Using .split("/").pop() could theoretically return undefined for edge cases (empty path). While file paths should always have a filename in practice, consider adding a fallback for robustness.

-<span className="truncate flex-1">{file.path.split("/").pop()}</span>
+<span className="truncate flex-1">{file.path.split("/").pop() || file.path}</span>
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/DiffViewer.tsx (1)

9-11: Consider importing DiffViewerProps from types.

The DiffViewerProps interface is defined locally here, but according to the AI summary, it's also defined in ../../types.ts. Consider importing it from the types file to avoid duplication and maintain a single source of truth.

As per coding guidelines, keep diffs minimal and avoid code duplication.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx (1)

86-92: Potential unnecessary effect re-runs due to fileTree dependency.

fileTree is derived from changedFiles via useMemo, so including both changedFiles and fileTree in the dependency array is redundant. Since fileTree changes whenever changedFiles changes, the effect will run correctly with just [changedFiles, expandAllFolders]. Additionally, getAllFolderPaths is a pure function call, not a dependency.

 	useEffect(() => {
 		if (changedFiles && changedFiles.length > 0) {
 			const allPaths = getAllFolderPaths(fileTree);
 			expandAllFolders(allPaths);
 		}
-	}, [changedFiles, fileTree, expandAllFolders]);
+	}, [changedFiles, fileTree, expandAllFolders]); // fileTree is derived from changedFiles

Alternatively, if the linter doesn't complain, you could simplify to [changedFiles, expandAllFolders] since fileTree is a stable derivation of changedFiles.

apps/desktop/src/lib/trpc/routers/diff/diff.ts (2)

364-369: Promise.all will fail entirely if any single file diff fails.

If one file's diff fails (e.g., binary file, permission issue), the entire getAllDiffs call fails. Consider using Promise.allSettled or adding individual error handling to provide partial results.

 			// Get all diffs in parallel
-			const diffs = await Promise.all(
+			const results = await Promise.allSettled(
 				files.map(async (file) => {
-					const rawDiff = await git.raw([...diffBase, "--", file.path]);
-					return parseGitDiff(rawDiff, file.path);
+					try {
+						const rawDiff = await git.raw([...diffBase, "--", file.path]);
+						return parseGitDiff(rawDiff, file.path);
+					} catch {
+						// Return empty diff on failure
+						return { path: file.path, isBinary: false, language: "", hunks: [] };
+					}
 				}),
 			);
+			const diffs = results
+				.filter((r): r is PromiseFulfilledResult<FileDiff> => r.status === "fulfilled")
+				.map((r) => r.value);

 			return diffs;

119-142: Consider extracting duplicated file parsing logic.

The logic for parsing --name-status output and merging numstat data is duplicated across all-changes and range modes (and partially in getAllDiffs). Extracting this into a helper function would improve maintainability.

For example:

function parseNameStatus(output: string): ChangedFile[] {
  const files: ChangedFile[] = [];
  const lines = output.trim().split("\n").filter(Boolean);
  for (const line of lines) {
    const parts = line.split("\t");
    if (parts.length >= 2) {
      const statusChar = parts[0][0] as FileStatus;
      const path = parts[parts.length - 1];
      const oldPath = parts[0].startsWith("R") && parts.length === 3 ? parts[1] : undefined;
      files.push({ path, status: statusChar, oldPath, additions: 0, deletions: 0 });
    }
  }
  return files;
}

Also applies to: 165-215, 217-272

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/AllDiffsViewer.tsx (1)

89-102: Consider calling resetAfterIndex when diffs change.

When diffs change, the flattened items array changes, but VariableSizeList caches item sizes internally. Without resetting the cache, stale sizes may cause layout issues.

+	// Reset list size cache when items change
+	useEffect(() => {
+		if (listRef.current) {
+			listRef.current.resetAfterIndex(0);
+		}
+	}, [items]);
+
 	// Pre-calculate changes for each file (for headers)
 	const fileChanges = useMemo(() => {
apps/desktop/src/renderer/hooks/useHighlighter.ts (1)

91-130: Consider cleanup for async effect.

The initOrUpdate async function runs without cleanup handling. If the component unmounts or shikiTheme changes rapidly while initialization is in progress, the stale callback may still call setHighlighter/setError on an unmounted component.

 	useEffect(() => {
 		if (!shikiTheme) return;
 
 		const themeJson = JSON.stringify(shikiTheme);
+		let cancelled = false;
 
 		const initOrUpdate = async () => {
 			try {
 				// Dynamically import shiki to enable code splitting
 				const { createHighlighter } = await import("shiki");
 
 				// Initialize highlighter if needed
 				if (!highlighterPromise) {
 					setIsLoading(true);
 					highlighterPromise = createHighlighter({
 						themes: [shikiTheme],
 						langs: PRELOAD_LANGUAGES,
 					});
 					highlighterInstance = await highlighterPromise;
 					currentThemeJson = themeJson;
 				} else if (currentThemeJson !== themeJson) {
 					// Theme changed, load the new theme
 					const hl = await highlighterPromise;
 					await hl.loadTheme(shikiTheme);
 					currentThemeJson = themeJson;
 					// Clear cache when theme changes
 					lineCache.clear();
 				}
 
+				if (cancelled) return;
 				setHighlighter(highlighterInstance);
 				setError(null);
 			} catch (err) {
+				if (cancelled) return;
 				console.error("Failed to initialize highlighter:", err);
 				setError(err instanceof Error ? err : new Error(String(err)));
 			} finally {
+				if (cancelled) return;
 				setIsLoading(false);
 			}
 		};
 
 		initOrUpdate();
+
+		return () => {
+			cancelled = true;
+		};
 	}, [shikiTheme]);
apps/desktop/src/renderer/stores/theme/utils/diff-theme.ts (1)

30-39: Make hexToRgba slightly more defensive without changing behavior

The implementation is fine given controlled inputs, but you can cheaply harden it by only stripping a leading # (rather than the first # anywhere), which avoids surprises if a malformed value ever sneaks in. This keeps the change minimal and behaviorally equivalent for valid color strings (e.g. #RRGGBB). Based on learnings, this keeps the diff tight while improving robustness.

-function hexToRgba(hex: string, alpha: number): string {
-	// Remove # if present
-	const cleanHex = hex.replace("#", "");
+function hexToRgba(hex: string, alpha: number): string {
+	// Remove leading # if present
+	const cleanHex = hex.replace(/^#/, "");
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a71d23 and 9e33026.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (44)
  • apps/desktop/package.json (2 hunks)
  • apps/desktop/src/lib/trpc/routers/diff/diff.ts (1 hunks)
  • apps/desktop/src/lib/trpc/routers/diff/index.ts (1 hunks)
  • apps/desktop/src/lib/trpc/routers/diff/types.ts (1 hunks)
  • apps/desktop/src/lib/trpc/routers/diff/utils/parent-branch.ts (1 hunks)
  • apps/desktop/src/lib/trpc/routers/diff/utils/parse-diff.ts (1 hunks)
  • apps/desktop/src/lib/trpc/routers/index.ts (2 hunks)
  • apps/desktop/src/renderer/hooks/index.ts (1 hunks)
  • apps/desktop/src/renderer/hooks/useDiffColors.ts (1 hunks)
  • apps/desktop/src/renderer/hooks/useHighlighter.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/ChangesContent.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/AllDiffsViewer.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/DiffHeader.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/DiffHunkHeader.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/DiffLine.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/DiffViewer.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/types.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/DiffModeSelector.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/FileNode.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/FileTree.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/FolderNode.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/hooks/useFileTree.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/index.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/types.ts (1 hunks)
  • apps/desktop/src/renderer/stores/diff/hooks.ts (1 hunks)
  • apps/desktop/src/renderer/stores/diff/index.ts (1 hunks)
  • apps/desktop/src/renderer/stores/diff/store.ts (1 hunks)
  • apps/desktop/src/renderer/stores/diff/types.ts (1 hunks)
  • apps/desktop/src/renderer/stores/index.ts (1 hunks)
  • apps/desktop/src/renderer/stores/theme/utils/diff-theme.ts (1 hunks)
  • apps/desktop/src/renderer/stores/theme/utils/index.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx
🧰 Additional context used
📓 Path-based instructions (7)
apps/desktop/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/index.ts
  • apps/desktop/src/lib/trpc/routers/diff/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/ChangesContent.tsx
  • apps/desktop/src/renderer/stores/theme/utils/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/index.ts
  • apps/desktop/src/lib/trpc/routers/diff/utils/parent-branch.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/index.ts
  • apps/desktop/src/renderer/stores/diff/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/DiffLine.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/hooks/useFileTree.ts
  • apps/desktop/src/lib/trpc/routers/diff/diff.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/index.ts
  • apps/desktop/src/renderer/stores/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/DiffHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/FileNode.tsx
  • apps/desktop/src/renderer/hooks/useDiffColors.ts
  • apps/desktop/src/renderer/hooks/useHighlighter.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/types.ts
  • apps/desktop/src/renderer/stores/diff/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/AllDiffsViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/DiffViewer.tsx
  • apps/desktop/src/renderer/stores/theme/utils/diff-theme.ts
  • apps/desktop/src/renderer/stores/diff/store.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/FolderNode.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/lib/trpc/routers/diff/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/DiffModeSelector.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/FileTree.tsx
  • apps/desktop/src/renderer/hooks/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/index.ts
  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/index.ts
  • apps/desktop/src/lib/trpc/routers/diff/utils/parse-diff.ts
  • apps/desktop/src/renderer/stores/diff/hooks.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/DiffHunkHeader.tsx
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: Please use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/index.ts
  • apps/desktop/src/lib/trpc/routers/diff/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/ChangesContent.tsx
  • apps/desktop/src/renderer/stores/theme/utils/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/index.ts
  • apps/desktop/src/lib/trpc/routers/diff/utils/parent-branch.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/index.ts
  • apps/desktop/src/renderer/stores/diff/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/DiffLine.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/hooks/useFileTree.ts
  • apps/desktop/src/lib/trpc/routers/diff/diff.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/index.ts
  • apps/desktop/src/renderer/stores/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/DiffHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/FileNode.tsx
  • apps/desktop/src/renderer/hooks/useDiffColors.ts
  • apps/desktop/src/renderer/hooks/useHighlighter.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/types.ts
  • apps/desktop/src/renderer/stores/diff/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/AllDiffsViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/DiffViewer.tsx
  • apps/desktop/src/renderer/stores/theme/utils/diff-theme.ts
  • apps/desktop/src/renderer/stores/diff/store.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/FolderNode.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/lib/trpc/routers/diff/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/DiffModeSelector.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/FileTree.tsx
  • apps/desktop/src/renderer/hooks/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/index.ts
  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/index.ts
  • apps/desktop/src/lib/trpc/routers/diff/utils/parse-diff.ts
  • apps/desktop/src/renderer/stores/diff/hooks.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/DiffHunkHeader.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid using any type - use explicit types instead for type safety
Use camelCase for variable and function names following existing codebase patterns
Keep diffs minimal with targeted edits only - avoid unnecessary changes when making modifications
Follow existing patterns and match the codebase style when writing new code

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/index.ts
  • apps/desktop/src/lib/trpc/routers/diff/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/ChangesContent.tsx
  • apps/desktop/src/renderer/stores/theme/utils/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/index.ts
  • apps/desktop/src/lib/trpc/routers/diff/utils/parent-branch.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/index.ts
  • apps/desktop/src/renderer/stores/diff/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/DiffLine.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/hooks/useFileTree.ts
  • apps/desktop/src/lib/trpc/routers/diff/diff.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/index.ts
  • apps/desktop/src/renderer/stores/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/DiffHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/FileNode.tsx
  • apps/desktop/src/renderer/hooks/useDiffColors.ts
  • apps/desktop/src/renderer/hooks/useHighlighter.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/types.ts
  • apps/desktop/src/renderer/stores/diff/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/AllDiffsViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/DiffViewer.tsx
  • apps/desktop/src/renderer/stores/theme/utils/diff-theme.ts
  • apps/desktop/src/renderer/stores/diff/store.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/FolderNode.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/lib/trpc/routers/diff/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/DiffModeSelector.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/FileTree.tsx
  • apps/desktop/src/renderer/hooks/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/index.ts
  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/index.ts
  • apps/desktop/src/lib/trpc/routers/diff/utils/parse-diff.ts
  • apps/desktop/src/renderer/stores/diff/hooks.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/DiffHunkHeader.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules (fs, path, os, net, etc.) in renderer process code - browser environment only

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/ChangesContent.tsx
  • apps/desktop/src/renderer/stores/theme/utils/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/index.ts
  • apps/desktop/src/renderer/stores/diff/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/DiffLine.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/hooks/useFileTree.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/index.ts
  • apps/desktop/src/renderer/stores/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/DiffHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/FileNode.tsx
  • apps/desktop/src/renderer/hooks/useDiffColors.ts
  • apps/desktop/src/renderer/hooks/useHighlighter.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/types.ts
  • apps/desktop/src/renderer/stores/diff/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/AllDiffsViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/DiffViewer.tsx
  • apps/desktop/src/renderer/stores/theme/utils/diff-theme.ts
  • apps/desktop/src/renderer/stores/diff/store.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/FolderNode.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/DiffModeSelector.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/FileTree.tsx
  • apps/desktop/src/renderer/hooks/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/index.ts
  • apps/desktop/src/renderer/stores/diff/hooks.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/DiffHunkHeader.tsx
apps/desktop/src/lib/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules in shared code like src/lib/electron-router-dom.ts - this code runs in both main and renderer processes

Files:

  • apps/desktop/src/lib/trpc/routers/diff/index.ts
  • apps/desktop/src/lib/trpc/routers/diff/utils/parent-branch.ts
  • apps/desktop/src/lib/trpc/routers/diff/diff.ts
  • apps/desktop/src/lib/trpc/routers/diff/types.ts
  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/lib/trpc/routers/diff/utils/parse-diff.ts
**/components/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

**/components/**/*.tsx: Create one folder per component with structure: ComponentName/ComponentName.tsx + index.ts for barrel export
Co-locate tests next to the component file they test (e.g., ComponentName.test.tsx)
Co-locate dependencies (utils, hooks, constants, config, stories) next to the file using them
Use nested components/ subdirectory within a parent component only if a sub-component is used 2+ times within that parent; otherwise keep it in the parent's components/ folder
One component per file - avoid multi-component files

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/ChangesContent.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/DiffLine.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/DiffHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/FileNode.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/AllDiffsViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/DiffViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/FolderNode.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/DiffModeSelector.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/FileTree.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/DiffHunkHeader.tsx
apps/desktop/src/renderer/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Call IPC methods using window.ipcRenderer.invoke() with object parameters - TypeScript will infer the exact response type automatically

Files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/ChangesContent.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/DiffLine.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/DiffHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/FileNode.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/AllDiffsViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/DiffViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/FolderNode.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/DiffModeSelector.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/FileTree.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/DiffHunkHeader.tsx
🧠 Learnings (10)
📚 Learning: 2025-11-28T01:03:47.951Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.951Z
Learning: Applies to **/*.{ts,tsx} : Keep diffs minimal with targeted edits only - avoid unnecessary changes when making modifications

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/index.ts
  • apps/desktop/src/lib/trpc/routers/diff/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/ChangesContent.tsx
  • apps/desktop/src/renderer/stores/theme/utils/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/index.ts
  • apps/desktop/src/lib/trpc/routers/diff/utils/parent-branch.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/index.ts
  • apps/desktop/src/renderer/stores/diff/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/DiffLine.tsx
  • apps/desktop/src/lib/trpc/routers/diff/diff.ts
  • apps/desktop/src/renderer/stores/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/DiffHeader.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/FileNode.tsx
  • apps/desktop/src/renderer/hooks/useDiffColors.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/types.ts
  • apps/desktop/src/renderer/stores/diff/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/AllDiffsViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/DiffViewer.tsx
  • apps/desktop/src/renderer/stores/theme/utils/diff-theme.ts
  • apps/desktop/src/renderer/stores/diff/store.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/lib/trpc/routers/diff/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/DiffModeSelector.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/FileTree.tsx
  • apps/desktop/src/renderer/hooks/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/index.ts
  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffViewer/index.ts
  • apps/desktop/src/lib/trpc/routers/diff/utils/parse-diff.ts
  • apps/desktop/src/renderer/stores/diff/hooks.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/DiffHunkHeader.tsx
📚 Learning: 2025-11-24T21:33:13.244Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.244Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Please use alias as defined in `tsconfig.json` when possible

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/index.ts
  • apps/desktop/src/renderer/stores/diff/index.ts
  • apps/desktop/src/renderer/stores/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/index.ts
  • apps/desktop/src/renderer/hooks/useHighlighter.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/types.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/types.ts
  • apps/desktop/src/renderer/hooks/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/index.ts
📚 Learning: 2025-11-28T01:03:47.951Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.951Z
Learning: Applies to apps/desktop/src/lib/**/*.ts : Never import Node.js modules in shared code like `src/lib/electron-router-dom.ts` - this code runs in both main and renderer processes

Applied to files:

  • apps/desktop/src/lib/trpc/routers/diff/index.ts
  • apps/desktop/src/renderer/stores/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/index.ts
  • apps/desktop/src/renderer/hooks/index.ts
  • apps/desktop/src/lib/trpc/routers/index.ts
📚 Learning: 2025-11-28T01:03:47.951Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.951Z
Learning: Applies to **/components/**/*.tsx : Create one folder per component with structure: `ComponentName/ComponentName.tsx` + `index.ts` for barrel export

Applied to files:

  • apps/desktop/src/lib/trpc/routers/diff/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/FolderNode.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/FileTree.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/index.ts
📚 Learning: 2025-11-28T01:03:47.951Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.951Z
Learning: Applies to **/components/**/*.tsx : Co-locate dependencies (utils, hooks, constants, config, stories) next to the file using them

Applied to files:

  • apps/desktop/src/renderer/stores/diff/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/DiffLine.tsx
  • apps/desktop/src/renderer/hooks/index.ts
  • apps/desktop/src/renderer/stores/diff/hooks.ts
📚 Learning: 2025-11-28T01:03:47.951Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.951Z
Learning: Applies to packages/ui/src/**/*.tsx : Use shadcn/ui components and TailwindCSS v4 for all UI component styling in the shared UI package

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/DiffLine.tsx
  • apps/desktop/package.json
📚 Learning: 2025-11-28T01:03:47.951Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.951Z
Learning: Applies to apps/desktop/src/renderer/**/*.{ts,tsx} : Never import Node.js modules (fs, path, os, net, etc.) in renderer process code - browser environment only

Applied to files:

  • apps/desktop/src/renderer/stores/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/index.ts
  • apps/desktop/src/renderer/hooks/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/index.ts
📚 Learning: 2025-11-24T21:33:13.244Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: apps/desktop/AGENTS.md:0-0
Timestamp: 2025-11-24T21:33:13.244Z
Learning: Applies to apps/desktop/**/*.{ts,tsx} : Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary

Applied to files:

  • apps/desktop/src/renderer/stores/index.ts
  • apps/desktop/src/renderer/stores/diff/store.ts
📚 Learning: 2025-11-28T01:03:47.951Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.951Z
Learning: Applies to apps/desktop/src/main/**/*.{ts,tsx} : Node.js modules (fs, path, os, net, etc.) can be used in main process code only

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/index.ts
📚 Learning: 2025-11-28T01:03:47.951Z
Learnt from: CR
Repo: superset-sh/superset PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-28T01:03:47.951Z
Learning: Applies to **/components/**/*.tsx : One component per file - avoid multi-component files

Applied to files:

  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileTree/FileTree.tsx
🧬 Code graph analysis (15)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/DiffLine.tsx (4)
apps/desktop/src/renderer/hooks/useHighlighter.ts (1)
  • useHighlighter (71-204)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/types.ts (2)
  • DiffLineProps (12-16)
  • DiffLine (3-3)
apps/desktop/src/renderer/hooks/useDiffColors.ts (1)
  • useDiffColors (23-33)
apps/desktop/src/lib/trpc/routers/diff/types.ts (1)
  • DiffLine (22-31)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/hooks/useFileTree.ts (2)
apps/desktop/src/lib/trpc/routers/diff/types.ts (1)
  • ChangedFile (7-18)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/types.ts (1)
  • FileTreeNode (5-16)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/FileNode.tsx (4)
apps/desktop/src/renderer/hooks/index.ts (1)
  • useDiffColors (2-2)
apps/desktop/src/renderer/hooks/useDiffColors.ts (1)
  • useDiffColors (23-33)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FileNode/index.ts (1)
  • FileNode (1-1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/types.ts (1)
  • FileNodeProps (29-36)
apps/desktop/src/renderer/hooks/useDiffColors.ts (3)
apps/desktop/src/renderer/stores/theme/utils/diff-theme.ts (2)
  • DiffColors (6-25)
  • createDiffColors (44-63)
apps/desktop/src/renderer/stores/theme/utils/index.ts (2)
  • DiffColors (10-10)
  • createDiffColors (7-7)
apps/desktop/src/renderer/hooks/index.ts (1)
  • useDiffColors (2-2)
apps/desktop/src/renderer/hooks/useHighlighter.ts (3)
apps/desktop/src/renderer/hooks/index.ts (1)
  • useHighlighter (3-3)
apps/desktop/src/renderer/stores/theme/utils/diff-theme.ts (1)
  • createShikiTheme (108-241)
apps/desktop/src/renderer/stores/theme/utils/index.ts (1)
  • createShikiTheme (8-8)
apps/desktop/src/renderer/stores/diff/types.ts (1)
apps/desktop/src/lib/trpc/routers/diff/types.ts (1)
  • DiffMode (74-74)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/AllDiffsViewer/AllDiffsViewer.tsx (5)
apps/desktop/src/lib/trpc/routers/diff/types.ts (4)
  • FileDiff (48-59)
  • DiffLineType (20-20)
  • DiffHunk (33-46)
  • DiffLine (22-31)
apps/desktop/src/renderer/stores/diff/hooks.ts (2)
  • useScrollToFilePath (6-7)
  • useClearScrollTarget (17-18)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHeader/DiffHeader.tsx (1)
  • DiffHeader (5-32)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffHunkHeader/DiffHunkHeader.tsx (1)
  • DiffHunkHeader (27-27)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/DiffLine.tsx (1)
  • DiffLine (92-92)
apps/desktop/src/renderer/stores/theme/utils/diff-theme.ts (1)
apps/desktop/src/renderer/stores/theme/utils/index.ts (5)
  • DiffColors (10-10)
  • createDiffColors (7-7)
  • ShikiThemeTokenColors (11-11)
  • createShikiTokenColors (9-9)
  • createShikiTheme (8-8)
apps/desktop/src/renderer/stores/diff/store.ts (3)
apps/desktop/src/renderer/stores/diff/index.ts (1)
  • useDiffStore (2-2)
apps/desktop/src/renderer/stores/diff/types.ts (1)
  • DiffStore (52-52)
apps/cli/src/lib/storage/lowdb-adapter.ts (1)
  • set (162-172)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/FolderNode.tsx (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/FolderNode/index.ts (1)
  • FolderNode (1-1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/types.ts (1)
  • FolderNodeProps (38-49)
apps/desktop/src/lib/trpc/routers/diff/types.ts (1)
apps/desktop/src/renderer/stores/diff/types.ts (1)
  • DiffMode (5-5)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/DiffModeSelector.tsx (4)
apps/desktop/src/lib/trpc/routers/diff/types.ts (2)
  • DiffMode (74-74)
  • Commit (61-72)
apps/desktop/src/renderer/stores/diff/types.ts (2)
  • DiffMode (5-5)
  • CommitRange (7-10)
packages/ui/src/components/button.tsx (1)
  • Button (61-61)
packages/ui/src/components/dropdown-menu.tsx (5)
  • DropdownMenu (245-245)
  • DropdownMenuTrigger (247-247)
  • DropdownMenuContent (248-248)
  • DropdownMenuItem (251-251)
  • DropdownMenuSeparator (255-255)
apps/desktop/src/lib/trpc/routers/index.ts (2)
apps/desktop/src/lib/trpc/routers/diff/diff.ts (1)
  • createDiffRouter (101-500)
apps/desktop/src/lib/trpc/routers/diff/index.ts (1)
  • createDiffRouter (1-1)
apps/desktop/src/lib/trpc/routers/diff/utils/parse-diff.ts (1)
apps/desktop/src/lib/trpc/routers/diff/types.ts (3)
  • FileDiff (48-59)
  • DiffHunk (33-46)
  • DiffLine (22-31)
apps/desktop/src/renderer/stores/diff/hooks.ts (2)
apps/desktop/src/renderer/stores/diff/index.ts (1)
  • useDiffStore (2-2)
apps/desktop/src/renderer/stores/diff/store.ts (1)
  • useDiffStore (12-72)
🪛 ast-grep (0.40.0)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/DiffLine.tsx

[warning] 27-27: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build

Comment on lines +83 to +92
// Handle renamed files (format: "old => new" or "{prefix => suffix}")
let normalizedPath = filePath;
if (filePath.includes(" => ")) {
// Extract the new path from rename notation
normalizedPath = filePath.replace(/.*\{.*? => (.*?)\}.*/, "$1");
if (normalizedPath === filePath) {
// Simple rename without braces
normalizedPath = filePath.split(" => ")[1] || filePath;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Regex for renamed file path extraction may not handle all cases.

The regex .*\{.*? => (.*?)\}.* extracts only the new portion but doesn't reconstruct the full path when the rename uses brace notation like src/{old.ts => new.ts}. The result would be new.ts instead of src/new.ts.

Consider a more robust approach:

 			// Handle renamed files (format: "old => new" or "{prefix => suffix}")
 			let normalizedPath = filePath;
 			if (filePath.includes(" => ")) {
-				// Extract the new path from rename notation
-				normalizedPath = filePath.replace(/.*\{.*? => (.*?)\}.*/, "$1");
-				if (normalizedPath === filePath) {
-					// Simple rename without braces
-					normalizedPath = filePath.split(" => ")[1] || filePath;
-				}
+				// Handle brace notation: path/{old => new}/file.ts -> path/new/file.ts
+				if (filePath.includes("{") && filePath.includes("}")) {
+					normalizedPath = filePath.replace(/\{[^}]* => ([^}]*)\}/g, "$1");
+				} else {
+					// Simple rename: old => new
+					normalizedPath = filePath.split(" => ")[1] || filePath;
+				}
 			}
📝 Committable suggestion

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

Suggested change
// Handle renamed files (format: "old => new" or "{prefix => suffix}")
let normalizedPath = filePath;
if (filePath.includes(" => ")) {
// Extract the new path from rename notation
normalizedPath = filePath.replace(/.*\{.*? => (.*?)\}.*/, "$1");
if (normalizedPath === filePath) {
// Simple rename without braces
normalizedPath = filePath.split(" => ")[1] || filePath;
}
}
// Handle renamed files (format: "old => new" or "{prefix => suffix}")
let normalizedPath = filePath;
if (filePath.includes(" => ")) {
// Handle brace notation: path/{old => new}/file.ts -> path/new/file.ts
if (filePath.includes("{") && filePath.includes("}")) {
normalizedPath = filePath.replace(/\{[^}]* => ([^}]*)\}/g, "$1");
} else {
// Simple rename: old => new
normalizedPath = filePath.split(" => ")[1] || filePath;
}
}
🤖 Prompt for AI Agents
In apps/desktop/src/lib/trpc/routers/diff/diff.ts around lines 83-92, the
current regex extracts only the inner "new" token from brace-style renames (e.g.
"src/{old.ts => new.ts}") producing "new.ts" instead of "src/new.ts"; change the
logic to detect brace notation, capture the parts before the brace, the new
inner path, and any suffix after the brace, then reconstruct the full path by
concatenating prefix + newPart + suffix; keep a fallback for simple "old => new"
notations by splitting on " => " and using the right-hand side when brace
parsing fails.

date: string;
}

export type DiffMode = "unstaged" | "staged" | "all-changes" | "range";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Inconsistent DiffMode type definition across files.

This file defines DiffMode as "unstaged" | "staged" | "all-changes" | "range", but the store types at apps/desktop/src/renderer/stores/diff/types.ts (line 4) defines it as "unstaged" | "all-changes" | "range" (missing "staged").

This inconsistency could cause type mismatches when the UI state interacts with the tRPC API.

Either:

  1. Add "staged" to the store's DiffMode type if staged mode is supported in the UI
  2. Remove "staged" from this file if it's not a supported UI mode
  3. Consider having the store import and re-export the type from this file to maintain a single source of truth
#!/bin/bash
# Check all DiffMode usages to understand intended behavior
rg -n "DiffMode" --type ts -B 2 -A 2

Comment on lines +51 to +55
export async function getCurrentBranch(worktreePath: string): Promise<string> {
const git = simpleGit(worktreePath);
const branch = await git.revparse(["--abbrev-ref", "HEAD"]);
return branch.trim();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add error handling for getCurrentBranch.

Unlike detectParentBranch, this function has no try/catch. If the repository is in a detached HEAD state or the revparse fails for other reasons, the error will propagate unhandled.

 export async function getCurrentBranch(worktreePath: string): Promise<string> {
 	const git = simpleGit(worktreePath);
-	const branch = await git.revparse(["--abbrev-ref", "HEAD"]);
-	return branch.trim();
+	try {
+		const branch = await git.revparse(["--abbrev-ref", "HEAD"]);
+		return branch.trim();
+	} catch {
+		// Detached HEAD or other error - return HEAD as fallback
+		return "HEAD";
+	}
 }
🤖 Prompt for AI Agents
In apps/desktop/src/lib/trpc/routers/diff/utils/parent-branch.ts around lines 51
to 55, getCurrentBranch lacks error handling and will throw if revparse fails
(e.g., detached HEAD); wrap the git.revparse call in a try/catch, handle
expected failure modes by returning a sensible fallback (such as an empty string
or null) or rethrowing a more descriptive error, and ensure the returned
type/signature reflects the fallback (update Promise<string> to Promise<string |
null> if returning null) and add a clear processLogger or debug log inside the
catch with the original error message.

Comment on lines +8 to +40
export function ChangesContent() {
const { data: activeWorkspace } = trpc.workspaces.getActive.useQuery();
const { data: workspaceGroups } = trpc.workspaces.getAllGrouped.useQuery();

// Diff store state
const mode = useDiffMode();
const commitRange = useCommitRange();

// Get worktree path from active workspace
const worktreePath = useMemo(() => {
if (!activeWorkspace || !workspaceGroups) return null;
for (const group of workspaceGroups) {
const ws = group.workspaces.find((w) => w.id === activeWorkspace.id);
if (ws) return ws.worktreePath;
}
return null;
}, [activeWorkspace, workspaceGroups]);

// Query for all diffs
const {
data: allDiffs,
isLoading,
error,
} = trpc.diff.getAllDiffs.useQuery(
{
worktreePath: worktreePath ?? "",
mode: mode,
range: commitRange || undefined,
},
{
enabled: !!worktreePath,
},
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Loading/empty state conflates “no changes” with “still resolving workspace”

Right now the loading spinner is tied only to the diff query’s isLoading. While the workspace queries are still in-flight (or if they error), worktreePath is null, the diff query is disabled, and the component falls through to the “No changes to display” state. That can briefly show an empty state while things are still loading, or mask workspace-level failures.

Consider:

  • Tracking isLoading / error from getActive and getAllGrouped, and
  • Using a combined loading flag (e.g. isWorkspaceLoading || isDiffLoading) before rendering “no changes”,
  • Optionally differentiating “no workspace / failed to load workspace” from “no diffs for this workspace”.

This will make the user-facing states more accurate.

Also applies to: 70-82

🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent.tsx
around lines 8-40 (and similarly update 70-82), capture the loading and error
flags from trpc.workspaces.getActive.useQuery and
trpc.workspaces.getAllGrouped.useQuery, derive a combined isLoadingWorkspace (or
isWorkspaceError) and use that to: 1) show a global loading spinner while either
workspace query is still loading (i.e. isWorkspaceLoading || isDiffLoading), 2)
surface workspace-level errors when either workspace query errored, and 3) only
render the “No changes to display” empty state when worktreePath is non-null,
workspace queries are settled without error, and the diff query is finished and
returned an empty list; ensure the diff query remains enabled only when
worktreePath is present.

Comment on lines +27 to +29
// biome-ignore lint/security/noDangerouslySetInnerHtml: Shiki output is trusted
dangerouslySetInnerHTML={{ __html: html }}
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the file under review and understand its context
find . -path "*DiffLine.tsx" -type f 2>/dev/null | head -5

Repository: superset-sh/superset

Length of output: 189


🏁 Script executed:

#!/bin/bash
# Search for escapeHtml function definition
ast-grep --pattern 'function escapeHtml($_) {
  $$$
}'

echo "---"

# Also search for it as a named export or const
rg -n "escapeHtml" --type ts --type tsx -A 3 | head -50

Repository: superset-sh/superset

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Find all usages of dangerouslySetInnerHTML in the codebase
rg -n "dangerouslySetInnerHTML" --type tsx --type ts

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Read the DiffLine.tsx file to understand context around lines 27-29
cat -n ./apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/components/DiffLine/DiffLine.tsx | head -50

Repository: superset-sh/superset

Length of output: 1597


🏁 Script executed:

#!/bin/bash
# Search for escapeHtml without type filter
rg -n "escapeHtml" -A 3 -B 1 2>/dev/null | head -60

Repository: superset-sh/superset

Length of output: 1775


🏁 Script executed:

#!/bin/bash
# Search for dangerouslySetInnerHTML without type filter
rg -n "dangerouslySetInnerHTML" -B 2 -A 2 2>/dev/null

Repository: superset-sh/superset

Length of output: 867


🏁 Script executed:

#!/bin/bash
# Search for useHighlighter hook
rg -n "useHighlighter" -A 5 -B 1 2>/dev/null | head -40

Repository: superset-sh/superset

Length of output: 2996


🏁 Script executed:

#!/bin/bash
# Read the complete useHighlighter.ts file to understand escapeHtml and highlightLine
cat -n ./apps/desktop/src/renderer/hooks/useHighlighter.ts

Repository: superset-sh/superset

Length of output: 6650


Review comment contains valid concerns but misses a critical vulnerability.

After verifying the implementation, the escapeHtml function is comprehensive and properly escapes all five critical HTML entities (&, <, >, ", '). The token content is correctly escaped at line 162 of useHighlighter.ts:

`<span style="color:${token.color}">${escapeHtml(token.content)}</span>`

However, there's a separate XSS/CSS injection vulnerability: token.color is directly interpolated into the style attribute without validation or sanitization. While Shiki is a trusted library and colors come from the app's theme configuration, this pattern is still risky:

  • An attacker could exploit Shiki bugs or malicious theme data to inject CSS expressions or values
  • Better approach: Use CSS classes with predefined colors instead of inline style="color:..." attributes, or validate/sanitize the color value to ensure it matches expected format (e.g., hex, rgb, named colors)

The original review comment's focus on escapeHtml coverage is verified as correct, but the real risk is the unvalidated token.color in the style attribute, not the escaped token content.

🧰 Tools
🪛 ast-grep (0.40.0)

[warning] 27-27: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

Comment on lines +12 to +16
export interface DiffLineProps {
line: DiffLine;
language: string;
style?: React.CSSProperties;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing React import for React.CSSProperties.

React.CSSProperties is used on line 15, but React is not imported. This will cause a TypeScript/runtime error.

+import type React from "react";
 import type { DiffHunk, DiffLine, FileDiff } from "lib/trpc/routers/diff/types";

Alternatively, import the type directly:

+import type { CSSProperties } from "react";
 import type { DiffHunk, DiffLine, FileDiff } from "lib/trpc/routers/diff/types";

 export interface DiffLineProps {
 	line: DiffLine;
 	language: string;
-	style?: React.CSSProperties;
+	style?: CSSProperties;
 }
📝 Committable suggestion

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

Suggested change
export interface DiffLineProps {
line: DiffLine;
language: string;
style?: React.CSSProperties;
}
import type React from "react";
import type { DiffHunk, DiffLine, FileDiff } from "lib/trpc/routers/diff/types";
export interface DiffLineProps {
line: DiffLine;
language: string;
style?: React.CSSProperties;
}
🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/ChangesContent/types.ts
around lines 12 to 16, the file references React.CSSProperties but does not
import React, causing a TypeScript error; fix by either importing React (e.g.
import React from 'react') so React.CSSProperties is available, or preferably
import the type directly (e.g. import type { CSSProperties } from 'react' and
change the prop to style?: CSSProperties) and update the type usage accordingly.

Comment on lines +9 to +11
import type { Commit } from "lib/trpc/routers/diff/types";
import { HiChevronDown } from "react-icons/hi2";
import type { CommitRange, DiffMode } from "renderer/stores/diff/types";
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all DiffMode type definitions
rg -n "type DiffMode\s*=" --type ts

Repository: superset-sh/superset

Length of output: 296


🏁 Script executed:

#!/bin/bash
# Search for usage of "staged" mode
rg -n '"staged"' --type ts -C 2

Repository: superset-sh/superset

Length of output: 2696


🏁 Script executed:

#!/bin/bash
# Check DiffModeSelector component to see how it uses DiffMode
cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/components/DiffModeSelector/DiffModeSelector.tsx

Repository: superset-sh/superset

Length of output: 4752


🏁 Script executed:

#!/bin/bash
# Search for other DiffMode usage in renderer components
rg -n "DiffMode\|mode === \"staged\"" apps/desktop/src/renderer --type ts

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

#!/bin/bash
# Check the diff store implementation
fd -t f "diff" apps/desktop/src/renderer/stores/ --exec cat -n {} \;

Repository: superset-sh/superset

Length of output: 7439


🏁 Script executed:

#!/bin/bash
# Check the diff types file directly
cat -n apps/desktop/src/renderer/stores/diff/types.ts

Repository: superset-sh/superset

Length of output: 1654


Type mismatch confirms "staged" mode is unsupported in renderer store.

The review comment is accurate. Two DiffMode definitions exist with a critical inconsistency:

  • lib/trpc/routers/diff/types.ts (backend): "unstaged" | "staged" | "all-changes" | "range"
  • renderer/stores/diff/types.ts (frontend): "unstaged" | "all-changes" | "range"

The backend tRPC router fully implements "staged" mode (three case handlers in diff.ts), but the renderer store's DiffMode type omits it. This prevents the frontend from ever handling "staged" mode due to type incompatibility—DiffModeSelector cannot pass "staged" to setMode() without a type error.

Fix: Add "staged" to the DiffMode type definition in renderer/stores/diff/types.ts line 5.

🤖 Prompt for AI Agents
In apps/desktop/src/renderer/stores/diff/types.ts around line 5, the DiffMode
union type omits "staged", causing a type mismatch with the backend and
preventing the frontend from passing staged to setMode(); update the DiffMode
definition to include the "staged" variant so it matches the backend ("unstaged"
| "staged" | "all-changes" | "range"), then run type checks to ensure no other
places need adjustments.

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.

2 participants