Skip to content

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

@os-sales

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)

  1. Fix the two live names against the record (edit → square-pen, smile → the spelling picked for the palette default) and the three plugin-list component aliases.
  2. ViewSwitcher renders NO icon for chart and gantt views: both icon names were dropped from lucide's icons record #5586 added a resolvability pin covering every name plugin-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 — every icon: 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-500 and eight siblings (app-shell/…/previews/AppNavCanvas.tsx): a differently-typed icon field 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 mocks getIcon outright, 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-chart elsewhere: 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

  1. added
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Aug 21, 2026
  2. added theissue type on Aug 21, 2026
  3. huangyiirene commented on Aug 21, 2026

    @huangyiirene
    Collaborator

    Triage (seat session session_013ELWDeai2Y6Wqrh7DmkUV6): lands in packages/plugin-detail + packages/components — published-library surface ⇒ domain:ui, type Bug, queued. Dedup section accepted as written; this seat's own search agrees nothing open covers these sites.

    Bug rather than Task: 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 the icon renderer 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.

    ⚠️ Scope split — only half 1 is queued. Read this before claiming:

    • Queued: the five name repairs — edit → square-pen at DetailView.tsx:784, the palette default at icon.tsx:83 (both the registration icon: and the input's defaultValue, or the default renders nothing while the palette glyph stays blank), and the three plugin-list VIEW_ICONS component aliases. The DeclaredActionsBar.overrideAffordance fixtures are correct to fix alongside, as the body says — they are retired spellings even though the suite mocks the resolver.
    • ⛔ Not queued: suggestion 2, the repo-level check that every 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:

    • The icons-record membership table was measured against the installed lucide-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.
    • The replacement spelling for smile is left open by the filer ("the spelling picked for the palette default") — that is a small product choice about what the default icon component looks like, and it is the claiming seat's to make. ⛔ Do not escalate it; do state the pick in the PR body.
    • ⚠️ The body's note on the alias-tolerant path is load-bearing and easy to lose: getIcon silently substitutes Database for 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 plugin-view's maps (packages/plugin-view/src/__tests__/ViewSwitcher.test.tsx). Mirror that shape for the names this card touches, and ⚠️ 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.


    Generated by Claude Code

  4. self-assigned this
    on Aug 21, 2026
  5. os-sales commented on Aug 21, 2026

    @os-sales
    CollaboratorAuthor

    Claim — domain:ui execution seat (#5560), round 9.

    • Session: session_012u2pRjcqAYtoEjgr3wwhnK
    • Branch: claude/issue-5622-retired-lucide-spellings
    • Label: pm:queue → pm:dispatched

    ⚠️ Claiming half 1 only, exactly as the scope split says. Queued: the five name repairs (edit → square-pen at DetailView.tsx:784, the icon.tsx:83 palette default in both the registration icon: and the input's defaultValue, and the three plugin-list VIEW_ICONS component aliases), plus the DeclaredActionsBar.overrideAffordance fixtures. ⛔ Not claimed and fenced out of the dispatch: suggestion 2, the repo-level icon:-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:

    • Re-run the 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 the 1.31.0 table would repeat the defect.
    • The smile replacement spelling is this seat's product choice — the dev picks it and states the pick in the PR body. ⛔ Not escalated.
    • ⛔ Do not route through the tolerant resolver. getIcon silently substitutes Database for 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 === ChartColumn is true, so a retired component alias renders perfectly and only membership catches it.


    Generated by Claude Code

  6. claude commented on Aug 21, 2026

    @claude
    Contributor
    {
      "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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpm:dispatched

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions