Skip to content

finding(tooling): check-lucide-icon-record-names.mjs's top-level maps cannot be moved behind the entry guard — guarding them prints a WRONG diagnosis, so the gate's last import-unsafe line needs a lazy build, not a guard #6147

Description

@yinlianghui-tw

Split out of #6092 by the PR 2 seat, under that card's own STOP condition. #6092's dispatch order ruled: "⛔ If those loops feed module-level exports that importers actually read, STOP and report rather than restructuring the module." They do. This records the measurement so the next seat does not re-derive it, and names the shape that would actually work.

What is there

scripts/check-lucide-icon-record-names.mjs builds two lookup maps in top-level for loops, outside any guard — :243 and :245 on main after #6092's PR 2:

constkeyByComponent=newMap();for(const[key,component]ofObject.entries(icons))if(!keyByComponent.has(component))keyByComponent.set(component,key);constkebabByKey=newMap();for(constkebaboficonNames)kebabByKey.set(toRecordKey(kebab),kebab);

It is the sole entry in check-entry-guard.mjs's KNOWN_IMPORT_UNSAFE, and that entry is true: those loops really do run inside an importer.

Why the obvious remedy is wrong — measured, not reasoned

The maps are module-private but they are read by the exportedliveSpellingFor and, through it, describeName. Importers really call both: scripts/__tests__/check-lucide-icon-record-names.test.ts calls liveSpellingFor at :153 and describeName at :204.

Ablation on #6092's PR 2 branch — both loops moved inside if (isEntrypoint(import.meta.url)), mutation confirmed on disk (top-level loop lines 1 to 0, indented-behind-guard 1), restored by trap with git checkout HEAD --:

  • check-entry-guard.mjsgoes green on rule 2 and reports the KNOWN_IMPORT_UNSAFE line as STALE. The baseline is satisfied.
  • scripts/__tests__/check-lucide-icon-record-names.test.tsfails 5 of 25.
  • And the failure is not merely a red test. With the maps empty for importers, describeName('BarChart3') stops saying write `chart-column` and says this instead:
"BarChart3" -> `BarChart3` is exported by lucide but is not a key of the runtime
`icons` record, and no live key names the same glyph.

That is a wrong diagnosis for a real violation, printed by a gate that still exits 1. Trading a correct message for a green baseline is a worse outcome than the unguarded loops, which is why #6092 stopped rather than shipping it.

The shape that satisfies both

Build the two maps lazily, on first use inside liveSpellingFor, memoised. Then:

  • nothing runs at module top level, so rule 2's line can be deleted honestly;
  • every importer keeps getting exactly what it gets today, because the maps are built the moment anything reads them;
  • the CLI path is unchanged.

Worth confirming while doing it: icons and iconNames are themselves top-level await import(...) of lucide-react (:188:191), so this module is never cheap to import. The lazy build removes the two loops, not that cost — if the goal is a genuinely cheap import, that is a separate and larger question about those four top-level awaits.

Why this is filed rather than done

#6092's ruling was explicit that restructuring a working module to satisfy a baseline is not a call an implementing seat makes. The KNOWN_IMPORT_UNSAFE comment in check-entry-guard.mjs now carries this measurement and points at the lazy-build shape; this card is the one it says that line owes.

Refs: #6092 (the sweep, and the STOP ruling) · #6133 (the gate and its rule 2) · #5633 (why the gate exists).


Generated by Claude Code

Metadata

Metadata

Labels

domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repofindingpm:dispatchedtooling

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions