Repository navigation
How Workspaces Work
A workspace is one or more folders opened together (a multi-root workspace), each holding any number of git repositories (nested too) or none. The user side is in Workspaces.
Many people keep several projects in one folder, so, we open any folder and find every repository inside:
- The backend finds repositories. Scanning runs in Rust; only the list of roots crosses the bridge.
-
The UI uses absolute paths. Two folders can both contain
src/index.ts, so we convert to repo-relative paths only for git. - One repository is active for branches, the Log and pull or push, while statuses are kept for all.
Opening a folder gets a WorkspaceInfo (root, name, repositories); the store then reads every status and starts one watcher per folder.
sequenceDiagram
participant UI as Welcome or Header
participant Store as repoStore
participant Card as OpeningProgress.svelte
participant Cmd as commands/workspace.rs
participant Git as git/workspace.rs
UI->>Store: openFolders(folderPaths)
Store->>Store: confirmDiscardAll (unsaved tabs)
Store->>Card: opening = { title, step }
loop each folder
Store->>Cmd: open_workspace (async)
Cmd->>Git: open(folder_path)
Git-->>Store: root, name, repos
end
Store->>Card: opening = null
Store->>Store: unionRepos, pickActive
Store->>Store: loadAllChanges, loadingChanges = { done, total }
Store->>Cmd: watch_workspace(root, repoRoots) per folder (async)
Finding repositories in a big folder can take seconds. So openFolders and addFolder set repoStore.opening to { title, step } ("Opening acme" and "Looking for repositories...", per folder for a workspace, "Adding X" for Add Folder). OpeningProgress.svelte shows it as a card over a dimmed window, but only after 150 ms so a quick open does not flash, and after 4 seconds adds a hint that folders such as node_modules are skipped.
Once the repositories are known the workspace is on screen, and loadAllChanges reads every status in parallel. It keeps loadingChanges = { done, total }, which the status bar shows as "Reading changes 2 of 5" (see How the status bar works). The texts come from stores/openingProgress.ts.
find_repos combines enclosing_repo (Repository::discover, for a folder inside a bigger repository) with scan, a breadth-first search limited by MAX_SCAN_DEPTH (6) and MAX_SCAN_DIRS (50,000) that skips SKIPPED_DIRS such as node_modules and never follows symlinks.
workspace is an OpenWorkspace (id, name, root, folders, optional linked file); each WorkspaceFolder keeps its repoRoots. close() and openFolders first ask confirmDiscardAll (the Unsaved Changes dialog of closeTabs). closeWorkspace(), behind Close Folder, also records an empty session.
watcher::watch puts one debounced recursive watcher on each folder. It sends repo-changed (the store refreshes that repository) and workspace-changed (the Files panel reloads, and with reposChanged the store rescans quietly). How each file event is sorted is in How folder watching works.
Workspace files (.nibtree-workspace and VS Code .code-workspace) and which folders reopen at start are in How workspace files work.
The activity bars are the icon strips at each edge. Two header buttons left of the theme button, and View > Left Activity Bar and Right Activity Bar, call settings.toggleActivityBar(side), which flips leftBarVisible or rightBarVisible (default true) in state.json. Workspace.svelte leaves a hidden bar out, and ACTIVITY_BARS counts 44 px per visible bar in the panel width math. The button icon, views/LayoutToggleIcon.svelte, is a small window with that side filled while the bar shows.
| File | What it does |
|---|---|
src-tauri/src/git/workspace.rs |
Repository scan, init, deepest_repo
|
src-tauri/src/commands/workspace.rs |
Workspace, discovery, watching and workspace file commands |
src-tauri/src/watcher.rs, src-tauri/src/state.rs
|
The watchers, see How folder watching works |
src/lib/stores/repo.svelte.ts |
openFolders, opening, loadAllChanges, closeWorkspace, rediscover, rescan
|
src/lib/views/OpeningProgress.svelte, src/lib/stores/openingProgress.ts
|
The opening card and its texts |
src/lib/stores/workspacePaths.ts |
folderFor, locateAbsolute, relativeTo, joinPath
|
src/lib/views/Header.svelte, Workspace.svelte
|
The activity bar toggles (library Button with a layout glyph) and widths |
Scan in Rust with hard limits, so a huge folder cannot freeze the app or flood IPC.
Show the window before the statuses. The repository list is enough to draw the workspace; statuses fill in with a counter, so a big workspace is usable sooner.
Rescan only on a hint. Rescanning on every change is costly in big folders, and polling costs CPU and memory.
Nested repository shown as an untracked folder.
-
The issue: the parent repository listed a nested repository as an untracked
dir/, so the same work showed twice. -
Why it happened: git reports another repository's folder as one untracked path ending in
/. -
The fix and why we chose it:
status::readand the Files panel skip it when the folder has its own.git(is_nested_repo), a cheap check.
Closing a folder threw away unsaved edits.
- The issue: Close Folder, Open Folder, a recent folder or a workspace file closed every tab, dropping unsaved edits without a question.
-
Why it happened:
close()cleared the tabs directly; onlycloseTabsasked first. -
The fix and why we chose it:
close()andopenFoldersask throughconfirmDiscardAll, the dialog of closing tabs, before loading anything, so Cancel or a failed open changes nothing. One familiar dialog for every close.
Opening a big folder froze the window.
- The issue: opening or adding a big folder froze the whole window: 0.3 to 1.1 seconds on a folder with 60,000 files, longer on bigger ones.
-
Why it happened:
watch_workspacewas notasync, so it ran on the main thread, and the watcher's default file id cache (RecommendedCache) walks every file when watching starts and keeps their paths in memory. -
The fix and why we chose it: the watcher uses
NoCache(see How folder watching works), andwatch_workspaceandunwatch_workspaceareasync, working throughblocking. The stall went from 516 ms to 9 ms; the progress card covers the rest.
A failed open showed the welcome screen with no message.
- The issue: when restoring the session or opening a folder failed, the welcome screen came up silently.
-
Why it happened:
openFolderscaught errors only aroundapi.openWorkspace. Anything that threw later, such as a bad reply, rejected a promise nobody caught. -
The fix and why we chose it:
openFoldersnever throws: any error is a toast from the pureopenFailure(openingProgress.ts) naming the folder and the error. One catch in the store covers every caller.
Option+Cmd+B did not match.
-
The issue: a check on
event.keynever fired, so the Files panel did not toggle. -
Why it happened: on macOS, Option changes the typed character, so
event.keyis notb. -
The fix and why we chose it:
workspaceShortcuts.tsmatchesevent.code === "KeyB", the physical key, a project rule for every Option shortcut.
-
src-tauri/src/commands/tests.rs: theopen_workspace_*andinit_repository_*tests. -
src-tauri/src/watcher.rs: see How folder watching works. -
src/lib/stores/workspacePaths.test.tsandopeningProgress.test.ts.
The opening card, live watchers and the unsaved-changes dialog need a check by hand with scripts/make-workspace-demo.sh. See Testing.
- Update this page when scanning, opening progress or rescans change, and How workspace files work or How folder watching works for their parts.
- Update Workspaces for visible changes.
- Retake
window-overview.png,workspace-folders.png,repository-switcher.png,init-repository.png,scan-repositories.png,unsaved-changes-close.pngandworkspace-opening.pngwhen they change. See Docs and Screenshots.
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