feat(ui): add optional Nerd Font file icons#474
Conversation
Greptile SummaryThis PR adds optional Nerd Font file and folder icons (disabled by default) behind a
Confidence Score: 4/5Safe to merge; the feature is disabled by default so no existing behaviour changes without opt-in. The implementation is clean and well-tested. The only notable issues are a dead src/ui/lib/fileIcons.ts (dead .d.ts entry) and src/ui/AppHost.interactions.test.tsx (hardcoded menu navigation count). Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["Config file\n(file_icons / nerd_font_icons)"] --> C[resolveConfiguredCliInput]
B["CLI flags\n(--file-icons / --nerd-font-icons)"] --> C
C --> D[loadAppBootstrap\ninitialNerdFontIcons]
D --> E["App.tsx\nuseState(nerdFontIcons)"]
E --> F[toggleNerdFontIcons]
F --> E
E --> G[buildAppMenus\n'File icons' toggle]
E --> H[SidebarPane\nnerdFontIcons prop]
E --> I[DiffPane\nnerdFontIcons prop]
H --> J[FileGroupHeader\niconForFile label, isDir=true]
H --> K[FileListItem\niconForFile entry.name]
I --> L[DiffSection\nnerdFontIcons prop]
L --> M[DiffFileHeaderRow\niconForFile file.path]
J & K & M --> N["fileIcons.ts\niconForFile()\nICON_BY_NAME → ICON_BY_EXT → default"]
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A["Config file\n(file_icons / nerd_font_icons)"] --> C[resolveConfiguredCliInput]
B["CLI flags\n(--file-icons / --nerd-font-icons)"] --> C
C --> D[loadAppBootstrap\ninitialNerdFontIcons]
D --> E["App.tsx\nuseState(nerdFontIcons)"]
E --> F[toggleNerdFontIcons]
F --> E
E --> G[buildAppMenus\n'File icons' toggle]
E --> H[SidebarPane\nnerdFontIcons prop]
E --> I[DiffPane\nnerdFontIcons prop]
H --> J[FileGroupHeader\niconForFile label, isDir=true]
H --> K[FileListItem\niconForFile entry.name]
I --> L[DiffSection\nnerdFontIcons prop]
L --> M[DiffFileHeaderRow\niconForFile file.path]
J & K & M --> N["fileIcons.ts\niconForFile()\nICON_BY_NAME → ICON_BY_EXT → default"]
Prompt To Fix All With AIFix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
src/ui/lib/fileIcons.ts:308
The `.d.ts` entry in `ICON_BY_EXT` is unreachable. Node's `extname("foo.d.ts")` returns `.ts` (only the last segment), so this key will never be matched — `.d.ts` declaration files fall through to the `.ts` entry instead, which uses a different colour (`#3178c6` vs the intended `#0188d1`). The same limitation exists in the upstream lazygit source, so this is an accepted trade-off, but the dead entry is misleading. Removing it would make the table self-consistent, or a comment could document why it can't be reached via `extname`.
```suggestion
// NOTE: extname("foo.d.ts") returns ".ts", so this key is unreachable via the
// iconForFile extension lookup; .d.ts files fall through to the ".ts" entry.
// ".d.ts": { icon: "\ue628", color: "#0188d1" },
```
### Issue 2 of 2
src/ui/AppHost.interactions.test.tsx:1435-1440
**Hardcoded navigation count is fragile.** The loop presses ↓ exactly 4 times to land the cursor on "File icons". Because `pressEnter` activates whichever item is highlighted, adding any checkable item above "File icons" in the View menu would silently toggle the wrong item and cause the final `iconForFile("alpha.ts").icon` assertion to fail with a confusing message. Consider asserting the highlighted label immediately before pressing Enter so the failure site is clear.
Reviews (1): Last reviewed commit: "fix(ui): resolve Gemfile file icon names" | Re-trigger Greptile |
Summary
This adds optional Nerd Font file and folder icons to Hunk's review UI. When enabled, the sidebar, folder groups, and diff file headers show familiar file-type icons next to paths, making large reviews easier to scan at a glance.
The feature is intentionally off by default so existing terminals and layouts keep rendering exactly as they do today. Users who have a Nerd Font installed can enable it from config, from the CLI, or temporarily while Hunk is running.
How to enable it
file_icons = trueornerd_font_icons = trueto Hunk config--file-icons/--nerd-font-iconson the command lineView > File iconsfrom the menu bar during a sessionBehavior
Git status markers stay unchanged. A modified TypeScript file still starts with
M; the Nerd Font icon is rendered as a separate visual cue between the status marker and the filename.Folder groups use folder icons, while files use lazygit's MIT-licensed Nerd Font icon definitions and lookup order: exact filename first, extension second, then the generic folder/file fallback.
Demo
2026.06.22.22.11.20.mp4
Testing
bun run format:checkbun run typecheckbun test src/core/cli.test.ts src/core/config.test.ts src/ui/components/ui-components.test.tsxbun test src/ui/AppHost.interactions.test.tsx -t "View menu can toggle Nerd Font file icons"bun test src/ui/lib/ui-lib.test.ts -t "iconForFile resolves exact basename"bun run test:tty-smokebun run src/main.tsx -- diff <before> <after>