Repository navigation
How File Icons Work
This chapter explains how the three levels of file icons are loaded, matched and drawn, and how switching levels keeps only one icon set in memory. The user side is in File Icons.
Code editors show an icon for each file type, and the user asked for the same in the Files panel and the file lists. Memory is a feature of Nibtree (see Architecture), so the icons had to cost nothing when they are off, and only one set may be in memory at a time. That is why the setting has three levels instead of a switch: No icons, Minimal (simple shapes colored by theme tokens) and Material Icons (the colored icons of Material Icon Theme).
A JavaScript module that the app imports stays in the module cache until the app restarts. So neither icon set is code. Each set is a folder in static/file-icons/, served next to the app:
| Folder | Files |
|---|---|
minimal/ |
map.json (file type map), minimal.css (one rule per glyph and color), one SVG per glyph |
material/ |
map.json, about 630 SVGs, the MIT LICENSE of Material Icon Theme |
The only icon code in the app bundle is the matcher iconMatch.ts and the store fileIcons.svelte.ts, a few hundred bytes.
flowchart LR
S["settings.fileIcons"] --> E["App.svelte effect"]
E --> F["setFileIcons(mode, light)"]
F -->|off| N["drop map, remove stylesheet, resolve = null"]
F -->|minimal| M["fetch minimal/map.json + link minimal.css"]
F -->|material| A["fetch material/map.json, remove minimal.css"]
M --> R["fileIcons.resolve"]
A --> R
R --> C["FileTypeIcon.svelte in each row"]
setFileIcons(mode, light) in src/lib/fileIcons/fileIcons.svelte.ts runs whenever the setting or the light or dark mode changes:
-
off: forgets the map, removes the
<link id="gm-file-icons-minimal">stylesheet and setsfileIcons.resolveto null. -
minimal or material: if another set was loaded, it is forgotten first, so two sets are never in memory together. Then the map is fetched with
cache: "no-store", so the resolver is the only thing holding it, andminimal.cssis linked or removed. - A theme change keeps the loaded map and only builds a new resolver, because Material has light variants.
- A
generationcounter drops the result of a slow fetch when the user already picked another level.
fileIcons.resolve is $state.raw, so every row redraws when it changes and nothing else is tracked.
iconFor(map, fileName, light) in iconMatch.ts follows the icon theme's own rules: the whole name in lowercase first (names, or lightNames in a light theme), then every extension from the longest (app.spec.ts tries spec.ts, then ts), and map.file when nothing matches. Lookups use Object.hasOwn, so a file called constructor never reads Object.prototype. normalizeIconMap defaults every part of a fetched map, so a damaged file gives plain icons instead of errors.
FileTypeIcon.svelte takes a file name or path and a plain flag:
-
Minimal: one empty
<span class="file-type-icon fi-g-code fi-t-blue">.minimal.csssets the glyph SVG as a CSS mask and the color asbackground-color: var(--term-blue). The colors are tokens from the UI library'sstyles/colors.css, so the icons follow every color theme. A long list holds no SVG nodes, and the web view decodes each glyph once. -
Material Icons: one
<img src="/file-icons/material/php.svg">. The web view loads a picture the first time a row shows it and shares it between all rows of that type. -
No icons: the Files panel (
plain) shows the old plainfileicon; the Changes list and commit file lists show nothing, as before.
Icon names are checked against [a-z0-9_.-] before they go into a URL or a class.
scripts/file-icons.ts writes both folders:
- Minimal comes from
src/lib/fileIcons/minimalSource.ts: the glyph paths, the tones (each one a color token) and the type tables. Only the script and the tests import it. - Material comes from the
material-icon-themepackage (a dev dependency). The script keeps the file extension and file name tables in lowercase and copies only the SVGs they use.
The output is committed, so a build needs nothing from node_modules. bun scripts/file-icons.ts --check fails when the folders are out of date; CI runs it.
The CSP in tauri.conf.json has 'self' in connect-src so the app may fetch its own map.json files.
| File | What it does |
|---|---|
src/lib/fileIcons/fileIcons.svelte.ts |
setFileIcons, the store, fetching and dropping sets |
src/lib/fileIcons/iconMatch.ts |
iconFor, normalizeIconMap
|
src/lib/fileIcons/FileTypeIcon.svelte |
One row's icon |
src/lib/fileIcons/minimalSource.ts |
The Minimal set as source (build time only) |
scripts/file-icons.ts |
Writes and checks static/file-icons/
|
src/lib/stores/settingsData.ts |
fileIcons, FILE_ICON_CHOICES
|
src/lib/menu/menuSpec.ts, menuState.ts, menuActions.ts
|
View > File Icons |
src/lib/views/files/FileExplorer.svelte, views/changes/FileRow.svelte, log/CommitDetails.svelte
|
The lists that show icons |
Three levels, off by default. Material Icons is the nicest but the largest. Minimal gives most of the help (code, data, images, lock files at a glance) for a fraction of it, and No icons keeps the old cost of zero.
Fetch, not import. An imported module cannot be unloaded. A fetched map is ordinary data that the garbage collector frees when the resolver is replaced, and a removed stylesheet frees its rules.
Material Icon Theme for the real icons. It is MIT licensed, covers over 1,000 types and ships a type map we can reuse. Other icon sets are not published with such a map.
Masks for Minimal, images for Material. Minimal glyphs are one color, so a mask lets theme tokens color them. Material icons have their own colors, so they are plain images.
-
iconMatch.test.ts: name before extension, the longest extension, light variants, fallbacks, prototype keys, damaged maps, the common Material and Minimal types, and that every Material icon in the map has its SVG. -
minimalSource.test.ts: the committedminimal/folder matches the source, every map value is a known glyph and tone, and every tone is a token incolors.css. -
settingsData.test.tsandmenuState.test.ts: the setting and the View menu ticks.
The memory of each level was measured in the real app with nibtree cli memory and run_menu_command on a repository with 2,400 changed files, three rounds of No icons, Minimal and Material Icons: Minimal added 2 to 10 MB of web content memory and Material Icons 10 to 15 MB more. Most of it is the extra row element per file in the Changes list and the decoded pictures. See How Memory Is Measured.
- New Minimal type or glyph: edit
minimalSource.ts, runbun scripts/file-icons.ts, commitstatic/file-icons/minimal. - Newer Material Icon Theme: update the dev dependency, run the script, commit
static/file-icons/material. - A new list that shows files: use
<FileTypeIcon fileName={...} plain={false} />.
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