Skip to content

Add list/tree view toggle to the Git tab's Changes list - #98

Merged
abasiri merged 2 commits into
doctly:mainfrom
vivi7:feat/git-changes-tree-view
Oct 1, 2026
Merged

abasiri merged 2 commits into
doctly:mainfrom
vivi7:feat/git-changes-tree-view

Conversation

@vivi7

@vivi7 vivi7 commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a List / Tree switch to the Changes section of a project's Git tab. List is today's flat view and stays the default. Tree groups the changed files into collapsible folders, like View as Tree in VS Code / Cursor Source Control.

Screenshot 2026-09-26 at 22 39 32

Why

With many changes under a few deep directories (e.g. 30+ untracked files under backend/docs/… and backend/script/…), the flat list repeats the same long prefix on every row and it's hard to see where the changes are. A tree makes the shape of the change obvious and lets you fold away folders you've already looked at.

What it does

  • A small segmented control (list / tree icons) in the Changes heading, next to the "N files" count. It uses the same look as the existing .pane-seg control.
  • The choice is saved in localStorage (gitChangesView) as one global preference, like the diff mode in the file panel. Unknown values, or storage that throws, fall back to list.
  • The tree is built inside each status group (Conflicted, Modified, Added, …), the way VS Code keeps its SCM groups and builds a tree inside each one.
  • Folders come before files, sorted naturally and case-insensitively. Chains of folders with a single child collapse into one row (backend/docs/ai-engine), like VS Code's compact folders.
  • Folder rows show a chevron, a folder icon and a file count. Clicking one expands or collapses it (aria-expanded), and keyboard focus stays on that folder after the list re-renders. Collapse state lives in memory, per repository.
  • File rows in tree mode are a compact single line: status code + file name. The full path (and old → new for renames) plus the staged/unstaged/untracked label move into the tooltip.
  • Switching view or folding a folder repaints only the file list. The selected file and its diff pane are left alone: no flash, and no second diff fetch while one is still loading.

Implementation notes

  • public/project-git-view.js:
    • pure helpers buildGitChangeTree() and gitTreeRows(), easy to unit-test;
    • createGitFileRow(), shared by both modes, and createGitFolderRow();
    • paintGitChanges() takes { repaintDiff }, so a view switch or folder toggle skips the diff pane;
    • the toggle wiring lives in paintGitRepository().
  • public/projects-view.js: new PICONS.tree icon (Lucide list-tree).
  • public/style.css: .git-view-toggle / .git-view-btn, .git-folder-row, and .git-file-row.tree. Indentation uses a --depth custom property.
  • No changes to main-process code or IPC: the tree is built entirely from the changes array the renderer already receives.

Testing

  • New test/project-git-view.test.js (11 tests), which loads the real public/project-git-view.js in a vm context. It covers:
    • nesting and folders-first natural sort;
    • folder compaction, and cases that must not compact;
    • root-level files and renames;
    • collapsed folders hiding their descendants, depth values and file counts;
    • stored-mode fallbacks, including a throwing localStorage;
    • list vs tree render dispatch;
    • a folder click leaving the diff pane untouched and restoring focus.
  • npm test: 429/429 pass.
  • Manual check in Chromium against the real style.css and project-git-view.js, with a 33-file fixture:
    • both modes, collapse/expand, persistence across reload;
    • keyboard (Tab / Enter / Space);
    • the narrow single-column layout;
    • a file named <b>x-inject-test.sh renders as literal text;
    • no console errors.

A List/Tree switch in the Changes heading. List keeps today's flat view
and stays the default; Tree groups each status group's files into
collapsible folders, with single-child folder chains compacted into one
row (like VS Code's Source Control "View as Tree").

The choice is remembered in localStorage. Switching views or folding a
folder repaints only the file list, leaving the selection and diff pane
untouched, and keeps keyboard focus on the toggled folder.
@abasiri

abasiri commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Thanks, @vivi7! This is a nice improvement. It's small and self-contained, escapes paths correctly, and the tests are thorough. We're happy to take it.

You're also welcome to make Tree the default view instead of List.

A few small things, none of them blocking:

  1. File names containing a backslash. Tree rows show pathBasename(change.path) (project-git-view.js:235), which splits on both / and \. On macOS/Linux, a file named docs/a\b.md is placed correctly but shown as b.md. Showing the tree node's own name (row.name) fixes it. The test's pathBasename stub only splits on /, so the test doesn't catch this.
  2. Staged / Unstaged only shows on hover in tree mode. It moved into the tooltip, so you can't see at a glance which files are staged. A small marker on the row would help, especially if Tree becomes the default.
  3. Collapsing the selected file's folder. The diff stays open but no row is highlighted. Consider highlighting the folder row that contains the selected file.

— Claude, on behalf of Ali

… visible

- Tree is now the default view; a stored 'list' choice is kept.
- Tree rows show the tree node's own name, so a file name containing a backslash isn't cut at it.
- Staged / Unstaged / Staged + unstaged shows on each tree row instead of only in the tooltip.
- Collapsing the folder that holds the selected file highlights that folder row.
@vivi7

vivi7 commented Sep 30, 2026

Copy link
Copy Markdown
Contributor Author

Thanks! All three are addressed, and Tree is now the default (a stored List choice is kept): tree rows use the node's own name, staged/unstaged shows on each row, and a collapsed folder holding the selected file is highlighted. Tests cover each case, including a backslash name with the real pathBasename behaviour.

@abasiri
abasiri merged commit c380d18 into doctly:main Oct 1, 2026
@abasiri

abasiri commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Thank you

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.

2 participants