Skip to content

fix(desktop): show hover action buttons without requiring file selection#1761

Merged
Kitenite merged 6 commits into
superset-sh:mainfrom
de1mat:fix/changes-view-hover-actions
Feb 25, 2026
Merged

fix(desktop): show hover action buttons without requiring file selection#1761
Kitenite merged 6 commits into
superset-sh:mainfrom
de1mat:fix/changes-view-hover-actions

Conversation

@de1mat
Copy link
Copy Markdown
Contributor

@de1mat de1mat commented Feb 24, 2026

Description

File row hover action buttons (stage/unstage) in the Changes View sidebar only appeared after the user clicked to select the file first. This fix ensures they show on hover regardless of selection state.

Additionally adds several UX improvements to the hover action buttons:

  • Discard/revert hover button on file rows (with confirmation dialog, shows undo icon for tracked files, trash icon for untracked)
  • Folder-level hover buttons for batch stage/unstage/discard operations
  • Batch git operations (gitStageFiles/gitUnstageFiles) to stage/unstage multiple files in a single git command, avoiding index.lock races
  • Consistent button ordering matching section header pattern (discard left, stage/unstage right)
  • Pointer-events fix preventing invisible hover buttons from being accidentally clickable
  • Layout alignment ensuring folder and file row action buttons share consistent right-side padding

Related Issues

Fixes #1759

Type of Change

  • Bug fix
  • New feature

Screenshot

image

Testing

  • Hover over file rows in the Changes View - stage/unstage/discard buttons should appear without clicking first
  • Hover over folder rows - batch stage/unstage/discard buttons should appear
  • Click discard on a file - confirmation dialog should appear
  • Click stage/unstage on a folder - all files in the folder should be staged/unstaged in a single operation
  • Verify button ordering matches section headers (discard left, action right)
  • Verify clicking the right edge of a row when buttons are hidden does not trigger actions

Summary by CodeRabbit

  • New Features

    • Batch stage and unstage multiple files in one operation.
    • New reusable discard confirmation dialog for discard actions.
  • UI/UX Improvements

    • Grouped hover action controls with clearer icons and tooltips for per-file and folder actions.
    • Hover-revealed action bar for folder rows and consolidated row action component.
  • Behavior

    • Action state and controls now reflect both single-file and multi-file operations.

- Fix file row hover buttons (stage/unstage) only appearing after clicking
  to select the file first
- Add discard/revert hover button on file rows with confirmation dialog
- Add folder-level hover buttons for batch stage/unstage/discard
- Add batch gitStageFiles/gitUnstageFiles endpoints to avoid index.lock
  races when staging multiple files
- Reorder hover buttons to match section header pattern (discard left,
  stage/unstage right)
- Fix invisible buttons remaining clickable via pointer-events-none
- Align folder and file row right-side padding for consistent layout

Fixes superset-sh#1759
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 24, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44969d0 and 1996171.

📒 Files selected for processing (8)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/DiscardConfirmDialog/DiscardConfirmDialog.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/DiscardConfirmDialog/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileItem/FileItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileListTree.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FolderRow/FolderRow.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/RowHoverActions/RowHoverActions.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/RowHoverActions/index.ts

📝 Walkthrough

Walkthrough

Adds backend batch git operations (gitStageFiles, gitUnstageFiles), exposes them as tRPC procedures (stageFiles, unstageFiles), and threads batch staging/unstaging through UI mutations and component prop propagation to enable folder- and group-level batch actions and discard confirmation dialogs.

Changes

Cohort / File(s) Summary
Backend Git Operations
apps/desktop/src/lib/trpc/routers/changes/security/git-commands.ts, apps/desktop/src/lib/trpc/routers/changes/security/index.ts, apps/desktop/src/lib/trpc/routers/changes/staging.ts
Added gitStageFiles and gitUnstageFiles (validate worktree and paths; run single git add/reset with multiple paths); exported and exposed via stageFiles/unstageFiles tRPC procedures returning { success: boolean }.
Frontend Mutations & Wiring
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx
Added stageFiles and unstageFiles mutations, wired calls to batch handlers, and included their pending states in aggregated isActioning.
Component Propagation
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileList.tsx, .../FileListGrouped.tsx, .../FileListTree.tsx
Added optional onStageFiles / onUnstageFiles props; propagated through tree/group/list components to enable batch operations and updated prop types and callbacks.
Folder Row Batch UI
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FolderRow/FolderRow.tsx
Added hover-visible action cluster with Stage All, Unstage All, and Discard All buttons, tooltips, and a discard confirmation dialog; wired handlers and stopPropagation behavior.
File Item Actions & Layout
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileItem/FileItem.tsx
Replaced inline per-item controls with RowHoverActions and DiscardConfirmDialog; adjusted hover layout, overflow handling, and action rendering while preserving stage/unstage/discard behavior.
Reusable UI Components
.../DiscardConfirmDialog/*, .../RowHoverActions/*
Added DiscardConfirmDialog component and RowHoverActions component (with exported RowHoverAction type) and corresponding index re-exports.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as React UI
    participant tRPC as tRPC Router
    participant Backend as Backend Logic
    participant Git as Git

    User->>UI: Click "Stage All" / "Unstage All"
    UI->>tRPC: Call stageFiles/unstageFiles(worktreePath, filePaths[])
    tRPC->>Backend: Invoke gitStageFiles/gitUnstageFiles
    Backend->>Backend: Validate worktree & each path
    Backend->>Git: Execute single git command (git add / git reset HEAD) with all paths
    Git-->>Backend: Return result
    Backend-->>tRPC: Return { success: boolean }
    tRPC-->>UI: Response
    UI->>UI: Refetch state / show toast
    UI-->>User: Updated changes view
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

I’m a rabbit with a dev-time grin, 🐇
I batch the files and stage them in.
No index.lock to make me stop,
I add and reset—then hop-hop-hop.
Folder actions—swift as spring! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: enabling hover action buttons to display without file selection, which aligns with the primary bug fix objective from the linked issue.
Description check ✅ Passed The description covers all required sections from the template: clear description of changes, related issues link, type of change checkboxes, testing instructions, screenshot, and additional context about UX improvements.
Linked Issues check ✅ Passed The PR successfully implements all objectives from issue #1759: hover buttons visible without selection, folder-level batch buttons, discard confirmation, batch git operations (gitStageFiles/gitUnstageFiles), and consistent button ordering.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the linked issue requirements: backend batch operations, UI hover button wiring, component prop propagation, and action handler integration for staging/unstaging files.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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: 1

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/FileList/FileListTree.tsx (1)

166-172: ⚠️ Potential issue | 🔴 Critical

Folder-level discard currently bypasses a confirmation boundary.

handleDiscardAll directly loops onDiscard(file) for every file. In the current wiring, that executes destructive mutations immediately, so one click can discard/delete an entire folder without a single confirm step.

💡 Proposed direction
 interface TreeNodeComponentProps {
   ...
   onDiscard?: (file: ChangedFile) => void;
+  onDiscardFiles?: (files: ChangedFile[]) => void;
 }

-const handleDiscardAll = useCallback(() => {
-  if (!onDiscard) return;
-  const files = collectFilesFromNode(node);
-  for (const file of files) {
-    onDiscard(file);
-  }
-}, [node, onDiscard]);
+const handleDiscardAll = useCallback(() => {
+  onDiscardFiles?.(collectFilesFromNode(node));
+}, [node, onDiscardFiles]);

 ...
- onDiscardAll={onDiscard ? handleDiscardAll : undefined}
+ onDiscardAll={onDiscardFiles ? handleDiscardAll : undefined}

Then handle a single folder-level confirmation upstream before running the batch discard.

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

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileListTree.tsx`
around lines 166 - 172, handleDiscardAll currently calls onDiscard(file) in a
loop causing immediate destructive actions; change it to request a single
folder-level confirmation and only run the batch discard if confirmed.
Specifically, update handleDiscardAll (which uses collectFilesFromNode(node) and
onDiscard) to: 1) gather files via collectFilesFromNode(node), 2) open/show a
single confirmation (or call a provided onRequestDiscardBatch/onConfirmDiscard
callback) with summary metadata (e.g., folder name and count), and 3) upon
confirmation invoke a batch discard API (either a new onDiscardBatch(files) prop
or call onDiscard for each file after confirmation). Ensure the new flow
references handleDiscardAll, collectFilesFromNode, and onDiscard (or the new
onDiscardBatch) so the destructive loop is guarded by one confirmation.
🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileListGrouped.tsx (1)

102-127: handleDiscardAll lacks a batch path, inconsistent with the PR's index.lock safety goal.

handleStageAll / handleUnstageAll were refactored to prefer a single batch call to prevent index.lock races. handleDiscardAll (unchanged) still fires a per-file onDiscard loop. Git discard operations (git checkout -- for tracked files, git clean for untracked) also acquire the index lock, so rapidly discarding all files in a folder can race just as staging/unstaging does.

Consider introducing an onDiscardFiles?: (files: ChangedFile[]) => void batch prop (mirroring onStageFiles/onUnstageFiles) and applying the same fallback pattern:

♻️ Proposed addition
 interface FolderGroupItemProps {
   ...
   onStageFiles?: (files: ChangedFile[]) => void;
   onUnstageFiles?: (files: ChangedFile[]) => void;
+  onDiscardFiles?: (files: ChangedFile[]) => void;
   ...
 }

 function FolderGroupItem({
   ...
   onStageFiles,
   onUnstageFiles,
+  onDiscardFiles,
   ...
 }: FolderGroupItemProps) {
   ...
   const handleDiscardAll = useCallback(() => {
-    if (!onDiscard) return;
-    for (const file of group.files) {
-      onDiscard(file);
-    }
-  }, [group.files, onDiscard]);
+    if (onDiscardFiles) {
+      onDiscardFiles(group.files);
+    } else if (onDiscard) {
+      for (const file of group.files) {
+        onDiscard(file);
+      }
+    }
+  }, [group.files, onDiscard, onDiscardFiles]);

The same prop would need to be threaded through FileListGroupedProps → FileListGrouped → FolderGroupItem, matching the pattern used for onStageFiles/onUnstageFiles.

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

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileListGrouped.tsx`
around lines 102 - 127, Add a batch discard path to mirror the staging/unstaging
change: introduce an optional prop onDiscardFiles?: (files: ChangedFile[]) =>
void (add it to FileListGroupedProps and thread it into FolderGroupItem), then
update handleDiscardAll to call onDiscardFiles(group.files) when present and
otherwise fall back to invoking onDiscard(file) per-file; keep the dependency
array [group.files, onDiscard, onDiscardFiles] to preserve memoization and avoid
index.lock races during bulk discards.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx`:
- Line 477: The isActioning prop only checks unstageFileMutation.isPending ||
unstageFilesMutation.isPending, allowing row/folder buttons to be clickable
while section-level operations run; update the isActioning computation wherever
used (e.g., the prop set at the isActioning= line around ChangesView.tsx and the
similar block at lines ~547-552) to also include pending flags for all
section-level mutations (stageAll, unstageAll, discardAllUnstaged,
discardAllStaged) and any other row/folder mutations (stageFileMutation,
stageFilesMutation, discardFileMutation, etc.) so it becomes a combined boolean
(any of these .isPending) and pass that into the row/folder action components to
prevent overlapping git commands.

---

Outside diff comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileListTree.tsx`:
- Around line 166-172: handleDiscardAll currently calls onDiscard(file) in a
loop causing immediate destructive actions; change it to request a single
folder-level confirmation and only run the batch discard if confirmed.
Specifically, update handleDiscardAll (which uses collectFilesFromNode(node) and
onDiscard) to: 1) gather files via collectFilesFromNode(node), 2) open/show a
single confirmation (or call a provided onRequestDiscardBatch/onConfirmDiscard
callback) with summary metadata (e.g., folder name and count), and 3) upon
confirmation invoke a batch discard API (either a new onDiscardBatch(files) prop
or call onDiscard for each file after confirmation). Ensure the new flow
references handleDiscardAll, collectFilesFromNode, and onDiscard (or the new
onDiscardBatch) so the destructive loop is guarded by one confirmation.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileListGrouped.tsx`:
- Around line 102-127: Add a batch discard path to mirror the staging/unstaging
change: introduce an optional prop onDiscardFiles?: (files: ChangedFile[]) =>
void (add it to FileListGroupedProps and thread it into FolderGroupItem), then
update handleDiscardAll to call onDiscardFiles(group.files) when present and
otherwise fall back to invoking onDiscard(file) per-file; keep the dependency
array [group.files, onDiscard, onDiscardFiles] to preserve memoization and avoid
index.lock races during bulk discards.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44da7ae and 1ebf11a.

📒 Files selected for processing (9)
  • apps/desktop/src/lib/trpc/routers/changes/security/git-commands.ts
  • apps/desktop/src/lib/trpc/routers/changes/security/index.ts
  • apps/desktop/src/lib/trpc/routers/changes/staging.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileItem/FileItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileList.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileListGrouped.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileList/FileListTree.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FolderRow/FolderRow.tsx

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.

♻️ Duplicate comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx (2)

471-487: isActioning for the staged section is now complete.

The onUnstageFiles batch handler and the expanded isActioning (covering unstageFileMutation, unstageFilesMutation, unstageAllMutation, and discardAllStagedMutation) correctly prevent concurrent git operations on the staged section.

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

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx`
around lines 471 - 487, Staged-section concurrency handling is already
implemented: ensure the onUnstageFiles handler uses unstageFilesMutation.mutate
with worktreePath and filePaths (as currently done in onUnstageFiles) and keep
the isActioning prop combining unstageFileMutation.isPending,
unstageFilesMutation.isPending, unstageAllMutation.isPending, and
discardAllStagedMutation.isPending so the staged view (component receiving
worktreePath, projectId, category="staged", isExpandedView) remains disabled
during any of those pending operations; no further code changes required.

546-565: isActioning for the unstaged section is now complete.

All six relevant pending flags are aggregated, correctly blocking row/folder buttons during any concurrent section-level operation.

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

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx`
around lines 546 - 565, The unstaged section's isActioning prop correctly
aggregates all six pending flags (stageFilesMutation, stageFileMutation,
stageAllMutation, discardChangesMutation, deleteUntrackedMutation,
discardAllUnstagedMutation) to block UI during operations; apply the same
aggregation pattern to the staged section's isActioning prop (where the
component that renders category="staged" is defined) so both sections
consistently check the same set of mutation.isPending flags and prevent
concurrent actions.
🧹 Nitpick comments (3)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FolderRow/FolderRow.tsx (2)

301-333: AlertDialog placement outside <Collapsible> is correct; minor: onDiscardAll?.() optional chain is unreachable.

The dialog is only opened (via setShowDiscardDialog(true)) from code paths that are themselves guarded by onDiscardAll being defined, so the ?. at line 326 can never be undefined. This isn't a bug, just unnecessary defensive coding.

♻️ Proposed nit
- onClick={() => {
-     setShowDiscardDialog(false);
-     onDiscardAll?.();
- }}
+ onClick={() => {
+     setShowDiscardDialog(false);
+     onDiscardAll();
+ }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FolderRow/FolderRow.tsx`
around lines 301 - 333, The optional chaining on the discard callback is
unnecessary because the dialog can only be opened when onDiscardAll is defined;
in FolderRow update the AlertDialog Discard button click handler to call
onDiscardAll directly (use onDiscardAll() or a non-null assertion if TypeScript
strictness requires) and keep the setShowDiscardDialog(false) call as-is,
ensuring you modify the onClick inside the AlertDialogFooter where
setShowDiscardDialog and onDiscardAll are referenced.

132-132: hasAction is typed as a function union, not boolean.

onStageAll || onUnstageAll || onDiscardAll evaluates to the first truthy function (or undefined), giving hasAction the type (() => void) | undefined. While this works as a JSX gate, an explicit boolean cast is clearer:

♻️ Proposed nit
- const hasAction = onStageAll || onUnstageAll || onDiscardAll;
+ const hasAction = !!(onStageAll || onUnstageAll || onDiscardAll);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FolderRow/FolderRow.tsx`
at line 132, The variable hasAction is currently assigned the first truthy
function (onStageAll || onUnstageAll || onDiscardAll) giving it type (() =>
void) | undefined; change the assignment in FolderRow.tsx to produce an explicit
boolean (e.g. use !!(onStageAll || onUnstageAll || onDiscardAll) or
Boolean(onStageAll || onUnstageAll || onDiscardAll)) so hasAction is typed as
boolean and remains usable as a JSX guard.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx (1)

104-118: onError handlers omit the failed file paths from the error message.

Unlike the single-file variants (stageFileMutation, unstageFileMutation) that include variables.filePath in the toast, the batch mutations log a generic message. Consider accepting variables in the onError callback to surface the paths that failed:

♻️ Proposed improvement
- const stageFilesMutation = electronTrpc.changes.stageFiles.useMutation({
-     onSuccess: () => refetch(),
-     onError: (error) => {
-         console.error("Failed to stage files:", error);
-         toast.error(`Failed to stage files: ${error.message}`);
-     },
- });
+ const stageFilesMutation = electronTrpc.changes.stageFiles.useMutation({
+     onSuccess: () => refetch(),
+     onError: (error, variables) => {
+         console.error(`Failed to stage files ${variables.filePaths.join(", ")}:`, error);
+         toast.error(`Failed to stage files: ${error.message}`);
+     },
+ });

- const unstageFilesMutation = electronTrpc.changes.unstageFiles.useMutation({
-     onSuccess: () => refetch(),
-     onError: (error) => {
-         console.error("Failed to unstage files:", error);
-         toast.error(`Failed to unstage files: ${error.message}`);
-     },
- });
+ const unstageFilesMutation = electronTrpc.changes.unstageFiles.useMutation({
+     onSuccess: () => refetch(),
+     onError: (error, variables) => {
+         console.error(`Failed to unstage files ${variables.filePaths.join(", ")}:`, error);
+         toast.error(`Failed to unstage files: ${error.message}`);
+     },
+ });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx`
around lines 104 - 118, The onError handlers for stageFilesMutation and
unstageFilesMutation should include the failed file paths from the mutation
variables—update the onError signature to accept (error, variables) for
electronTrpc.changes.stageFiles.useMutation and
electronTrpc.changes.unstageFiles.useMutation, and log/Toast the variables
(e.g., variables.filePaths or variables.files) so the console.error and
toast.error messages show which paths failed alongside error.message; keep the
existing onSuccess refetch behavior unchanged and ensure you handle undefined
variables defensively.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx`:
- Around line 471-487: Staged-section concurrency handling is already
implemented: ensure the onUnstageFiles handler uses unstageFilesMutation.mutate
with worktreePath and filePaths (as currently done in onUnstageFiles) and keep
the isActioning prop combining unstageFileMutation.isPending,
unstageFilesMutation.isPending, unstageAllMutation.isPending, and
discardAllStagedMutation.isPending so the staged view (component receiving
worktreePath, projectId, category="staged", isExpandedView) remains disabled
during any of those pending operations; no further code changes required.
- Around line 546-565: The unstaged section's isActioning prop correctly
aggregates all six pending flags (stageFilesMutation, stageFileMutation,
stageAllMutation, discardChangesMutation, deleteUntrackedMutation,
discardAllUnstagedMutation) to block UI during operations; apply the same
aggregation pattern to the staged section's isActioning prop (where the
component that renders category="staged" is defined) so both sections
consistently check the same set of mutation.isPending flags and prevent
concurrent actions.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx`:
- Around line 104-118: The onError handlers for stageFilesMutation and
unstageFilesMutation should include the failed file paths from the mutation
variables—update the onError signature to accept (error, variables) for
electronTrpc.changes.stageFiles.useMutation and
electronTrpc.changes.unstageFiles.useMutation, and log/Toast the variables
(e.g., variables.filePaths or variables.files) so the console.error and
toast.error messages show which paths failed alongside error.message; keep the
existing onSuccess refetch behavior unchanged and ensure you handle undefined
variables defensively.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FolderRow/FolderRow.tsx`:
- Around line 301-333: The optional chaining on the discard callback is
unnecessary because the dialog can only be opened when onDiscardAll is defined;
in FolderRow update the AlertDialog Discard button click handler to call
onDiscardAll directly (use onDiscardAll() or a non-null assertion if TypeScript
strictness requires) and keep the setShowDiscardDialog(false) call as-is,
ensuring you modify the onClick inside the AlertDialogFooter where
setShowDiscardDialog and onDiscardAll are referenced.
- Line 132: The variable hasAction is currently assigned the first truthy
function (onStageAll || onUnstageAll || onDiscardAll) giving it type (() =>
void) | undefined; change the assignment in FolderRow.tsx to produce an explicit
boolean (e.g. use !!(onStageAll || onUnstageAll || onDiscardAll) or
Boolean(onStageAll || onUnstageAll || onDiscardAll)) so hasAction is typed as
boolean and remains usable as a JSX guard.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ebf11a and c1fc8da.

📒 Files selected for processing (2)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FolderRow/FolderRow.tsx

…scope

- Add confirmation dialog to FolderRow discard action (both hover button
  and context menu) to prevent accidental bulk discard
- Include section-level mutation states in isActioning props to disable
  row/folder buttons while stageAll/unstageAll/discardAll operations run
@de1mat de1mat force-pushed the fix/changes-view-hover-actions branch from c1fc8da to 6d99db8 Compare February 25, 2026 00:40
@de1mat
Copy link
Copy Markdown
Contributor Author

de1mat commented Feb 25, 2026

Hope this one can get in @Kitenite - it is quite annoying not being able to just hover over the files and click the + or - to stage or unstage them. Also not being able to stage a bunch of files at once, even from the menu option when you right-click on a folder ... it will stage the first file and error on all the rest. All fixed in this PR.

@Kitenite
Copy link
Copy Markdown
Collaborator

Checking now @de1mat !

…r-actions

# Conflicts:
#	apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileItem/FileItem.tsx
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: 1

🧹 Nitpick comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileItem/FileItem.tsx (1)

217-280: Indentation inconsistency between the discard and stage/unstage action blocks.

Lines 219–241 (the onDiscard button group) use deeper indentation than lines 242–279 (onStage/onUnstage buttons), which appear to be siblings inside the same div. This is a copy-paste artifact that makes the intent harder to read.

♻️ Proposed fix (align indentation)
 {hasAction && (
   <div className="flex items-center shrink-0 opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity">
-    {onDiscard && (
-      <Tooltip>
-        <TooltipTrigger asChild>
-          <Button
-            variant="ghost"
-            size="icon"
-            className="size-5 hover:bg-accent hover:text-destructive"
-            onClick={(e) => {
-              e.stopPropagation();
-              handleDiscardClick();
-            }}
-            disabled={isActioning}
-          >
-          {isDeleteAction ? (
-            <LuTrash2 className="size-3" />
-          ) : (
-            <LuUndo2 className="size-3" />
-            )}
-          </Button>
-        </TooltipTrigger>
-        <TooltipContent side="bottom">{discardLabel}</TooltipContent>
-      </Tooltip>
-    )}
+    {onDiscard && (
+      <Tooltip>
+        <TooltipTrigger asChild>
+          <Button
+            variant="ghost"
+            size="icon"
+            className="size-5 hover:bg-accent hover:text-destructive"
+            onClick={(e) => {
+              e.stopPropagation();
+              handleDiscardClick();
+            }}
+            disabled={isActioning}
+          >
+            {isDeleteAction ? (
+              <LuTrash2 className="size-3" />
+            ) : (
+              <LuUndo2 className="size-3" />
+            )}
+          </Button>
+        </TooltipTrigger>
+        <TooltipContent side="bottom">{discardLabel}</TooltipContent>
+      </Tooltip>
+    )}
     {onStage && (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileItem/FileItem.tsx`
around lines 217 - 280, The JSX indentation for the sibling action blocks inside
the hasAction container is inconsistent: the onDiscard block (using onDiscard,
Tooltip, Button, handleDiscardClick, discardLabel) is indented more deeply than
the subsequent onStage and onUnstage blocks (using onStage, onUnstage, Tooltip,
Button). Fix by aligning the onStage and onUnstage JSX to the same indentation
level as the onDiscard block so all three conditional siblings under the div
with className "flex items-center ..." have consistent indentation and visual
grouping.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FolderRow/FolderRow.tsx (1)

301-333: Consider using AlertDialogCancel/AlertDialogAction for proper accessibility semantics.

The AlertDialog uses plain Button components instead of AlertDialogCancel/AlertDialogAction. The Radix UI composition expects these primitives to correctly wire accessibility roles (alertdialog's action/cancel semantics). The onOpenChange escape-key behavior still works, but screen-reader users lose the semantic close/confirm role mapping. Consistent with the pattern in FileItem.tsx.

♻️ Proposed refactor
+import {
+  AlertDialogAction,
+  AlertDialogCancel,
+  ...
+} from "@superset/ui/alert-dialog";
 ...
 <AlertDialogFooter className="px-4 pb-4 pt-2 flex-row justify-end gap-2">
-  <Button
-    variant="ghost"
-    size="sm"
-    className="h-7 px-3 text-xs"
-    onClick={() => setShowDiscardDialog(false)}
-  >
-    Cancel
-  </Button>
-  <Button
-    variant="destructive"
-    size="sm"
-    className="h-7 px-3 text-xs"
-    onClick={() => {
-      setShowDiscardDialog(false);
-      onDiscardAll?.();
-    }}
-  >
-    Discard
-  </Button>
+  <AlertDialogCancel asChild>
+    <Button variant="ghost" size="sm" className="h-7 px-3 text-xs">
+      Cancel
+    </Button>
+  </AlertDialogCancel>
+  <AlertDialogAction asChild onClick={onDiscardAll}>
+    <Button variant="destructive" size="sm" className="h-7 px-3 text-xs">
+      Discard
+    </Button>
+  </AlertDialogAction>
 </AlertDialogFooter>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FolderRow/FolderRow.tsx`
around lines 301 - 333, Replace the plain Button components inside the
AlertDialog in FolderRow with the Radix-aware primitives AlertDialogCancel and
AlertDialogAction so accessibility semantics are preserved; keep the same
props/styles (variant, size, className) and handlers: use AlertDialogCancel for
the Cancel button to call setShowDiscardDialog(false) (or simply let the
primitive close the dialog) and use AlertDialogAction for the Discard button to
close the dialog and call onDiscardAll (ensure onDiscardAll is invoked after
closing if needed). Ensure these replacements occur within the existing
AlertDialogContent/Footer markup so showDiscardDialog, setShowDiscardDialog, and
onDiscardAll behave the same while gaining proper action/cancel semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FolderRow/FolderRow.tsx`:
- Around line 320-331: The click handler calls onDiscardAll() which is typed as
(() => void) | undefined; change the handler in the Button inside FolderRow (the
onClick that currently calls setShowDiscardDialog(false); onDiscardAll();) to
guard/call safely—e.g., use optional chaining (onDiscardAll?.()) or an explicit
if (onDiscardAll) onDiscardAll()—so TypeScript knows the call is safe while
still closing the dialog via setShowDiscardDialog(false).

---

Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileItem/FileItem.tsx`:
- Around line 217-280: The JSX indentation for the sibling action blocks inside
the hasAction container is inconsistent: the onDiscard block (using onDiscard,
Tooltip, Button, handleDiscardClick, discardLabel) is indented more deeply than
the subsequent onStage and onUnstage blocks (using onStage, onUnstage, Tooltip,
Button). Fix by aligning the onStage and onUnstage JSX to the same indentation
level as the onDiscard block so all three conditional siblings under the div
with className "flex items-center ..." have consistent indentation and visual
grouping.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FolderRow/FolderRow.tsx`:
- Around line 301-333: Replace the plain Button components inside the
AlertDialog in FolderRow with the Radix-aware primitives AlertDialogCancel and
AlertDialogAction so accessibility semantics are preserved; keep the same
props/styles (variant, size, className) and handlers: use AlertDialogCancel for
the Cancel button to call setShowDiscardDialog(false) (or simply let the
primitive close the dialog) and use AlertDialogAction for the Discard button to
close the dialog and call onDiscardAll (ensure onDiscardAll is invoked after
closing if needed). Ensure these replacements occur within the existing
AlertDialogContent/Footer markup so showDiscardDialog, setShowDiscardDialog, and
onDiscardAll behave the same while gaining proper action/cancel semantics.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1fc8da and 44969d0.

📒 Files selected for processing (4)
  • apps/desktop/src/lib/trpc/routers/changes/security/git-commands.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/ChangesView.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FileItem/FileItem.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/FolderRow/FolderRow.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/lib/trpc/routers/changes/security/git-commands.ts

@de1mat
Copy link
Copy Markdown
Contributor Author

de1mat commented Feb 25, 2026

Did you want me to sort those new issues @Kitenite ?

@Kitenite
Copy link
Copy Markdown
Collaborator

No worries likely good to merge just reviewing now

@Kitenite Kitenite merged commit b86e5fc into superset-sh:main Feb 25, 2026
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.

[bug] File hover action buttons only appear after selecting the file

2 participants