Skip to content

chore(sync): derive icon-map.json from the shared icon keys, and guard it - #378

Merged
IgorShevchik merged 2 commits into
mainfrom
chore/sync-icon-map
Aug 13, 2026
Merged

chore(sync): derive icon-map.json from the shared icon keys, and guard it#378
IgorShevchik merged 2 commits into
mainfrom
chore/sync-icon-map

Conversation

@IgorShevchik

@IgorShevchik IgorShevchik commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Verifies and lands the icon-map content from the closed #67rebuilt rather than imported, per the follow-up flagged in #377. Revised after review; see the last section for what changed and why.

The map is derived, not chosen

b24ui and nuxt/ui define the same semantic icon keys: src/runtime/dictionary/icons.ts here, src/theme/icons.ts upstream. For every key both sides carry, the row follows mechanically — (upstream's lucide name → whatever our dictionary maps that key to). 43 upstream keys × 39 of ours = 37 derived rows, no judgement involved.

Every value in both the old map and #67's proposal resolves to a real icon in @bitrix24/b24icons-vue@2.0.7, so "does it exist" settles nothing. The dictionary does.

⚠️ When re-checking this, do not read node_modules/@nuxt/ui. That copy is 4.8.2, pulled in transitively by nuxtseo-layer-devtools, and is older than the sync cursor — three reviewers read it and concluded star was fabricated and the intersection was 36. Read the raw src/theme/icons.ts at cursor 3dbca02, which has star: 'i-lucide-star' and 43 keys.

Three wrong values in #67

None would have failed an import — each resolves:

#67 correct why
i-lucide-rotate-cw → RefreshIcon i-lucide-rotate-ccw upstream's reload is counter-clockwise
i-lucide-circle-check → CircleCheckIcon i-lucide-copy-check copyCheck's lucide name
i-lucide-refresh-cw → Refresh6Icon no upstream key uses it

Plus seven derivable pairs it missed: drag (grip-vertical), panelClose, panelOpen, star, stop (square), copyCheck, reload.

The one row that mattered most was in neither map

i-lucide-terminal is the only i-lucide-* literal upstream hardcodes under src/ (src/theme/prose/code-icon.ts) — the exact case this table exists for — and prose/CodeIcon.vue has answered it with file-type/TerminalIcon all along. Neither the old map nor #67 had it.

i-lucide-circle-x (upstream's error) also gains a row: our dictionary comments caution: AlertIcon with // this for error, so it was mappable, not unmappable.

Final map: 46 rows — 37 derived, 9 judgement calls, each named in the file's own $schema-note.

#67's five corrections are confirmed — but not for the reason first given

The old map disagreed with the dictionary on check, chevronDown, chevronUp, minus and x. Correcting them is right. The justification in the first draft of this PR was wrong, and is corrected:

key old map dictionary but also rendered by
i-lucide-check main/CheckIcon outline/CheckLIcon Checkbox.vue:62
i-lucide-minus actions/Minus20Icon actions/Minus30Icon Checkbox.vue:61
i-lucide-x actions/Cross20Icon outline/CrossMIcon Badge.vue:64
i-lucide-chevron-down outline/ChevronDownSIcon outline/ChevronDownLIcon Button.vue:101

Those are not glyphs the library never renders — they are glyphs it renders from components that bypass the dictionary. Roughly half the @bitrix24/b24icons-vue/… paths under src/ are hardcoded that way. Split out as #380.

The guard — test/utils/icon-map.spec.ts

The table rots silently: nothing imports it, nothing renders it, and a wrong row surfaces only as the wrong glyph in whatever gets ported next.

  1. Every derived row equals what its semantic key resolves to — the check with teeth. An explicit i-lucide-* → key table, re-resolved through the dictionary on every run.
  2. No derived row may disappear — (1) only walks rows that are present.
  3. Every value resolves in the installed @bitrix24/b24icons-vue.
  4. Every value is an icon src/ actually uses, or one of five documented legacy entries — deliberately not narrowed to the dictionary, which would reject terminal.
  5. No duplicate JSON key — the import silently keeps the last of two, discarding a possibly-correct value with no signal.
  6. Plus sorted-order, i-lucide-* shape, and non-trivial-parse checks.

Proven by mutation rather than assumed. The defect that escaped the first version — swapping i-lucide-check for another icon the dictionary genuinely uses — now fails by name:

× gives every derived name the icon its semantic key resolves to
+ "i-lucide-check: .../outline/ChevronDownLIcon (key `check` resolves to .../outline/CheckLIcon)"

and so do a deleted row, a nonexistent path, an unused icon, and a right-name/wrong-group value.

PORTING.md

§1 gains the icons note: most icon diffs are 1:1 because the keys are shared, so icons.<key> ports unchanged — with the caveat that the dictionary is the declared authority, not the only one, so check the component too (#380). It also states plainly that the guard catches wrong rows, not stale ones: nothing here notices if upstream renames a default, so re-verify when a port touches an icon key.

No .sync/log/ or ledger entry — this is not a port of an upstream commit, same as #343, #346, #351 and #377.

Review round

Five specialist reviews plus /code-review. Substantive findings, all fixed here: the false "does not render" claim; the guard not checking the derivation; the missing terminal row and the too-narrow allowlist that would have rejected it; the self-contradicting $schema-note; error listed as unmappable. Smaller ones: stray values now report their key, duplicate keys are caught, a bundled assertion is split, a redundant cast dropped.

Security review: no meaningful impact — require.resolve locates without loading, and neither .sync/ nor test/ ships in the package.

Follow-ups split out rather than folded in: #380 (components bypass the dictionary — needs a design call, changes what users see) and #381 (the icons integration docs are stale against icons.ts, which asks to keep them in sync).

Verify (CI=true)

lint · typecheck · test · build — all green. Tests 6218 passed | 6 skipped across 266 files.

…ard it

Verifies and lands the icon-map content from the closed PR #67, rebuilt rather
than imported.

The map is now **derived**. b24ui and nuxt/ui define the same semantic icon
keys — `src/runtime/dictionary/icons.ts` here, `src/theme/icons.ts` upstream —
so for each of the 37 keys both sides carry, the entry is (upstream's lucide
name -> b24ui's component). Nothing is chosen by eye.

That turned up three wrong values in #67, none of which would have failed an
import because each resolves to a real icon:

- `i-lucide-rotate-cw` where upstream's `reload` is `i-lucide-rotate-ccw`
- `i-lucide-circle-check` for `copyCheck`, whose lucide name is
  `i-lucide-copy-check` (`circle-check` is upstream's `success`, a key we do
  not have)
- `i-lucide-refresh-cw`, which no upstream key uses

and seven derivable pairs it had missed: `drag`, `panelClose`, `panelOpen`,
`star`, `stop`, `copyCheck`, `reload`.

The five entries #67 dropped (`activity`, `arrow-up-to-line`, `house`,
`settings`, `user`) are kept — they match no key on either side, which is
exactly the hardcoded-literal case this table exists for.

#67's five corrections to the committed values are confirmed: `check`,
`chevronDown`, `chevronUp`, `minus` and `x` all pointed at glyphs this library
does not render for those roles.

`test/utils/icon-map.spec.ts` holds the result: every value must resolve in the
installed `@bitrix24/b24icons-vue`, must come from the dictionary (or a
five-entry documented exception list), and must not pick the wrong group for a
name the dictionary already places — 233 icon names exist in more than one
group and 16 of those are in this map, so `solid/RefreshIcon` for
`outline/RefreshIcon` is a live failure mode, not a hypothetical.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JS8ypVfQSFzYVZzkTHhURb
Review follow-up on this branch. Three of the findings were substantive.

**The recorded justification was false.** The changelog said the five corrected
values pointed at "glyphs the library does not render for those roles". It
does render them: `Checkbox.vue` renders `main/CheckIcon` and
`actions/Minus20Icon`, `Badge.vue` renders `actions/Cross20Icon`, `Button.vue`
renders `outline/ChevronDownSIcon`. Roughly half the icon paths under `src/`
are hardcoded in components that never read the dictionary. The corrections
stand — the map must agree with the dictionary — but the reason is restated,
and the underlying inconsistency is now #380.

**The guard did not check the derivation.** Pointing `i-lucide-check` at any
other icon the dictionary genuinely uses passed every assertion. There is now
an explicit `i-lucide-* -> semantic key` table, and each derived row must equal
what that key resolves to; a row disappearing fails too.

**The map was missing the one literal it exists for.** `i-lucide-terminal` is
the only `i-lucide-*` string upstream hardcodes under `src/`, and
`prose/CodeIcon.vue` has answered it all along. The old dictionary-only
allowlist would have rejected it, so the guard's basis widens to any icon used
anywhere in `src/`.

Also: `i-lucide-circle-x` gains a row (our `caution` is commented "this for
error"); the `$schema-note` no longer contradicts itself about `circle-check`;
duplicate JSON keys, which the import silently collapses, are now caught; stray
values report their key; a redundant cast is gone; and both the note and the
changelog now say the guard catches wrong rows, not stale ones.

Not changed: the 37-pair / 43x39 counts. Three reviewers read the installed
`@nuxt/ui@4.8.2` — pulled in transitively by `nuxtseo-layer-devtools`, older
than the sync cursor — and concluded `star` was fabricated. Upstream at
`3dbca02` has it. That trap is now written down.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JS8ypVfQSFzYVZzkTHhURb
@IgorShevchik
IgorShevchik merged commit af5dd57 into main Aug 13, 2026
1 check passed
@IgorShevchik
IgorShevchik deleted the chore/sync-icon-map branch August 13, 2026 06:27
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.

2 participants