Files
paseo/docs/file-icons.md
T
Mohamed Boudra ab8151d33f feat(app): sidebar display preferences, a shared menu engine, and a colour pass
Sidebar polish round.

Menus. `dropdown-menu` and `context-menu` were two copies of one engine that
differed only in how the trigger fires — identical from `computePosition` down —
and neither could nest. Both are now thin wrappers over
`components/ui/menu/`, which owns anchoring, a pure page-stack navigation model,
and one presentation fork: flyout on pointer, pushed pages in a sheet on compact.
All 31 call sites are unchanged. Items got a rounded inset fill, a visible
separator, and `selected` (a check) split from `active` (a fill).

Sidebar display preferences. The flat wall of four sections becomes one row per
decision with its value, and per-feature toggles over what a workspace row shows:
host, pull request, checks, scripts. Each is independent — a ticked toggle that
draws nothing is a lie. `display-preferences/` hides that grouping lives in
zustand while title source and row items are app settings.

Timestamps. "Last activity" now ticks itself. The label rounds to the minute and
never shows seconds; past a week it becomes a date. The timer lives in the leaf
`<Text>`, at most one per resolution tier shared by every row, and a tick only
re-renders when the string actually changed — so a memoized row that legitimately
never re-renders still stays current.

Colour. Full status tier regenerated at 90% of gamut max (was 85), same rule as
the muted tier, lightness and hue untouched. File icons are pulled toward neutral
in OKLab behind `getFileIconSvg` — material-icon-theme picks its palette for a
tree where the icon is the only colour on the row, and ours is not. New/deleted
files use the forge square-plus/square-minus glyphs at muted weight instead of
tinted pills. Project icons round a little more; uploaded icons never do.

The project status badge is a knockout again: it fills with the colour of the row
behind it, so the ring reads as a gap in the icon rather than a white halo.

File rows. The always-visible kebab is gone from the file explorer and diff tree
in favour of right-click, which also reclaims the reserved action slot.
2026-08-03 18:58:06 +02:00

5.6 KiB

File Icons

The file explorer uses colored SVG icons from material-icon-theme (installed as a dev dependency in packages/app).

How it works

Two files:

  • packages/app/src/components/material-file-icons.ts — the vendor table. SVG_ICONS maps icon names to SVG strings copied verbatim from the theme, EXTENSION_TO_ICON maps extensions to icon names, and getRawFileIconSvg(fileName) looks one up with a generic fallback. Keep it a faithful copy so re-copying an icon stays a mechanical edit.
  • packages/app/src/components/file-icon-svg.ts — getFileIconSvg(fileName), the only thing anyone should import. It desaturates the vendor colours and caches the result.

Render with <MaterialFileIcon fileName size /> rather than reaching for SvgXml yourself.

Why the colours are toned down

material-icon-theme picks its palette for VS Code's file tree, where the icon is the only colour on the row. Ours share rows with status dots, diff stats, and check badges, and a column of them at full chroma is the loudest thing in the panel.

Every hex in the SVG is pulled toward neutral at the same perceived lightness (desaturateHexColor in packages/app/src/utils/color.ts, OKLab — scaling HSL saturation instead would darken the yellows and barely touch the blues). Hue survives, which is the only part of an icon's colour that carries meaning at 16pt.

ICON_CHROMA in file-icon-svg.ts is the single knob. Move it; do not add per-icon overrides, or we are back to hand-picking 53 colours.

Adding a new icon

  1. Find the icon name in the material-icon-theme manifest:
node -e "
const m = require('./node_modules/material-icon-theme/dist/material-icons.json');
console.log('fileExtensions:', m.fileExtensions['YOUR_EXT']);
console.log('languageIds:', m.languageIds['YOUR_LANG']);
"
  1. Verify the SVG exists:
cat node_modules/material-icon-theme/icons/ICON_NAME.svg
  1. Add two things to material-file-icons.ts:

    • The SVG string in SVG_ICONS:

      "icon_name": `<svg ...>...</svg>`,
      
    • The extension mapping in EXTENSION_TO_ICON:

      "ext": "icon_name",
      
  2. Run npm run typecheck to verify.

Currently included icons

53 unique icons covering these extensions:

Extension(s) Icon
ts typescript
tsx react_ts
js javascript
jsx react
py python
go go
rs rust
rb ruby
java java
kt kotlin
c c
cpp cpp
h h
hpp hpp
cs csharp
swift swift
dart dart
ex, exs elixir
erl erlang
hs haskell
clj clojure
scala scala
ml ocaml
r r
lua lua
zig zig
nix nix
php php
html html
css css
scss sass
less less
json json
yml, yaml yaml
xml xml
toml toml
md, markdown markdown
sql database
graphql, gql graphql
sh, bash console
tf terraform
hcl hcl
vue vue
svelte svelte
astro astro
wasm webassembly
svg svg
png, jpg, jpeg, gif, webp, ico image
txt document
conf, cfg, ini settings
lock lock
groovy groovy
gradle gradle