Skip to content

feat(desktop): persist v2 diff-pane expand state, auto-expand clicked file#3885

Merged
Kitenite merged 2 commits intomainfrom
persist-file-expand-state
Apr 30, 2026
Merged

feat(desktop): persist v2 diff-pane expand state, auto-expand clicked file#3885
Kitenite merged 2 commits intomainfrom
persist-file-expand-state

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Apr 29, 2026

Summary

  • Lift showFullDiff out of DiffFileEntry local state into a persisted expandedFiles array on DiffPaneData, synced through the existing paneLayout collection — "Show diff" decisions on large files now survive navigation and reload.
  • Clicking a file from the v2 changes tab now adds its path to expandedFiles (and clears it from collapsedFiles), so a large file auto-renders its diff in the changes pane instead of showing the deferred placeholder.

Test plan

  • Open a workspace with a >250-line diff, click the file in the v2 changes tab → diff renders immediately (no "Show diff" placeholder).
  • Click "Show diff" on another large file, switch tabs/panes and back → file remains expanded.
  • Reload the workspace → previously expanded files are still expanded.
  • Manual chevron collapse on a small file still toggles and persists as before.
  • Open Diff in New Tab on a large file → new tab opens with that file already expanded.

Summary by cubic

Persisted expanded state for large diffs in the v2 diff pane and auto-expand the clicked file from the changes tab. Large files now render immediately, and your expand choice survives navigation and reload; also fixes a crash with legacy panes.

  • New Features

    • Store expand decisions in DiffPaneData.expandedFiles (persisted via paneLayout).
    • Clicking a file in the v2 changes tab adds it to expandedFiles and removes it from collapsedFiles; new tabs open with that file expanded.
    • Existing collapse/viewed toggles continue to work and persist as before.
  • Bug Fixes

    • Guard collapsedFiles reads in the diff pane opener to handle legacy panes without this field and avoid undefined.filter errors.

Written for commit 7b7e194. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features
    • Centralized file expansion state for diff panes, enabling constant-time expansion checks.
    • "Show diff" now respects and updates the shared expansion state so UI stays in sync.
    • New persistence of expanded files: files open expanded when diffed and expansion is preserved across pane reuse and navigation.

…lick

Lift `showFullDiff` out of `DiffFileEntry` local state into a persisted
`expandedFiles` array on `DiffPaneData`, synced through the existing
paneLayout collection. Clicking a file from the v2 changes tab now adds
its path to `expandedFiles` (and clears it from `collapsedFiles`), so a
large file auto-renders its diff instead of showing the deferred
placeholder, and the decision survives navigation/reload.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 390ea8fb-9d90-4756-8393-825f668de23d

📥 Commits

Reviewing files that changed from the base of the PR and between a837259 and 7b7e194.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspacePaneOpeners/useWorkspacePaneOpeners.ts

📝 Walkthrough

Walkthrough

Lifted per-file "expanded" state from individual DiffFileEntry components into the DiffPane container. DiffPane now stores and persists expandedFiles, exposes a stable setExpanded callback, and DiffFileEntry receives expanded and onSetExpanded props. Pane-open logic initializes or updates expandedFiles when opening diffs.

Changes

Cohort / File(s) Summary
Type Definition
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/types.ts
Add optional expandedFiles?: string[] to DiffPaneData to persist per-file expansion state at the pane level.
Diff Pane Container & State Management
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/DiffPane.tsx
Introduce memoized expandedSet for O(1) expanded-file checks; add stable setExpanded(path, value) callback that updates expandedFiles and persists via context.actions.updateData.
Pane Opener Logic
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspacePaneOpeners/useWorkspacePaneOpeners.ts
When opening/creating diff panes, initialize or merge expandedFiles so the target file is included (remove from collapsedFiles if reusing pane).
Diff File Entry Component
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/components/DiffFileEntry/DiffFileEntry.tsx
Remove local showFullDiff state; accept expanded: boolean and onSetExpanded(path, value) props and use them to control "show diff" behavior and rendering.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Openers as useWorkspacePaneOpeners
  participant Pane as DiffPane
  participant Entry as DiffFileEntry

  User->>Openers: openDiffPane(filePath)
  Openers->>Pane: createOrReusePane({... expandedFiles: [filePath] ...})
  Pane->>Pane: updateData(expandedFiles)
  Pane->>Entry: render(entryProps { expanded, onSetExpanded })
  User->>Entry: click "Show diff"
  Entry->>Pane: onSetExpanded(filePath, true)
  Pane->>Pane: updateData(expandedFiles) / persist
  Pane->>Entry: re-render with expanded=true
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I nudge a pane, I hop and dream,

Files unfurl like ribbons stream,
A memoized set, a tidy cue,
Callbacks bound — the view is true,
Hooray, the diffs remember too! ✨

🚥 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 title 'feat(desktop): persist v2 diff-pane expand state, auto-expand clicked file' accurately captures the main changes: persisting expand state and auto-expanding clicked files in the diff pane.
Description check ✅ Passed The PR description is well-structured with a clear summary, test plan, and commit message details, though it does not follow the exact template structure with explicit sections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch persist-file-expand-state

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
Review rate limit: 4/8 reviews remaining, refill in 24 minutes and 18 seconds.

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 29, 2026

Greptile Summary

This PR lifts showFullDiff from local component state into a persisted expandedFiles array on DiffPaneData, and automatically adds a file's path to that array whenever it is clicked from the v2 changes tab. The implementation cleanly mirrors the existing collapsedFiles pattern across all four touched files.

Confidence Score: 5/5

Safe to merge; both findings are P2 and do not block the primary user path.

No P0/P1 bugs. The unbounded-growth issue is a maintenance concern and the null-guard gap requires very old persisted state to trigger. Core logic correctly mirrors the existing collapsed-files pattern.

useWorkspacePaneOpeners.ts (missing null-guard on collapsedFiles); DiffPane.tsx (expandedFiles never pruned).

Important Files Changed

Filename Overview
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/types.ts Adds optional expandedFiles?: string[] to DiffPaneData; clean backward-compatible addition.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/DiffPane.tsx Introduces expandedSet + setExpanded callback mirroring the existing collapsed-files pattern; expandedFiles grows unboundedly as no removal path exists.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/components/DiffFileEntry/DiffFileEntry.tsx Replaces local showFullDiff state with the expanded prop; logic for deferred rendering and shouldMount is unchanged and correct.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspacePaneOpeners/useWorkspacePaneOpeners.ts Auto-adds clicked file to expandedFiles and removes it from collapsedFiles in all three pane-open paths; prev.collapsedFiles.filter(...) lacks a null-guard for old persisted data.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User clicks file in v2 changes tab] --> B[openDiffPane called]
    B --> C{Pane already exists?}
    C -- Yes --> D[setPaneData: add filePath to expandedFiles\nremove from collapsedFiles]
    C -- No / New Tab --> E[Create pane with expandedFiles: filePath\ncollapsedFiles: empty]
    D --> F[DiffPane re-renders]
    E --> F
    F --> G[expandedSet = new Set of expandedFiles]
    G --> H[DiffFileEntry receives expanded=true]
    H --> I{deferReason?}
    I -- large/deleted + expanded=true --> J[Render WorkspaceDiff immediately]
    I -- null --> K[Render via viewport proximity as before]
    L[User clicks Show diff button] --> M[handleShowFullDiff → onSetExpanded path true]
    M --> N[setExpanded adds path to expandedFiles]
    N --> F
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/DiffPane.tsx
Line: 87-98

Comment:
**`expandedFiles` grows without bound**

`setExpanded` is only ever called with `value = true` (from `handleShowFullDiff`), so paths are appended to `expandedFiles` but never removed. Over a long session with many file navigations, the persisted array accumulates every file the user has ever clicked or expanded. Unlike `collapsedFiles`, which shrinks when a file is uncollapsed, `expandedFiles` has no equivalent trim path. The `Array.includes` check in `setExpanded` is also O(n) on each call.

Consider pruning stale entries when the changeset changes (e.g., keep only paths that are still in the current `files` list), or simply mirror the `collapsedFiles` pattern and allow callers to set `value = false` to remove entries.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspacePaneOpeners/useWorkspacePaneOpeners.ts
Line: 52

Comment:
**Potential null-dereference on `collapsedFiles`**

`prev.collapsedFiles.filter(...)` will throw if an existing persisted pane loaded from an older snapshot omits the `collapsedFiles` field. The `DiffPane` update helpers already guard against this with `current.collapsedFiles ?? []`, and the same defensive pattern should be applied here.

```suggestion
							collapsedFiles: (prev.collapsedFiles ?? []).filter((p) => p !== filePath),
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(desktop): persist large-diff expand..." | Re-trigger Greptile

Comment on lines +87 to +98
const setExpanded = useCallback(
(path: string, value: boolean) => {
const current = dataRef.current;
const expanded = current.expandedFiles ?? [];
const has = expanded.includes(path);
if (value === has) return;
const next = value
? [...expanded, path]
: expanded.filter((p) => p !== path);
updateData({ ...current, expandedFiles: next } as PaneViewerData);
},
[updateData],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 expandedFiles grows without bound

setExpanded is only ever called with value = true (from handleShowFullDiff), so paths are appended to expandedFiles but never removed. Over a long session with many file navigations, the persisted array accumulates every file the user has ever clicked or expanded. Unlike collapsedFiles, which shrinks when a file is uncollapsed, expandedFiles has no equivalent trim path. The Array.includes check in setExpanded is also O(n) on each call.

Consider pruning stale entries when the changeset changes (e.g., keep only paths that are still in the current files list), or simply mirror the collapsedFiles pattern and allow callers to set value = false to remove entries.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/usePaneRegistry/components/DiffPane/DiffPane.tsx
Line: 87-98

Comment:
**`expandedFiles` grows without bound**

`setExpanded` is only ever called with `value = true` (from `handleShowFullDiff`), so paths are appended to `expandedFiles` but never removed. Over a long session with many file navigations, the persisted array accumulates every file the user has ever clicked or expanded. Unlike `collapsedFiles`, which shrinks when a file is uncollapsed, `expandedFiles` has no equivalent trim path. The `Array.includes` check in `setExpanded` is also O(n) on each call.

Consider pruning stale entries when the changeset changes (e.g., keep only paths that are still in the current `files` list), or simply mirror the `collapsedFiles` pattern and allow callers to set `value = false` to remove entries.

How can I resolve this? If you propose a fix, please make it concise.

data: {
...prev,
path: filePath,
collapsedFiles: prev.collapsedFiles.filter((p) => p !== filePath),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Potential null-dereference on collapsedFiles

prev.collapsedFiles.filter(...) will throw if an existing persisted pane loaded from an older snapshot omits the collapsedFiles field. The DiffPane update helpers already guard against this with current.collapsedFiles ?? [], and the same defensive pattern should be applied here.

Suggested change
collapsedFiles: prev.collapsedFiles.filter((p) => p !== filePath),
collapsedFiles: (prev.collapsedFiles ?? []).filter((p) => p !== filePath),
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspacePaneOpeners/useWorkspacePaneOpeners.ts
Line: 52

Comment:
**Potential null-dereference on `collapsedFiles`**

`prev.collapsedFiles.filter(...)` will throw if an existing persisted pane loaded from an older snapshot omits the `collapsedFiles` field. The `DiffPane` update helpers already guard against this with `current.collapsedFiles ?? []`, and the same defensive pattern should be applied here.

```suggestion
							collapsedFiles: (prev.collapsedFiles ?? []).filter((p) => p !== filePath),
```

How can I resolve this? If you propose a fix, please make it concise.

Older persisted DiffPane snapshots may not have collapsedFiles set; the
existing setCollapsed helper already uses `?? []` for the same reason.
Match the defensive read here so reusing an existing diff pane doesn't
TypeError on undefined.filter.
@Kitenite Kitenite merged commit b2f740e into main Apr 30, 2026
7 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch

Thank you for your contribution! 🎉

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