[WEB-8236] feat: publish @makeplane/propel/icons subpath - #2
[WEB-8236] feat: publish @makeplane/propel/icons subpath#2anmolsinghbhatia wants to merge 20 commits into
Conversation
Normalizes the one same-glyph duplicate before generating: overwrites icons/miscellaneous/toggle-off-outline.svg with icons/properties/toggle-off-outline.svg (minor Figma geometry drift, same icon; properties wins as first in category order). The other 12 filenames shared across two categories are genuinely different icons (not drift) — the generator now emits both, with the later category's export name carrying a category suffix (e.g. actions/delete-filled.tsx exports DeleteFilled, arrows/delete-filled.tsx exports DeleteFilledArrows).
- monochromify: url(...) paints now count toward the color total (no longer
excluded); white (#fff/#ffffff/white, case-insensitive) is a knockout paint —
excluded from the count and never replaced. Fixes 6 wrongly-flattened brand
marks in other-products (instagram, ms-word, ms-excel, ms-powerpoint, codex,
jira), which reverted to their full palettes. As a consequence, three other
single-color+white other-products logos (facebook, postman, youtube) now also
reduce to currentColor — a knock-on effect of the same white-knockout rule,
not called out in the original casualty list; flagged for controller review.
- log-config-{filled,outline}.tsx now correctly emit currentColor + white
knockout (previously two-tone #313435 + #fff).
- Normalized the two near-identical darks (#313435/#4E5355 Figma export drift)
in ai-filter-{filled,outline}.svg and embed-filled.svg to a single #313435;
they now generate as pure currentColor icons.
- generate-icons.mjs: added a category color policy — any non-brand icon
(outside sub-brands/other-products) whose defs/clipPath-stripped content
still contains a fill/stroke value other than none/currentColor/white
(including url(...)) now fails the build with every offending file+paint
listed in one error.
- generate-icons.mjs --check: guard missing src/icons/ with a friendly
'run pnpm gen:icons' message instead of a raw ENOENT.
- icons.test.ts: replaced the 'exactly one hardcoded color' test with a
file-source-based invariant mirroring the generator's policy; tsxFiles now
returns paths (not basenames) so category can be derived for the brand-vs-
non-brand split.
- .oxfmtrc.json: broadened the icons ignore pattern to **/src/icons/** to
match .oxlintrc.json.
- AGENTS.md icons section + docs/propel-icons/plan.md Task 5: pnpm command and
duplicate-filename wording corrected to match the generator's real behavior.
Regeneration: 739 icon components, check:icons OK (740 files). 13 generated
.tsx files changed (6 reverted brand marks + log-config pair + ai-filter pair
+ embed-filled + the 3 unflagged facebook/postman/youtube knock-on changes).
Verified: 6 reverted brand marks byte/multiset-compared clean against source
SVGs; unit test project 482/482 passing; pnpm check (11/11 tasks) and pnpm
test (1462/1462) green from repo root; category policy verified to fire by
temporarily injecting a second dark paint into a copy of a non-brand source
SVG, confirming the listed error, then reverting.
monochromify now skips the other-products category entirely (its call site
passes category through; VERBATIM_CATEGORIES = {other-products}) — third-party
logos ship with their source palette untouched, regardless of paint structure.
The white-knockout + url-counting rules from 715c38c stay in force for every
other category, including sub-brands (Plane's own product marks): all 6
sub-brands glyphs are single-color sources with no white/gradient, so they
still reduce cleanly to currentColor under the strict policy — the exemption
did not need to widen back to sub-brands.
The color policy's exemption is tightened to other-products only, matching
monochromify's skip list. icons.test.ts mirrors the same VERBATIM_CATEGORIES
set.
Regenerating produced a larger delta than facebook/postman/youtube alone: 22
other-products files changed vs 715c38c, all reverting to their exact source
fill (case/hex-shorthand aside, per svgo). Previously many single-flat-color
logos (asana, chatgpt, claude, clickup, discord, docker, github, kubernetes,
linear, linkedin, sentry, x, and others) had been silently monochromified to
currentColor because they had no white knockout and exactly one countable
paint — 715c38c's rule change didn't anticipate that these were also
'monochrome-shaped'. codex.tsx is also not byte-identical to 715c38c: at
715c38c its sole gradient reference (fill="url(#paint0_linear_8_4)") was
itself the one counted paint and got replaceAll'd to fill="currentColor",
silently discarding the gradient while leaving an orphaned <linearGradient>
in defs — a latent bug in the previous fix that this change corrects; codex
now correctly renders fill="url(#a)" against its gradient. instagram, jira,
ms-excel, ms-powerpoint, ms-word, log-config-{filled,outline}, and
ai-filter-{filled,outline}/embed-filled are unchanged (byte-identical to
715c38c) — verified via git diff.
Verified: 739 components, check:icons OK (740); unit test project 482/482;
pnpm check 11/11 tasks green from repo root.
The two `@` glyphs were exported from Figma as `filled.svg` / `outline.svg` (the `@` survived only in the layer id), so the generator derived the export names `Filled` and `Outline` and published them from `@makeplane/propel/icons`. Two of the most generic identifiers in the barrel, permanently reserved, for an icon nobody could find by name.
SVG ids are document-global, but svgo's cleanupIds minifies them to `a`, `b`, … independently per file, so all 14 icons carrying a <defs> shipped `id="a"` and `url(#a)`. Rendering two on one page — which the new Iconography gallery does — made the second's reference resolve to the first icon's def: Jira after Instagram painted with Instagram's gradient, Codex resolved its gradient ref to a <clipPath> and rendered unpainted. A prefixIds pass keyed on the (unique) export name namespaces every id and reference; the dedupe hash is taken before it so cross-category duplicates still collapse. Covered by a new test that renders all 739 components and asserts id uniqueness across the set. Alongside it, five ways the generator could mislead: - gen:icons removed src/icons before generating, so any of the four throw sites left the committed tree destroyed. It now builds into a sibling temp dir and renames on success (sibling, not os.tmpdir(), to keep the swap same-device). - The other-products verbatim exemption applied to five sources that carry no brand palette at all — chatgpt, docker, glm-z-ai, kimi and kubernetes are plain neutral exports painted with propel's own icon foreground, and freezing them at #313435 made them invisible on the dark theme. The exemption now requires an actual non-neutral paint. - An icon whose only paint is a url(...) ref was flattened to currentColor, dropping the gradient and orphaning its <defs> — the codex bug, still live for every category outside the other-products exemption. Left alone now, and reported by the color policy. - The duplicate-filename baseline was never updated, so a third occurrence was compared against the first category's hash instead of every prior one. - SVGs outside icons/<category>/ were silently skipped; they now fail the build, matching the existing unknown-category error. Two source-side problems the generator cannot fix are now warned about instead of hidden: two themed icons paint a rendering white (a Figma knockout that halos on dark), and 114 groups of icons are byte-identical, mostly -filled and -outline exported from the same glyph. Both need a fix in Figma.
All 739 icon modules were stamped as client components. They are pure static
SVG — no hooks, no state, no events — so in a Next.js App Router server
component `import { AddFilled }` opened a client boundary and shipped the
glyph's JS to the browser instead of rendering it on the server. tsdown's
banner takes a per-chunk function, so icons/** opts out.
The verbatim carve-out only de-exempted the exact neutral foreground
(#313435), so single-colour near-black marks shipped their hex: github and
copilot (#0F0F10), x, clickup and intercom (black). The dark theme's page
background is itself near-black, so those rendered at ~1:1 contrast —
invisible. Replace the exact-hex check with an achromatic test: a lone paint
whose RGB channels spread <=16 is a monochrome wordmark, not a palette, and is
themed like any other icon. A lone url(...) stays verbatim (codex's gradient).
Also:
- Strip <mask>/<symbol>/<pattern> and attributed <defs> alongside <clipPath>
before counting paints. Figma routinely exports <mask mask-type="alpha">
wrappers, whose stencil fills would otherwise read as a second colour and
hard-fail the colour policy on a legitimately monochrome icon.
- Move the generation scratch dir out of src/ to .icons.tmp/ at the package
root, and gitignore it. A Ctrl-C'd run skips the finally, and src/icons.tmp
sat inside tsconfig's include and outside every lint/format ignore, so the
stray output made check:types, check:lint and check:format start reporting
on files that were never meant to exist.
- Set process.exitCode instead of calling process.exit in --check, which
terminated before the finally and leaked a 740-file mkdtemp dir on every
failing run.
- Express log-config-{filled,outline}'s knockout as a <mask> instead of a white
overlay path. Identical on white, no bright halo on dark; drops the last
white-knockout warning.
- Fix source filenames that were becoming permanent public export names:
righ-arrow-outline -> right-arrow-outline, unsyn-* -> unsync-*. Drop
asterisk-filled-2 and upgrade-outline-2, byte-identical to their base files.
739 -> 737 components. icons.test.ts mirrors the new strip and gains a
regression test for the achromatic rule.
Figma exported a second cut of 14 glyphs with a `-2` copy suffix, and a 15th/16th under a misspelled `transfer-worki-item-*`. Those became the public export names `ShareFilled2`, `AgentOutline2`, `PlaneAi2`, `TransferWorkiItemFilled` — a copy artifact and a typo, both permanent once @makeplane/propel/icons is published. They are deliberate alternate variants, so name them as such: `-alt` sits before the style suffix (`share-alt-filled`, not `share-filled-alt`) to keep `-filled`/`-outline` terminal, as it is on every other icon in the set. `transfer-worki-item-*` becomes `transfer-work-item-alt-*`, which also drops the typo — it could not simply be corrected, since `transfer-work-item-*` already exists with different art. Export names change accordingly: ShareAltFilled, ToggleListAltFilled, AgentAltFilled, LibraryAltFilled, LogsAltFilled, UpgradeAltFilled, UserAltFilled, PlaneAiAlt, TransferWorkItemAltFilled (and the -outline twins).
|
Linked to Plane Work Item(s) This comment was auto-generated by Plane |
The story claimed every `other-products` logo keeps its brand palette. That stopped being true when monochrome marks started theming: GitHub, X, Intercom, ChatGPT, Docker and friends now follow `currentColor` like any other icon. These comments render as the Storybook description, so the stale text was user-visible.
|
@anmolsinghbhatia We should also add documentations in |
|
Maybe worth reordering to |
When two categories claim the same filename with different art, the later one disambiguated its export by appending the whole category directory — after the state suffix. That produced TableFilledFormattingAndEditors: long, and with Filled buried mid-name it breaks the one pattern a consumer can rely on when guessing an import, since every other icon with a state ends in Filled or Outline. Each category now carries a short CATEGORY_ALIAS, and the qualifier is inserted *before* the state rather than appended after it: TableFilledFormattingAndEditors -> TableEditorFilled DeleteFilledArrows -> DeleteArrowFilled NewTabFilledArrows -> NewTabArrowFilled FolderFilledMiscellaneous -> FolderMiscFilled VerifiedFilledMiscellaneous -> VerifiedMiscFilled WorkflowsFilledProperties -> WorkflowsPropertyFilled 12 exports across 6 concepts. A category with no alias fails the build, so the map cannot drift from CATEGORY_ORDER, and a new test asserts no export ever carries Filled/Outline anywhere but the end.
|
@codingwolf-at Fixed — each category now has a short alias and the qualifier goes before the state, so TableFilledFormattingAndEditors TableEditorFilled (12 exports across 6 concepts). Added a test so a future collision can't bury the state mid-name again. |
b9d4573 to
b803388
Compare



Description
Publishes propel's icon set as a new @makeplane/propel/icons subpath — 737 flat named exports generated from 739 SVG sources across 12 Figma categories, each icon its own chunk so consumers only ship the glyphs they import.
src/icons/ is generated, never hand-edited. scripts/generate-icons.mjs runs each source through svgo + svgr and applies the policies that make the set safe to ship:
The generator also warns without failing on two source-side problems it cannot fix: groups of byte-identical glyphs (112 groups, mostly -filled/-outline pairs cut from the same Figma glyph — ICONS_REPORT_DUPES=1 lists them), and themed icons painting a rendering white knockout.
Because these export names become public API on release, source filenames were cleaned up first: Figma's -2 copy suffixes are named as the deliberate alternate variants they are (share-alt-filled → ShareAltFilled, with -alt before the style suffix so -filled/-outline stays terminal), and the righ-arrow-outline / unsyn-* / transfer-worki-item-* typos are corrected.
Rule 2a still stands: lucide-react remains the icon source inside components. Migrating propel's own components onto this set is a separate effort.
Type of Change
Media
Test Scenarios