Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions apps/desktop/src/lib/trpc/routers/changes/changes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ export const createChangesRouter = () => {
z.object({
worktreePath: z.string(),
filePath: z.string(),
oldPath: z.string().optional(),
category: z.enum(["against-main", "committed", "staged", "unstaged"]),
commitHash: z.string().optional(),
defaultBranch: z.string().optional(),
Expand All @@ -236,14 +237,15 @@ export const createChangesRouter = () => {
.query(async ({ input }): Promise<FileContents> => {
const git = simpleGit(input.worktreePath);
const defaultBranch = input.defaultBranch || "main";
const originalPath = input.oldPath || input.filePath;
let original = "";
let modified = "";

switch (input.category) {
case "against-main": {
try {
original = await git.show([
`origin/${defaultBranch}:${input.filePath}`,
`origin/${defaultBranch}:${originalPath}`,
]);
} catch {
original = "";
Expand All @@ -262,7 +264,7 @@ export const createChangesRouter = () => {
}
try {
original = await git.show([
`${input.commitHash}^:${input.filePath}`,
`${input.commitHash}^:${originalPath}`,
]);
} catch {
original = "";
Expand All @@ -279,7 +281,7 @@ export const createChangesRouter = () => {

case "staged": {
try {
original = await git.show([`HEAD:${input.filePath}`]);
original = await git.show([`HEAD:${originalPath}`]);
} catch {
original = "";
}
Expand All @@ -293,10 +295,10 @@ export const createChangesRouter = () => {

case "unstaged": {
try {
original = await git.show([`:0:${input.filePath}`]);
original = await git.show([`:0:${originalPath}`]);
} catch {
try {
original = await git.show([`HEAD:${input.filePath}`]);
original = await git.show([`HEAD:${originalPath}`]);
} catch {
original = "";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export function ChangesContent() {
{
worktreePath: worktreePath || "",
filePath: selectedFile?.path || "",
oldPath: selectedFile?.oldPath,
category: selectedCategory,
commitHash: selectedCommitHash || undefined,
defaultBranch: effectiveBaseBranch,
Expand Down Expand Up @@ -107,19 +108,21 @@ export function ChangesContent() {

return (
<>
<div className="flex-1 h-full flex flex-col overflow-hidden bg-background">
<FileHeader file={selectedFile} worktreePath={worktreePath} />
<DiffToolbar
viewMode={viewMode}
onViewModeChange={setViewMode}
category={selectedCategory}
onStage={isUnstaged ? stage : undefined}
onUnstage={isStaged ? unstage : undefined}
onDiscard={isUnstaged ? handleDiscard : undefined}
isActioning={isPending}
/>
<div className="flex-1 overflow-hidden">
<DiffViewer contents={contents} viewMode={viewMode} />
<div className="flex-1 h-full flex flex-col bg-tertiary rounded-lg">
<div className="flex flex-col h-full bg-background m-2 rounded-lg overflow-hidden">
<FileHeader file={selectedFile} worktreePath={worktreePath} />
<DiffToolbar
viewMode={viewMode}
onViewModeChange={setViewMode}
category={selectedCategory}
onStage={isUnstaged ? stage : undefined}
onUnstage={isStaged ? unstage : undefined}
onDiscard={isUnstaged ? handleDiscard : undefined}
isActioning={isPending}
/>
<div className="flex-1">
<DiffViewer contents={contents} viewMode={viewMode} />
</div>
</div>
</div>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ScrollArea } from "@superset/ui/scroll-area";
import { useState } from "react";
import { useEffect, useState } from "react";
import { trpc } from "renderer/lib/trpc";
import { useChangesStore } from "renderer/stores/changes";
import type { ChangeCategory, ChangedFile } from "shared/changes-types";
Expand Down Expand Up @@ -51,6 +51,13 @@ export function ChangesView() {
new Set(),
);

// Reset expanded commits when workspace changes to avoid querying
// old commit hashes against the new worktree
// biome-ignore lint/correctness/useExhaustiveDependencies: intentionally resets on worktreePath change
useEffect(() => {
setExpandedCommits(new Set());
}, [worktreePath]);

Comment on lines +54 to +60
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

🧩 Analysis chain

🏁 Script executed:

cat -n apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx

Repository: superset-sh/superset

Length of output: 8037


🏁 Script executed:

# Search for all usages of expandedCommits in the file
rg -n "expandedCommits" apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx

Repository: superset-sh/superset

Length of output: 300


Resetting expandedCommits in useEffect allows stale getCommitFiles queries to be registered against the new worktree.

When worktreePath changes, the component re-renders with trpc.useQueries(...) (line 61-68) still using the old expandedCommits state. Only after the render completes does the effect run to reset the state. This means queries for previous commit hashes execute against the new worktree before being cleared.

A more robust approach is to scope expansion state by worktreePath so switching workspaces naturally starts with an empty set without needing an effect:

- import { useEffect, useState } from "react";
+ import { useState } from "react";
  ...
- const [expandedCommits, setExpandedCommits] = useState<Set<string>>(
-   new Set(),
- );
-
- // Reset expanded commits when workspace changes to avoid querying
- // old commit hashes against the new worktree
- // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally resets on worktreePath change
- useEffect(() => {
-   setExpandedCommits(new Set());
- }, [worktreePath]);
+ const [expandedCommitsByWorktree, setExpandedCommitsByWorktree] = useState<
+   Record<string, Set<string>>
+ >({});
+
+ const expandedCommits = worktreePath
+   ? (expandedCommitsByWorktree[worktreePath] ?? new Set<string>())
+   : new Set<string>();
  ...
  const handleCommitToggle = (hash: string) => {
-   setExpandedCommits((prev) => {
-     const next = new Set(prev);
-     if (next.has(hash)) next.delete(hash);
-     else next.add(hash);
-     return next;
-   });
+   if (!worktreePath) return;
+   setExpandedCommitsByWorktree((prev) => {
+     const current = prev[worktreePath] ?? new Set<string>();
+     const next = new Set(current);
+     if (next.has(hash)) next.delete(hash);
+     else next.add(hash);
+     return { ...prev, [worktreePath]: next };
+   });
  };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
apps/desktop/src/renderer/screens/main/components/WorkspaceView/Sidebar/ChangesView/ChangesView.tsx
around lines 54 to 60, the effect-based reset of expandedCommits runs after
render which allows stale getCommitFiles queries for previous commits to fire
against a new worktree; instead, make the expansion state scoped to worktreePath
(e.g. track expanded commits per-worktree via an object/Map keyed by
worktreePath or include worktreePath in the state shape) so the initial render
uses an empty expansion set for a new worktree; update all accesses and setters
to read/write the per-worktree entry and initialize useState with the entry for
the current worktreePath (removing the post-render useEffect reset).

const commitFilesQueries = trpc.useQueries((t) =>
Array.from(expandedCommits).map((hash) =>
t.changes.getCommitFiles({
Expand Down
Loading