Repository navigation
How Settings Work
Settings live in ~/.nibtree/: one file for preferences, one for app state. For the user side, see Settings and Settings Files. Every key with its default and range is in Settings Reference.
People expect a desktop app to remember their theme, fonts, layout and folders, and power users want to edit or copy settings by hand. Browser storage in the web view would be invisible; a plain JSON file in a known place is honest and easy to fix. A hand-edited file can contain anything, so every value is checked on load.
config.rs only knows two names, settings and state, in its FILES list. Any other name is an error, so the frontend can never read or write other paths (a test tries ../secrets).
sequenceDiagram
participant S as settings store
participant API as api.ts
participant CMD as commands/config.rs
participant CFG as config.rs
participant FS as config folder
S->>API: loadConfig("settings"), loadConfig("state"), each on its own
API->>CMD: invoke load_config
CMD->>CFG: load_in(dir, name)
CFG->>FS: read file
CFG-->>S: JSON, or null when missing or empty, or an error for bad JSON
Note over S: validate every value
S->>API: saveConfig(name, value), 200 ms after the last change
API->>CMD: invoke save_config
CMD->>CFG: save_in(dir, name, value)
CFG->>FS: write .settings.json.tmp, then rename
load_in returns None for a missing or empty file and AppError::Invalid for invalid JSON. save_in writes a temporary file and renames it, so a crash never leaves half a file.
src/lib/stores/settings.svelte.ts exports settings, a SettingsStore with one $state field per value ($state.raw for the mcpTools record). The pure load, validate and save decisions live in src/lib/stores/settingsData.ts, with defaultPreferences for every preference.
stateDiagram-v2
[*] --> Loading: init()
Loading --> Migrating: both files missing
Loading --> Validating: files read
Loading --> ParseError: a file is invalid JSON
Migrating --> Ready: legacy browser storage copied when present
Validating --> Ready: bad values replaced by defaults
ParseError --> Ready: defaults for that file only, loadError or stateLoadError shown
Ready --> Saving: change, save()
Saving --> Ready: after 200 ms, flush writes the files that loaded
Ready --> Validating: Try Again rereads settings.json
Ready --> Validating: Try Again rereads state.json
Validation lives in the pure functions parsePreferences and parseState:
- Pickers per type:
pickBoolean,pickNumber(clamped),pickInteger,pickTenths,roundToandpickOneOffor enums. - Special values have their own:
normalizeFontFamily(nothing that could break out of the CSS value),pickThemeId,parseMcpPort,pickToolStatesandclampTerminalScrollback. - Unknown keys are kept and written back, so hand-added keys and keys from newer versions survive.
init() loads each file on its own; an invalid one falls back to defaults alone and sets loadError or stateLoadError. save() debounces writes by 200 ms, and flush() writes only the files writableConfigs allows.
setPreference(key, value) sets the field, calls applyAppearance() and saves. applyAppearance() pushes the look into the document: the color theme (applyColorTheme, see How Color Themes Work), --ui-size, --code-size, --code-line-height, --font-mono, data-ligatures and data-rounded-panels (see How Rounded Panels Work). data-theme is now always set, also for System, which is watched with matchMedia (systemDark, colorMode).
Settings that need the backend or a running service are applied by $effects in App.svelte once settings have loaded:
-
gitConsolecallsgitConsoleSetEnabled(see How the Git Console Works). -
mcpEnabled,cliEnabled,mcpPortandmcpToolsgo tomcpStore.configure, and the memory log keys tomemoryLog.configure, only in the main window, since agit mergetoolwindow would compete for the port. -
renderWhitespacegoes tosetRenderWhitespaceineditor/whitespace.ts, which updates open editors.
Others are read where they are used, such as terminal/options.ts.
SettingsDialog.svelte lists the sections of SETTINGS_SECTIONS: Appearance, Editor, Git (id merge), Layout, Terminal, GitHub, Automation, Updates, Settings Files and About. settings.openDialog(section) picks the start: Editor from the status bar's Spaces item, Terminal from Default Shell..., Automation from the MCP tools dialog, About from Nibtree > About, any section from the MCP tool open_settings.
Terminal loads the shell list when first shown; Automation refreshes the server status. Changed from defaults lists changedPreferenceKeys(), which compares records such as mcpTools by content.
| File | What it does |
|---|---|
src-tauri/src/config.rs, commands/config.rs
|
Allowed files, load_in, atomic save_in; the commands |
src/lib/stores/settings.svelte.ts |
The store: load errors, debounced save, migration, openDialog, appearance |
src/lib/stores/settingsData.ts |
Pure validation, JSON shape, what may be saved, session steps |
src/lib/views/SettingsDialog.svelte |
The dialog, Reset, Try Again, state.json banner |
src/lib/views/settings/SettingsSectionView.svelte, *Settings.svelte
|
One file per section |
src/lib/views/settings/ColorThemePicker.svelte |
The color theme lists |
src/lib/views/settings/settingsSearch.ts |
Search in Settings |
src/lib/App.svelte |
Effects that pass settings to the backend |
UI library colors.css
|
Built-in colors |
Two files, split by owner. settings.json is what users edit; state.json is what the app remembers (recent folders, session, panel sizes, skippedVersion). Mixing them would make hand edits risky.
Validate on load, not on use. Every value is correct once init() returns, so no component needs guards.
Never overwrite a file that failed to parse. Resetting it would silently destroy settings or history. The guard covers both files.
Migrate once from browser storage. Settings used to live in localStorage under git-merger:settings. shouldMigrateLegacy copies them only on a real first run.
Section ids outlive labels. "Merge and Log" became Git when commit and console settings joined it, but its id stays merge, so code, screenshots and the MCP tool did not change.
Editor fonts sat under Appearance.
- The issue: editor font family and size were in the Appearance tab.
- Why it happened: the first layout grouped everything visual.
- The fix and why we chose it: the user asked for them in the Editor tab.
The Settings dialog could not be moved.
- The issue: the dialog was fixed in the middle of the window.
- Why it happened: it was a plain centered modal.
- The fix and why we chose it: it became draggable by its title areas, clamped so it can always be grabbed again; a double-click re-centers it.
A typo in one settings file wiped the other.
- The issue: when settings.json or state.json was not valid JSON, both fell back to defaults, and the next save rewrote state.json, losing recent folders and panel sizes.
-
Why it happened:
init()loaded both files with a singleloadError, andflush()always wrote state.json. -
The fix and why we chose it: each file loads on its own, and
writableConfigsnever writes a file that failed to parse. A toast, a banner (Try Again, Reset) and a Welcome note explain it; the decisions moved into testedsettingsData.ts.
Settings hints did not match what the app does.
- The issue: the Current line blame hint said a click copies the hash, the Ignore whitespace hint said you could toggle it per file, and "Nothing yet" never showed.
- Why it happened: the blame click later started opening the Log, the merge tool toggle writes the global setting, and the list looped over every key.
- The fix and why we chose it: the hints now say what the app does, and the list loops over the changed keys. Honest hints are cheaper than surprised users.
The Show all branches hint said git log --all.
-
The issue: the hint promised the Log works like
git log --all, which also follows tags and stashes. -
Why it happened:
log::pagepushes HEAD,refs/headsandrefs/remotesonly, and the hint was written loosely. -
The fix and why we chose it: the hint (and the MCP
git_logdescription) now saygit log --branches --remotesand that tags and stashes are not followed. Walking stashes would fill the graph with stash commits, so the hint changed, not the walk.
-
src-tauri/src/config.rs:missing_files_load_as_none_and_the_folder_is_created_on_save,files_are_pretty_printed_and_kept_separate,unknown_names_and_bad_json_are_errors. -
src/lib/stores/settingsData.test.ts: every validator,parseState,stateToJson,writableConfigs,shouldMigrateLegacy,sessionStepsandchangedPreferenceKeys. -
src/lib/stores/fontFamily.test.tsandsrc/lib/editor/wheelZoom.test.ts.
- Adding a setting: follow the checklist in Settings Reference, and describe it in Settings or Terminal, GitHub and Automation Settings.
- Update this page when
config.rschanges or a section is added, and retake thesettings-*.pngscreenshots anddark-theme.pngwhen a section changes. - Related: How Updates Work, Frontend.
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