Skip to content

feat(runner): DEV-2155 icon system — tabler-icons + seti-ui file icons - #91

Merged
demtario merged 3 commits into
feat/DEV-2027-redesignfrom
feat/DEV-2155-icon-system
Jul 30, 2026
Merged

feat(runner): DEV-2155 icon system — tabler-icons + seti-ui file icons#91
demtario merged 3 commits into
feat/DEV-2027-redesignfrom
feat/DEV-2155-icon-system

Conversation

@demtario

@demtario demtario commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

T1 of the DEV-2027 redesign. One icon layer so T2 (chrome), T3 (sidebar) and T4 (tabs) don't each invent their own. Blocking for those three. Decisions recorded in ADR-0024.

What's in it

tabler-icons as a real dependency. @tabler/icons-react@^3.46.0 in editor-shell, 20 icons re-exported from src/icons/ui.tsx under stable local names through a wrapper that pins the design's 16px/2px rendering (tabler defaults to 24px) and marks glyphs aria-hidden — labels belong on the enclosing button. All but sun/moon and the two toggled counterparts (layout-sidebar-left-collapse, chevron-right) were read straight off Figma layer names in section 18.1. Sun and moon appear in no frame at all; they come from T0, which inlined them by hand and handed them here by name in a comment.

seti-ui generated from source. No React distribution exists, so scripts/sync-seti-icons.mjs reads mapping.less, ui-variables.less and the needed icons/*.svg from a pinned commit and emits src/icons/generated/seti.ts — 29 icons, 38 suffixes, 6 exact filenames, ~22KB. Committed output; CI installs --frozen-lockfile and never runs the generator.

Colour comes from the mapping, not each SVG's baked fill. The two disagree upstream (typescript.svg bakes #529BBA where @blue is #519aba; yml.svg bakes #9F74B3 where @purple is #a074c4), the mapping is what seti's own editor uses, and only the mapping carries per-key colour — .test.ts is an orange typescript, .ts a blue one. Geometry renders with fill="currentColor" and is coloured through CSS color, so selection and muted states need no second code path. folder is the one exception: no mapping entry, so it borrows its own fill (#ababab, exactly what the frame measures).

Resolution order: exact basename → longest dotted suffix (covers compound suffixes, dotfiles and plain extensions in one walk) → seti's generic default. Exact-name matching is load-bearing, not decorative: 72:16991 gives tsconfig.json and pnpm-lock.yaml distinct icons.

One import surface: everything re-exported from the package barrel. editor-shell/package.json has main only, and both consumers alias the bare specifier straight at the barrel, so @handsontable/demo-editor-shell/icons would not resolve.

ThemeToggle drops its hand-inlined sun/moon, as its own comment asked. The unicode glyphs elsewhere (✎ ✕ + ↗ ▾ ▸ ✓) are deliberately untouched — swapping those is T2/T3/T4 restyle work.

Calls made during implementation

  • No optimizeDeps entry, against my own plan. A bare include: ["@tabler/icons-react"] fails outright — the app's node_modules can't reach a dependency of the shell. The nested "@handsontable/demo-editor-shell > @tabler/icons-react" form works but measured slower on a cleared node_modules/.vite: 1029ms vs 741ms with no config at all. Vite's scanner crawls the aliased shell source and pre-bundles the package itself. Config dropped.
  • .zip / .pdf / .mp4 excluded. Their upstream icons are two-colour (a white <g> over a coloured body) and can't survive flattening to currentColor. No runner demo contains those types, and the generator now fails loudly rather than drawing a broken glyph if one is ever added.
  • Geometry is per-<path>, not one concatenated string. The generator's assertions fired four times while writing it: default.svg is 0 0 1200 1000, vite.svg is -75 -105 560 554, tsconfig.svg has no viewBox at all (width/height instead), yml.svg needs fill-rule="evenodd". Each icon carries its own viewBox and path list.
  • .gitignore renders near-invisible. seti maps it to @ignore #41535b, which is ~1.6:1 on the dark sidebar. Upstream means it as "this file is ignored"; a demo's .gitignore isn't. <FileIcon color> exists if T3 wants an override. Noted in ADR-0024 consequences.

Two open items for design (appended to the plan's running log, not resolved here)

  1. The frames take file icons from seti's raw SVGs, baked fills and all — sampled out of 72:16991, main.ts is #529bba and pnpm-lock.yaml is #9f74b3. Two rows deviate from seti's own mapping: 72:17026 (index.html) and 72:17056 (tsconfig.json) are both the layer ejs 1, measured #d3c238, where the mapping gives html/orange and tsconfig/blue. Reads like a neighbouring sheet cell got grabbed. Everything else matches the frame exactly — package.json's generic json yellow included, since seti has no package.json entry and falls through to .json precisely as drawn.
  2. T1's scope line in the plan lists "panel toggles" while the same document states the design has none. No such icon shipped; the scope line is the stale half.

Verification

pnpm install --frozen-lockfile, pnpm build, pnpm typecheck clean · pnpm test 67/67 · pnpm e2e 10 passed. First e2e run reused a stray server on 4173 and proved nothing (reuseExistingServer: !CI); redone after killing it.

Generator re-run against the committed output produces zero diff.

Browser: dev and preview both exercised — the toggle click flips tabler-icon-moontabler-icon-sun with no page errors. A throwaway gallery rendered all 29 icons plus 25 resolution cases: App.test.ts orange TS, app.component.ts → typescript, index.astro / Makefile / noextdefault.

Bundle 6684K → 6696K, so the barrel didn't collapse.

Automated gate, stated plainly: there is no vitest/jsdom anywhere in the repo and editor-shell has no build step, so pipeline/*.test.mjs cannot import the resolver. The gate is tsc --noEmit plus the generator's assertions and its 14-fixture replay of the resolution order — that replay is what backs "unknown extension falls back to a generic icon". Visual confirmation of file icons in a real tree lands with T3.

Closes DEV-2155.


Note

Low Risk
Mostly additive shell infrastructure and docs; runtime UX change is limited to the theme toggle icons, with a new dependency and ~22KB generated icon data.

Overview
Adds the DEV-2027 T1 icon layer in editor-shell: one barrel export for chrome and file-type glyphs, documented in ADR-0024 and wired into the redesign plan as shipped.

UI chrome uses @tabler/icons-react, re-exported from src/icons/ui.tsx with 16px / 2px defaults and aria-hidden. ThemeToggle drops its hand-inlined sun/moon in favor of those imports; other unicode stand-ins are intentionally unchanged until T2–T4.

File types come from seti-ui via scripts/sync-seti-icons.mjs (pinned upstream SHA) into committed src/icons/generated/seti.ts. <FileIcon />, <FolderIcon />, and resolveFileIcon() resolve paths (exact name → partial → longest suffix → default), with colours from mapping.less and geometry as currentColor. ADR-0022 / theme.ts now treat the generated seti palette as a third allowed colour-literal exception.

Reviewed by Cursor Bugbot for commit 61c82ca. Bugbot is set up for automated code reviews on this repo. Configure here.

demtario added 2 commits July 30, 2026 13:42
…cons

One icon surface for the redesign, so T2/T3/T4 don't each invent their own.

tabler-icons ships as a real dependency, re-exported from src/icons/ui.tsx
under stable local names through a wrapper that pins the design's 16px/2px
rendering (tabler defaults to 24) and marks glyphs aria-hidden. Twenty icons,
all but sun/moon and the two toggled counterparts read straight off Figma
layer names in section 18.1. No Vite config needed: the scanner crawls the
aliased shell source and pre-bundles the package on its own — measured faster
than an explicit optimizeDeps.include (741ms vs 1029ms to first paint on a
cleared .vite).

seti-ui has no React distribution, so scripts/sync-seti-icons.mjs generates
src/icons/generated/seti.ts from a pinned commit: 29 icons, 38 suffixes,
6 exact filenames, ~22KB. Colour resolves through mapping.less +
ui-variables.less rather than each SVG's baked fill — the two disagree
upstream and only the mapping carries per-key colours (.test.ts is an orange
typescript, .ts a blue one). Geometry is flattened to fill="currentColor" and
coloured via CSS, so selection and muted states need no second code path.

The generator fails rather than degrades: every curated key must resolve, every
icon must have geometry, no transform, no non-<path> geometry, at most one
distinct fill. It also replays the runtime resolver over 14 fixtures, which is
what gates "unknown extension falls back to a generic icon" — editor-shell has
no build step and the repo has no DOM test runner.

Resolution order is exact basename, then longest dotted suffix (covering
compound suffixes, dotfiles and plain extensions), then seti's generic default.

ThemeToggle drops its hand-inlined sun/moon for imports from the new layer, as
its own comment asked. The unicode glyphs elsewhere stay — swapping those is
T2/T3/T4 restyle work.

Refs DEV-2155, ADR-0024.
ADR-0024 records the three calls: tabler as a dependency, seti generated from a
pinned commit, curated coverage with a generic fallback. ADR-0022 and theme.ts
gain the generated seti palette as a third named exception to the
colour-literal rule — upstream brand values, identical in both modes.

Two items appended to the redesign plan's running log rather than resolved
inline, per that table's convention:

4. The frames take file icons from seti's raw SVGs, baked fills and all
   (main.ts is #529bba, yml #9f74b3). Two rows — index.html and tsconfig.json —
   use the yellow ejs glyph where seti's mapping gives html/orange and
   tsconfig/blue. Needs a design nod. Everything else matches, package.json's
   generic json icon included.
5. T1's scope line lists "panel toggles" while the same document states the
   design has none. No such icon shipped.

Refs DEV-2155.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ccb68ff. Configure here.

Comment thread runner/scripts/sync-seti-icons.mjs
…resolves

Bugbot caught it: every starter in catalog.json ships LICENSE.txt, and the
generator reduced upstream's `.icon-partial` rules to exact-name lookups. The
exact key "LICENSE" therefore never hit and those rows fell through to the
`.txt` suffix, drawing seti's generic `default` glyph instead of the licence
one.

Upstream has two distinct match kinds and now so do we: `.icon-set` keys stay
exact-name or suffix lookups, `.icon-partial` keys become an ordered substring
table checked between the two. That position matters — `.txt` is a curated
suffix, so a partial step after the suffix walk would change nothing. It also
mirrors mapping.less, where the partial rules are stated after the extension
rules and win on CSS order.

Curated partials are one entry (LICENSE), and the generator resolves it against
mapping.less like any other key, so upstream dropping it fails the run. Fixture
15 pins LICENSE.txt -> license.

Substring matching is greedy the way upstream is — MY_LICENSE_NOTES.md gets the
licence glyph too. That's seti's behaviour, kept deliberately rather than
half-reimplemented.

Refs DEV-2155, ADR-0024.
@demtario demtario self-assigned this Jul 30, 2026
@demtario
demtario merged commit 48d3a74 into feat/DEV-2027-redesign Jul 30, 2026
2 checks passed
@demtario
demtario deleted the feat/DEV-2155-icon-system branch July 30, 2026 11:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant