Icon sets that arrived INSIDE an installed kit, needing no code and no configuration.
Why this exists
MishkaCmsCore.Runtime.PageBuilder.Icons.Heroicons reads deps/heroicons at compile time, which
means a second icon set needed a second module and a line of config. That is the wrong shape for a
CMS: installing a kit should be enough. Nobody adding Lucide to their site should have to write an
Elixir module to see its glyphs in the picker.
A kit that ships its glyphs in its bundle needs neither. mix mishka.ui.export writes them under a
top-level icons key, MishkaCmsCore.Runtime.Handlers.UiKitHandler leaves that file in
priv/cms_ui_kits/, and this module reads it.
Why reading a file at runtime is safe here, and was not for heroicons
The constraint that forced heroicons to compile-time is that deps/ is a BUILD artefact — it does
not exist in a release. priv/ does: it is shipped, it is exactly where the installed bundles
already live, and the CMS reads them at runtime anyway. So the same instinct that was right for
deps/ is unnecessary here.
Caching
The glyphs are ~0.7 MB per set and the picker asks on every keystroke, so the parse happens once
into :persistent_term. That is the right store rather than ETS: written once at first read,
read from every process with no copy, and the cost of a write — a global GC — is paid one time.
refresh/0 clears it, which is what an install calls after replacing a bundle.
Summary
Functions
Whether any installed kit shipped an icon set at all.
Drops the cache, so the next read sees a bundle that has just been installed or replaced.
Functions
@spec available?() :: boolean()
Whether any installed kit shipped an icon set at all.
False on a stock install, which is why the facade must not offer this library then — an empty set in the picker's library row is a button that finds nothing.
@spec refresh() :: :ok
Drops the cache, so the next read sees a bundle that has just been installed or replaced.