Skip to content

feat(desktop): add VSCode-style scroll indicators to diff viewer - #2378

Closed
Jc7j wants to merge 2 commits into
ParadevAI:mainfrom
Jc7j:feat/diff-scroll-indicators
Closed

Jc7j wants to merge 2 commits into
ParadevAI:mainfrom
Jc7j:feat/diff-scroll-indicators

Conversation

@Jc7j

@Jc7j Jc7j commented Mar 11, 2026 •

Copy link
Copy Markdown

Hi! Love superset and I use it all the time. Took the opportunity to create this PR (using superset haha) and add something I noticed that went missing in the latest versions (current v1.1.16). Lmk if there's anything you guys need from me, thank you!

Summary

  • Add a narrow minimap strip alongside the diff viewer showing colored markers for additions (green) and deletions (red), proportionally positioned to reflect where changes sit in the file
  • Clicking anywhere on the strip smoothly scrolls to that position in the diff
  • A semi-transparent viewport thumb shows the current visible area
  • Strip auto-hides when there are no changes to display

Closes #2315

Changes

  • New component: DiffScrollIndicators — renders the minimap strip using @pierre/diffs to parse hunks and compute change regions
  • Modified: FileViewerContent.tsx — wraps the diff container in a flex layout and mounts the indicator strip alongside it

Test plan

  • Open a workspace with file changes in the desktop app
  • Verify colored markers appear in the narrow strip to the right of the diff
  • Click on a marker — diff should smooth-scroll to that change
  • Scroll the diff and verify the viewport thumb tracks correctly
  • Open a file with no changes — strip should not render
  • Test with both split and unified diff view modes

Summary by CodeRabbit

  • New Features
    • Added a vertical diff indicators panel next to file diffs showing additions, deletions, and modifications as colored bands.
    • Indicators show the current viewport, support hover feedback, and let you jump to locations by clicking the track with smooth scrolling.
    • Updated diff layout to include the indicators while preserving the existing diff view and interactions.

Add a narrow minimap strip alongside the diff viewer that shows colored
markers for additions (green) and deletions (red), proportionally
positioned to reflect where changes sit in the file. Clicking a marker
smoothly scrolls to that change. A semi-transparent viewport thumb
shows the current scroll position.

Closes #2315
@coderabbitai

coderabbitai Bot commented Mar 11, 2026 •

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ecf027d0-54ce-4975-8d11-15135e437451

📥 Commits

Reviewing files that changed from the base of the PR and between 916c29c and 2a2dcc0.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx

📝 Walkthrough

Walkthrough

Adds a new DiffScrollIndicators component and integrates it into FileViewerContent by changing the diff layout to a two-column structure, placing the LightDiffViewer and the scrollbar indicators as sibling panels and moving diff event handlers to an inner diff container.

Changes

Cohort / File(s) Summary
DiffScrollIndicators component
apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/DiffScrollIndicators/DiffScrollIndicators.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/DiffScrollIndicators/index.ts
New exported component that parses file diffs into regions, renders proportional colored bands (add/remove/modify), shows a viewport thumb, handles clicks to jump to locations, supports hover, and uses ResizeObserver and scroll tracking.
FileViewerContent integration
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx
Reworked diff layout into a two-column flex container: left holds the LightDiffViewer (inner diff container now receives event handlers and no longer provides the outer ref), right renders DiffScrollIndicators when unchanged regions are shown; passes scrollRef, original/modified, and filePath to indicators.

Sequence Diagram

sequenceDiagram
    participant User
    participant FileViewerContent
    participant DiffScrollIndicators
    participant DiffParser
    participant ScrollContainer as Scroll Container

    User->>FileViewerContent: Open file diff
    FileViewerContent->>DiffScrollIndicators: Mount with original, modified, filePath, scrollRef
    DiffScrollIndicators->>DiffParser: parseDiffFromFile(original, modified)
    DiffParser-->>DiffScrollIndicators: regions (add/delete/modify)
    DiffScrollIndicators->>ScrollContainer: attach scroll & resize listeners
    DiffScrollIndicators->>DiffScrollIndicators: compute band positions & render viewport thumb
    User->>DiffScrollIndicators: click on a band
    DiffScrollIndicators->>ScrollContainer: scrollTo(targetPosition)
    ScrollContainer-->>User: visible viewport jumps to change
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped a trail beside the pane,

little bands that show the change,
click a stripe and off we go —
a swift, precise, delightful flow! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(desktop): add VSCode-style scroll indicators to diff viewer' clearly and concisely describes the main change—adding a new visual indicator feature to the diff viewer component.
Description check ✅ Passed The PR description is comprehensive, covering the feature summary, implementation details, test plan, and linked issue reference, though it deviates from the template structure.
Linked Issues check ✅ Passed The code changes implement all primary requirements from issue #2315: VSCode-style indicators with colored markers for additions/deletions, proportional positioning, click-to-scroll navigation, and support for both diff view modes.
Out of Scope Changes check ✅ Passed All changes are focused on implementing the diff scroll indicators feature: new DiffScrollIndicators component, FileViewerContent layout refactoring, and index re-export. No out-of-scope modifications detected.

✏️ 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.

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (2)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/DiffScrollIndicators/DiffScrollIndicators.tsx (2)

27-36: Consider extracting hardcoded colors to theme tokens.

The PR objective mentions using "existing theme color tokens (e.g., --diff-add-bg, --diff-remove-bg) for visual consistency." While MIDNIGHT_CODE_COLORS is used for deletion/addition colors, TRACK_BG, TRACK_BG_HOVER, and the viewport thumb colors (lines 242-244) are hardcoded hex values. This could cause visual inconsistency if themes change.

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

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/DiffScrollIndicators/DiffScrollIndicators.tsx`
around lines 27 - 36, Replace hardcoded track and thumb hex colors in the
DiffScrollIndicators component with theme tokens: swap TRACK_BG and
TRACK_BG_HOVER for CSS variables (e.g., var(--diff-track-bg, "#1e2127") and
var(--diff-track-bg-hover, "#22262e")) and replace the viewport/thumb colors
currently set near the thumb rendering (the thumb fill styles) with vars like
var(--diff-thumb-bg, "<current-fallback>") and var(--diff-thumb-border,
"<current-fallback>"). Update the constants TRACK_BG and TRACK_BG_HOVER and any
inline styles that set the viewport/thumbnail colors to use these CSS variables
so themes can override while preserving the existing hex values as fallbacks;
keep existing DELETION_COLOR and ADDITION_COLOR unchanged if they already use
MIDNIGHT_CODE_COLORS.

197-207: Consider using CSS :hover pseudo-class instead of JS handlers.

The hover effect could be achieved with pure CSS, eliminating the need for these handlers and the inline transition style:

♻️ CSS-based hover alternative
-			onMouseEnter={handleMouseEnter}
-			onMouseLeave={handleMouseLeave}

Then in a stylesheet or Tailwind class:

.diff-scroll-track:hover {
  background-color: `#22262e`;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/DiffScrollIndicators/DiffScrollIndicators.tsx`
around lines 197 - 207, The handleMouseEnter and handleMouseLeave functions (and
the inline transition/background style on the track element using trackRef)
should be replaced with a CSS :hover rule: remove handleMouseEnter,
handleMouseLeave and any direct style manipulations of trackRef, add a stable
class on the track element (e.g. "diff-scroll-track") and put the base
background (TRACK_BG) and hover background (TRACK_BG_HOVER) into CSS (or
Tailwind classes) with a transition rule there so the hover color is handled
purely by CSS; keep the trackRef only if still needed for other logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/DiffScrollIndicators/DiffScrollIndicators.tsx`:
- Around line 27-36: Replace hardcoded track and thumb hex colors in the
DiffScrollIndicators component with theme tokens: swap TRACK_BG and
TRACK_BG_HOVER for CSS variables (e.g., var(--diff-track-bg, "#1e2127") and
var(--diff-track-bg-hover, "#22262e")) and replace the viewport/thumb colors
currently set near the thumb rendering (the thumb fill styles) with vars like
var(--diff-thumb-bg, "<current-fallback>") and var(--diff-thumb-border,
"<current-fallback>"). Update the constants TRACK_BG and TRACK_BG_HOVER and any
inline styles that set the viewport/thumbnail colors to use these CSS variables
so themes can override while preserving the existing hex values as fallbacks;
keep existing DELETION_COLOR and ADDITION_COLOR unchanged if they already use
MIDNIGHT_CODE_COLORS.
- Around line 197-207: The handleMouseEnter and handleMouseLeave functions (and
the inline transition/background style on the track element using trackRef)
should be replaced with a CSS :hover rule: remove handleMouseEnter,
handleMouseLeave and any direct style manipulations of trackRef, add a stable
class on the track element (e.g. "diff-scroll-track") and put the base
background (TRACK_BG) and hover background (TRACK_BG_HOVER) into CSS (or
Tailwind classes) with a transition rule there so the hover color is handled
purely by CSS; keep the trackRef only if still needed for other logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 49eb1750-38cf-44f9-af61-c2423739b463

📥 Commits

Reviewing files that changed from the base of the PR and between cd51b6b and 916c29c.

📒 Files selected for processing (3)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/DiffScrollIndicators/DiffScrollIndicators.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/DiffScrollIndicators/index.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

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.

3 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx">

<violation number="1" location="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx:332">
P2: By wrapping both the diff container and `DiffScrollIndicators` inside `DiffViewerContextMenu`, the diff's context menu will unexpectedly trigger when right-clicking the scroll indicators. This breaks menu actions: 'Edit Here' will jump to a stale location (the last line right-clicked in the diff), and 'Select All' will select text in the adjacent diff viewer. Move the flex layout wrapper outside of `DiffViewerContextMenu` so the menu is strictly bound to the diff container.</violation>

<violation number="2" location="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx:369">
P1: The `hideUnchangedRegions` prop is not passed to or handled by `DiffScrollIndicators`. When unchanged regions are collapsed, the diff viewer's scroll height is drastically reduced. However, the scroll indicators still position markers based on the full file's line count. This causes the visual markers and click-to-scroll targets to be completely out of sync with the rendered diff. Consider conditionally hiding the indicators (e.g., `{!hideUnchangedRegions && <DiffScrollIndicators ... />}`) when regions are hidden.</violation>
</file>

<file name="apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/DiffScrollIndicators/DiffScrollIndicators.tsx">

<violation number="1" location="apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/DiffScrollIndicators/DiffScrollIndicators.tsx:231">
P2: Clamp region start/size before converting to percentages so deletion markers at EOF are still visible in the minimap.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

contents={diffData}
viewMode={diffViewMode}
hideUnchangedRegions={hideUnchangedRegions}
<div className="flex h-full min-h-0">

@cubic-dev-ai cubic-dev-ai Bot Mar 11, 2026 •

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.

P2: By wrapping both the diff container and DiffScrollIndicators inside DiffViewerContextMenu, the diff's context menu will unexpectedly trigger when right-clicking the scroll indicators. This breaks menu actions: 'Edit Here' will jump to a stale location (the last line right-clicked in the diff), and 'Select All' will select text in the adjacent diff viewer. Move the flex layout wrapper outside of DiffViewerContextMenu so the menu is strictly bound to the diff container.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx, line 332:

<comment>By wrapping both the diff container and `DiffScrollIndicators` inside `DiffViewerContextMenu`, the diff's context menu will unexpectedly trigger when right-clicking the scroll indicators. This breaks menu actions: 'Edit Here' will jump to a stale location (the last line right-clicked in the diff), and 'Select All' will select text in the adjacent diff viewer. Move the flex layout wrapper outside of `DiffViewerContextMenu` so the menu is strictly bound to the diff container.</comment>

<file context>
@@ -328,40 +329,48 @@ export function FileViewerContent({
-						contents={diffData}
-						viewMode={diffViewMode}
-						hideUnchangedRegions={hideUnchangedRegions}
+				<div className="flex h-full min-h-0">
+					<div
+						ref={diffContainerRef}
</file context>
Fix with Cubic

onMouseLeave={handleMouseLeave}
>
{regions.map((region) => {
const topPercent = ((region.startLine - 1) / totalLines) * 100;

@cubic-dev-ai cubic-dev-ai Bot Mar 11, 2026 •

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.

P2: Clamp region start/size before converting to percentages so deletion markers at EOF are still visible in the minimap.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/DiffScrollIndicators/DiffScrollIndicators.tsx, line 231:

<comment>Clamp region start/size before converting to percentages so deletion markers at EOF are still visible in the minimap.</comment>

<file context>
@@ -0,0 +1,250 @@
+			onMouseLeave={handleMouseLeave}
+		>
+			{regions.map((region) => {
+				const topPercent = ((region.startLine - 1) / totalLines) * 100;
+				const heightPercent = (region.lineCount / totalLines) * 100;
+				return renderBand(region, topPercent, heightPercent);
</file context>
Fix with Cubic

…psed

When hideUnchangedRegions is true, the diff viewer collapses context
lines, reducing scroll height. The scroll indicators position bands
based on full file line counts, causing markers and click-to-scroll
targets to be out of sync with the rendered diff. Hide the indicators
in this mode to avoid the coordinate mismatch.
@Jc7j

Jc7j commented Mar 11, 2026

Copy link
Copy Markdown
Author

PR Feedback Triage

Reviewed all AI bot comments. Here's the breakdown:

✅ Resolved (1)

# Reviewer Comment Action
1 @cubic-dev-ai P1: hideUnchangedRegions not handled — scroll indicator markers out of sync when unchanged regions are collapsed Fixed in 2a2dcc0. Indicators are now hidden when hideUnchangedRegions is true. The bands position based on full file line counts, but when LightDiffViewer collapses context lines the scroll height shrinks — causing markers and click-to-scroll targets to map to wrong positions. Hiding the strip avoids this coordinate mismatch.

❌ Rejected (3)

# Reviewer Comment Reasoning
2 @cubic-dev-ai P2: Context menu fires when right-clicking the 14px scroll strip The strip is 14px wide and decorative. Users left-click it to navigate; right-clicking it is vanishingly unlikely. Even if triggered, selectAll correctly targets the diff container, and onEditAtLocation uses the last diff-area right-click location. Not worth restructuring the DOM for this edge case.
3 @coderabbitai Nitpick: Extract hardcoded track colors to CSS variable theme tokens The codebase uses MIDNIGHT_CODE_COLORS constants for theming, not CSS variables. Track colors are consistent with the existing palette. Introducing CSS vars just for this component would be inconsistent.
4 @coderabbitai Nitpick: Use CSS :hover instead of JS mouse handlers The component uses inline style props throughout (values are dynamic hex constants). CSS :hover can't override inline styles without !important. Switching only hover to class-based while keeping everything else inline would be inconsistent.

📋 Deferred (1)

# Reviewer Comment Reasoning
5 @cubic-dev-ai P2: Clamp EOF deletion markers so they're visible in the minimap Valid edge case but minor — max(3px, ...) ensures the band is at least 3px tall, and startLine tracks the modified file position so the marker is mostly visible. Worth a follow-up but not blocking this PR.

@coderabbitai

coderabbitai Bot commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

@Jc7j Thanks for the thorough triage — the reasoning on all decisions is clear and well-grounded.

Acknowledged on my two rejected nitpicks:

  • Bump react from 18.3.1 to 19.2.0 #3 (CSS variable tokens): Makes sense — MIDNIGHT_CODE_COLORS constants are the established theming pattern here; adding CSS vars in isolation would be out of place.
  • Bump tailwind-merge from 2.6.0 to 3.3.1 #4 (CSS :hover): Fair point — with inline styles as the consistent pattern throughout the component, switching only hover to class-based would require !important overrides and create an inconsistency.

I'll resolve my open comments and record the codebase conventions.


✏️ Learnings added
Learnt from: Jc7j
URL: https://github.com/superset-sh/superset/pull/2378

Timestamp: 2026-03-11T20:46:55.788Z
Learning: In the `DiffScrollIndicators` component (`apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/DiffScrollIndicators/DiffScrollIndicators.tsx`), theming uses `MIDNIGHT_CODE_COLORS` constants (not CSS variables). Do not suggest extracting colors to CSS variable tokens for this component — it would be inconsistent with the established codebase pattern.

Learnt from: Jc7j
URL: https://github.com/superset-sh/superset/pull/2378

Timestamp: 2026-03-11T20:46:55.788Z
Learning: The `DiffScrollIndicators` component (`apps/desktop/src/renderer/screens/main/components/WorkspaceView/components/DiffScrollIndicators/DiffScrollIndicators.tsx`) uses inline `style` props throughout with dynamic hex constants. Hover states are handled via JS `onMouseEnter`/`onMouseLeave` handlers rather than CSS `:hover`, which is the correct approach here — mixing CSS `:hover` with inline styles would require `!important` overrides and be inconsistent with the rest of the component.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Actions performed

Comments resolved. Auto-approval is disabled; enable reviews.request_changes_workflow to approve automatically.

@Jc7j Jc7j closed this by deleting the head repository Apr 14, 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.

Add VSCode-style scrollbar change indicators to diff viewer

1 participant