Repository navigation
How the Changes Tab Works
Clicking N changes in the status bar opens a Changes editor tab for that repository: every uncommitted file on the left, and the selected file's diff against the last commit on the right. For the user side, see Status Bar and Help.
The Changes sidebar splits files into Staged and Changes and shows one side at a time, which is right for building a commit. Sometimes you just want to read through everything you changed since the last commit, in a big view. The status bar count is the natural door to that view, and before this tab it only switched the sidebar, which did nothing visible when the sidebar was already open.
The tab is a pseudo tab, like the branch compare tabs (see How the Branches Popup Works). branchTabPath({ kind: "changes", repoRoot }) in stores/branchTabs.ts makes branches-changes:<repo>, and BranchTab.svelte renders ChangesTab.svelte for it. Reusing the branch tab kind gives the tab title, closing with its workspace folder and the Auto repository follow for free.
flowchart LR
SB["Status bar: N changes"] -->|"openPseudoTab"| T["Changes tab"]
RS["repoStore.statuses[repoRoot]"] --> L["changesTabFiles"]
L --> T
T -->|"selected file"| C["compare_with_revision<br/>(HEAD, old name for renames)"]
C --> D["DiffView, read-only"]
The list comes from the repository's status, which the store already keeps fresh, so the tab costs no extra git call and its count always matches the status bar. changesTabFiles in views/git/changesTab.ts (pure, tested) turns each FileStatus into one row:
-
changeAgainstHeadpicks one letter for both areas, since the tab compares HEAD with the work tree: a deletion wins, then untracked, then a staged add or rename, then the work tree kind. A conflicted file has no kind and shows C in red. - Conflicts come first, then the status order.
- Only renames keep
origPath, so the diff reads the old name from HEAD.
pickSelected keeps the selected file while it is still changed, else falls back to the first. stepSelection moves with Up and Down and with the toolbar's previous and next buttons. Enter or a double-click opens the file in the editor.
Each row keeps its FileStatus (status), so the tab can act on the index like the Changes sidebar. Three pure helpers in changesTab.ts decide what to offer:
-
fileActionsgives a row's hover buttons: Resolve in merge tool for a conflict, Unstage when the file has staged changes, and Discard changes and Stage when it has unstaged ones. A partly staged file gets all three. A submodule gets no Discard, as in the sidebar. -
stagedStateadds a small staged or partly staged tag, since one list mixes both areas and the letter compares with HEAD. -
bulkTargetscollects the files for Unstage all, Discard all and Stage all in the list header. Conflicts are left out; they belong to the merge tool.
The buttons and the right-click menu (Stage, Unstage, Discard Changes..., Open File, Shelve Changes..., Add to .gitignore, Copy Path) call the sidebar's own stage, unstage and discard from views/changes/mutations.ts. So the confirmation dialog, the busy state and the status refresh are the same in both places, and the list updates from the new status.
flowchart LR
R["Row or list header button"] --> F["fileActions / bulkTargets"]
F --> M["mutations.ts: stage, unstage, discard"]
M -->|"repoStore.run, refresh status"| S["repoStore.statuses"]
S --> L["changesTabFiles"]
Below the list sits the sidebar's own CommitBox.svelte, with multiRepo off. Its staged count is bulkTargets(files).unstage, the files with staged changes, and its conflict count the conflicted rows, so it enables Commit exactly when the sidebar would. The message lives in commitDraft, keyed by repository, so the tab and the sidebar edit one draft. Hiding the list hides the box too.
The list's width is changesListWidth in state.json (default 320 px, at least 160) and whether it shows is changesListVisible, both on settings and saved like the terminal list width. A ResizeHandle sits between the list and the diff; it writes the width while dragging and saves on release. changesListBounds (pure, tested) caps the width so the diff keeps MIN_CHANGES_DIFF_WIDTH (240 px) in a narrow tab; the saved width stays as dragged. The toolbar's layout button calls settings.toggleChangesList(). With the list hidden, the toolbar shows the selected file, its letter and "2 of 5", so you still know where you are.
compare_with_revision takes the file, HEAD and an optional orig_path. The backend (diff::against_revision) reads the old side from HEAD under orig_path when given, else under the path, and the new side from the work tree. Without orig_path a renamed file would look brand new.
A new status object, or a new repoStore.fileVersions[repoRoot] (a file edited again keeps its status), means files changed on disk, so an $effect loads the diff again. The loaded diff and any error are stored with their file path, so picking another file never shows the previous file's diff while the new one loads. Submodules, and repositories with no commits yet, show a short note instead of a diff.
| File | What it does |
|---|---|
src/lib/views/git/ChangesTab.svelte |
Toolbar, keys, split, diff loading |
src/lib/views/git/ChangesTabList.svelte |
File list, menu, commit box |
src/lib/views/git/changesTab.ts |
changeAgainstHead, changesTabFiles, fileActions, stagedState, bulkTargets, pickSelected, stepSelection, changesListBounds
|
src/lib/views/changes/mutations.ts |
stage, unstage, discard, shared with the Changes sidebar |
src/lib/views/changes/CommitBox.svelte |
The commit box, shared with the Changes sidebar |
src/lib/stores/settingsData.ts |
changesListWidth and changesListVisible, validated |
src/lib/stores/branchTabs.ts |
The changes kind: path, title, folder cleanup |
src/lib/views/StatusBar.svelte |
Opens the tab from the changes count |
src-tauri/src/git/diff.rs |
against_revision with the optional old name |
src-tauri/src/commands/history.rs |
compare_with_revision |
Compare with HEAD, not the index. The question the tab answers is "what did I change since the last commit", so staged and unstaged edits show as one diff.
Read-only diff, file-level staging. Hunk staging needs a side (index or work tree), and this diff compares HEAD with the work tree, so it stays read-only. Whole files can be staged, unstaged and discarded from the list, which covers the common "clean up before committing" pass without switching to the sidebar. The diff's Open File button and a double-click lead to editing.
One list, not two groups. Splitting the list into Staged and Changes would show a partly staged file twice with the same diff. One row per file with the right buttons and a staged tag keeps the list matching the diff.
Discard means unstaged changes. As in the sidebar, Discard restores the staged version, so staged work is never thrown away by accident. Unstage first to discard everything.
One list setting for every Changes tab. The width and the hidden state are a layout habit, like the sidebar width, so they live in state.json and apply to every repository's tab.
Live, not a snapshot. The list follows the status, so files you commit or discard leave the tab at once.
-
src/lib/views/git/changesTab.test.ts: one kind per file, conflicts first, old names only for renames, row actions per area (submodules without Discard), staged tags, bulk targets without conflicts, selection kept and stepped, list width bounds. -
src/lib/stores/settingsData.test.ts:changesListWidthandchangesListVisibleare validated and saved. -
src/lib/stores/branchTabs.test.ts: thechangespath round-trips, bad paths are refused, and the title. -
src-tauri/src/commands/history.rs:compare_with_revision_reads_a_renamed_file_by_its_old_name.
- Update this page when
ChangesTab.svelte,ChangesTabList.svelte,changesTab.ts,mutations.ts,CommitBox.svelteorcompare_with_revisionchange. - Update Status Bar and Help for new clicks or keys.
- Related: How the Status Bar Works, How the Branches Popup Works.
These pages are generated from docs/wiki. To correct something, open a pull request against the source file; edits made here are overwritten.
Using Nibtree
- Getting Started
- Welcome Screen
- Workspaces
- Files Panel
- File Operations
- File Icons
- Rounded Panels
- Editor and Tabs
- Pin Reorder and Wrap Tabs
- Single Tab Title
- Split Editor
- New File and Unsaved Changes
- Unload Hidden Tabs
- Editing Code
- Code Appearance
- Markdown Editor
- Image and PDF Preview
- Search Everywhere
- Recent Files
- Navigation Bar
- Find and Replace
- Changes and Commits
- Sign In Prompts
- Commit Box Layout
- Repository Actions
- Commit Options
- Ignoring Files
- Diffs
- Inline Diffs
- Staging View
- Revert a Change
- Resolving Conflicts
- Merge Tool
- Blame
- History and Log
- Merge Commits
- Branches and Tags
- Branches Popup
- Remotes
- Stashes
- Shelf
- Git Menu
- Git Dialogs
- Interactive Rebase
- Worktrees
- AI Agents
- AI Tasks
- AI Usage
- Keep Computer Awake
- Submodules
- Git LFS
- GitHub
- Git Console
- Terminal
- Terminal Features
- Scripts
- Navigation
- Menus
- Settings
- Settings Search
- Color Themes
- Settings Terminal and Automation
- Settings Files
- Updates
- Status Bar and Help
- Notifications
- Memory Use
- Clear Cache
- MCP and CLI
- Git Mergetool
- Keyboard Shortcuts
- Keyboard Shortcuts Editor
- Troubleshooting
- Troubleshooting Git
Developers
- Developer Guide
- Architecture
- Project Layout
- Backend
- How Folder Watching Works
- Folder Watching Bugs We Fixed
- How Workspace Files Work
- How the Preview Scheme Works
- Backend Services
- How Terminal Features Work
- Terminal Bugs We Fixed
- Frontend
- How the Rich Markdown Editor Works
- Rich Markdown Bugs We Fixed
- Git Menu Links
- Menu Keys and Routing
- Frontend Modules
- Blame Bugs We Fixed
- Project Color And Title Bar
- One Diff View
- Diff Bugs We Fixed
- Merge Tool Layouts
- Merge Tool Bugs We Fixed
- Color Theme Bugs We Fixed
- How the Connected Diff Works
- Rounded Panels Bugs We Fixed
- Update Bugs We Fixed
- Status Bar Bugs We Fixed
- MCP and CLI Bugs We Fixed
- Repository Actions Bugs We Fixed
- How the Changes Tab Works
- CodeMirror Patterns
- How the Cursor Works
- How Editor Features Work
- How the Path Bar Works
- Commands and Events
- Settings Reference
- Commands Workspace and Changes
- Commands Branches and Remotes
- Commands History and Extras
- Commands App and Tools
- Testing
- Test Suites
- Debugging
- Debugging with MCP
- Measuring Setting Memory
- How Terminal Memory Is Measured
- How Markdown Diagrams Work
- Releases and CI
- Publishing the Wiki
- Versioning and Changelog
- Browser Version Packages
- Browser Release Checks
- Docker Image
- Platforms and Signing
- Windows Support
- Windows Bugs We Fixed
- Windows MCP and CLI
- Docs and Screenshots
- UI Library
- UI Library MCP
- UI Library Bugs
- UI Library Migration
- Contributing
How features work
- Welcome screen
- Workspaces and folders
- Files panel
- File operations
- File icons
- Editor and tabs
- Pin, reorder and wrap tabs
- Single tab title
- Split editor
- New File and unsaved changes
- Unload hidden tabs
- Editing code
- Code appearance
- Markdown editor
- Image and PDF preview
- Search Everywhere
- Recent Files
- Navigation Bar
- Find and replace
- Changes and commits
- Sign-in prompts
- Commit box layout
- Repository actions
- Commit options
- Ignoring files
- Diffs
- Inline diffs
- Staging view
- Revert a change
- Resolving conflicts
- Merge tool
- Blame
- History and log
- Merge commits
- Commit tabs
- Branches and tags
- Branches popup
- Remotes
- Stashes
- Shelf
- Git menu
- Git dialogs
- Interactive rebase
- Worktrees
- AI agents
- AI usage
- Keep computer awake
- AI tasks
- Submodules
- Git LFS
- GitHub account
- Git Console
- Back and Forward
- Integrated terminal
- Scripts tool window
- Menu bar
- Settings
- Search in Settings
- Color themes
- Rounded panels
- Updates and release channels
- Status bar and help links
- Notifications
- Memory readout and memory log
- Clear Cache
- MCP server and command line tool
- git mergetool mode