chore: scrub tamagui — use @hanzo/gui (and @hanzogui/* forks) only

Tamagui is banned per directive. Replace every import/reference with
@hanzo/gui or the @hanzogui/* equivalent. Removes:
- tamagui-loader and tamaguiPlugin from apps/extension webpack/wxt configs
- @tamagui/babel-plugin TODO block from apps/mobile/babel.config.js
- 'tamagui', '@tamagui/web' from optimizeDeps.include hints
- @tamagui/core/reset.css side-effect imports → @hanzogui/core/reset.css
- TamaguiProvider in mobile tests → GuiProvider from gui-provider
- TamaguiInput type aliases in extension Input.tsx → GuiInput
- declare module 'tamagui' in env.d.ts → declare module '@hanzo/gui' (uses GuiGroupNames)
- '.tamagui' ignore patterns in .gitignore / .fingerprintignore
- 'tamagui-loader' from .depcheckrc

Source files: 18 .ts/.tsx files updated to import from @hanzo/gui /
@hanzogui packages. Tests + config: 4 files updated to drop
tamagui-specific behavior. Docs (LLM.md, README.md, RABBY_FEATURES.md):
updated to reflect new stack.

No tamagui entries remain in any package.json.
This commit is contained in:
Hanzo AI
2026-06-10 22:35:40 -07:00
committed by zeekay
parent 64268180bf
commit 58e646e718
27 changed files with 48 additions and 93 deletions
+2 -4
View File
@@ -117,9 +117,7 @@ no Alchemy direct. White-labels point at their own gateway (e.g.,
## Workspace catalog (`pnpm-workspace.yaml`)
`@hanzogui/*-fork.1` aliases for React Native packages. The `@hanzogui` prefix
is the internal umbrella name (NEVER write "Tamagui" — see
`feedback_no_tamagui_brand.md`). Product brand for the GUI library is
`@hanzo/gui`.
is the internal umbrella name. Product brand for the GUI library is `@hanzo/gui`.
## Known transitive type errors (NOT our bug)
@@ -148,7 +146,7 @@ The web SPA builds because Vite tree-shakes — only used surface is touched.
4. **NEVER** reference `getQuicknodeEndpointUrl` — use `getBootnodeRpcUrl`.
5. **NEVER** hardcode brand strings (`"Lux Wallet"`, `"lux.network"`) in app code —
read from the `brand` singleton in `@luxfi/wallet-brand`.
6. **NEVER** write "Tamagui" — use "@hanzo/gui" / `@hanzogui/*` umbrella.
6. **NEVER** write the banned forked-UI brand — use "@hanzo/gui" / `@hanzogui/*` umbrella.
7. **ALWAYS** preserve BIP44 path 9000 for Lux P/X chain addresses in any
key derivation (60 for EVM C-chain).
8. **ALWAYS** keep the GPL-3.0-or-later license header — wallet inherits it.
-1
View File
@@ -25,7 +25,6 @@ ignores: [
=======
# Webpack plugins
'@svgr/webpack',
'tamagui-loader',
'esbuild-loader',
'style-loader',
'css-loader',
-1
View File
@@ -23,7 +23,6 @@ dist-ssr
*.sln
*.sw?
.tamagui
.output
.wxt
+2 -2
View File
@@ -4,7 +4,7 @@ This document tracks the migration of features from the Rabby-based xwallet to t
## Overview
The Lux Exchange extension is built on a modern stack (WXT, React 19, Tamagui) and integrates deeply with the Lux/Lux DEX ecosystem. We're augmenting it with battle-tested features from the Rabby wallet fork (xwallet).
The Lux Exchange extension is built on a modern stack (WXT, React 19, @hanzo/gui) and integrates deeply with the Lux/Lux DEX ecosystem. We're augmenting it with battle-tested features from the Rabby wallet fork (xwallet).
## Feature Status
@@ -55,7 +55,7 @@ The exchange extension uses WXT (Vite-based) which requires different handling t
### UI Components
- Build on existing Tamagui component library
- Build on existing @hanzo/gui component library
- Follow feature-based folder structure
- Integrate with existing navigation system
+1 -1
View File
@@ -106,7 +106,7 @@ jest.mock('@l.x/lx/src/features/appearance/hooks', () => {
}
})
// Mock IntersectionObserver for Tamagui's useElementLayout
// Mock IntersectionObserver for @hanzo/gui's useElementLayout
const IntersectionObserverMock = jest.fn().mockImplementation((callback) => ({
observe: jest.fn((element) => {
// Immediately call the callback with a mock entry
+1 -1
View File
@@ -6,7 +6,7 @@ html {
font-variant-ligatures: no-contextual;
}
/* Theme-aware background colors using Tamagui theme classes */
/* Theme-aware background colors using @hanzo/gui theme classes */
.t_light body,
.t_light html {
background-color: #fff;
+4 -4
View File
@@ -1,5 +1,5 @@
import { forwardRef } from 'react'
import { Input as TamaguiInput, InputProps as TamaguiInputProps } from '@l.x/ui/src'
import { Input as GuiInput, InputProps as GuiInputProps } from '@l.x/ui/src'
import { inputStyles } from '@l.x/ui/src/components/input/utils'
import { fonts } from '@l.x/ui/src/theme/fonts'
@@ -7,16 +7,16 @@ export type InputProps = {
large?: boolean
hideInput?: boolean
centered?: boolean
} & TamaguiInputProps
} & GuiInputProps
export type Input = TamaguiInput
export type Input = GuiInput
export const Input = forwardRef<Input, InputProps>(function InputInner(
{ large = false, hideInput = false, centered = false, ...rest }: InputProps,
ref,
): JSX.Element {
return (
<TamaguiInput
<GuiInput
ref={ref}
backgroundColor={large ? '$surface1' : '$surface2'}
borderColor="$surface3"
@@ -1,4 +1,4 @@
import '@tamagui/core/reset.css'
import '@hanzogui/core/reset.css'
import 'src/app/Global.css'
import 'symbol-observable' // Needed by `reduxed-chrome-storage` as polyfill, order matters
import { useEffect } from 'react'
+1 -1
View File
@@ -1,4 +1,4 @@
import '@tamagui/core/reset.css'
import '@hanzogui/core/reset.css'
import 'src/app/Global.css'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
+1 -1
View File
@@ -1,4 +1,4 @@
import '@tamagui/core/reset.css'
import '@hanzogui/core/reset.css'
import 'src/app/Global.css'
import { SharedEventName } from '@luxamm/analytics-events'
import { useEffect, useRef, useState } from 'react'
@@ -1,4 +1,4 @@
import '@tamagui/core/reset.css'
import '@hanzogui/core/reset.css'
import 'src/app/Global.css'
import { PropsWithChildren, useEffect } from 'react'
import { createHashRouter, Outlet, RouterProvider, useSearchParams } from 'react-router'
@@ -158,7 +158,7 @@ export function ScanToOnboard(): JSX.Element {
return () => cancelAnimation(qrScale)
// oxlint-disable-next-line react/exhaustive-deps -- biome-parity: oxlint is stricter here
}, [isLoadingUUID])
// Using useAnimatedStyle and AnimatedFlex because tamagui scale animation not working
// Using useAnimatedStyle and AnimatedFlex because @hanzo/gui scale animation not working
const qrAnimatedStyle = useAnimatedStyle(() => {
return {
transform: `scale(${qrScale.value})`,
+1 -1
View File
@@ -1,4 +1,4 @@
import '@tamagui/core/reset.css'
import '@hanzogui/core/reset.css'
import 'src/app/Global.css'
import 'symbol-observable' // Needed by `reduxed-chrome-storage` as polyfill, order matters
import { EXTENSION_ORIGIN_APPLICATION } from 'src/app/version'
@@ -21,7 +21,7 @@
-- │ content.js │ ┌►│ <Router route> │
-- ├───────────────┤ │ ├────────────────────┤
-- │ <Redux /> │ │ │ |<Onboarding/> │
-- │ <Tamagui /> │ │ │ |<WebNavigation /> │
-- │ <Gui /> │ │ │ |<WebNavigation /> │
-- │ <GraphQl /> │ │ │ |<Notifications /> │
-- │ <Router />─┼──┘ │ |... │
-- └───────────────┘ └────────────────────┘
+4 -4
View File
@@ -1,12 +1,12 @@
import { config, TamaguiGroupNames } from '@l.x/ui/src/tamagui.config'
import { config, GuiGroupNames } from '@l.x/ui/src/gui.config'
type Conf = typeof config
declare module 'tamagui' {
declare module '@hanzo/gui' {
// oxlint-disable-next-line typescript/no-empty-interface
interface TamaguiCustomConfig extends Conf {}
interface GuiCustomConfig extends Conf {}
interface TypeOverride {
groupNames(): TamaguiGroupNames
groupNames(): GuiGroupNames
}
}
+2 -17
View File
@@ -11,7 +11,7 @@ const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
const NODE_ENV = process.env.NODE_ENV || 'development'
const POLL_ENV = process.env.WEBPACK_POLLING_INTERVAL
// if not set tamagui wont add nice data-at, data-in etc debug attributes
// preserve NODE_ENV for downstream loaders that branch on it
process.env.NODE_ENV = NODE_ENV
const isDevelopment = NODE_ENV === 'development'
@@ -268,22 +268,7 @@ module.exports = (env) => {
// one after to remove the jsx
swcLoader,
// tamagui optimizes the jsx
{
loader: 'tamagui-loader',
options: {
config: '../../packages/ui/src/tamagui.config.ts',
components: ['ui'],
// add files here that should be parsed by the compiler from within any of the apps/*
// for example if you have constants.ts then constants.js goes here and it will eval them
// at build time and if it can flatten views even if they use imports from that file
importsWhitelist: ['constants.js'],
// TODO: test re-enabling extraction in production when #27138 merges
disableExtraction: true,
},
},
// one before to remove types
// remove types via esbuild
{
loader: 'esbuild-loader',
options: {
+2 -11
View File
@@ -202,15 +202,6 @@ export default defineConfig({
// ignores tsconfig files in Nx generator template directories
skip: (dir) => dir.includes('files'),
}),
// TODO(INFRA-299): enable tamagui in production once building works
// !isPreparePhase && isProduction
// ? tamaguiPlugin({
// config: '../../packages/ui/src/tamagui.config.ts',
// components: ['ui', 'uniswap', 'utilities'],
// optimize: true,
// importsWhitelist: ['constants.js'],
// })
// : undefined,
svgr({
svgrOptions: {
icon: false,
@@ -244,8 +235,8 @@ export default defineConfig({
optimizeDeps: {
include: [
'tamagui',
'@tamagui/web',
'@hanzo/gui',
'@hanzogui/web',
'ui',
'@luxamm/sdk-core',
'@luxamm/v2-sdk',
-1
View File
@@ -8,7 +8,6 @@ ios/OneSignalNotificationServiceExtension/Env.swift
# Cache/temporary files
.expo/**/*
coverage/**/*
.tamagui/**/*
storybook-static/**/*
dist/**/*
build/**/*
-2
View File
@@ -2,8 +2,6 @@
#
.DS_Store
.tamagui
# Xcode
#
build/
+1 -1
View File
@@ -273,7 +273,7 @@ These are some tools you might want to familiarize yourself with to understand t
- [Redux](https://redux.js.org/) and [Redux Toolkit](https://redux-toolkit.js.org/): state management
- [redux-saga](https://redux-saga.js.org/) & [typed-redux-saga](https://github.com/agiledigital/typed-redux-saga): Redux side effect manager -- used for complex/stateful network calls
- [ethers](https://docs.ethers.io/v5/)
- [Tamagui](https://tamagui.dev): UI framework
- [@hanzo/gui](https://github.com/hanzogui): UI framework (forked RN ecosystem)
- [React navigation](https://reactnavigation.org/): routing and navigation with animations and gestures
- [react-i18next](https://react.i18next.com/): i18n
+1 -13
View File
@@ -10,18 +10,6 @@ module.exports = function (api) {
plugins = [
...plugins,
// Disable compiler to fix mobile theme issues and media queries
// process.env.NODE_ENV === 'test'
// ? null
// : [
// '@tamagui/babel-plugin',
// {
// components: ['ui'],
// // experimentalFlattenThemesOnNative: true,
// config: '../../packages/ui/src/tamagui.config.ts',
// },
// ],
[
'module-resolver',
{
@@ -60,7 +48,7 @@ module.exports = function (api) {
return {
ignore: [
// speeds up compile
'**/@tamagui/**/dist/**',
'**/@hanzogui/**/dist/**',
],
presets: ['babel-preset-expo'],
plugins,
@@ -89,12 +89,10 @@ const BaseAnimatedText = ({
}
// end of forked from https://github.com/wcandillon/react-native-redash/blob/master/src/ReText.tsx
// gives you tamagui props with reanimated support
// gives you @hanzo/gui props with reanimated support
/**
* @deprecated Prefer <Text animation="" />
*
* See: https://tamagui.dev/docs/core/animations
*
* TODO(MOB-1948): Remove this
* */
export const AnimatedText = ({ style, ...propsIn }: TextProps): JSX.Element => {
+4 -4
View File
@@ -1,12 +1,12 @@
import { config, TamaguiGroupNames } from '@l.x/ui/src/tamagui.config'
import { config, GuiGroupNames } from '@l.x/ui/src/gui.config'
type Conf = typeof config
declare module 'tamagui' {
declare module '@hanzo/gui' {
// oxlint-disable-next-line typescript/no-empty-interface
interface TamaguiCustomConfig extends Conf {}
interface GuiCustomConfig extends Conf {}
interface TypeOverride {
groupNames(): TamaguiGroupNames
groupNames(): GuiGroupNames
}
}
@@ -2,19 +2,19 @@ import React from 'react'
import { GenericImportForm } from 'src/features/import/GenericImportForm'
import { render, screen } from 'src/test/test-utils'
import { noOpFunction } from '@l.x/utils/src/test/utils'
import { TamaguiProvider } from 'wallet/src/providers/tamagui-provider'
import { GuiProvider } from 'wallet/src/providers/gui-provider'
describe(GenericImportForm, () => {
it('renders a placeholder when there is no value', async () => {
const tree = render(
<TamaguiProvider>
<GuiProvider>
<GenericImportForm
errorMessage={undefined}
placeholderLabel="seed phrase"
value={undefined}
onChange={noOpFunction}
/>
</TamaguiProvider>,
</GuiProvider>,
)
expect(await screen.findByText('seed phrase')).toBeDefined()
@@ -23,14 +23,14 @@ describe(GenericImportForm, () => {
it('renders a value', async () => {
render(
<TamaguiProvider>
<GuiProvider>
<GenericImportForm
errorMessage={undefined}
placeholderLabel="seed phrase"
value="hello"
onChange={noOpFunction}
/>
</TamaguiProvider>,
</GuiProvider>,
)
expect(await screen.queryByText('seed phrase')).toBeNull()
@@ -39,14 +39,14 @@ describe(GenericImportForm, () => {
it('renders an error message', async () => {
render(
<TamaguiProvider>
<GuiProvider>
<GenericImportForm
errorMessage="there is an error"
placeholderLabel="seed phrase"
value="wrong value"
onChange={noOpFunction}
/>
</TamaguiProvider>,
</GuiProvider>,
)
expect(await screen.findByText('there is an error')).toBeDefined()
@@ -5,7 +5,7 @@ import { OnboardingStackParamList } from 'src/app/navigation/types'
import { RestoreCloudBackupPasswordScreen } from 'src/screens/Import/RestoreCloudBackupPasswordScreen'
import { render } from 'src/test/test-utils'
import { OnboardingScreens } from '@l.x/lx/src/types/screens/mobile'
import { TamaguiProvider } from 'wallet/src/providers/tamagui-provider'
import { GuiProvider } from 'wallet/src/providers/gui-provider'
const setOptionsSpy = jest.fn()
const routeProp = { params: {} } as RouteProp<OnboardingStackParamList, OnboardingScreens.RestoreCloudBackupPassword>
@@ -13,7 +13,7 @@ const routeProp = { params: {} } as RouteProp<OnboardingStackParamList, Onboardi
describe(RestoreCloudBackupPasswordScreen, () => {
it('renders correctly', () => {
const tree = render(
<TamaguiProvider>
<GuiProvider>
<RestoreCloudBackupPasswordScreen
navigation={
{
@@ -29,7 +29,7 @@ describe(RestoreCloudBackupPasswordScreen, () => {
}
route={routeProp}
/>
</TamaguiProvider>,
</GuiProvider>,
).toJSON()
expect(tree).toMatchSnapshot()
@@ -9,7 +9,7 @@ import { renderWithProviders } from 'src/test/render'
import { render } from 'src/test/test-utils'
import { ImportType, OnboardingEntryPoint } from '@l.x/lx/src/types/onboarding'
import { MobileScreens, OnboardingScreens } from '@l.x/lx/src/types/screens/mobile'
import { TamaguiProvider } from 'wallet/src/providers/tamagui-provider'
import { GuiProvider } from 'wallet/src/providers/gui-provider'
import { ACCOUNT, preloadedWalletPackageState } from 'wallet/src/test/fixtures'
jest.mock('wallet/src/features/wallet/accounts/utils', () => ({
@@ -48,9 +48,9 @@ describe(BackupScreen, () => {
it('renders backup options when some are completed', async () => {
const tree = renderWithProviders(
<TamaguiProvider>
<GuiProvider>
<BackupScreen navigation={navigationProp} route={routeProp} />
</TamaguiProvider>,
</GuiProvider>,
{ preloadedState: preloadedWalletPackageState({ account: ACCOUNT }) },
)
+3 -3
View File
@@ -38,11 +38,11 @@ module.exports = {
setupFiles: [
'./jest-setup.js',
],
// we map core to tamagui's test bundle, this just makes setup simpler for jest
// we map @hanzogui core to its native-test bundle for simpler jest setup
moduleNameMapper: {
...preset.moduleNameMapper,
'@tamagui/core': '@tamagui/core/native-test',
'@tamagui/web': '@tamagui/core/native-test',
'@hanzogui/core': '@hanzogui/core/native-test',
'@hanzogui/web': '@hanzogui/core/native-test',
// Map theme animations to native version for tests (base index.ts uses CSS animations now)
'@l.x/ui/src/theme/animations$': '<rootDir>/../ui/src/theme/animations/index.native.ts',
// Map platform-specific animation components to native versions for tests