Skip to content

Conversation

@mtsgrd
Copy link
Contributor

@mtsgrd mtsgrd commented Jan 7, 2026

No description provided.

@vercel
Copy link

vercel bot commented Jan 7, 2026

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

Project Deployment Review Updated (UTC)
gitbutler-web Ready Ready Preview, Comment Jan 23, 2026 7:53pm

Request Review

@mtsgrd
Copy link
Contributor Author

mtsgrd commented Jan 7, 2026

@PavelLaptev there's a few more things I need to do here, but could you have a quick look at this and provide feedback if you have any?

@mtsgrd mtsgrd force-pushed the preview-refactor branch 2 times, most recently from 186079e to 7b4172f Compare January 9, 2026 12:58
@mtsgrd
Copy link
Contributor Author

mtsgrd commented Jan 9, 2026

@krlvi could you give this a quick spin and share your impressions? Pavel is ready to work on it if we agree this is the right direction.

@mtsgrd mtsgrd marked this pull request as ready for review January 9, 2026 14:28
Copilot AI review requested due to automatic review settings January 9, 2026 14:28
@mtsgrd mtsgrd changed the title [wip] Show scrollable diffs rather than one at a time Add MultiDiffView component and use it in StackView Jan 9, 2026
@mtsgrd mtsgrd changed the title Add MultiDiffView component and use it in StackView Show diffs in virtual list rather than one at a time Jan 9, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This WIP pull request refactors the diff viewing experience to show scrollable diffs rather than one at a time. The changes introduce a new MultiDiffView component that uses VirtualList for efficient rendering of multiple file diffs in a single scrollable view, replacing the previous drawer-based single-file preview approach.

Key changes:

  • Introduced MultiDiffView component for displaying multiple file diffs in a scrollable container
  • Removed intersection observer-based lazy loading from HunkDiffBody (all rows now render immediately)
  • Simplified BranchRef from a branded type to a plain string
  • Enhanced several components (FileViewHeader, Drawer, ChangedFiles) with new props for better layout control

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 20 comments.

Show a summary per file
File Description
packages/ui/src/lib/index.ts Exports new VirtualList component
packages/ui/src/lib/components/hunkDiff/HunkDiffBody.svelte Removes chunking/lazy loading logic, renders all rows immediately
packages/ui/src/lib/components/file/FileViewHeader.svelte Replaces "transparent" prop with "solid" (inverted logic), adds sticky/border props
packages/ui/src/lib/components/VirtualList.svelte Adds scrollToIndex function, removes overflow:hidden, inlines updateRange logic
apps/desktop/src/lib/utils/branch.ts Simplifies BranchRef from branded type to plain string
apps/desktop/src/lib/stacks/stackService.svelte.ts Updates to use string instead of BranchRef type
apps/desktop/src/lib/selection/fileSelectionUtils.ts Changes return type from boolean|undefined to void
apps/desktop/src/components/WorktreeChanges.svelte Updates onselect callback to include index parameter
apps/desktop/src/components/StackView.svelte Major refactor replacing drawer-based previews with MultiDiffView
apps/desktop/src/components/ReduxResult.svelte Adds hideLoading prop for conditional spinner display
apps/desktop/src/components/PreviewHeader.svelte Adds transparent and sticky props
apps/desktop/src/components/MultiDiffView.svelte New component for rendering multiple file diffs in scrollable list
apps/desktop/src/components/FileListItemWrapper.svelte Whitespace cleanup
apps/desktop/src/components/FileList.svelte Adds reactivity workaround, updates onselect to include index
apps/desktop/src/components/Drawer.svelte Adds topBorder, transparent, and stickyHeader props
apps/desktop/src/components/CommitView.svelte Removes bottom border, adds padding
apps/desktop/src/components/ChangedFiles.svelte Adds maxHeight, border, and header transparency props
apps/desktop/src/components/BranchList.svelte Integrates changed files display inline with branch
apps/desktop/src/components/BranchHeader.svelte Adds changedFiles snippet slot
apps/desktop/src/components/BranchCommitList.svelte Adds inline changed files for expanded commits
apps/desktop/src/components/BranchCard.svelte Adds changedFiles snippet slot
e2e/playwright/tests/commitActions.spec.ts Updates test IDs from stack-preview to stack

@krlvi
Copy link
Member

krlvi commented Jan 9, 2026

I am running this locally and I have to say, at least in the first 30 mins... I really enjoy this.

You are right that the visual distinction between files is not very clear, this can probably be improved by tweaking the design

@mtsgrd
Copy link
Contributor Author

mtsgrd commented Jan 9, 2026

Alright, let's move ahead. @PavelLaptev I'll add the same behaviour for the unassigned view!

Copilot AI review requested due to automatic review settings January 9, 2026 22:53
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 5 comments.

@PavelLaptev
Copy link
Contributor

Alright, let's move ahead. @PavelLaptev I'll add the same behaviour for the unassigned view!

@mtsgrd cool! I'll continue to work in this PR then.

@PavelLaptev
Copy link
Contributor

I’ll also convert it to draft for now, so Copilot can stop commenting for a while.

@PavelLaptev PavelLaptev marked this pull request as draft January 9, 2026 23:39
@PavelLaptev PavelLaptev changed the title Show diffs in virtual list rather than one at a time Changes list and diffs preview redesign Jan 9, 2026
@mtsgrd
Copy link
Contributor Author

mtsgrd commented Jan 10, 2026

@PavelLaptev sorry for force pushing, but I wanted to fix one thing and add highlighting as an example for you.

@mtsgrd mtsgrd force-pushed the preview-refactor branch 2 times, most recently from 3fb5ee2 to 540110e Compare January 10, 2026 13:24
@PavelLaptev
Copy link
Contributor

@mtsgrd, no problem. I’ll likely begin working on this today in the evening.

PavelLaptev and others added 17 commits January 23, 2026 21:28
Moves the highlighted state and animation from FileViewHeader to PreviewHeader and Drawer components. MultiDiffView and StackView now pass the highlighted prop to the correct header components, and FileViewHeader no longer handles highlighting. This centralizes the visual indication of active or highlighted headers, improving maintainability and consistency.
Adjusted the width of the vertical line from 4px to 6px in the CommitRow component for improved visibility.
Set the background color of the diff view container using the --clr-bg-1 CSS variable for improved visual consistency.
Eliminates the childrenWrapHeight and childrenWrapDisplay props from Drawer.svelte and their usage in ChangedFiles.svelte, simplifying the component interface and usage.
Replaces the ChangedFiles component with the new NestedChangedFiles component in BranchCommitList, updating CommitRow to support rendering changed files in a nested layout. Refactors CommitLine to use a dotOnTop prop instead of alignDot, and updates related styles and props for improved UI consistency. Adds the NestedChangedFiles.svelte component for a more flexible and modern file list display.
Adjusted the CSS gap property in the StackView component's container to create a more compact layout.
Moved the changed files indicator in BranchHeader to display only when the branch is selected, and updated its styling and structure. Replaced ChangedFiles with NestedChangedFiles in BranchList for a more consistent display. Adjusted CommitRow and NestedChangedFiles to improve layout and visual consistency for changed files containers.
Added an EmptyStatePlaceholder with an empty folder illustration and message when there are no changed files to display. This improves the user experience by providing clear feedback in the absence of file changes.
Only render the empty-state placeholder in NestedChangedFiles when there are no change entries and there are no conflict entries. Previously the empty state would appear even when conflicts existed because conflictEntries being an empty object or undefined wasn't considered. This prevents hiding conflicts from the user and ensures the UI properly reflects when conflict entries are present.
Use derived store for conflict presence check

Use a derived value to determine whether there are any conflict entries instead of recalculating Object.keys(conflictEntries).length > 0 inline. This simplifies the template condition, avoids repeated calculations, and makes the intent clearer when checking for an empty change list.

- Add hasConflicts derived store
- Replace template condition with !hasConflicts as part of the empty-state check
This makes the initial render use the previously measured height.
Set the startIndex prop on MultiDiffView instances mounted from StackView so that if there are one or more selected files (tracked via $activeLastAdded), the diff view opens at that selected file. This restores the previously reverted behavior in a cleaner way by passing startIndex={$activeLastAdded?.index} to the component where it is rendered.
This ensures that branch/commit gets selected when clicking one of the
files.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 47 out of 48 changed files in this pull request and generated 6 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

apps/desktop/src/components/LazyList.svelte:35

  • displayCount is declared with $derived(...) but later reassigned in loadMore(). $derived values are read-only in runes mode, so this will fail to compile. Use $state for displayCount (and reset it when items changes via $effect) instead of $derived.

Comment on lines +2 to +3
A helper component for testing the VirtuaList. It would be good to find a
better location for this file, something accessible to both tests as well
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

Typo in comment: "VirtuaList" should be "VirtualList".

Copilot uses AI. Check for mistakes.
Comment on lines 170 to 174
{#snippet branchGroup(props: { title: string; children: SidebarEntrySubject[] })}
{#if props.children.length > 0}
<BranchesListGroup title={props.title}>
<ChunkyList items={props.children} item={sidebarEntry}></ChunkyList>
<LazyList items={props.children} template={sidebarEntry}></LazyList>
</BranchesListGroup>
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

LazyList calls its template snippet with two arguments (item, context). Here it is passed sidebarEntry, which in this component is used elsewhere as a 1-arg snippet. If sidebarEntry is still typed/defined as Snippet<[SidebarEntrySubject]>, this will cause a TS mismatch and/or require updating callers to accept the extra context parameter. Consider adapting sidebarEntry (and its call sites) to accept the context arg, or making LazyList's context parameter optional in its template type.

Copilot uses AI. Check for mistakes.
Comment on lines +88 to 95
/**
* The initial index to start rendering from when the list first loads.
* If provided, the list will initialize at this position instead of top or bottom.
* Takes precedence over stickToBottom for initial render.
*/
startIndex?: number;
renderDistance?: number;
};
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

renderDistance is typed as a required prop in Props, but the destructuring provides a default (renderDistance = 0) and most call sites don't pass it. This makes the component require renderDistance at type-level and will break consumers. Mark renderDistance as optional (renderDistance?: number) in Props.

Copilot uses AI. Check for mistakes.
Comment on lines 158 to 163
const index = indexAttr ? parseInt(indexAttr) : undefined;
if (index !== undefined) {
const oldHeight = heightMap[index] || defaultHeight;
if (target.clientHeight === heightMap[index]) {
return;
}
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

Inside the ResizeObserver loop, return is used when an observed element's height hasn't changed. That exits the entire observer callback and skips processing any remaining entries, which can leave heightMap and offsets stale. Replace this return with continue (or restructure the logic) so all entries are handled.

Copilot uses AI. Check for mistakes.
Comment on lines +480 to +486
const scrollTop = viewport?.scrollTop;
if (lastScrollTop && lastScrollTop > scrollTop) {
lastScrollDirection = 'up';
} else if (lastScrollTop && lastScrollTop < scrollTop) {
lastScrollDirection = 'down';
} else {
lastScrollDirection = undefined;
}
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

Scroll direction detection uses if (lastScrollTop && ...) which fails when lastScrollTop is 0 (falsy). This causes lastScrollDirection to stay undefined for the first scrolls starting at the top, and can break the resize/scroll adjustment logic that relies on direction. Use an explicit lastScrollTop !== undefined check instead of truthiness.

Copilot uses AI. Check for mistakes.
@mtsgrd mtsgrd merged commit c4c84a0 into master Jan 23, 2026
23 of 24 checks passed
@mtsgrd mtsgrd deleted the preview-refactor branch January 23, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants