Skip to content

feat(desktop): auto-commit messages, built-in PR creation & inline diff comments#1380

Open
Kitenite wants to merge 2 commits intomainfrom
kitenite/changes-sidebar-impro
Open

feat(desktop): auto-commit messages, built-in PR creation & inline diff comments#1380
Kitenite wants to merge 2 commits intomainfrom
kitenite/changes-sidebar-impro

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Feb 10, 2026

Summary

  • Add three new features to the desktop app's changes sidebar: auto-generated commit messages, built-in PR creation via gh CLI, and inline commenting on diffs
  • These features improve the git workflow by keeping developers inside the app instead of context-switching to the terminal or browser

Changes

Auto-generate commit messages

  • New generateCommitMessage tRPC procedure in git-operations.ts that analyzes staged diffs (--name-status, --stat, --cached)
  • Heuristic-based conventional commit message generation: detects type (feat/fix/test/docs/refactor), extracts scope from common directories, and builds descriptive messages
  • Sparkle button (HiSparkles) in the commit textarea that triggers generation when staged changes exist

Built-in PR creation using gh CLI

  • Replaced the old createPR procedure (which opened a GitHub compare URL in the browser) with gh pr create execution
  • New generatePRBody query that auto-generates PR title from branch name and body from commit log
  • New CreatePRDialog component with title input, markdown description textarea, draft checkbox, and base branch support
  • All "Create PR" actions in CommitInput now open the dialog instead of redirecting to GitHub

Inline commenting of diffs

  • New diff-comments Zustand store (persisted to localStorage) with add/edit/delete/clear operations, keyed by workspace and file path
  • InlineComment component: displays individual comments with edit/delete actions and relative time formatting
  • DiffCommentThread component: renders sorted comment thread per file with "Add comment" form (line number + text input)
  • Integrated below each file's diff viewer in FileDiffSection
  • Comment count badge in FileDiffHeader (chat bubble icon + count)

Test Plan

  • Stage files and click the sparkle button — verify a conventional commit message is generated
  • Click "Create PR" from the dropdown — verify the dialog opens with auto-populated title/body
  • Create a PR via the dialog — verify it succeeds and shows a toast with the PR link
  • Click "Add comment" below a file diff — verify comment form appears and comments persist
  • Edit and delete comments — verify operations work correctly
  • Verify comment count badge appears in file headers when comments exist

Summary by CodeRabbit

  • New Features
    • Auto-generate commit messages from staged changes
    • Auto-generate PR titles and descriptions based on branch history
    • Add, edit, and delete inline comments on diff lines with comment count indicators
    • Enhanced PR creation dialog with draft status and base branch selection options
    • New commit message suggestion button for quick message generation

…line diff commenting

- Add generateCommitMessage tRPC procedure that analyzes staged diffs
  and produces conventional commit messages with sparkle button in UI
- Replace browser-redirect PR creation with built-in `gh pr create`
  flow via CreatePRDialog with auto-generated title/body
- Add inline commenting system for diffs with persistent Zustand store,
  comment thread UI per file, and comment count badges in headers
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

The PR introduces AI-powered commit message generation, PR body composition via git diff analysis, inline diff commenting with a persistent Zustand store, and refactors PR creation to use GitHub CLI. It adds three new TRPC endpoints, a comment management store, multiple UI components for comments and PR dialogs, and integrates these features into the existing diff and commit workflows.

Changes

Cohort / File(s) Summary
Git Operations Backend
apps/desktop/src/lib/trpc/routers/changes/git-operations.ts
Added generateCommitMessage and generatePRBody TRPC endpoints leveraging git diff analysis for message synthesis; refactored createPR to use gh CLI with enhanced inputs (title, body, draft, baseBranch) and expanded return type to include PR number.
Diff Comments Store
apps/desktop/src/renderer/stores/diff-comments/store.ts, apps/desktop/src/renderer/stores/diff-comments/index.ts
Introduced Zustand-based DiffComments store managing per-worktree, per-file inline comments with add/edit/delete/query operations; includes persistence and devtools middleware.
Inline Comments Components
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InlineComment/DiffCommentThread.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InlineComment/InlineComment.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InlineComment/index.ts
Added DiffCommentThread component for managing comment threads on file diffs; added InlineComment component for rendering individual comments with edit/delete affordances; exported both via index.
FileDiff Integration
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/FileDiffSection/FileDiffSection.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/FileDiffSection/components/FileDiffHeader/FileDiffHeader.tsx
Integrated inline comments into diff view; FileDiffSection now retrieves comment count and renders DiffCommentThread; FileDiffHeader displays comment count badge when comments exist.
Commit and PR Dialog Integration
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/CommitInput/CommitInput.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/CommitInput/CreatePRDialog.tsx
Added CreatePRDialog component for PR creation workflow with auto-fill from generatePRBody; integrated generate-commit-message mutation with sparkles UI trigger; refactored push-to-PR flow to open dialog instead of direct creation.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant CommitInput as CommitInput UI
    participant Store as generateCommitMessage<br/>(tRPC)
    participant GitOps as Git Operations
    participant Git as Git CLI

    User->>CommitInput: Click sparkles button
    CommitInput->>Store: generateCommitMessage(worktreePath)
    Store->>Git: Get staged diff & file list
    Git-->>Store: Diff output, file statuses
    Store->>GitOps: Analyze diff for conventional commit
    GitOps->>GitOps: Build prompt + extract scope
    GitOps-->>Store: Suggested message
    Store-->>CommitInput: { message: string }
    CommitInput->>CommitInput: Auto-fill message field
    CommitInput-->>User: Display generated message
Loading
sequenceDiagram
    participant User as User
    participant CommitInput as CommitInput UI
    participant Dialog as CreatePRDialog
    participant PRBody as generatePRBody<br/>(tRPC)
    participant GitOps as Git Operations
    participant GH as gh CLI
    participant Store as useDiffCommentsStore

    User->>CommitInput: Click create PR / push
    CommitInput->>CommitInput: Show CreatePRDialog
    Dialog->>Dialog: onOpenChange(true)
    Dialog->>PRBody: generatePRBody(worktreePath)
    PRBody->>GitOps: Analyze branch & commits
    GitOps-->>PRBody: { title, body }
    PRBody-->>Dialog: Auto-fill title & body
    User->>Dialog: Review & click Create
    Dialog->>Dialog: createPR mutation
    Dialog->>GH: Execute gh pr create<br/>(title, body, draft, baseBranch)
    GH-->>Dialog: PR URL output
    Dialog->>Dialog: Parse PR number from URL
    Dialog-->>CommitInput: onSuccess()
    CommitInput-->>User: Toast with PR link & number
    CommitInput->>CommitInput: Close dialog
Loading
sequenceDiagram
    participant User as User
    participant DiffView as FileDiffSection
    participant CommentUI as DiffCommentThread
    participant Store as useDiffCommentsStore

    User->>DiffView: View file diff
    DiffView->>Store: getFileCommentCount(worktreePath, filePath)
    Store-->>DiffView: commentCount
    DiffView->>DiffView: Render FileDiffHeader + badge
    DiffView->>CommentUI: Render DiffCommentThread
    CommentUI->>Store: getFileComments(worktreePath, filePath)
    Store-->>CommentUI: DiffComment[]
    CommentUI->>CommentUI: Render comment list
    User->>CommentUI: Click add comment
    CommentUI->>CommentUI: Show form (line, text)
    User->>CommentUI: Enter text & submit
    CommentUI->>Store: addComment({ worktreePath, filePath, lineNumber, side, text })
    Store->>Store: Generate id & createdAt
    Store-->>CommentUI: Comment added
    CommentUI->>CommentUI: Re-render with new comment
    CommentUI-->>User: Display comment inline
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly Related PRs

Poem

🐰 A fluffy tail wags with glee,
As comments bloom on diffs we see,
With AI whispers, messages bloom,
And PRs dance through GitHub's room,
Oh what a diff-erent world we make! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the three main additions to the desktop app: auto-commit message generation, built-in PR creation, and inline diff comments.
Description check ✅ Passed The PR description follows the template structure with clear Summary and Changes sections detailing each feature, but lacks explicit sections for Related Issues, Type of Change, Testing, and Additional Notes.

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

✨ 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 kitenite/changes-sidebar-impro

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

❤️ Share

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

Copy link
Copy Markdown
Contributor

@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: 8

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/CommitInput/CommitInput.tsx (1)

85-96: 🛠️ Refactor suggestion | 🟠 Major

createPRMutation is dead code — never invoked.

PR creation now goes through CreatePRDialog's own internal mutation. This mutation is declared but createPRMutation.mutate() is never called anywhere in this component. Its isPending state (line 120) will never be true, adding noise to the pending check.

Remove this mutation and its reference in isPending.

Proposed fix
-	const createPRMutation = electronTrpc.changes.createPR.useMutation({
-		onSuccess: (data) => {
-			toast.success(`PR #${data.number} created`, {
-				action: {
-					label: "Open",
-					onClick: () => window.open(data.url, "_blank"),
-				},
-			});
-			onRefresh();
-		},
-		onError: (error) => toast.error(`Failed: ${error.message}`),
-	});
-
 ...
 	const isPending =
 		commitMutation.isPending ||
 		pushMutation.isPending ||
 		pullMutation.isPending ||
 		syncMutation.isPending ||
-		createPRMutation.isPending ||
 		fetchMutation.isPending ||
 		generateMessageMutation.isPending;
🤖 Fix all issues with AI agents
In `@apps/desktop/src/lib/trpc/routers/changes/git-operations.ts`:
- Line 504: The fallback to a hardcoded "main" in the assignment const base =
input.baseBranch || "main" is brittle for repos using "master" or other
defaults; change logic in the git-operations handler to resolve the remote
default branch when input.baseBranch is absent (e.g., run a git query such as
`git remote show origin` or `git rev-parse --abbrev-ref origin/HEAD` or use the
GitHub API/gh CLI to fetch defaultBranchRef) and assign that result to base,
falling back to a sensible last-resort value only if detection fails; update any
callers that expect base to be present to handle async detection and surface
detection errors via the existing error path in the surrounding function.
- Around line 170-178: The isFix heuristic (diffLower and isFix) is too broad
and causes false positives; change isFix to only inspect added lines from the
diff (lines starting with '+') and match narrower patterns such as /\bfixes?\b/,
/\bbugfix\b/, /^fix:/, /fixes\s+#\d+/ or similar commit-like tokens rather than
any occurrence of "fix", "bug", "error", or "issue"; then use that tightened
isFix when computing type (alongside existing isTest and isDocs) so type
assignment remains correct (the symbols to update are isFix, diffLower usage,
and the type = isTest ? ... line).
- Around line 513-516: The empty catch after the git log call swallows all
errors; replace it with handling that logs the failure (including error.message
and error.stack) with context (e.g., "failed to run git log to generate PR body
from base branch <baseBranch>") and only fall back to the simple branch name for
the known "missing base branch" case — for other errors rethrow (or propagate)
so callers can surface the failure; implement this change in the git log
handling block in git-operations.ts (the catch directly after the git log
invocation that builds the PR body).
- Around line 77-102: _buildCommitMessagePrompt is unused dead code; either
remove it or wire it into the commit-message flow. If you intend to keep an
LLM-based generator, update generateCommitMessage (or the function that
currently calls generateMessageFromDiff) to call _buildCommitMessagePrompt with
{stagedFiles, diffStat, detailedDiff} and pass its result to the LLM invocation;
otherwise delete the _buildCommitMessagePrompt function and any imports/comments
referencing it to avoid unused code warnings.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InlineComment/DiffCommentThread.tsx`:
- Around line 26-38: handleAdd currently sets lineNumber to 0 when newLine is
empty; change it so an empty newLine does not produce 0 by either making
submission invalid or by omitting/defaulting the value — e.g., in handleAdd
replace the ternary that produces 0 with a value of undefined or 1 (or disable
the add action when newLine.trim() === ""), and update the call to addComment
(and any consumers expecting lineNumber) to accept undefined/omit and avoid
showing "line 0" in InlineComment; reference: handleAdd, addComment, newLine,
lineNumber, InlineComment.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/CommitInput/CommitInput.tsx`:
- Around line 412-417: CommitInput currently renders CreatePRDialog without
passing the optional baseBranch prop, causing generatePRBody to default to
"main"; update the CreatePRDialog JSX in CommitInput to pass the repository's
default branch via the baseBranch prop (e.g., baseBranch={defaultBranch} or
derived value) — ensure you obtain the default branch from the surrounding
CommitInput props/state/context where it's available (or compute it from the
repo metadata used in CommitInput) and pass it into CreatePRDialog so
generatePRBody receives the correct base branch.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/CommitInput/CreatePRDialog.tsx`:
- Around line 42-47: The effect in CreatePRDialog currently re-runs when
title/body change which causes cleared fields to be immediately re-filled;
update the useEffect that reads prSuggestion/open/title/body so it only
auto-populates once when suggestion data first arrives (or when dialog opens) by
removing title and body from the dependency array and adding a ref flag (e.g.,
hasAutoFilledRef) to track whether auto-fill has already occurred; inside
useEffect (watching prSuggestion and open) check hasAutoFilledRef.current before
calling setTitle/setBody and set the flag true after populating, or
alternatively move the population into the query's success handler that calls
setTitle/setBody once.

In `@apps/desktop/src/renderer/stores/diff-comments/store.ts`:
- Around line 122-130: getFileComments currently returns a fresh [] when no
comments exist which breaks reference stability; change the selector to return a
stable shared value instead of a new array (e.g. introduce a single frozen
EMPTY_COMMENTS constant and return workspaceComments[filePath] ??
EMPTY_COMMENTS) or return undefined and update consumers to handle that; modify
the getFileComments function (leave getFileCommentCount unchanged) so consumers
like DiffCommentThread (which spreads the result) receive a stable reference.
🧹 Nitpick comments (7)
apps/desktop/src/renderer/stores/diff-comments/store.ts (1)

40-42: Positional arguments for getFileComments and getFileCommentCount violate the coding guideline.

These functions take 2 positional parameters. Per coding guidelines, use object parameters for functions with 2 or more parameters.

Proposed fix

In the interface:

-	getFileComments: (worktreePath: string, filePath: string) => DiffComment[];
-	getFileCommentCount: (worktreePath: string, filePath: string) => number;
+	getFileComments: (params: { worktreePath: string; filePath: string }) => DiffComment[];
+	getFileCommentCount: (params: { worktreePath: string; filePath: string }) => number;

And in the implementation:

-			getFileComments: (worktreePath, filePath) => {
-				const workspaceComments = get().comments[worktreePath] ?? {};
-				return workspaceComments[filePath] ?? [];
+			getFileComments: ({ worktreePath, filePath }) => {
+				const workspaceComments = get().comments[worktreePath] ?? {};
+				return workspaceComments[filePath] ?? [];
 			},
-			getFileCommentCount: (worktreePath, filePath) => {
-				const workspaceComments = get().comments[worktreePath] ?? {};
-				return (workspaceComments[filePath] ?? []).length;
+			getFileCommentCount: ({ worktreePath, filePath }) => {
+				const workspaceComments = get().comments[worktreePath] ?? {};
+				return (workspaceComments[filePath] ?? []).length;
 			},

As per coding guidelines: "Use object parameters for functions with 2 or more parameters instead of positional arguments"

apps/desktop/src/lib/trpc/routers/changes/git-operations.ts (2)

264-269: Extract the truncation limit to a named constant.

8000 is a magic number. As per coding guidelines, extract hardcoded magic numbers to named constants at module top.

Proposed fix

At the top of the file (after imports):

const MAX_DIFF_LENGTH = 8000;

Then:

-				const truncatedDiff =
-					detailedDiff.length > 8000
-						? `${detailedDiff.slice(0, 8000)}\n... (truncated)`
-						: detailedDiff;
+				const truncatedDiff =
+					detailedDiff.length > MAX_DIFF_LENGTH
+						? `${detailedDiff.slice(0, MAX_DIFF_LENGTH)}\n... (truncated)`
+						: detailedDiff;

As per coding guidelines: "Extract hardcoded magic numbers, strings, and enums to named constants at module top instead of leaving them inline in logic"


244-281: generateCommitMessage is defined as a mutation, but it performs no writes.

This is a read-only operation that analyzes staged diffs and returns a message. A query would be more semantically correct and would allow tRPC to deduplicate and cache the result. However, since it's triggered by a user action (button click) and you likely want fresh results each time, a mutation is pragmatically fine — just noting the semantic mismatch.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InlineComment/InlineComment.tsx (2)

18-19: editText can go stale if comment.text changes while not editing.

If the parent re-renders with an updated comment.text (e.g., after a store sync), the local editText state retains the old value. Consider resetting it when the comment prop changes.

Suggested fix
 const [isEditing, setIsEditing] = useState(false);
 const [editText, setEditText] = useState(comment.text);
+
+ useEffect(() => {
+   if (!isEditing) {
+     setEditText(comment.text);
+   }
+ }, [comment.text, isEditing]);

108-117: getTimeAgo is fine as a co-located helper, but consider using a well-known library.

If the app already depends on a date utility (e.g., date-fns), formatDistanceToNow would handle edge cases (e.g., months, years) and i18n. If not, this is acceptable for now.

apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InlineComment/DiffCommentThread.tsx (2)

17-20: Unselective store subscription will cause unnecessary re-renders.

Line 20 destructures actions from useDiffCommentsStore() without a selector, subscribing to all store state changes. Since the comment list selector on line 17 already handles data reads, extract the actions separately to avoid coupling:

Suggested fix
 const comments = useDiffCommentsStore((s) =>
   s.getFileComments(worktreePath, filePath),
 );
-const { addComment, deleteComment, editComment } = useDiffCommentsStore();
+const addComment = useDiffCommentsStore((s) => s.addComment);
+const deleteComment = useDiffCommentsStore((s) => s.deleteComment);
+const editComment = useDiffCommentsStore((s) => s.editComment);

Also note: the getFileComments selector returns workspaceComments[filePath] ?? [], which creates a new array reference when there are no comments, triggering re-renders on every store update. Consider returning a stable empty array constant from the store.


48-50: Sorting on every render — consider useMemo.

sortedComments creates a new sorted copy each render. With useMemo keyed on comments, the sort only re-runs when comments actually change.

Suggested fix
+import { useMemo, useState } from "react";
-import { useState } from "react";
...
-const sortedComments = [...comments].sort(
-  (a, b) => a.lineNumber - b.lineNumber || a.createdAt - b.createdAt,
-);
+const sortedComments = useMemo(
+  () => [...comments].sort(
+    (a, b) => a.lineNumber - b.lineNumber || a.createdAt - b.createdAt,
+  ),
+  [comments],
+);

Comment on lines +77 to +102
function _buildCommitMessagePrompt({
stagedFiles,
diffStat,
detailedDiff,
}: {
stagedFiles: string;
diffStat: string;
detailedDiff: string;
}): string {
return [
"Generate a concise conventional commit message for these changes.",
"Use format: <type>(<scope>): <description>",
"Types: feat, fix, refactor, docs, style, test, chore, perf",
"Keep the description under 72 characters.",
"Only output the commit message, nothing else.",
"",
"Files changed:",
stagedFiles,
"",
"Diff stat:",
diffStat,
"",
"Diff:",
detailedDiff,
].join("\n");
}
Copy link
Copy Markdown
Contributor

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:

rg -n '_buildCommitMessagePrompt' --type ts --type tsx

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

rg -n '_buildCommitMessagePrompt'

Repository: superset-sh/superset

Length of output: 163


🏁 Script executed:

# Check if _buildCommitMessagePrompt is exported or used as a callback/reference anywhere
rg -n 'buildCommitMessagePrompt' apps/desktop/src/lib/trpc/routers/changes/git-operations.ts

Repository: superset-sh/superset

Length of output: 103


🏁 Script executed:

# Check entire codebase for any usage outside the definition
rg '_buildCommitMessagePrompt' --count-matches

Repository: superset-sh/superset

Length of output: 125


🏁 Script executed:

# Examine the generateCommitMessage and generateMessageFromDiff functions to confirm they don't call _buildCommitMessagePrompt
rg -A 20 'function generateMessageFromDiff' apps/desktop/src/lib/trpc/routers/changes/git-operations.ts

Repository: superset-sh/superset

Length of output: 895


🏁 Script executed:

# Check for any generateCommitMessage procedure that might call _buildCommitMessagePrompt
rg -A 30 'generateCommitMessage.*:.*procedure' apps/desktop/src/lib/trpc/routers/changes/git-operations.ts | head -40

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

# Search for generateCommitMessage procedure definition
rg -B 5 -A 30 'generateCommitMessage' apps/desktop/src/lib/trpc/routers/changes/git-operations.ts | head -50

Repository: superset-sh/superset

Length of output: 1221


🏁 Script executed:

# Get the complete generateCommitMessage procedure to see what it returns
rg -A 40 'generateCommitMessage: publicProcedure' apps/desktop/src/lib/trpc/routers/changes/git-operations.ts

Repository: superset-sh/superset

Length of output: 1154


_buildCommitMessagePrompt is dead code — never called anywhere.

This function builds a prompt string but is never invoked. The generateCommitMessage procedure delegates to the heuristic-based generateMessageFromDiff instead. Remove it if it's a leftover from an earlier LLM-based approach, or wire it up if intentional.

🤖 Prompt for AI Agents
In `@apps/desktop/src/lib/trpc/routers/changes/git-operations.ts` around lines 77
- 102, _buildCommitMessagePrompt is unused dead code; either remove it or wire
it into the commit-message flow. If you intend to keep an LLM-based generator,
update generateCommitMessage (or the function that currently calls
generateMessageFromDiff) to call _buildCommitMessagePrompt with {stagedFiles,
diffStat, detailedDiff} and pass its result to the LLM invocation; otherwise
delete the _buildCommitMessagePrompt function and any imports/comments
referencing it to avoid unused code warnings.

Comment on lines +170 to +178

// Analyze diff for fix indicators
const isFix =
diffLower.includes("fix") ||
diffLower.includes("bug") ||
diffLower.includes("error") ||
diffLower.includes("issue");

const type = isTest ? "test" : isDocs ? "docs" : isFix ? "fix" : "feat";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

isFix heuristic has a high false-positive rate.

Matching "fix", "bug", "error", or "issue" against the lowercased diff content will trigger on extremely common patterns — error handling code, CSS prefixes (-webkit-→ no, but fix appears in "prefix"), import of ErrorBoundary, issue tracker URLs in comments, etc. This could mislabel many commits as fix when they're actually feat or refactor.

Consider narrowing the heuristic to only match added lines (+ prefixed lines in the diff) or to match more specific patterns like fixes #, bugfix, or commit-message-like patterns rather than raw diff content.

🤖 Prompt for AI Agents
In `@apps/desktop/src/lib/trpc/routers/changes/git-operations.ts` around lines 170
- 178, The isFix heuristic (diffLower and isFix) is too broad and causes false
positives; change isFix to only inspect added lines from the diff (lines
starting with '+') and match narrower patterns such as /\bfixes?\b/,
/\bbugfix\b/, /^fix:/, /fixes\s+#\d+/ or similar commit-like tokens rather than
any occurrence of "fix", "bug", "error", or "issue"; then use that tightened
isFix when computing type (alongside existing isTest and isDocs) so type
assignment remains correct (the symbols to update are isFix, diffLower usage,
and the type = isTest ? ... line).


const git = simpleGit(input.worktreePath);
const branch = (await git.revparse(["--abbrev-ref", "HEAD"])).trim();
const base = input.baseBranch || "main";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded "main" fallback won't work for repos using "master" or other default branches.

When baseBranch is not provided, this falls back to "main". Repositories using master, develop, or other conventions will get an empty or incorrect commit log since origin/main..HEAD won't resolve. Consider detecting the default branch dynamically, e.g., via git remote show origin or gh repo view --json defaultBranchRef.

🤖 Prompt for AI Agents
In `@apps/desktop/src/lib/trpc/routers/changes/git-operations.ts` at line 504, The
fallback to a hardcoded "main" in the assignment const base = input.baseBranch
|| "main" is brittle for repos using "master" or other defaults; change logic in
the git-operations handler to resolve the remote default branch when
input.baseBranch is absent (e.g., run a git query such as `git remote show
origin` or `git rev-parse --abbrev-ref origin/HEAD` or use the GitHub API/gh CLI
to fetch defaultBranchRef) and assign that result to base, falling back to a
sensible last-resort value only if detection fails; update any callers that
expect base to be present to handle async detection and surface detection errors
via the existing error path in the surrounding function.

Comment on lines +513 to +516
]);
} catch {
// Fall back to just the branch name
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silent catch block swallows errors without logging.

This violates the guideline to never swallow errors silently. If git log fails for a reason other than a missing base branch (e.g., corrupt repo, permission error), the failure is silently ignored and the user gets an empty PR body with no indication of the problem.

Proposed fix
 			} catch {
-				// Fall back to just the branch name
+				catch (error) {
+					console.warn("[git/generatePRBody] Failed to get commit log, falling back to branch name:", error);
+				}

As per coding guidelines: "Never swallow errors silently; at minimum log errors with context before rethrowing or handling them explicitly"

🤖 Prompt for AI Agents
In `@apps/desktop/src/lib/trpc/routers/changes/git-operations.ts` around lines 513
- 516, The empty catch after the git log call swallows all errors; replace it
with handling that logs the failure (including error.message and error.stack)
with context (e.g., "failed to run git log to generate PR body from base branch
<baseBranch>") and only fall back to the simple branch name for the known
"missing base branch" case — for other errors rethrow (or propagate) so callers
can surface the failure; implement this change in the git log handling block in
git-operations.ts (the catch directly after the git log invocation that builds
the PR body).

Comment on lines +26 to +38
const handleAdd = () => {
if (!newText.trim()) return;
addComment({
worktreePath,
filePath,
lineNumber: newLine ? Number.parseInt(newLine, 10) : 0,
side: "modified",
text: newText.trim(),
});
setNewText("");
setNewLine("");
setIsAdding(false);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Line number defaults to 0 when omitted, which is not a valid line number.

If the user leaves the line field blank, the comment is stored with lineNumber: 0. This could confuse users seeing "on line 0" in the InlineComment display. Consider either requiring a line number (disable submit when empty) or using 1 as a default, or omitting the line display when it's unset.

🤖 Prompt for AI Agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InlineComment/DiffCommentThread.tsx`
around lines 26 - 38, handleAdd currently sets lineNumber to 0 when newLine is
empty; change it so an empty newLine does not produce 0 by either making
submission invalid or by omitting/defaulting the value — e.g., in handleAdd
replace the ternary that produces 0 with a value of undefined or 1 (or disable
the add action when newLine.trim() === ""), and update the call to addComment
(and any consumers expecting lineNumber) to accept undefined/omit and avoid
showing "line 0" in InlineComment; reference: handleAdd, addComment, newLine,
lineNumber, InlineComment.

Comment on lines +412 to +417
<CreatePRDialog
open={showCreatePRDialog}
onOpenChange={setShowCreatePRDialog}
worktreePath={worktreePath}
onSuccess={onRefresh}
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

baseBranch prop is not passed to CreatePRDialog.

The dialog accepts an optional baseBranch prop which feeds into generatePRBody. Without it, the backend falls back to hardcoded "main". If the CommitInput component has access to the current repo's default branch (or if it can be derived), passing it here would make the PR body generation more accurate for repos not using main.

🤖 Prompt for AI Agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/CommitInput/CommitInput.tsx`
around lines 412 - 417, CommitInput currently renders CreatePRDialog without
passing the optional baseBranch prop, causing generatePRBody to default to
"main"; update the CreatePRDialog JSX in CommitInput to pass the repository's
default branch via the baseBranch prop (e.g., baseBranch={defaultBranch} or
derived value) — ensure you obtain the default branch from the surrounding
CommitInput props/state/context where it's available (or compute it from the
repo metadata used in CommitInput) and pass it into CreatePRDialog so
generatePRBody receives the correct base branch.

Comment on lines +42 to +47
useEffect(() => {
if (prSuggestion && open) {
if (!title) setTitle(prSuggestion.title);
if (!body) setBody(prSuggestion.body);
}
}, [prSuggestion, open, body, title]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bug: Users cannot clear the auto-filled title or body.

Including title and body in the dependency array means that when a user clears either field (setting it to ""), the effect re-fires, !title / !body evaluates to true, and the cached prSuggestion immediately re-fills the field.

A simpler approach is to populate the fields only once when the suggestion data first arrives, using a ref to track whether auto-fill has already occurred, or by leveraging the query's onSuccess-like pattern.

Proposed fix using a ref flag
+import { useEffect, useRef, useState } from "react";
 ...
+	const hasAutoFilled = useRef(false);
+
+	// Reset form when dialog closes
+	useEffect(() => {
+		if (!open) {
+			setTitle("");
+			setBody("");
+			setIsDraft(false);
+			hasAutoFilled.current = false;
+		}
+	}, [open]);
 
 	useEffect(() => {
-		if (prSuggestion && open) {
-			if (!title) setTitle(prSuggestion.title);
-			if (!body) setBody(prSuggestion.body);
+		if (prSuggestion && open && !hasAutoFilled.current) {
+			setTitle(prSuggestion.title);
+			setBody(prSuggestion.body);
+			hasAutoFilled.current = true;
 		}
-	}, [prSuggestion, open, body, title]);
+	}, [prSuggestion, open]);
🤖 Prompt for AI Agents
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/CommitInput/CreatePRDialog.tsx`
around lines 42 - 47, The effect in CreatePRDialog currently re-runs when
title/body change which causes cleared fields to be immediately re-filled;
update the useEffect that reads prSuggestion/open/title/body so it only
auto-populates once when suggestion data first arrives (or when dialog opens) by
removing title and body from the dependency array and adding a ref flag (e.g.,
hasAutoFilledRef) to track whether auto-fill has already occurred; inside
useEffect (watching prSuggestion and open) check hasAutoFilledRef.current before
calling setTitle/setBody and set the flag true after populating, or
alternatively move the population into the query's success handler that calls
setTitle/setBody once.

Comment on lines +122 to +130
getFileComments: (worktreePath, filePath) => {
const workspaceComments = get().comments[worktreePath] ?? {};
return workspaceComments[filePath] ?? [];
},

getFileCommentCount: (worktreePath, filePath) => {
const workspaceComments = get().comments[worktreePath] ?? {};
return (workspaceComments[filePath] ?? []).length;
},
Copy link
Copy Markdown
Contributor

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:

rg -n --type=ts --type=tsx 'getFileComments|getFileCommentCount' -g '!**/store.ts' -B2 -A2

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

rg -n 'getFileComments|getFileCommentCount' -g '!**/store.ts' --type-list | head -20

Repository: superset-sh/superset

Length of output: 453


🏁 Script executed:

rg -n 'getFileComments|getFileCommentCount' -g '!**/store.ts' -B2 -A2 2>/dev/null | head -100

Repository: superset-sh/superset

Length of output: 1721


🏁 Script executed:

cat -n apps/desktop/src/renderer/stores/diff-comments/store.ts | head -150

Repository: superset-sh/superset

Length of output: 4762


🏁 Script executed:

cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InlineComment/DiffCommentThread.tsx | sed -n '10,30p'

Repository: superset-sh/superset

Length of output: 739


🏁 Script executed:

cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/FileDiffSection/FileDiffSection.tsx | sed -n '210,230p'

Repository: superset-sh/superset

Length of output: 773


🏁 Script executed:

# Check if there are any infinite loop or performance issues reported related to these components
git log --oneline --all --grep="DiffCommentThread\|FileDiffSection\|re-render" 2>/dev/null | head -20

Repository: superset-sh/superset

Length of output: 792


🏁 Script executed:

# Check if the components use these selectors in ways that would expose the reference problem
cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/InlineComment/DiffCommentThread.tsx | head -80

Repository: superset-sh/superset

Length of output: 2786


Separate concerns: getFileComments has a reference stability issue, but getFileCommentCount does not.

getFileComments returns a new empty array ([]) when a file has no comments, causing the selector to return a different reference on each store change. DiffCommentThread then spreads this array at line 48, compounding the issue. However, getFileCommentCount returns a number (compared by value), so it won't trigger re-renders due to reference changes.

For getFileComments, consider either:

  1. Having consumers select the raw comments state and derive values inline, or
  2. Wrapping the selector with a shallow equality check:
const comments = useDiffCommentsStore(
  (s) => s.comments[worktreePath]?.[filePath] ?? [],
  shallow,
);

getFileCommentCount can remain as-is.

🤖 Prompt for AI Agents
In `@apps/desktop/src/renderer/stores/diff-comments/store.ts` around lines 122 -
130, getFileComments currently returns a fresh [] when no comments exist which
breaks reference stability; change the selector to return a stable shared value
instead of a new array (e.g. introduce a single frozen EMPTY_COMMENTS constant
and return workspaceComments[filePath] ?? EMPTY_COMMENTS) or return undefined
and update consumers to handle that; modify the getFileComments function (leave
getFileCommentCount unchanged) so consumers like DiffCommentThread (which
spreads the result) receive a stable reference.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant