Repository navigation
Frontend
The UI is a Svelte 5 app written in TypeScript, in src/. It runs inside the macOS web view and talks to Rust only through src/lib/api.ts. This page covers the shell, the stores, the rules for runes, keys and menus, and the CodeMirror patterns used everywhere. The feature folders (terminal, search, Markdown, themes, MCP and more) are mapped on Frontend Modules.
SvelteKit builds the UI with @sveltejs/adapter-static and an index.html fallback, and src/routes/+layout.ts sets ssr = false. There is no server: Tauri loads the built files from build/, or from Vite on port 1420 during bun tauri dev. The only route, +page.svelte, renders src/lib/App.svelte. src/hooks.client.ts starts the dev-only IPC bridge in dev builds, and nothing in a release build (see Architecture).
On start, App.svelte:
- loads settings from
~/.nibtree(settings.init()) and starts the update check; - asks Rust for the launch mode (
api.getLaunchMode()) and builds the native menu bar (appMenu); - in mergetool mode shows
MergeToolApp; otherwise opens the folder or workspace file passed on the command line, or restores the last session (sessionSteps).
Then it shows Workspace or Welcome. The hosts for toasts, dialogs, Git dialogs and the context menu are mounted once here, with the Settings, Update, What's New and opening progress views. Small effects keep the backend in step with settings: the Git Console, the MCP server and the memory log.
State lives in small classes with rune fields, exported as singletons. Pure logic sits in plain .ts files that the stores call, so it can be tested without Svelte.
| Store | File | Owns |
|---|---|---|
repoStore |
stores/repo.svelte.ts |
the workspace and its folders, repositories, per-repository statuses, the active repository, refs, stashes, tabs, the main view, busy state |
settings |
stores/settings.svelte.ts |
preferences (settings.json) and UI state (state.json), saved 200 ms after the last change; a file that failed to parse is never overwritten |
| settings data | stores/settingsData.ts |
pure validation (parsePreferences, parseState), writableConfigs, sessionSteps
|
| tabs | stores/tabs.ts |
pure functions for tabs and the preview tab: openTab, pinTab, closeTabs
|
navigation |
stores/navigation.svelte.ts |
Back and Forward, wrapping the pure NavigationHistory in stores/navHistory.ts
|
| paths | stores/workspacePaths.ts |
pure path helpers: folderFor, locateAbsolute, relativeTo, joinPath
|
editorStatus |
stores/editorStatus.svelte.ts |
the cursor line, column, selection and language for the status bar |
fileCommands |
stores/fileCommands.svelte.ts |
Save, Revert and the Markdown view mode of each open file editor, for the File and View menus |
| pseudo tabs | stores/pseudoTabs.ts |
tabs that are not files: commits (commitTabs.ts), Git history and compare (gitTabs.ts, branchTabs.ts) and terminals in the editor area |
| opening progress | stores/openingProgress.ts |
the "Opening acme" card and the "Reading changes 2 of 5" counter |
flowchart LR
Views["Views and components"] --> Repo["repoStore"]
Views --> Nav["navigation"]
Views --> Settings["settings"]
Views --> EdStatus["editorStatus"]
Repo --> Tabs["tabs.ts (pure)"]
Repo --> Settings
Repo --> UI["dialogs, toast"]
Nav --> NavHistory["navHistory.ts (pure)"]
Nav --> Repo
Views --> Paths["workspacePaths.ts (pure)"]
Repo --> Api["api.ts"]
Settings --> Api
Api --> Rust["Tauri commands and events"]
Every mutation goes through repoStore.run(label, work, { success, repoPath }). It sets busy to the label, runs work(repoPath), shows a success toast or an error toast ("<label> failed"), and then refreshes that repository. Views never handle these steps themselves, so they cannot forget one.
runOp(label, work, successMessage, repoPath) is the same for operations that can stop on conflicts (merge, rebase, pull, cherry-pick, revert, stash apply and pop). When the result says conflicts, it switches to that repository and opens the conflicts dialog.
await repoStore.run("Stage", (repoPath) => api.stageFiles(repoPath, filePaths), { repoPath: repoRoot });Refreshes are coalesced by key. refreshRepo rereads status, and for the active repository also refs and stashes, and bumps historyVersion so the Log reloads.
-
Runes only:
$state,$derived,$effect,$props. Noexport let, no stores fromsvelte/store. -
Event attributes are
onclick={...}style, noton:click. -
$state.rawfor big data that is replaced, not mutated:repos,statuses,tabs,editorStatus.info. A plain$statewould wrap every item in a deep proxy, which costs memory and time for arrays that are thrown away on the next refresh. Replace them with a new value (this.statuses = { ...this.statuses, [repoRoot]: status }). -
$effectcleanup destroys what it creates, such as CodeMirror views, timers and listeners.
api.ts has one typed wrapper per Tauri command and a listener per event (onRepoChanged, onWorkspaceChanged, onGitProgress, onTerminalExited, onGitCommand, onMcpUiRequest, onMcpActivity). Streaming commands take a Channel from @tauri-apps/api/core. types.ts mirrors every Rust DTO in camelCase. Keep both in step with Rust in the same change: a renamed field that only one side knows about arrives as undefined and fails silently. Views never call invoke directly.
Every text pane is a CodeMirror 6 view, set up in src/lib/editor/setup.ts; languageFor(path) loads a grammar only when a file needs it. The rules for state fields, compartments, widgets, keymaps and update listeners are on CodeMirror Patterns.
-
dialogs.confirm,dialogs.prompt,dialogs.chooseanddialogs.pick(a filterable list) inui/dialog.svelte.tsreturn promises;DialogHost.sveltedraws them with the UI library'sDialog. Destructive actions always ask first withdanger: true. The Git menu's bigger dialogs (Push, Pull, Reset HEAD, Clone...) go throughviews/git/gitDialogs.svelte.ts, one at a time. -
toast.info,toast.successandtoast.error(ui/toast.svelte.ts) show short notes; errors stay longer. -
contextMenu.open(event, items)(ui/menu.svelte.ts) shows the custom right-click menu.
The menu bar is plain data in menu/menuSpec.ts, turned into Tauri menus by menu/appMenu.svelte.ts and run by menu/menuActions.ts. macOS gives a key to the web view first and passes it to the menu only when the page leaves it unhandled. So a key the page handles (the editor keymaps in editor/editorShortcuts.ts, the window shortcuts in views/workspaceShortcuts.ts) never also runs its menu item, and a key only the menu knows still reaches it. Both routes call the same functions (views/workspaceActions.ts, views/gitActions.ts). Help > Keyboard Shortcuts reads its menu rows from the same menuSpec (help/shortcuts.ts), so those cannot drift; the other rows are written by hand. See How the Menus Work and Menu Keys and Routing.
Window-level key handlers must skip when dialogs.active is set or event.defaultPrevented is true, or a key would be handled twice. Shortcuts with Option must match event.code, because on macOS Option changes the typed character in event.key.
All colors are CSS tokens in the UI library's styles/colors.css (generated from ui/design-tokens/colors.json): --bg, --panel, --text, --accent, --danger, the --diff-*, --tok-* and terminal families, and more. The Nibtree Light and Dark themes are the sets in colors.css; any other color theme is one generated <style> element (themes/apply.ts) with values from the lazy catalog. settings.svelte.ts sets data-theme and data-color-theme on <html>, and code that copies colors out of CSS (xterm, mermaid) rereads them through themes/watch.ts. Components never hard-code a color. A new token goes in colors.json (then bun run ui:tokens) and the UI library's themes/colorTokens.ts and themes/derive.ts. The page and code rules (scrollbars, selectable text, syntax colors) are the library's styles/base.css and styles/code.css. See How Color Themes Work.
Svelte a11y warnings. bun run check must end with 0 warnings, and svelte-check flagged a few elements that are correct on purpose: the resize handle (a focusable role="separator", the ARIA pattern for a splitter), interactive rows and drag handles, and the release notes box that catches clicks on its links. Buttons would have broken their layout and focus. So each got a <!-- svelte-ignore ... --> comment naming the exact warning, directly above that element. Never ignore a warning for a whole file.
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