Skip to content

refactor(ui-core): the frontend draws lucide again - #777

Merged
YaelAnaya merged 5 commits into
mainfrom
refactor/icons-back-to-lucide
Aug 25, 2026
Merged

refactor(ui-core): the frontend draws lucide again#777
YaelAnaya merged 5 commits into
mainfrom
refactor/icons-back-to-lucide

Conversation

@YaelAnaya

Copy link
Copy Markdown
Collaborator

The frontend draws lucide-react again. One hundred and ninety-six references over
thirty-nine files, both manifests, the lockfile, the configuration, the gate that
holds it, the documentation that describes it, and the ten reference images that
photograph it.

This reverses the icon half of #771 and #774. Those changes were sound when they
were made; this is a product decision to draw a different set, not a defect being
corrected, and everything that recorded the old choice moves with it rather than
being left to contradict the code.

The migration

Seventy-two distinct glyphs, every one resolved against the installed
lucide-react before anything was rewritten — the same discipline the migration in
the other direction used, and for the same reason: icon names are not guessable
between libraries. None was missing.

files rewritten        39
references rewritten   196
distinct glyphs        72  (72 mapped, 0 missing)

Two details the mapping alone could not carry.

The rewrite is keyed on names, not on a prefix. The demo's tool strip has a
local component called IconButton. A blanket Icon* rewrite would have renamed a
component belonging to neither library.

ProjectNav imports the icon component type, and the libraries spell it
differently
— Tabler's Icon against lucide's LucideIcon. That surfaced as a
compile error rather than as anything subtler, and the fix moved the type while
leaving the local icon: Icon destructuring alone, which is a value.

The geometry glyphs return to the spelling they had before: Spline for the polygon
and Waypoints for the lane. Worth one note, since the migration the other way made
a point of this: GeometryIcon argues that a lane must read as an open path and
that Spline is the closed shape above it. That reasoning is the original code's and
is coherent again as written — but lucide's Spline does render as an open curve, so
if the closed/open distinction matters more than the restoration does, that is a
separate and much smaller change to make deliberately.

The configuration, and one honest divergence

components.json says iconLibrary: lucide. The strict shadcn schema accepts the
value — checked with rawConfigSchema.safeParse rather than assumed, since that
schema rejects a field it does not define rather than ignoring it.

The preset does not. b3bXyyPdWj decodes iconLibrary: tabler, and DESIGN.md
still says so on the line that reports the decode, because falsifying that to match
the product would make the document lie about the CLI's own output. What changed is
that the configuration now diverges from the preset on this one property, and both
DESIGN.md's Source of Truth and the architecture note record the divergence in
the section built for exactly this distinction. It is the only place where reading
the preset and reading the configuration give different answers, and nobody should
have to discover that by diffing them.

The gate

It guarded lucide; it guards Tabler now. The rule it holds is one icon library
rather than one particular library — what costs a reader is two sets on a screen,
where the same idea arrives at two weights and two grids — so the guarded name is
the only thing that changes when the choice does, and the docstring says that.

Both halves were re-proved by breaking them: a manifest that declares the other set
fails, and a source that imports it fails naming the file.

projectNav's own assertion was written to prove the migration away from lucide,
and as written it would have passed for a file that stopped drawing icons at all. It
asserts both directions now.

The baselines

All ten reference images hold icons, so all ten changed. They were regenerated in
the canonical container, opened and read rather than accepted from a green run, and
the suite passes three consecutive runs against them with no further update.

Them going red on an icon swap is the gate working. Regenerating was the answer only
because the change was intended.

Test plan

Every command ran inside Docker/Linux; no repository validation ran natively on the
host.

install (pnpm install --frozen-lockfile, from an empty tree)  PASS
build (pnpm -r build)                                         PASS
@visionset/ui-core lint (incl. typecheck)                     PASS
@visionset/ui-core test                                       PASS
@visionset/app build                                          PASS
@visionset/app lint (incl. typecheck:e2e)                     PASS
@visionset/annotator test                                     PASS
pnpm test:scripts                                             PASS
pnpm version:check                                            PASS

full browser suite (all projects)                             276 passed
visual project, three consecutive runs                        10 / 10 / 10

Overlap

DEPENDENCY OVERLAP with #775. It edits frontend/app/package.json,
frontend/ui-core/package.json and pnpm-lock.yaml — the same three files, and it
raises lucide-react to 1.33.0, which is the version this branch resolves to
anyway. Whichever lands second wants a rebase rather than a merge resolution.

#775 also raises shadcn from 4.18.0 to 4.19.0, and that package is a runtime CSS
dependency here. If the bump moves base styles, the visual suite goes red on merge —
which is the gate working, and wants the diff images read rather than a blind
regeneration.

Scope

Routes changed:            No
Product IA changed:        No
Backend / wire / domain:   No
Design tokens changed:     No
Primitive API changed:     No
Annotator behaviour:       No

Nothing about the product's behaviour changes here. What changes is which set the
glyphs come from, and every record of that choice.

Swaps the declared icon library back: `lucide-react` in, `@tabler/icons-react`
out, in both manifests and the lockfile. The lockfile is resolved from the
manifests and a frozen install runs from an empty tree, so the two agree rather
than being asserted to.

Tabler goes out in the same change rather than being left declared. Nothing
imports it once the sources move, and an icon library nobody draws from is the
dependency the previous cleanup removed for exactly this reason.
One hundred and ninety-six references over thirty-nine files - the primitives, the
screens, the patterns, the annotation workspace, the shell and the styleguide -
move from `@tabler/icons-react` back to `lucide-react`. All seventy-two distinct
glyphs were resolved against the installed package before anything was rewritten,
and every one has an equivalent.

The rewrite is keyed on the mapped names rather than on an `Icon*` prefix, which
matters in one place: the demo's tool strip has a local component called
`IconButton`, and a prefix rewrite would have renamed a component that belongs to
neither library.

`ProjectNav` needed a second edit the mapping could not carry. It imports the icon
*component type*, and the two libraries spell it differently - Tabler's `Icon`
against lucide's `LucideIcon` - so the name moved with the import while the local
`icon: Icon` destructuring, which is a value and not the type, stayed as it was.

The geometry glyphs return to the spelling they had before: `Spline` for the
polygon and `Waypoints` for the lane. `GeometryIcon`'s own note argues that pairing
and is coherent again as written.
The rule the gate holds is "one icon library", not "this particular library" - what
costs a reader is two sets on one screen, where the same idea arrives at two
weights and two grids. So the guarded name is the only thing that changes when the
choice does, and it now names Tabler. Both halves were re-proved by breaking them:
a manifest that declares it fails, and a source that imports it fails naming the
file.

`components.json` says `iconLibrary: lucide`, which the strict shadcn schema
accepts - checked with `rawConfigSchema.safeParse` rather than assumed, since that
schema rejects a field it does not define rather than ignoring it.

`projectNav`'s assertion was written to prove the migration *away* from lucide and
would have passed for a file that stopped drawing icons at all. It asserts both
directions now: the set the file draws, and the one it must not reach back for.
…ce is written down

Four surfaces said Tabler was the set. They say lucide now, and the anti-pattern
states the rule as *one* set rather than one particular set, so the next swap
updates a name instead of an argument.

One line deliberately still says `tabler`, and it is the one that decodes the
preset: `b3bXyyPdWj` really does decode `iconLibrary: tabler`, and falsifying that
to match the product would make the document lie about the CLI's own output. What
changed is that `components.json` now diverges from the preset on this one
property - a decision the schema accepts - so both `DESIGN.md`'s Source of Truth
and the architecture note record the divergence in the place built for exactly this
distinction. Nobody should have to find it by diffing the two.
Every reference image holds icons, so all ten change. They were regenerated in the
canonical container, opened and read rather than accepted from a green run, and the
suite passes three consecutive times against them with no further update.

The baselines going red on an icon swap is the gate working. Regenerating them is
the answer only because the change was intended.
@YaelAnaya
YaelAnaya merged commit 7247b73 into main Aug 25, 2026
15 checks passed
@YaelAnaya
YaelAnaya deleted the refactor/icons-back-to-lucide branch August 25, 2026 03:49
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