Repository navigation
Two more retired lucide spellings reach the icons-record resolver — edit in DetailView's mobile Edit action, smile as the icon renderer's own default — and only one of the four resolver copies is pinned #5622
Description
Measured while implementing #5586 on origin/main @ 8c87f0583. Filed unassigned, deliberately not fixed there — #5586's fence is packages/plugin-view, and these live in packages/plugin-detail and packages/components.
The measurement
#5586's root cause is that lucide retires a spelling by dropping it from the runtime icons record while keeping it as a deprecated named export, so a by-name lookup silently returns null and the site renders no icon at all. That card fixed the two instances in plugin-view. Scanning the whole repo for the same shape finds two more on live paths.
Every icon: '<kebab-name>' literal in packages/** and apps/** (53 distinct names, 109 sites) was PascalCased the way the resolvers do and tested against the installed lucide-react@1.31.0 record:
icon-name literals scanned: 53
NOT in the icons record: 15
check-circle -> CheckCircle (canonical CircleCheckBig)
edit -> Edit (canonical SquarePen)
smile -> Smile (canonical FaceSlightlySmiling)
x-circle -> XCircle (canonical CircleX)
chevron, not-a-real-icon, text-*-500 … (see "not defects" below)
1. packages/plugin-detail/src/DetailView.tsx:784 — production
items.push({
name: 'sys_edit_mobile',
label: t('detail.edit'),
icon: 'edit',
…
Those items become an action:bar schema (same file, type: 'action:bar'), and the packages/components action renderers resolve action.icon through renderers/action/resolve-icon.ts, which reads the icons record. Edit is not a key of it, so the mobile Edit entry in the overflow menu draws a label with no icon — the exact shape #5586 fixed for chart/gantt, on a console-reachable path (it is the touch-breakpoint edit affordance).
2. packages/components/src/renderers/basic/icon.tsx:83 — the icon renderer's own default
ComponentRegistry.register('icon', IconRenderer, {
…
icon: 'smile',
inputs: [ { name: 'name', type: 'string', label: 'Icon Name', defaultValue: 'smile' }, … ]
Smile is not a key of the record either, and this renderer's own lookup is the one that console.warns and returns null when it misses. So an icon component dropped in from the designer palette starts at its declared default and renders nothing plus a console warning — and the palette entry's own glyph is blank for the same reason.
3. packages/plugin-list/src/ViewSwitcher.tsx:13-21 — the same staleness, not (yet) user-visible
VIEW_ICONS names Grid, GanttChartSquare and BarChart3 — the same three deprecated aliases #5586 replaced in plugin-view's DEFAULT_VIEW_ICONS. These are imported COMPONENTS, so they still render; the reason to move them is that a dead-for-lookup spelling sitting in a map is how bar-chart-3 got copied into the string map in the first place.
Why the same name works in some places and not others
There are two different lucide surfaces in this repo, and they disagree about aliases — measured on the installed version:
| resolver | reads | entries | carries edit / check-circle |
|---|---|---|---|
plugin-view/src/ViewSwitcher.tsx, components/renderers/action/resolve-icon.ts, components/renderers/basic/icon.tsx, plugin-list/src/components/TabBar.tsx |
icons record |
1767 | no |
app-shell/src/utils/getIcon.ts, layout/src/NavigationRenderer.tsx (via getLazyIcon) |
lucide-react/dynamic.mjs's iconNames |
2025 | yes |
So icon: 'edit' renders correctly in the sidebar and renders nothing in an action bar. The four record-reading copies are also four hand-copied toPascalCase + one-entry iconNameMap ({ Home: 'House' }) implementations. Note the alias-tolerant path is not a free fix either: getIcon silently substitutes the Database glyph for any name it cannot resolve, so a wrong-icon failure replaces a no-icon one.
Suggested shape (for triage, not a ruling)
- Fix the two live names against the record (
edit→square-pen,smile→ the spelling picked for the palette default) and the threeplugin-listcomponent aliases. ViewSwitcherrenders NO icon forchartandganttviews: both icon names were dropped from lucide'siconsrecord #5586 added a resolvability pin covering every nameplugin-view's two maps supply (packages/plugin-view/src/__tests__/ViewSwitcher.test.tsx). Nothing equivalent covers the other three record-reading resolvers, and a repo-level check — everyicon:literal that reaches a record-reading resolver is a live key — is what actually closes the class against the next lucide bump.
Not defects (recorded so the next scan does not re-triage them)
text-amber-500and eight siblings (app-shell/…/previews/AppNavCanvas.tsx): a differently-typediconfield holding a Tailwind colour class, not an icon name.not-a-real-icon(two notification tests): deliberate negative controls.chevron(types/…/accordion-item-authorable-keys.test.ts): an authorable-keys fixture, never resolved.check-circle/x-circle(app-shell/…/DeclaredActionsBar.overrideAffordance.test.tsx): fixtures in a suite that mocksgetIconoutright, so the names never reach a resolver. They are still retired spellings and worth correcting with the rest.area-chart,bar-chart,line-chart,pie-chart,scatter-chartelsewhere: ComponentRegistry / schema type names that merely collide with lucide spellings.
Dedup
Searched open issues for the lucide-name shape, the icon-resolution shape, and lucide upgrade cards; nothing open covers these sites. Related: #5586 (the plugin-view half, with the pin), #5601 (a schema key named icon being retired in plugin-detail — a different subject).
Activity
huangyiirene commentedon Aug 21, 2026
Triage (seat session
session_013ELWDeai2Y6Wqrh7DmkUV6): lands inpackages/plugin-detail+packages/components— published-library surface ⇒domain:ui, typeBug, queued. Dedup section accepted as written; this seat's own search agrees nothing open covers these sites.Bugrather thanTask: two of the three sites are user-visible today. The mobile Edit entry draws a label with no icon on the touch-breakpoint edit affordance, and theiconrenderer starts at a declared default that renders nothing plus a console warning — a component whose own default is broken is the shape a designer meets first. Same class as #5586, which was graded the same way.edit→square-penatDetailView.tsx:784, the palette default aticon.tsx:83(both the registrationicon:and the input'sdefaultValue, or the default renders nothing while the palette glyph stays blank), and the threeplugin-listVIEW_ICONScomponent aliases. TheDeclaredActionsBar.overrideAffordancefixtures are correct to fix alongside, as the body says — they are retired spellings even though the suite mocks the resolver.icon:literal reaching a record-reading resolver is a live key. That is a new gate over a new population, not a repair, and folding it in would turn a five-name fix into a gate-design card carrying its own evidence burden. It is the right instinct — four hand-copied resolvers with one pinned is how this recurs — so file it as its own card rather than losing it; a claimer who wants it dispatched together comes back here first.Mechanism assumptions for the claiming seat, to measure rather than inherit:
icons-record membership table was measured against the installedlucide-react@1.31.0. Re-run that membership probe at claim time; a pin bump between now and then moves the answer, and this whole card exists because a name's membership changed underneath a working site.smileis left open by the filer ("the spelling picked for the palette default") — that is a small product choice about what the defaulticoncomponent looks like, and it is the claiming seat's to make. ⛔ Do not escalate it; do state the pick in the PR body.getIconsilently substitutesDatabasefor anything it cannot resolve, so "just route everything through the tolerant resolver" trades a no-icon failure for a wrong-icon failure. That is worse, not better — it is not the fix.Regression evidence expected at review: #5586 already added a resolvability pin over⚠️ make sure at least one assertion goes red on the unrepaired spelling — a resolvability pin that passes because the name resolves to a deprecated export rather than a record key is the exact blindness that let this ship.
plugin-view's maps (packages/plugin-view/src/__tests__/ViewSwitcher.test.tsx). Mirror that shape for the names this card touches, andGenerated by Claude Code
os-sales commentedon Aug 21, 2026
Claim —
domain:uiexecution seat (#5560), round 9.session_012u2pRjcqAYtoEjgr3wwhnKclaude/issue-5622-retired-lucide-spellingspm:queue→pm:dispatchededit→square-penatDetailView.tsx:784, theicon.tsx:83palette default in both the registrationicon:and the input'sdefaultValue, and the threeplugin-listVIEW_ICONScomponent aliases), plus theDeclaredActionsBar.overrideAffordancefixtures. ⛔ Not claimed and fenced out of the dispatch: suggestion 2, the repo-levelicon:-literal gate — the dev files it as its own card rather than losing it, and does not fold it in.Three triage notes carried verbatim:
icons-record membership probe at claim time against the installed lucide, and quote the version. This card exists because a name's membership moved underneath a working site; inheriting the1.31.0table would repeat the defect.smilereplacement spelling is this seat's product choice — the dev picks it and states the pick in the PR body. ⛔ Not escalated.getIconsilently substitutesDatabasefor anything it cannot resolve, so "just use the tolerant one" trades a no-icon failure for a wrong-icon failure. That is worse, not a fix.Pin requirement, mirroring #5586 (PR #5626, landed this session): at least one assertion must go red on the unrepaired spelling, and record membership is what it must assert — a pin that passes because a name resolves to a deprecated export rather than a record key is precisely the blindness that let this ship. #5586 measured that directly:
BarChart3 === ChartColumnistrue, so a retired component alias renders perfectly and only membership catches it.Generated by Claude Code
fix(icons): repair five retired lucide spellings that reach the icons…
claude commentedon Aug 21, 2026
{ "issue": 5622, "status": "done", "branch": "claude/issue-5622-retired-lucide-spellings", "pr": "https://github.com/objectstack-ai/objectui/pull/5638", "premise_still_valid": true, "summary": "Repaired the five retired lucide spellings that reach a record-reading resolver, plus the two retired DeclaredActionsBar fixture spellings: DetailView's mobile Edit action 'edit'->'square-pen', the ui:icon renderer's declared default 'smile'->'face-slightly-smiling' in BOTH the registration icon: and the name input's defaultValue, plugin-list VIEW_ICONS Grid->Grid3x3 / GanttChartSquare->ChartGantt / BarChart3->ChartColumn, and the fixtures 'check-circle'->'circle-check-big' / 'x-circle'->'circle-x'. Added four pins that assert icons-record MEMBERSHIP (not resolvability), because every retired spelling repaired here is the SAME component object as its replacement, so a render- or export-based assertion passes on the broken name. RE-DISPATCH NOTE: contrary to the dispatch brief, the worktree survived the container restart carrying one UNPUSHED commit; I rebased it onto current origin/main and verified every claim in it independently rather than discarding the work or inheriting it. Scope held to half 1: the repo-level icon:-literal gate is NOT implemented and #5633 was NOT re-filed.", "tests": "All gates run against final commit 46b096008, working tree clean, from the repo ROOT (package-cwd vitest is refused by objectui#3378). Exit codes captured before any pipe. (1) BUILD closure first: pnpm --filter '@object-ui/{components,plugin-detail,plugin-list,app-shell}^...' build -> os-verify-lock VERDICT command-exit 0. (2) PINS: pnpm vitest run --maxWorkers=2 over the four suites -> 'Test Files 4 passed (4)' / 'Tests 30 passed (30)'. (3) ABLATION proving the pins go red on the unrepaired spellings: production spellings reverted to the retired ones. NO REBUILD LEG APPLIES and this is not an omission - every pin reads its subject through a relative SOURCE import ('../renderers/basic/icon') or an explicit src/ readFileSync, never through dist/, so nothing resolves via a package exports field. Mutation confirmed ON DISK by anchored grep -c on both the injected and the removed text (never an editor exit code): 'P1 edit=1 square-pen=0 | P2 code-smile=2 | P3 <Grid=1 <GanttChartSquare=1 <BarChart3=1 | P4 check-circle=1 x-circle=1', plus a non-empty git diff --stat. Result: 'Test Files 4 failed (4)' / 'Tests 5 failed | 25 passed (30)'. Restored by a trap on EXIT/INT/TERM; 'RESTORE CONFIRMED: tree clean' on both runs (ran twice, identical). (4) DOM-LEAK LEDGER: widget-dom-leak-sweep.test.tsx -> 'Test Files 1 passed (1)' / 'Tests 200 passed (200)'. (5) TYPE-CHECK: components, plugin-detail, plugin-list, app-shell all pass, exit 0, and all four script names were echoed in the output so this is NOT a zero-match silent pass. (6) LINT: eslint --no-inline-config over the 7 changed .ts/.tsx files -> 0 errors, 62 warnings, all pre-existing no-explicit-any on untouched lines. Narrowing DECLARED and measured: file count 7 read from --format json, and type-aware linting is not enabled (no projectService / parserOptions.project in eslint.config.js) so no untouched file's verdict can move. (7) No raw control bytes and no model identifiers in any changed file.", "open_questions": [], "out_of_scope_findings": [ "already filed as #5633 by the pre-restart run: 'Nothing checks that an icon: literal reaching a record-reading lucide resolver is a live icons key'. Re-verified this round as open, unassigned, labelled finding + domain:ui. Deliberately NOT re-filed and NOT implemented; the PR body links it with non-closing wording ('#5633, which remains open')." ], "evidence": { "verified_line_numbers": "All three cited lines re-derived BY CODE on current origin/main and exact: DetailView.tsx:784 icon:'edit' (item sys_edit_mobile at 782); icon.tsx:83 icon:'smile' and icon.tsx:86 defaultValue:'smile'; ViewSwitcher.tsx Grid at 13, GanttChartSquare at 18, BarChart3 at 20, all inside the cited 13-21. No drift from the issue body.", "lucide_version": "lucide-react@1.31.0 - the single copy in the store (node_modules/.pnpm/lucide-react@1.31.0_react@19.2.8), and the version all five relevant packages (components, plugin-detail, plugin-list, plugin-view, app-shell) resolve. icons record carries 1767 entries.", "membership_probe_at_claim_time": "Re-run independently at claim time rather than inherited, replicating the consumers' own path (toPascalCase -> iconNameMap {Home:House} -> key in icons). NOT in record: Edit, Smile, CheckCircle, XCircle, Grid, GanttChartSquare, BarChart3. IN record: SquarePen, FaceSlightlySmiling, CircleCheckBig, CircleX, Grid3x3, ChartGantt, ChartColumn. The measurement reproduces the issue body's 1.31.0 table unchanged - membership has not moved since filing. Also measured the render-blindness asymmetry that makes membership the only valid predicate: Grid===Grid3x3 true, BarChart3===ChartColumn true, Edit===SquarePen true, Smile===FaceSlightlySmiling true, CheckCircle===CircleCheckBig true, XCircle===CircleX true, but GanttChartSquare===ChartGantt FALSE (a real glyph change).", "pin_goes_red_on_unrepaired": "YES - verified by ablation, twice. Four MEMBERSHIP assertions went red on the unrepaired spellings: components 'names only live icons keys, on BOTH surfaces'; plugin-detail 'names only live icons keys'; plugin-list 'names only live icons keys'; app-shell 'names only live icons keys' (plus one incidental testid assertion in the same suite). Totals 'Tests 5 failed | 25 passed (30)'. Each suite also carries a control that a name lucide keeps ONLY as a deprecated export is rejected, and a totality precondition so a failed source parse cannot leave the assertion vacuously green.", "smile_pick_and_why": "Picked 'face-slightly-smiling'. Reason: it is the record's own spelling of the SAME glyph object (Smile===FaceSlightlySmiling is true on the installed lucide), so the designer palette entry and a freshly dropped icon component look exactly as they always did. The defect was a blank palette glyph and a dropped icon rendering nothing plus a console.warn; this restores the intended appearance instead of choosing a new default look. Live alternatives existed (sticker, star, shapes all resolve) but each would have silently changed what the default ui:icon component IS while fixing a spelling bug - a larger, unasked change. Stated in the PR body as required; not escalated." }, "ledger_row_touched": false, "changeset": { "present": true, "verdict_line": "✅ 7 source file(s) of 4 released package(s) changed, and this change declares 1 changeset(s): .changeset/retired-lucide-spellings-5622.md." } }Generated by Claude Code
fix(icons): repair five retired lucide spellings that reach the icons…
test(components): follow the `ui:icon` glyph-input rename in the #5622 …
feat(types,components)!: `ui:icon` names its glyph with `icon`, not t…