chore(deps): lucide-react 1.31.0 -> 1.43.0, with the one retired spelling repaired - #8941
chore(deps): lucide-react 1.31.0 -> 1.43.0, with the one retired spelling repaired#8941os-zhuang wants to merge 4 commits into
Conversation
…tired spelling Supersedes the stale Dependabot branch for this bump (objectui#7058), whose base was weeks behind `main`. Measured against the installed artifact, not the changelog: across the whole 1.31.0 -> 1.43.0 jump lucide removes ZERO public names from `lucide-react` (6072 -> 6230 d.ts names, none dropped; 6068 -> 6224 runtime exports, none dropped) and ZERO names from `lucide-react/dynamic.mjs`. Exactly one key leaves the runtime `icons` record: `Trash2`, which upstream retired in favour of `trash` (lucide-icons/lucide#3141). It survives as a DEPRECATED EXPORT of the same object, so nothing breaks at a named import or in the type checker -- only STRING lookups through `resolveIcon`, which read record membership. Repaired, all four sites the gate names: - examples/schema-catalog .../action-button-variants.json - examples/schema-catalog .../confirmation-dialog.json - examples/schema-catalog .../profile-detail-card.json - packages/plugin-detail/src/DetailView.tsx `trash` is the visually lossless replacement: lucide 1.43.0's `trash` icon node is byte-identical to 1.31.0's `trash-2` icon node (control: `pencil` identical across both versions). Also re-anchored the two ranges that quote the workspace: - packages/cli/src/utils/app-generator.ts (app-generator.test.ts anchor) - skills/objectui/guides/plugin-development.md (doc-version-claims anchor) and the four doc/comment examples that taught the retired spelling. The lockfile is regenerated from current `main`, so the @objectstack 17.4.0 -> 17.3.0 downgrade, the zod 4.4.3/4.5.4 split and the resulting duplicated vendor chunk that reddened objectui#7058 are all absent here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
…alias classes retired
lucide-react 1.43.0 reshaped `createLucideIcon` to take an icon-data object
carrying `aliases`, and it now emits one CSS class per retired alias. Measured
on the installed artifact: 248 of 1818 icon modules carry an alias; 1.31.0 icon
modules have no `aliases` field at all.
Censused the whole tree for the two shapes this can break, using the alias
index as the oracle: 28 distinct `lucide-*` class names are referenced anywhere
in the repository (all file types, snapshots included) and exactly two of them
are aliases in 1.43.0 -- `lucide-home` (owned by `house`) and `lucide-loader-2`
(owned by `loader-circle`). Both are repaired here; the other 26 still name a
canonical icon and stay discriminating. Control: injecting the known alias
`trash-2` into the census is found, and `check` is correctly absent.
- examples/schema-catalog svg-host-dom-leak-5632: asserts the EXACT attribute
set, so the added class has to be named. Kept exact on purpose -- that list
is what catches an authored prop reaching the DOM.
- components button-shared-icon-resolver: its negative half read
`querySelector('svg.lucide-home')` is null to prove `home` resolved through
the rename to House. `house` now emits `lucide-home` itself, so that class
no longer says which glyph was drawn. Replaced with a form that still fires
for the same defect: one svg, and the alias class on that same element.
Also adds the changeset the four touched released packages owe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
Conflicts, both taken deliberately:
- apps/site/package.json — kept our `lucide-react: ^1.43.0` AND main's
`next: 16.3.3`. Neither side's other change is dropped.
- pnpm-lock.yaml — took main's file and regenerated it with `pnpm install`
rather than hand-merging YAML. Verified after: exactly one
`lucide-react@1.43.0`, and every `@objectstack/*` package resolves to a
single 17.4.0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
❌ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. Which half objected:
📦 Bundle Size Report
Size Limits
|
…de 1.43.0
Measured on this branch's console build: `ui-components` is 410,904 bytes
gzipped against a 399,000 ceiling -- over by 11,904.
Ruled out as a tree-shaking regression BEFORE the number moved, because "the
bundler stopped shaking" and "the dependency got bigger" want opposite fixes
and only one of them is a ceiling:
- ALL 1,818 keys of lucide 1.43.0's runtime `icons` record appear verbatim in
the built chunk. Of 1.31.0's 1,767 keys, 1,766 appear in the same chunk --
the one absent is `Trash2`, the key lucide retired, which is what shows the
probe discriminates rather than matching everything. A fabricated key is
not found.
- Icons therefore do not tree-shake here and did not before. That is
deliberate: `resolve-icon.ts` imports the whole record because string
lookups read record MEMBERSHIP (the objectui#5935 seam). Moving it to
`lucide-react/dynamic.mjs` to shed bytes would resolve the very names the
record drops -- `trash-2`, `edit`, `smile` -- which the icon gate's own
header calls worse than having no gate.
- The library is bigger two ways. Same-artwork control: `pencil.mjs`, whose
icon node is byte-identical across versions, goes 455 -> 522 bytes because
1.43.0 reshaped every icon module into an `__iconData` object carrying
`name`, `size` and `aliases`. And there are more icons: the icon directory,
licence headers stripped and gzipped, goes 166,705 -> 176,968 bytes
(+10,263, +6.2%) -- the same order as the chunk delta.
Ceiling and baseline move together, in this one commit. New headroom is 9,096
bytes = 0.10x REGRESSION_THIS_GATE_MUST_CATCH_BYTES, the proportion
objectui#7399 re-pinned this key to -- chosen over "just enough to pass",
because a line left with almost no headroom is the defect objectui#8816
records. REGRESSION_THIS_GATE_MUST_CATCH_BYTES did not move. Nothing else
moved: the aggregate was re-read on the same build and passes on its own
(3,589,068 of 3,597,000, headroom 7,932), so it is left alone.
The raised gate still fails: padding `ui-components` by 15,000 bytes in a COPY
of the report gives `OVER by 5.8 KB` and exit 1, while the same invocation on
an unpadded copy exits 0. The real report was never mutated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
Bundle Analysis — measured, root-caused, and the ceiling raised deliberatelyRe-measured on my own merge result ( Exactly First: is this tree-shaking, or is the library bigger?Asked before touching a ceiling, because the two want opposite fixes. It is not a tree-shaking regression. All 1,818 keys of lucide 1.43.0's runtime So icons do not tree-shake here and did not before. That is deliberate, not a defect: The library is genuinely bigger, two independent ways:
So the ceiling moves, in the objectui#8888 / objectui#8901 shape
New headroom is 9,096 bytes = 0.10x
Proof the raised gate can still failBoth legs run the same
What the bytes buy — and the part that makes this not optionalRoughly 16 KB gzipped on every console page load, for 51 new icons and a metadata reshape. On its own that is a thin trade. But the published ranges are already If the trade still reads wrong, the lever is not this ceiling: it is pinning Generated by Claude Code |
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Supersedes the stale Dependabot branch objectui#7058 (
dependabot/npm_and_yarn/lucide-react-1.35.0). That branch is not pushed to here — Dependabot force-pushes over it on every rebase — so this is a fresh branch cut from currentmain. Once this lands, objectui#7058 can be closed as superseded. It is deliberately not named with a closing keyword above; closing it is a maintainer action, not this PR's.1. Which of the seven red checks were staleness, and which were real
objectui#7058's base is
4e3a4f07e— weeks behindmain. I brought its head up to date in a throwaway worktree, re-locked from currentmain, and re-measured. Four of the seven were artefacts of the stale base; three were real.Type Checkpnpm check:icon-record-names, not attsc.tscnever ran.Test (shard 1/4)packages/cli/src/__tests__/app-generator.test.ts— the generator's literallucide-reactrange stayed at^1.31.0while the 22 sibling manifests moved.Test (shard 3/4)check-lucide-icon-record-names.test.tsREAL (same four sites).ci-cd-pipeline-doc.test.tsSTALE — the lockfile carried two@objectstack/specversions.Test (shard 4/4)doc-version-claims.test.tsREAL (skills/objectui/guides/plugin-development.mdstill taught^1.31.0).check-lockfile-integrity.test.tsSTALE — its fixture forks azod@4.4.3key the stale lockfile had replaced with4.5.4.Lockfile Integrity CheckVERDICT 12 finding(s): 7 backward move(s), 5 duplication(s)— Dependabot re-locked on an old base and dragged@objectstack/client,core,formula,lint,sdui-parser,typesfrom 17.4.0 back to 17.3.0, plus azod4.4.3/4.5.4 split.Bundle Analysisvendor-objectstack 2874.0 KB / 1224.6 KB ceiling (OVER by 1649.3 KB)— that is the duplicated@objectstacktree above, which the lockfile gate itself names as the cause: "a bundle-size red downstream of this is caused by THIS, not by the dependency being bumped."dependabotOn this branch, re-locked from current
main,node scripts/check-lockfile-integrity.mjsreportsVERDICT clean — no @objectstack/* identity moved backward and no package gained a copy.2. Root cause of what survives — measured against the installed artifact
The hypothesis in the dispatch was a broad source-compatibility break. The measurement narrows it to a single retired name, and falsifies the type-checking half entirely.
I installed 1.31.0, 1.41.0 and 1.43.0 side by side and diffed three surfaces:
lucide-reactlucide-react.d.tsiconNamesfromlucide-react/dynamic.mjsiconsrecordTrash2Then I enumerated what this repository actually imports — 4700 source files, 333 distinct value names, one type name (
LucideIcon), six namespace or default imports — and checked every one against both artefacts. Zero fail to resolve, in either version. Controls fired on the same run in both worlds:Trashresolves in both, a fabricated name resolves in neither.So nothing breaks at an import and nothing breaks in the compiler.
Trash2is still exported by 1.43.0; it is simply no longer a key of the runtimeiconsrecord, andTrash2 === Trashthere (in 1.31.0 they were distinct objects). That is exactly the classscripts/check-lucide-icon-record-names.mjsexists to catch: a retired spelling still imports, still type-checks, still renders as a COMPONENT, and resolves to nothing as a STRING.Four authored sites resolve
trash-2throughrenderers/action/resolve-icon.ts, which reads record membership:examples/schema-catalog/src/schemas/actions/action-button-variants.jsonexamples/schema-catalog/src/schemas/actions/confirmation-dialog.jsonexamples/schema-catalog/src/schemas/components-layout-card/profile-detail-card.jsonpackages/plugin-detail/src/DetailView.tsx— the record delete actionAll four now write
trash. The glyph does not change: 1.43.0'strashicon node is byte-identical to 1.31.0'strash-2icon node (control:pencilis byte-identical across both versions, so the comparison is not returning equal for everything). Upstream replaced the old bin artwork under thetrashname and kepttrash-2as a deprecated alias.3. Why 1.43.0 and not the 1.41.0 the Dependabot PR advertises
The workspace declares a caret range, so a re-lock from current
mainresolves the newest 1.x — that is 1.43.0 today, under^1.41.0just as much as under^1.43.0. Holding exactly 1.41.0 would need an exact pin or an override, which is not this repo's convention and not mine to invent. Measured, the extra two minors cost nothing in removals: 1.41.0 to 1.43.0 removes zero exports, zero record keys and zero dynamic names.It does bring one behavioural change that 1.41.0 does not have, and it is worth a maintainer's eye:
1.43.0 reshaped
createLucideIconto take an icon-data object carryingaliases, and it now emits one CSS class per retired alias. A spinner rendersclass="lucide lucide-loader-circle lucide-loader-2 ...". Measured: 248 of 1818 icon modules carry an alias; 1.31.0 icon modules have noaliasesfield at all. This is additive, so a.lucide-loader-circleselector still matches — but a selector asserting the exact, complete class string does not, and neither does an assertion that a givenlucide-class is ABSENT.I censused the whole tree for both shapes, using the alias index built from the installed artefact as the oracle: 28 distinct
lucide-class names are referenced anywhere in the repository (all file types, snapshots included) and exactly two are aliases in 1.43.0 —lucide-home(owned byhouse) andlucide-loader-2(owned byloader-circle). Controls: injecting the known aliastrash-2into the census is found;checkis correctly absent. Both affected pins are repaired here:examples/schema-catalog/test/svg-host-dom-leak-5632.test.tsxasserts the exact attribute set. The added class is named. The assertion stays exact on purpose — that list is what catches an authored prop reaching the DOM, which is the whole point of objectui#5632.packages/components/src/renderers/form/__tests__/button-shared-icon-resolver.test.tsxprovedhomeresolved through the rename toHouseby assertingsvg.lucide-homeis null.housenow emitslucide-homeitself, so that class no longer says which glyph was drawn. Replaced with a form that still fires for the same defect: exactly one svg, and the alias class on that same element. Had the rename been dropped,Homeis not a record key and the button would render no glyph at all.4. Something the bump gives back
maininstalls two physical copies of the icon library: 1.31.0 for this workspace and 1.35.0 pulled byfumadocs-ui. On this branch both collapse onto a single 1.43.0. Lockfile resolutions go 1822 to 1821.5. What was measured here
pnpm check:icon-record-names—OK ... 184 authored/declared names reaching 1 record-reading resolver are live icons keys (record 1818 keys).trash-2atDetailView.tsx:929turns that gate red naming exactly that one site and nothing else; restoring the file returns it to green. Restoration proved by blob hash equality with HEAD and an emptygit diff HEAD, not by an exit code.pnpm type-checkacross the workspace — turbo81 successful, 81 totalover 45 packages, zeroerror TS.pnpm exec vitest run packages/components/ examples/schema-catalog/ packages/plugin-view/ packages/plugin-list/—Test Files 395 passed (395),Tests 5688 passed (5688).pnpm exec vitest run packages/plugin-detail/ packages/types/ examples/schema-catalog/— 350 files, 6769 tests, green after the pin repair.pnpm exec vitest run packages/cli/src/__tests__/app-generator.test.ts— 43 passed.pnpm exec vitest run scripts/__tests__/doc-version-claims.test.ts— 29 passed.pnpm exec vitest run scripts/__tests__/check-lucide-icon-record-names.test.ts scripts/__tests__/ci-cd-pipeline-doc.test.ts scripts/__tests__/check-lockfile-integrity.test.ts— green.node scripts/check-lockfile-integrity.mjs—VERDICT clean.node scripts/check-changeset-presence.mjsandcheck-changeset-no-major.mjs— both green.Declared narrowing: the full four-shard
pnpm testand the repo-widepnpm lintare left to CI.eslintover the changed source files reports one pre-existing error ataction-bar.tsx:267in code this PR does not touch (its only change to that file is a docblock comment); that attribution is stated, not yet controlled againstmain. TheBundle Analysismeasurement was still building locally when this was opened and is reported as NOT MEASURED rather than assumed — the stale-base cause of its red is independently established above.6. Governed surface — this PR stays a draft
node scripts/check-governed-queue-guard.mjs --testreportsGOVERNED — 1 of 37 path(s):skills/objectui/guides/plugin-development.md, the published skills catalog. One governed path governs the whole PR. It is not to be flipped ready, enqueued, or armed with auto-merge; a human merge is the review record.The changed line there is the plugin skeleton's
lucide-reactrange, whichdoc-version-claims.test.tsanchors to the in-repo range. ItsKNOWN_CLAIMSentry is keyed by the literal text, so the inventory key moved with it.Session that produced this:
https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH🤖 Generated with Claude Code
https://claude.ai/code/session_01611D6ZaRaMmwTNQmSbk8MH
Generated by Claude Code