feat: replace native-ui components with the React Native Reusables set - #144
feat: replace native-ui components with the React Native Reusables set#144adelrodriguez wants to merge 4 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
@init/native-ui had four hand-rolled components while packages/ui has the full shadcn set, so mobile screens kept hitting missing primitives. The package now vendors all 31 components from the React Native Reusables Uniwind registry (pinned at founded-labs/react-native-reusables@119d0b1, MIT), adapted to repo conventions, with a components.json + shadcn CLI workflow mirroring packages/ui for future adds and diffs. Also updates Adamantite to 0.35.0 (oxlint 1.78, oxfmt 0.63, knip 6.32), documents the package in docs/packages.md, and keeps the README sparse. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
f2f06c9 to
3241bde
Compare
Metro treats an `imports` mapping that targets a bare package name
("#utils" -> "@init/utils/ui") as an invalid package configuration and
falls back to file-based resolution for the whole package, which broke
every #components/* import during expo export. Nothing imports #utils
anymore, so drop the mapping and point the shadcn CLI aliases directly
at @init/utils/ui.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Important
react-native-svg now resolves to two different versions inside the mobile bundle. Every other shared native dependency dedupes; this one does not, because it is declared only as a "*" peer. Details inline on packages/native-ui/package.json.
Reviewed changes — the full 44-file diff at f2f06c9, with each vendored component diffed against the upstream React Native Reusables Uniwind registry at the pinned commit.
- 31 vendored components —
packages/native-ui/src/components/gains 29 new files and rewritesbutton,text, andiconto the upstream shape (Slotgeneric instead ofSlot.Text,TextClassContext-awareIcon,useMemodropped fromButton).large-title-headeris untouched;activity-indicatoris deleted. - Dependency surface — 24
@rn-primitives/*at1.5.2,lucide-react-native,shadcnas a devDependency, areact-native-svgpeer, and a#utilssubpath-import alias.apps/mobilebumps@rn-primitives/portalto1.5.2and addsreact-native-svg@15.15.4. - Theme tokens —
globals.cssgains--color-card-foreground,--color-popover, and--color-popover-foregroundin both light and dark, which the new overlay components need. - Registry workflow —
components.json+components:add/components:diffscripts mirroringpackages/ui, documented in a newdocs/packages.md#native-uisection and a new package README. - Toolchain bumps — adamantite
0.35.0, knip, oxfmt, oxlint1.78.0; thescripts/template/add.tschange is a pure oxfmt reformat that falls out of the bump.
I verified locally that the vendoring is faithful: all 32 components match the pinned upstream apart from the four deviations the description declares, plus the two minor ones in Nitpicks below. bunx oxlint packages/native-ui reports 0 errors (the per-file eslint-disable headers are honored) and tsc --noEmit in the package exits clean. #components/*.tsx resolution, uniwind's @source coverage of the package, and the mounted PortalHost all check out. The Reanimated hooks are sound — the omitted dependency arrays are inert because the Worklets Babel plugin is active, and react/rules-of-hooks and react/react-compiler are both enforced on this package.
⚠️ 31 components merge with no runtime verification and nothing in the repo that can provide it
The description is candid that on-device behavior is unverified, and Plan 19 — the catalog workspace that was the stated gate for this work — is still Planned. That leaves 3210 lines of UI with no tests, no stories, and no way for a reviewer or a future contributor to confirm that the portal-rendered overlays or the group-active: pressed-state text colors actually work before shipping a screen that uses them. This is a scope call only you can make: whether @init/native-ui ships broad-but-unproven now and Plan 19 validates it later, or whether the vendoring lands in slices behind the catalog.
Technical details
# No verification path for the vendored component set
## Affected sites
- `packages/native-ui/src/components/` — 31 components, no tests and no catalog entry.
- `docs/plans/20-expand-native-ui-with-react-native-reusables.md:3` — status now records that Plan 19's gate was bypassed, but Plan 19 itself is unchanged and still `Planned`.
- `apps/mobile/src/app/` — only `Button`, `Text`, and `LargeTitleHeader` are exercised by real screens; the other 28 components have zero call sites.
## Required outcome
- A decision, recorded in the plan docs, on whether the unverified components are acceptable to merge ahead of Plan 19, and what the verification trigger is.
## Open questions for the human
- Should the overlay family (`alert-dialog`, `context-menu`, `dialog`, `dropdown-menu`, `hover-card`, `menubar`, `popover`, `select`, `tooltip`) be smoke-tested on iOS and Android before merge, given they all depend on the single `PortalHost` and `FullWindowOverlay` path?
- Does Plan 19's scope need updating now that the component set it was meant to gate has already landed?ℹ️ Nitpicks
packages/native-ui/package.json:9declares#utils → @init/utils/uiandcomponents.jsonpoints itslib/utilsaliases at it, but no component uses#utils— they all import@init/utils/uidirectly, anddocs/packages.md:83then instructs rewriting the CLI's#utilsimport by hand after every add.packages/uidoes use#utilsin its sources. Keeping the alias and dropping the manual rewrite step would make the two packages consistent and remove a per-add chore.docs/plans/19-cross-platform-component-catalog.md:60still says "render@init/native-uiButton, Text, and ActivityIndicator in an iOS simulator" —ActivityIndicatoris deleted by this PR.button.tsx:101(accessibilityRole="button") andcard.tsx:37(accessibilityRole="header") use the legacy prop where upstream and the rest of this package userole(alert.tsx:31,text.tsx:82). React Native givesroleprecedence and treatsaccessibilityRoleas the older API, sorole="button"/role="heading"would be both upstream-faithful and internally consistent.menubar.tsx:154drops upstream'soverlayClassName/overlayStyleprops fromMenubarContent. Upstream never uses them either, so this is harmless today, but it is an undeclared local edit thatcomponents:diffwill surface on every future upstream sync.
Claude Opus | 𝕏
There was a problem hiding this comment.
ℹ️ The new commit is a correct, well-scoped fix. Nothing new to flag — the three findings from the previous review are untouched and still open.
Reviewed changes — only a895629, the single commit pushed since the f2f06c9 review.
#utilssubpath import removed —packages/native-ui/package.jsondrops"#utils": "@init/utils/ui"fromimports, keeping just"#*": "./src/*", andcomponents.jsonrepoints itslib/utilsaliases straight at@init/utils/ui.
The commit message explains the failure mode precisely: Metro treats an imports mapping whose target is a bare package name as an invalid package configuration and falls back to file-based resolution for the entire package, which broke every #components/* import during expo export. That is a strictly better resolution than the one I suggested — the alias was never used by any component, so pointing the shadcn CLI at @init/utils/ui matches what the sources already do and removes the manual rewrite step from the add workflow at the same time.
I re-verified on a895629: Bun.resolveSync("#components/text.tsx") from packages/native-ui resolves correctly, bunx oxlint packages/native-ui reports 0 errors, and tsc --noEmit exits clean. packages/ui still carries the same "#utils": "@init/utils/ui" mapping, but it is not reachable from a Metro bundle — apps/mobile does not depend on @init/ui, and Plan 19 routes @init/ui through the Vite renderer rather than Metro — so there is nothing to change there.
⚠️ The react-native-svg version split from the previous review is still present
Restating only because it is the one blocking item and this commit did not touch it: packages/native-ui declares react-native-svg solely as a "*" peer, so lucide-react-native resolves 15.15.5 while apps/mobile pins and autolinks 15.15.4. I re-confirmed the split after bun install --frozen-lockfile on a895629. The two docs/packages.md and text.tsx comments are likewise still open.
Claude Opus | 𝕏
…ockquote border - Pin react-native-svg 15.15.4 in native-ui dependencies and add a root override so lucide-react-native's peer resolves to the same install the app autolinks, instead of a second 15.15.5 copy. - Add lucide-react-native to apps/mobile so the documented icon pattern (importing icon components directly) resolves from app code. - Restore border-border on the blockquote text variant, which the vendored upstream source had silently dropped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Important
ab90907 commits an unformatted regeneration of apps/mobile/src/uniwind-types.d.ts, which makes the CI format job fail. One bun run format clears it.
Reviewed changes — only ab90907, the single commit pushed since the a895629 review.
react-native-svgdeduped — pinned at15.15.4inpackages/native-uidependenciesalongside the existing"*"peer, plus a new rootoverridesentry.lucide-react-nativeadded toapps/mobile— so the icon pattern documented indocs/packages.md:65resolves from app code; the doc now states that requirement explicitly.blockquoteborder restored —border-borderis back on theblockquotetext variant intext.tsx:20.uniwind-types.d.tsregenerated — the tracked generated file was rewritten with single quotes and 4-space indent.
All three inline threads from the f2f06c9 review are genuinely fixed and already resolved. I re-verified the dependency work on a clean bun install --frozen-lockfile: apps/mobile, packages/native-ui, and lucide-react-native's peer all resolve the same react-native-svg@15.15.4 install, bun.lock carries no 15.15.5 entry, and lucide-react-native now resolves from apps/mobile. bun run check:monorepo reports no issues.
The f2f06c9 scope question — 31 components merging with no runtime verification while Plan 19 is still Planned — is untouched by this commit and remains yours to call.
ℹ️ Nitpicks
apps/mobile/package.json:55—lucide-react-nativehas no imports anywhere inapps/mobile/src, sobunx knip --workspace apps/mobilereports it under "Unused dependencies". It is non-blocking (knip.config.tssetsdependencies: "warn", and the run exits 0 once codegen has produced the i18n files), but it stays inbun run analyzeoutput until a mobile screen actually renders an icon.
Claude Opus | 𝕏
| "react": "19.2.3", | ||
| "react-dom": "19.2.3" | ||
| "react-dom": "19.2.3", | ||
| "react-native-svg": "15.15.4" |
There was a problem hiding this comment.
The packages/native-ui dependencies pin alone is enough — this global override is redundant. I removed it, deleted bun.lock, and ran a clean bun install: the regenerated lockfile has exactly one react-native-svg entry at 15.15.4 and lucide-react-native's peer symlink points at it. The contrary observation in the commit message is what a sticky lockfile looks like: bun install against the existing lock reports no changes and keeps the stale 15.15.5 entry regardless of the new pin.
Technical details
# Redundant root `overrides` entry for `react-native-svg`
## Affected sites
- `package.json:64` — `"react-native-svg": "15.15.4"` under `overrides`.
- `packages/native-ui/package.json:51` — already pins `"react-native-svg": "15.15.4"` under `dependencies`.
- `apps/mobile/package.json:64` — pins the same version.
## Evidence
With the override removed and `bun.lock` regenerated from scratch:
```
grep 'react-native-svg@' bun.lock
"react-native-svg": ["react-native-svg@15.15.4", ...] # single entry
readlink node_modules/.bun/lucide-react-native@*/node_modules/react-native-svg
../../react-native-svg@15.15.4+.../node_modules/react-native-svg
```
## Required outcome
- The three declarations of this version collapse to the two that carry real meaning, so a future Expo SDK bump of `apps/mobile`'s pin is not silently reverted by a root override nobody remembers to update.
## Suggested approach
- Drop the `overrides` line, delete `bun.lock`, re-run `bun install`, and confirm a single `react-native-svg@15.15.4` lock entry. Keeping it is also fine if you prefer belt-and-braces, but then it needs to move in lockstep with the app pin.
@init/native-uionly had four hand-rolled components (button, text, icon, activity-indicator), so building mobile screens meant constantly hitting primitives that exist inpackages/uibut not natively. This replaces the package's components with the full React Native Reusables Uniwind registry — 31 components, from accordion to tooltip — so the native package now mirrors the web one in breadth and workflow.Upstream's Uniwind variant already matches this repo's stack (Uniwind + Tailwind v4, cva,
TextClassContext,cn), so the sources are vendored copy-owned (pinned atfounded-labs/react-native-reusables@119d0b1, MIT) and adapted to repo conventions rather than ported. Acomponents.json+shadcnCLI setup mirroringpackages/ui(bun run components:add @rnr/<name>) makes future adds and upstream diffs repeatable. This realizes the intent ofdocs/plans/20, ahead of the Plan 19 catalog gate.Notable decisions:
apps/mobilecall sites (Button,Textvariants,LargeTitleHeader) needed no changes.large-title-headerstays (no upstream equivalent); unusedactivity-indicatoris deleted.placeholderClassNamefrom input/textarea (NativeWind API that doesn't exist in Uniwind; nativeplaceholder:classes preserve the styling), typed menubar's controlled/uncontrolled props honestly, moved skeleton's animation intouseAnimatedStylefor React Compiler compliance.import/namespacecan't resolve theexport *re-exports in@rn-primitivesdist bundles (still broken in oxlint 1.78 after updating Adamantite to 0.35.0 to check), andjsx-no-constructed-context-valuesfalse-positives on string-valued contexts (string equality prevents consumer re-renders). Both are suppressed per-file with reasons; the package stays fully linted and type-checked, unlikepackages/uiwhich is excluded wholesale.@rn-primitives/*packages pinned at 1.5.2,lucide-react-native+react-native-svgadded; missingcard-foreground/popover/popover-foregroundtheme tokens added toglobals.css. Package docs live indocs/packages.md; the README stays sparse.Not verified here: on-device behavior. Worth smoke-testing overlay components (dialog/select/dropdown render through the existing
PortalHost) andgroup-active:pressed-state text colors on iOS/Android.Verified with
bun run format,bun run check(0 errors),bun run analyze,bun run check:monorepo, andbun test(22 pass).Changes made by Claude Fable 5 via Claude Code.
🤖 Generated with Claude Code