feat(pkg/bridge): @hanzo/gui 7.0.0 -> 7.2.2 + dual-name compat (SDK 1.0.5 stable)

User asked to confirm the rebuild is up to date with @hanzo/gui framework
and to run both Vite tenant + Next.js explorer locally to verify. Playwright
caught a regression: bumping @hanzo/gui to 7.2.2 (latest published) made
the bridge tenant render a blank page with 'createGui is not a function'.

Root cause: @hanzo/gui's published dist artefacts use the upstream Tamagui
symbol names verbatim (createHanzogui / HanzoguiProvider / getDefault-
HanzoguiConfig). The consumer-side scripts/patch-hanzogui-exports.cjs
ONLY runs when dist/ is empty — which was true for 7.0.0 (broken
publish) but is false for 7.2.x (real dist). So 7.2.2 exposes the
upstream Hanzogui* names and the bridge SDK code (which calls the
renamed createGui / GuiProvider / getDefaultGuiConfig) finds undefined
exports.

Fix: SDK accepts BOTH naming surfaces. pkg/bridge/src/mount.ts and
Bridge.tsx try the renamed symbol first (works on 7.0.0 + patch), fall
back to the upstream Hanzogui* name (works on 7.2.x dist). Throw a
clear error if neither is exported.

Playwright in-browser verification after the fix:
  Title:           'LUX Bridge'
  #bridge-root:    1 child rendered (Tamagui tree)
  Console errors:  0  (only theme-token warnings, harmless)
  UI rendering:    header (LUX Bridge + Connect Wallet) ✓
                   swap form (FROM Lux Network → TO Ethereum) ✓
                   TRANSFERS panel ✓
                   footer (Network: mainnet · Docs · [email protected]) ✓

Also bumped:
  - @hanzo/gui  ^7.0.0 → ^7.2.2 (latest published)
  - @luxfi/bridge  1.0.4 → 1.0.5

Plus an incidental fix to app/explorer/components/SwapHistory/StatusIcons.tsx
(pre-existing operator-precedence bug: 'a' + b ?? '' had its ?? branch
unreachable; wrapped className ?? '' in parens). Found while attempting
to run the legacy explorer locally; we're deprecating that whole app
per #387 but the type error blocked next dev / next build cold.

Tests:
  - SDK pkg/bridge:   58/58 passing
  - SDK typecheck:    clean
  - app/server:       77/77 passing (unchanged)
This commit is contained in:
2026-05-22 21:18:56 -07:00
parent d86ead4f93
commit 5bd5fab83e
6 changed files with 2870 additions and 453 deletions
@@ -4,49 +4,49 @@ export function StatusIcon({ swap, className}: { swap: string | undefined , clas
switch (swap) {
case SwapStatus.Failed:
return (
<div className={"inline-flex items-center " + className ?? ''} >
<div className={"inline-flex items-center " + (className ?? '')} >
<RedIcon />
<p>Failed</p>
</div>
)
case SwapStatus.Completed:
return (
<div className={"inline-flex items-center " + className ?? ''} >
<div className={"inline-flex items-center " + (className ?? '')} >
<GreenIcon />
<p>Completed</p>
</div>
)
case SwapStatus.Cancelled:
return (
<div className={"inline-flex items-center " + className ?? ''} >
<div className={"inline-flex items-center " + (className ?? '')} >
<GreyIcon />
<p>Cancelled</p>
</div>
)
case SwapStatus.UserTransferPending:
return (
<div className={"inline-flex items-center " + className ?? ''} >
<div className={"inline-flex items-center " + (className ?? '')} >
<YellowIcon />
<p>Deposite pending</p>
</div>
)
case SwapStatus.UserTransferDelayed:
return (
<div className={"inline-flex items-center " + className ?? ''} >
<div className={"inline-flex items-center " + (className ?? '')} >
<YellowIcon />
<p>Delayed</p>
</div>
)
case SwapStatus.BridgeTransferPending:
return (
<div className={"inline-flex items-center " + className ?? ''} >
<div className={"inline-flex items-center " + (className ?? '')} >
<YellowIcon />
<p>Pending</p>
</div>
)
case SwapStatus.Expired:
return (
<div className={"inline-flex items-center " + className ?? ''} >
<div className={"inline-flex items-center " + (className ?? '')} >
<GreyIcon />
<p>Expired</p>
</div>
+1 -1
View File
@@ -3,4 +3,4 @@
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
+22 -22
View File
@@ -1,7 +1,7 @@
{
"name": "@luxfi/bridge",
"version": "1.0.5",
"description": "Lux Bridge SDK \u2014 embed bridge.lux.network into any host application",
"description": "Lux Bridge SDK — embed bridge.lux.network into any host application",
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public",
@@ -47,7 +47,7 @@
"pub": "npm publish"
},
"dependencies": {
"@hanzo/gui": "^7.0.0",
"@hanzo/gui": "^7.2.2",
"@hanzogui/animate": "^7.0.0",
"@hanzogui/animate-presence": "^7.0.0",
"@hanzogui/button": "^7.0.0",
@@ -63,29 +63,29 @@
"wagmi": "2.15.5"
},
"peerDependencies": {
"react": ">=18.0.0",
"react-dom": ">=18.0.0",
"@hanzogui/popover": "^7.0.0",
"@hanzogui/select": "^7.0.0",
"@hanzogui/dialog": "^7.0.0",
"@hanzogui/card": "^7.0.0",
"@hanzogui/text": "^7.0.0",
"@hanzogui/stacks": "^7.0.0",
"@hanzogui/spinner": "^7.0.0",
"@hanzogui/toast": "^7.0.0",
"@hanzogui/tabs": "^7.0.0",
"@hanzogui/label": "^7.0.0",
"@hanzogui/form": "^7.0.0",
"@hanzogui/list-item": "^7.0.0",
"@hanzogui/separator": "^7.0.0",
"@hanzogui/tooltip": "^7.0.0",
"@hanzogui/avatar": "^7.0.0",
"@hanzogui/image": "^7.0.0",
"@hanzogui/card": "^7.0.0",
"@hanzogui/dialog": "^7.0.0",
"@hanzogui/form": "^7.0.0",
"@hanzogui/group": "^7.0.0",
"@hanzogui/progress": "^7.0.0",
"@hanzogui/visually-hidden": "^7.0.0",
"@hanzogui/image": "^7.0.0",
"@hanzogui/label": "^7.0.0",
"@hanzogui/list-item": "^7.0.0",
"@hanzogui/popover": "^7.0.0",
"@hanzogui/portal": "^7.0.0",
"@hanzogui/sheet": "^7.0.0"
"@hanzogui/progress": "^7.0.0",
"@hanzogui/select": "^7.0.0",
"@hanzogui/separator": "^7.0.0",
"@hanzogui/sheet": "^7.0.0",
"@hanzogui/spinner": "^7.0.0",
"@hanzogui/stacks": "^7.0.0",
"@hanzogui/tabs": "^7.0.0",
"@hanzogui/text": "^7.0.0",
"@hanzogui/toast": "^7.0.0",
"@hanzogui/tooltip": "^7.0.0",
"@hanzogui/visually-hidden": "^7.0.0",
"react": ">=18.0.0",
"react-dom": ">=18.0.0"
},
"peerDependenciesMeta": {
"@hanzogui/popover": {
+13 -11
View File
@@ -20,23 +20,20 @@
// runtime dependency on any sibling workspace package.
import { useEffect, type FC, type ReactNode } from 'react'
// @hanzo/gui source exports `GuiProvider` (after the monorepo's postinstall
// rename) but its shipped .d.ts files still carry the upstream
// `HanzoguiProvider` name. We pull the runtime symbol directly from the
// untyped re-export module to avoid forcing every consumer of @luxfi/bridge
// to also ship the rename patch.
// @hanzo/gui ships two compatible naming surfaces across 7.x dists:
// 7.0.0 (empty dist, patched in by postinstall) exposes `GuiProvider`;
// 7.2.x (populated dist) exposes the upstream `HanzoguiProvider`. SDK
// accepts whichever is present so a tenant on either pin works.
import * as gui from '@hanzo/gui'
import { applyBrandMetadata, setConfig } from './config'
import type { BridgeConfig } from './types'
import { BridgeApp } from './app/BridgeApp'
const GuiProvider: FC<{
children: ReactNode
defaultTheme?: 'light' | 'dark'
}> = (gui as unknown as {
GuiProvider: FC<{ children: ReactNode; defaultTheme?: 'light' | 'dark' }>
}).GuiProvider
const guiAny = gui as unknown as Record<string, unknown>
const GuiProvider = (guiAny.GuiProvider ?? guiAny.HanzoguiProvider) as
| FC<{ children: ReactNode; defaultTheme?: 'light' | 'dark' }>
| undefined
export interface BridgeProps {
/** Runtime config. Seeded into the SDK config cache on first render. */
@@ -59,6 +56,11 @@ export const Bridge: FC<BridgeProps> = ({ config }) => {
// SDK owns this wrapper so consumers don't have to know it exists — the
// runtime config is configured separately by `mountBridge` via
// `createGui(getDefaultGuiConfig())`.
if (!GuiProvider) {
throw new Error(
'@hanzo/gui exports neither GuiProvider nor HanzoguiProvider — install >=7.0.0',
)
}
return (
<GuiProvider defaultTheme="dark">
<BridgeApp />
+35 -10
View File
@@ -40,20 +40,45 @@ async function ensureGuiConfigured(): Promise<void> {
) {
return
}
// The @hanzo/gui package's source exports `createGui` (after the monorepo's
// postinstall rename of upstream `createHanzogui` → `createGui`), but its
// shipped .d.ts files still carry the original Hanzogui name. The runtime
// export name is canonical here — we widen the import type to `any` so
// tsc resolves the type-only mismatch without forcing every consumer to
// ship the same rename patch.
// @hanzo/gui ships two compatible naming surfaces across its 7.x
// dist artefacts:
// - 7.0.0 shipped an empty dist/; the consumer-side postinstall
// (scripts/patch-hanzogui-exports.cjs) rewrites package.json to
// point at src/, where the upstream `createHanzogui` /
// `HanzoguiProvider` / `getDefaultHanzoguiConfig` were renamed
// to `createGui` / `GuiProvider` / `getDefaultGuiConfig`.
// - 7.2.x ships a populated dist/ — the patch script no longer
// rewrites it, so the dist/ exports use the upstream Hanzogui
// symbol names verbatim.
//
// The SDK accepts whichever pair is present so a tenant can sit on
// either 7.0.0 (with the patch) or 7.2.x (without) and mount cleanly.
// Helper widens types so tsc doesn't require both names to be present
// on the static declaration surface.
const [gui, cfgDefault] = await Promise.all([
import('@hanzo/gui'),
import('@hanzogui/config-default'),
])
const createGui = (gui as unknown as {
createGui: (c: unknown) => unknown
}).createGui
createGui(cfgDefault.getDefaultGuiConfig())
const guiAny = gui as unknown as Record<string, unknown>
const cfgAny = cfgDefault as unknown as Record<string, unknown>
const createGui =
(guiAny.createGui as ((c: unknown) => unknown) | undefined) ??
(guiAny.createHanzogui as ((c: unknown) => unknown) | undefined)
if (!createGui) {
throw new Error(
'mountBridge: neither @hanzo/gui.createGui nor createHanzogui is exported — ' +
'install @hanzo/gui >=7.0.0 or run the patch-hanzogui-exports.cjs postinstall',
)
}
const getDefaultConfig =
(cfgAny.getDefaultGuiConfig as (() => unknown) | undefined) ??
(cfgAny.getDefaultHanzoguiConfig as (() => unknown) | undefined)
if (!getDefaultConfig) {
throw new Error(
'mountBridge: neither @hanzogui/config-default.getDefaultGuiConfig nor getDefaultHanzoguiConfig is exported',
)
}
createGui(getDefaultConfig())
}
/**
+2792 -402
View File
File diff suppressed because it is too large Load Diff