From fa5b28ea69f3bff0941f8a097a9746b7a67fa900 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Wed, 24 Jun 2026 14:44:28 -0400 Subject: feat(theme-studio): nerd-icons gallery as a hue-ordered icon grid The nerd-icons pane is now a grid: one row per color face, the rows ordered by hue so families cluster, distinct icons (deduped within a color) drawn in their color with the icon's nerd-font name beneath. A "preview:" dropdown above the grid picks the glyph size in points, with Left/Right arrows to step it. Single-pane apps show it disabled, naming the preview. This replaces the v1 legend in the pane, whose data is still captured for round-trip. build-nerd-icons-legend.el is now a library. A cj/nerd-icons-write-legend entry point requires nerd-icons only at write time, so the capture logic loads and unit-tests without it. It dedupes icons by name within a face, computes each face's native hue, and orders the groups by hue. Writing the test surfaced a latent bug: face-hsl used (cadr (assoc t spec)), which grabs the first keyword instead of the plist. It only worked because the real faces fall through to the face-foreground branch. I fixed it to a correct t-clause parse. Coverage: 7 ERT capture tests (dedupe, hue order, lightness tiebreak, name sort, skip rules), 4 Python validator edges, and browser gates for the grid and the size dropdown. Locate stays color-level: clicking a color flashes its icons, and clicking an icon flashes its color row. Icons aren't individually editable, so there's nothing per-icon to select. --- docs/specs/theme-studio-nerd-icons-colors-spec.org | 107 ++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/specs/theme-studio-nerd-icons-colors-spec.org b/docs/specs/theme-studio-nerd-icons-colors-spec.org index 53a11f847..717ee2ec7 100644 --- a/docs/specs/theme-studio-nerd-icons-colors-spec.org +++ b/docs/specs/theme-studio-nerd-icons-colors-spec.org @@ -37,7 +37,8 @@ theme-studio already inventories the 34 =nerd-icons-*= faces (via the generic pa ** Scope tiers - v1: capture a curated filetype legend (=nerd-icons-legend.json=); a bespoke nerd-icons preview rendering it; assign the theme colors and drop the tint atomically; export/round-trip; live verification in completion/dirvish/dashboard. Native colors already ride the existing default-face seed pipeline, so there is no new color capture. - Out of scope: per-filetype assignment; editing the filetype→face mapping itself (that lives in nerd-icons). -- vNext: extend the legend beyond file extensions to buffer-mode and command/symbol categories if the file set proves insufficient; a "reset to nerd-icons native palette" button. +- vNext (gallery, SHIPPED): the nerd-icons pane becomes an icon grid — one row per color face, rows ordered by hue so families cluster, distinct icons (deduped within a color) drawn in their color with the icon name beneath, plus a per-size preview dropdown. Replaces the v1 legend in the pane (legend data retained). See "vNext — nerd-icons gallery" below. Subsumes the two roam asks (one representative icon per color; group the colors together). +- vNext (later): extend the legend beyond file extensions to buffer-mode and command/symbol categories if the file set proves insufficient; a "reset to nerd-icons native palette" button. * Design @@ -233,7 +234,111 @@ The feature changes specific contracts; each gets a named target rather than lea - Export/import round-trip: a WIP with assigned =nerd-icons-*= colors exports and re-imports to the same state; =nerd-icons-completion-dir-face= (separate app) is untouched by the nerd-icons pane. - Config: =make validate-modules= + launch smoke after the tint removal (Phase 3). +* vNext — nerd-icons gallery (icon grid by color) — SHIPPED + +This increment builds on shipped v1 and the shipped glyph-rendering infrastructure (the embedded =ThemeStudioNerd= woff2 + the unquoted-inline-font fix that lets nerd-font glyphs render in the browser). It is purely additive display — no config change, no theme change, no new editable surface. The 34 =nerd-icons-*= faces are already themed and editable from v1; the gallery is a read-only view of every distinct icon, organized by the color it renders in. + +The design evolved during the build. The first cut rendered the *full* catalog (every face-bearing mapping, ~700 glyphs, duplicates kept) as a sequence of flowing color sections below the v1 legend, ordered by glyph count, with the source key on hover. Craig redesigned it after a live look into the shipped shape below: a grid of *distinct* icons (deduped within a color), rows ordered by *hue* so families cluster, the icon *name* shown beneath each glyph, the v1 legend dropped from the pane, and a *per-size preview dropdown* so the designer can view the grid at different font sizes. The decisions below record the shipped choices; the superseded ones are noted inline. + +** Summary + +The nerd-icons pane is a grid: one row per =nerd-icons-*= color face, the rows ordered by hue (ascending) so color families sit together (pinks/reds/oranges, yellows, greens, cyans, the grays, blues, purples). Each row is a swatch + face-name header over a wrapping set of cells; each cell draws one icon in the face's color with the icon's nerd-font name (=nf-dev-terminal=) beneath it. Icons are deduplicated within a color, so the ~700 face-bearing mappings collapse to ~314 distinct glyphs. Recoloring a face repaints its swatch and every icon in its row live. Above the grid, a "preview:" dropdown selects the glyph size. + +** For the user + +The pane shows the grid (the v1 legend preview is gone from view; its data is still captured for round-trip and reference). Color-level locate is preserved: clicking a color in the faces table flashes every icon in that row, and clicking an icon flashes its color's row — icons aren't individually editable, only their color is. A "preview:" dropdown above the grid picks the font size; Left/Right arrows step through the sizes when it is focused. + +** For the implementer + +Three integration points; no config or theme path is touched. + +1. Catalog capture. =build-nerd-icons-legend.el= is now a library (capture functions + one entry point, =cj/nerd-icons-write-legend=), so the pure logic unit-tests without nerd-icons. It walks every =:face=-bearing alist (=nerd-icons-extension-icon-alist=, =nerd-icons-regexp-icon-alist=, =nerd-icons-mode-icon-alist=), dedupes icons by name within each owner face, sorts each face's icons by name, computes each face's native hue from its defface foreground, and orders the groups by hue (ascending, ties by descending lightness). It emits into the *same* =nerd-icons-legend.json= under a =gallery= key (v1 rows stay under =legend=). nerd-icons is required only inside the writer, so the file loads tint-free for tests; the daemon invocation is =(progn (load …) (cj/nerd-icons-write-legend))=. =generate.py='s fallback covers absent/malformed/empty → generic app, never an error. + +2. Grid renderer. =renderNerdIconsPreview(sizePt)= in =previews.js= draws the =gallery= groups: per group a swatch + face-name header, then a cell per icon (the glyph in the face's color at =sizePt=, the icon name beneath). It reads color through the same effective-color registry, so recolor repaints live; glyphs render in =ThemeStudioNerd=. With no gallery it falls back to =genericPreview=. Registered under the existing bespoke nerd-icons app. + +3. Preview-pane dropdown. =previewPanes(app)= / =buildPkgPreview= in =app.js= turn the old static preview label into "preview:" + a ==. One pane → disabled, names the preview. Multiple panes → enabled. nerd-icons gets one pane per font size in *points* (10/12/14/16/20/24, default 14) — pt because Emacs sizes fonts in =:height= (1/10 pt), so a pane maps to a real buffer size. Left/Right arrows step the focused dropdown; the size persists per app. The dropdown is multi-pane only when the gallery actually exists, so a failed capture can't promise sizes the renderer can't draw. +- Consequences: easier — size selection with no new UI surface; the mechanism generalizes to any future multi-pane app. Harder — a second piece of per-pane state (the selected index). + +*** DONE Font embed: full Symbols Nerd Font Mono stays embedded +- Context: v1 deferred the full-font (2.1M HTML) vs glyph-subset call; the gallery forces it. +- Decision: keep the full =ThemeStudioNerd= woff2 embedded as a data: URI — the grid draws the whole glyph set, so a v1-curated subset would not cover it. +- Consequences: easier — every glyph renders, no subset bookkeeping. Harder — the ~2.1M self-contained HTML stays (fine for a personal tool). + +** Locate under the grid (color-level association) + +The gallery inverts the studio's usual ~1:1 element↔face association: the visible unit is the icon, but the only editable, locatable handle is the color, which owns ~10-40 icons. Decision (Craig): keep it color-level. Clicking a color in the faces table flashes all its icons; clicking an icon flashes its color row. Icons get no individual editable identity — their name is already on screen, so no flash is needed to identify one. The size dropdown rides cleanly on top: only the selected pane is ever rendered, so a flash always targets the visible size. The alternative (icons as first-class selectable entities) was rejected — there's nothing per-icon to edit, so it would invent a selection concept that fights the rest of the studio. + +** vNext implementation phases (as shipped) + +*** Phase G1 — Catalog capture (library + grid data) +=build-nerd-icons-legend.el= refactored to a library with =cj/nerd-icons-write-legend= (runtime nerd-icons require). Emits the deduped, hue-ordered =gallery= groups (=face= / =hue= / =glyphs:[{glyph,name}]=) into =nerd-icons-legend.json=. =generate.py= parses it (=load_nerd_icons_gallery= with absent/malformed/legacy-array/invalid-group fallbacks) and attaches it via =add_nerd_icons_app=. Unit-tested by =test-nerd-icons-legend-dump.el= (synthetic alists/faces: dedupe, hue order, lightness tiebreak, name sort, the skip rules). + +*** Phase G2 — Grid renderer +=renderNerdIconsPreview(sizePt)= draws the per-color grid (swatch + name header, a cell per icon with the name beneath), replacing the legend render. Live recolor; =genericPreview= fallback when no gallery. Browser-gated (=#nerdiconstest=: grid, hue order, dedupe, recolor). + +*** Phase G3 — Preview-pane dropdown +=previewPanes= / =buildPkgPreview= + the template's "preview:" =