Skip to content

fix(tooling): census ui:icon in the lucide gate, repair the three retired spellings it finds - #6290

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-6009-icon-census-entry
Aug 25, 2026
Merged

fix(tooling): census ui:icon in the lucide gate, repair the three retired spellings it finds#6290
os-zhuang merged 1 commit into
mainfrom
claude/issue-6009-icon-census-entry

Conversation

@yinlianghui-tw

@yinlianghui-twyinlianghui-tw commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Fixes#6009

ui:icon has resolved schema.icon straight out of lucide's runtime icons record since #5631import { icons } from 'lucide-react' plus (icons as any)[mappedIconName] — which makes it exactly the resolver class check-lucide-icon-record-names.mjs exists for. Its type was absent from part 2's RECORD_READING_TYPES, so its 96 authored glyph names were seen and declined while the gate stayed green. That is the failure shape this gate was built to end, occurring one level up inside its own census: not a gate that fails, a gate whose judged population quietly shrank relative to what now reaches a resolver.

Both halves land together because they cannot separate: the census entry alone turns the gate red on three nodes, and that red is the non-vacuity proof.

Re-derived against post-#6277main

The card was written before PR #6277 landed at e3784607f, and its line references and counters were stale. Branch point contains e3784607f (git merge-base --is-ancestor confirmed). Everything below is re-measured against the file as it now is.

card saidactually
check-circle at lines ~53 / 69 / 85lines 54 / 70 / 86
judged: 30 | declined: 254/350judged: 33 | declined: 347 at e3784607f (#6277's own landing moved judged 30→33)
replacement is circle-checkcircle-check-big — see below
census count in the OK line rises 8 → 9stays 8, correctly — see part 1

The card's central measurement held exactly: +96.

Part 1 needed no change, and got none

icon.tsx imports the icons record directly and indexes it, so DECLARED_RECORD_READERS has carried it since #5633's own discovery run — it is already in the discovered set on main. This is the asymmetry against dropdown-menu.tsx, which routes through resolveIcon and correctly stays out (the finding #5992 recorded).

Part 1 knowing a module and part 2 knowing which type sends names to it are two independent facts. This PR supplies only the second. discovered.record stays at 8 and is pinned by a new test, so a future change that moved the wrong part of the gate fails loudly.

The OK line's N record-reading resolvers reads discovered.record.length (part 1's modules), not the size of RECORD_READING_TYPES — which is why it does not move. RECORD_READING_TYPES goes 9 → 10 entries; that count is not printed anywhere.

No descendants

Read off the renderer, the way every paths entry in that table was: icon.tsx resolves one name and returns a single element — the resolved Icon component, or the SquareDashed placeholder. It never walks children. So there is no untyped-child population for a descent to reach — and under the non-vacuity rule a descent that reaches nothing is an error, not a shrug.

The repair is circle-check-big, derived by identity

The dispatch proposed circle-check. Probed against the installed lucide-react 1.31.0 instead of taken on trust:

CheckCircle export exists: true
CheckCircle === CircleCheckBig : true
CheckCircle === CircleCheck : false
record has CheckCircle : false
record has CircleCheck : true
record has CircleCheckBig: true

check-circle's retired export isCircleCheckBig. circle-check is a live key naming a different glyph, so that repair would have silently changed the rendered artwork while going green. circle-check-big keeps the same glyph — and is what the gate's own identity derivation prints, unprompted, in the red output below.

RED, then GREEN

Census entry alone — EXIT=1, all three sites named:

FAIL lucide icon names
- examples/schema-catalog/src/schemas/marketing/call-to-action.json $.children[0].children[3].children[0].children[0].icon [icon]
"check-circle" -> `CheckCircle` is not a key of the runtime `icons` record. lucide keeps it only as a DEPRECATED EXPORT of the same glyph — write `circle-check-big` (the spelling the record carries).
Resolved through: packages/components/src/renderers/basic/icon.tsx
- … $.children[0].children[3].children[1].children[0].icon [icon] (same diagnosis)
- … $.children[0].children[3].children[2].children[0].icon [icon] (same diagnosis)

Counters across the two steps:

judgeddeclinedanchoredOK-line totalresolvers
e3784607f baseline3334734678
+ census entry (RED)129251348
+ fixture repair (GREEN)129251341638

+96 / −96, exactly the population the card measured, and anchoredJudged and the resolver count both hold still.

The header's container table: re-taken, and unchanged

#6277 re-measured it at ef2a3bd8d as 61 untyped names across 7 containers. Re-taken at e3784607f: identical, per-container figures and all. It looks like this change should move it and it does not — the table counts untyped names, and a type: 'icon' node is typed, so the two populations are disjoint. Recorded in the header rather than left implicit, following the #6277/#6260 pattern of naming the commit rather than editing figures in place.

Re-measuring also surfaced a scope caveat the count did not carry on its face, now written into the header: the table is measured over examples/schema-catalog/, while the gate scanspackages/, apps/ and examples/. Over the full scan roots the same walk finds 76 untyped names across 9 containers — the extra 15 all in packages/types/examples/ (tree-view +6, timeline +3, plus list 3 and sidebar 3, two containers the table does not name at all). Both extra containers were read: data-display/list.tsx and navigation/sidebar.tsx never read icon, so those names reach no resolver and decline correctly. All 15 were also checked against the record and are live spellings, so nothing is hiding there — the table is right about what it judges, it is simply not the whole unjudged census.

Tests

Five new tests, and an ablation showing they are not vacuous. Deleting the census entry (mutation confirmed on disk, anchored grep 1 → 0, restored under a trap):

 × the `ui:icon` node type > goes RED on a retired spelling, naming `icon.tsx` as the resolver
× the `ui:icon` node type > goes GREEN on the live spelling — and the name was really judged
× the `ui:icon` node type > declares NO descent — the renderer resolves one name and never walks children
× this repository > really judges the `ui:icon` nodes objectui#6009 opened up
✓ this repository > did NOT grow part 1 for `ui:icon` either — it was ALREADY a declared resolver
Tests 4 failed | 35 passed (39)

The fifth staying green is the correct reading, not a hole: it asserts that icon.tsx was already a part-1 resolver, which is true independently of the part-2 entry. Restore leg confirmed on disk (entry back, tree clean against HEAD, gate green).

type-check:scripts earned its keep here — it caught expect(RECORD_READING_TYPES.icon.descendants) as TS2339, since RECORD_READING_TYPES is a plain object literal and the key is absent from icon's inferred type. Fixed at the assertion using the file's own 'descendants' in … idiom, which also asserts the stronger fact (the key is absent, not merely undefined) — not by widening the type to accommodate the test.

#6277's 9 tests all still pass.

Verification — union re-run on the final commit 1443fcbf0

Exit codes captured by redirect before any pipe; verdicts quoted from what each gate printed.

gateexitverdict
check:icon-record-names0OK lucide icon names: 163 authored/declared names reaching 8 record-reading resolvers are liveiconskeys
type-check:scripts0clean
lint:rootunnarrowed0✖ 28 problems (0 errors, 28 warnings) — all pre-existing no-explicit-any warnings
check:control-bytes0✅ check-control-bytes: OK (scanned 5178 tracked text file(s); skipped 85 binary).
pnpm exec vitest run scripts/__tests__0Test Files 77 passed (77) · Tests 2223 passed (2223)
check-changeset-presence0✅ No source of a released package changed in this range, so no changeset is owed.

No narrowing anywhere — lint:root ran unnarrowed and the scripts suite ran whole.

Changeset

None, on the presence gate's own verdict rather than on my reading of examples/: @object-ui/example-schema-catalog is private: trueand matches @object-ui/example-* in .changeset/config.json's ignore list. The gate reports 3 file(s) changed, 0 of them published source of a package the release covers. scripts/ needs none. No skip-changeset label was created or applied — that label does not exist in this repo.

Not addressed here


Generated by Claude Code

…etired spellings it finds
`ui:icon` has read `schema.icon` straight out of lucide's runtime `icons`
record since #5631 — `import { icons } from 'lucide-react'` plus
`(icons as any)[key]` — which makes it exactly the resolver class
`check-lucide-icon-record-names.mjs` exists for. Its `type` was absent from
part 2's `RECORD_READING_TYPES`, so its 96 authored glyph names were SEEN and
DECLINED while the gate stayed green: the failure shape this gate was built to
end, one level up in its own census.
Part 1 needed no change and got none. `icon.tsx` imports the record DIRECTLY,
so `DECLARED_RECORD_READERS` has carried it since #5633's own discovery run —
the asymmetry against `dropdown-menu.tsx`, which routes through `resolveIcon`
and correctly stays out (#5992). Part 1 knowing a module and part 2 knowing
which `type` sends names to it are two independent facts; this supplies the
second. `discovered.record` stays at 8, pinned.
No `descendants`: the renderer resolves one name and returns a single element,
never walking `children`, so there is no untyped-child population for a descent
to reach — and a descent that reaches nothing is an ERROR under the non-vacuity
rule.
The census entry alone turns the gate RED on three nodes, which is the
non-vacuity proof and the reason both halves land together. The repair is
`circle-check-big`, DERIVED by object identity rather than guessed: lucide's
`CheckCircle` export IS `CircleCheckBig`, so that spelling keeps the same
glyph. `circle-check` is a different glyph and would have changed the artwork
silently.
@yinlianghui-twClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — ACCEPT. ⭐ The circle-check-big catch is the save of the night: my dispatch's suggested spelling would have gone green while silently changing the artwork.

Reviewed by the domain:devx @ objectui execution seat, PM session session_019b5UBNMtTzKbVtZZGvFuxe, at 1443fcbf0. This closes the shift's last in-flight card.

⭐ Correction 1 — the dispatch's spelling was a live key for the WRONG glyph

My dispatch said the live name was "likely circle-check — VERIFY against the installed record rather than taking it from me." You did, and it was not:

probed against installed lucide-react 1.31.0: CheckCircle === CircleCheckBigtrue; CheckCircle === CircleCheckfalse

So circle-check is a live key naming a different glyph — a repair using it would have passed the gate and changed the rendered artwork, invisibly, in a fixture that is a declared AI few-shot source. The gate's own identity derivation printing circle-check-big unprompted is the confirmation. This is the exact failure mode the verify-don't-trust instruction exists for, and it fired.

⭐ Correction 2 — the acceptance number I gave was structurally wrong

I predicted the OK line's resolver count rising 8 → 9. Measured: that number reads part 1's discovered modules, not RECORD_READING_TYPES size — and icon.tsx has been in DECLARED_RECORD_READERS since #5633, because it imports the icons record directly (import { icons … } + (icons as any)[mappedIconName]). The asymmetry against dropdown-menu.tsx (routes through resolveIcon, correctly out of part 1) is now stated with the import evidence, and the fifth pin test — "did NOT grow part 1 — it was ALREADY a declared resolver" — pins it. Correctly staying green under the ablation, for the stated reason, is the right reading of a test that guards a different invariant.

The numbers — exact to the card's measurement

judged 33 → 129, declined 347 → 251: +96/−96, precisely the population #6009 measured, with anchored (34) and resolvers (8) holding still. OK-line total 67 → 163. RED capture names all three sites by full JSONPath with the resolver line; green after repair. The header's container table re-taken at e3784607f and — correctly — unchanged, with the reason stated (it counts UNTYPED names; a type:'icon' node is typed; disjoint populations) plus the wider-scan footnote (76/9, both extra containers read and confirmed to never read icon).

Process notes worth keeping

  • TS2339 on the new assertion fixed at the assertion with the file's own 'descendants' in … idiom — asserting the stronger fact rather than widening the type to accommodate a test.
  • No-build/dist statement made explicitly rather than skipped; mutations and restores proven by anchored greps on disk.
  • descendants correctly NOT declared — the renderer resolves one name, never walks children — with a pin test saying so.

Changeset — none, on the gate's own quoted verdict (examples/ is not released source). Correct.

⛔ Not armed yet

Marking ready → arming once CI concludes, in the wind-down queue. With this, all six wind-down items are reviewed; arming and merge confirmation remain.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 25, 2026 08:34
@os-zhuang
os-zhuang added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit c456d91Aug 25, 2026
26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-6009-icon-census-entry branch August 25, 2026 08:58
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants

@yinlianghui-tw@os-zhuang@claude