typescript 7.0.2 is the native compiler and exports no JavaScript API, so
typescript-eslint and @typescript-eslint/eslint-plugin cannot read it —
typescript-eslint refuses TS 7 outright. It comes in aliased as tsgo and does
the typechecking; typescript stays on the 6 line for the tools. lint:tsc
reports the same 239 diagnostics under both compilers.
Co-authored-by: Hanzo Dev <[email protected]>
Ranges drift once nothing moves them: a caret that has not been rewritten
since it was written pins whatever was current that day, and an exact pin
cannot even take a patch. Both are updated here.
One line per package, so a surface reads the same components, the same
tokens and the same face as every other one.
The explorer had no way for a person to change how it reads. Colour mode was
the only control, and it is one bit.
@hanzo/appearance stores the preference and puts it on <html>; what the numbers
mean is bound in styles/tokens.css. Type rides the root font size, so every
Tailwind rung — all of them in rem — moves from one declaration and nothing is
restated. Density rides --spacing, the single base every p-*, m-* and gap-*
computes from. Both default to 1, so an install that never opens the panel
renders exactly what it rendered before; measured in a browser against the
production build: 16px root, p-4 at 16px, unchanged.
@hanzo/design's token sheets stay out. They declare --text-*, --radius-* and
--breakpoint-* — the names Tailwind v4 publishes — outside any layer, and v4
compiles utilities to var() references rather than baked values, so importing
them silently reanchors the app: --text-base 16px to 14px, --radius-lg 8px to
12px. Two sets under one set of names is a fork, not a migration. They become
the ramps when Tailwind leaves and there is one ramp to own.
The boot script is hashed for CSP from the same call that renders it, the way
the adbutler scripts are. A pasted literal goes stale on the next release of
the package and fails silently — policy parses, browser drops the script, page
paints at the defaults.
The version link was a module constant naming github.com/luxfi/explore, so
every brand's footer sent readers to Lux's repo — explore.hanzo.ai showed
"Frontend: v1.1.30" pointing at the Lux org. Brand identity in this image is
resolved per host, and the source link now resolves with it: ChainBranding
gains sourceUrl (NEXT_PUBLIC_BRAND_SOURCE_URL for external operators, like
every other branding field), so Lux keeps its own repo and Hanzo, Zoo, Pars,
SPC and Osage each name theirs.
Also sets Hanzo's copyright to Hanzo AI, Inc., the entity its terms, privacy,
DPA and brand.json all name; Hanzo Industries Inc. predates that consolidation.
chainRegistry.spec.ts asserts a brand host surfaces no other org's name or URL
in what the footer renders.
Adds .hanzo/workflows/cicd.yml calling the canonical pipeline and drops the
GitHub-hosted workflows it replaces. Lands here because this is where the repo
is written — git.hanzo.ai is upstream and github.com is its push mirror, so the
same change made on github.com never reaches the runner fleet.
/chains showed fifteen rows on every network, from a hardcoded table. Four of
them — T, R, I, O — are registered on no network at all; the node answers
"there is no ID with alias: T". A fifth, M, runs on devnet alone. And every
/chains/<slug> page printed the same four numbers: the primary network's
validator count, its total stake, its chain count, its threshold. Nothing on
Q-Chain's page was about Q-Chain. Fifteen pages, one set of facts.
The network already knows its own answer. platform.getBlockchains is the chain
list, and it is per-network for free: mainnet and testnet report nine chains,
devnet reports ten including M. That is what /chains lists now, joined with
configs/app/primaryChains.ts for presentation only — a chain the network
reports and we have no metadata for still gets a row, because the network is
the source of truth and our table is not.
Three facts per chain, each read from that chain:
* the blockchain ID, from the registry — the thing that actually tells one
chain from another, and it was already on screen while the numbers above
it were not
* whether the node has bootstrapped it, from info.isBootstrapped, which the
gateway routes for every chain. This is how Q-Chain's page now says
Syncing where the other nine say Bootstrapped: Q has not come up on any of
the three networks and is the sole reason all three report healthy:false
* its head, in its own dialect — eth_blockNumber for C, platform.getHeight
for P, xvm.getHeight for X. Not avm: that service is gone from this build,
and asking it returns "can't find service" rather than a height
Validators are the P-Chain's model, so they render on the P-Chain and nowhere
else. The head is labelled for the model it comes from — the X-Chain's is an
accepted height, not a block height.
Where a chain has no head to read, it says so. The gateway routes exactly P, X
and C on all three networks; D, A, B, Z, G, K and Q are registered and
bootstrapped on the node but their RPC 404s from a browser. Proof it is the
route and not the chain: C-Chain's own blockchain ID, /v1/bc/25td8att…/rpc,
404s while /v1/bc/C/rpc returns a height — same chain, same node. Those pages
read "No public RPC" and say plainly that the chain is registered and running
but not published. A zero there would have read as a measurement.
One proxy replaces the P-only one. /v1/pchain hardcoded /v1/bc/P; /v1/node/
<endpoint> resolves the endpoint against the primary-chain table and never
treats it as a URL. A chain the gateway does not route comes back 200 carrying
a JSON-RPC error rather than 502 — nothing is broken, and 502 put a red failed
request in the console of eight of the ten chain pages.
Deleted: CHAIN_INDEXER_URLS and the hook that read it — fifteen hostnames of
the shape api-indexer-<x>chain.lux.network, none of which resolve. Fifteen of
fifteen NXDOMAIN. It fetched them on every chain page and rendered the null.
Measured on mainnet, 1440x900 and 390x844, fresh context per URL: 20 of 20
chain/screen rows agree with RPC on both status and head — C-Chain 1,107,826
against a live head bracketed at 1,107,822..1,107,829, P and X at 0, seven
reading No public RPC, Q Syncing while the rest are Bootstrapped. /chains
lists the same ten chains the P-Chain reports, and 10 of 10 row links reach a
real page. Zero 4xx or 5xx from our own origin.
/bridge showed CONNECTED CHAINS 14, BRIDGE ROUTES 4, BRIDGE SIGNERS 5, ACTIVE
ROUTES 4 — the same four values on lux, hanzo and zoo, while its only backing
RPC 404'd twice per page load. Not one was measured:
* Connected Chains counted the keys of CHAIN_LABELS, a hardcoded lookup table,
plus an L1 list that comes back empty. It is 14 because the map has 14
entries.
* Bridge Routes and Active Routes counted four pairs unshifted into an array
three lines above, each hardcoded status: 'active'.
* Bridge Signers asked the B-Chain and, when that failed, printed the P-Chain
VALIDATOR count under a signer label. A real number, under the wrong name —
the worst of the four, because it moves.
The B-Chain is not served on any prefix: api.<brand>.network answers 404 to
/ext/bc/B/rpc and to /v1/bc/B/rpc alike, so there is no live bridge data to
show and the page has been reporting constants as measurements. Fixing the
path would have moved the 404, not the lie.
So the cards are gone, and with them lib/api/bchain, whose whole job was to
swallow that failure into zeroes. That leaves lib/api/luxnet with no caller —
the P-Chain hooks go through the /v1/pchain proxy, and getPChain/getInfo/
getHealth were re-exported and never called — so it goes too, and the luxnet
dependency with it: 889 lines of lockfile, an elliptic-curve SDK this app
never used at runtime. The retired /ext/ prefix now has no caller here at all.
What stays is what is true: the routes table, C/P/X atomic swaps marked active
and L1 transfers marked coming soon, and the paragraph that says so.
Measured, 1440x900: before, two POST https://api.lux.network/ext/bc/B/rpc per
load, both 404, four stat cards reading 14/4/5/4. After, zero /ext/ or /bc/
requests, zero stat cards, the four route rows unchanged.
Also: name every field when a broadcast becomes a block. toApiBlock spread the
raw EVMBlock and renamed three keys, so gas_used, gas_limit, base_fee_per_gas
and parent_hash stayed camelCase and arrived undefined — a live-pushed row
rendered 0 gas beside REST rows showing the real number, the same block reading
two ways depending on how it got here. It now builds the API shape explicitly,
field for field, matching formatBlock in luxfi/indexer.
Filter rendered twice on /tokens and dropped into the search input. Both
blocks in TokensActionBar are meant to exist — one for mobile with the sort
control, one for desktop inside the sticky ActionBar — and the Chakra port
turned each HStack into a bare div, losing display={{base,lg}} and gap. With
no breakpoint both drew at once, and with no flex the button, the sort and
the input stacked instead of sitting in a row. Every sibling action bar
(VerifiedContracts, NameDomains, Clusters) kept `flex lg:hidden` /
`hidden lg:flex`; tokens is the one file where the port dropped them, so this
restores that pattern rather than inventing a second one. Desktop now
measures one filter at x=32 w=92 and one input at x=136, no overlap.
The clipped label is a different fault underneath it. @luxfi/ui's ButtonFrame
sets inline-flex, alignItems, gap, a fixed height and overflow:hidden but
never flexDirection, and it is built on a react-native View, which lays out
in a column — so icon and label stacked and the fixed height cut the label
off. Swept 39 buttons over nine pages at both viewports: only three carry
more than one child and only Filter clips, so this says row at FilterButton,
the one component every filter in the app goes through. Inline because the
frame's own class outranks a utility. The primitive is still wrong and wants
fixing upstream.
The footer's social row was never a colour problem. Its branch of Footer
leaves the three left-hand blocks unwrapped, so a two-column grid takes them
as three children and the links land in the 470px track instead of the wide
one; five 160px columns do not fit there, the two mobile 1fr columns were
still the only explicit tracks at lg, and they collapsed to zero width with
Contribute, X (Twitter) and Discord painting on top of each other. Wrapping
the left column the way the custom-links branch already does, and dropping
the mobile tracks at lg, spreads them 576 to 1392 with nothing overlapping at
1440, 1024 or 390.
Addresses render check-summed again. The comment said the API always answers
in EIP-55; it case-folds now, deliberately, so a checksummed URL resolves
instead of 404ing, and `data.hash ?? getCheckedSummedAddress(hash)` took the
lower-case payload every time the address existed. Checksum whatever is
displayed instead — getAddress reads any case and is idempotent, so one call
covers a hash from the API, from the route, or from neither yet. Display
only; the backend keeps folding.
Co-authored-by: Hanzo Dev <[email protected]>
The tag was pushed from the previous commit earlier today; reusing the number
would put two different builds behind one version.
Co-authored-by: Hanzo Dev <[email protected]>
The AMM subgraph has been indexed and at chain head the whole time; nothing
read it. /dex queried only the native CLOB at 0x9999, which has genuinely
never emitted an event, so the page reported MARKETS 0 / VOLUME 0 while ~1
swap per block was landing in the WLUX/LZOO pool.
Wire the amm subgraph in as a SECOND venue rather than folding it into the
first. The two are different exchanges and their figures are never summed —
each section reports only its own state, so "live" can never be borrowed by
the venue that is not trading.
Swaps were rendering as anonymous EVM traffic; they now read as trades:
pool-signed amounts resolved into sold/bought, scaled by each token's own
decimals, with symbols, pair, fee tier and links to sender, block and txn.
Status uses the words already in use elsewhere (useNetworkValidators,
useFeeSplit) — live/idle/empty/unavailable/unknown — defined once in
lib/api/venueStatus. There is deliberately no 'stale': separating a stalled
indexer from a quiet venue needs an indexer head this API does not serve.
Also decomplected while here: one fetcher for both subgraphs, and an
unreachable endpoint is no longer flattened into "empty" — an outage and a
venue that has never traded are different facts and now render differently.
Co-authored-by: Hanzo Dev <[email protected]>
The release workflow has never published. Every run since v1.1.19 built
cleanly and then died on its own smoke gate:
image stamps <unset> but is published as v1.1.23
The gate was right. GIT_TAG and GIT_COMMIT_SHA were declared as ARG/ENV
only in the `builder` stage, and ENV does not cross a stage boundary, so
they reached the compiled bundle and never the runtime image. The footer
resolves at container start, not at build time — make_envs_script.sh
iterates the live process environment into window.__envs and
configs/app/ui.ts reads frontendVersion from there — so with nothing set
in the final stage, every image ever published reported no version.
`crane config ghcr.io/luxfi/explore:v1.1.23` confirms it: no
NEXT_PUBLIC_GIT_TAG in the config at all, and the deployed ConfigMaps do
not supply one either.
Declare both in the runner stage, after the copies so a new commit
invalidates only the ENV layer.
Co-authored-by: Hanzo Dev <[email protected]>
The magnifying glass was drawn over the leading "S" of the placeholder. The
cause is not the search bar: @luxfi/ui's InputGroup measures its adornments
correctly and then throws the measurement away, because the clone is guarded on
`displayName === 'FieldInput'` — a component that does not exist in the package
— and the props it would have set (`ps`/`pe`) are Chakra style props that the
Tailwind/gui Input spreads onto the DOM and ignores. Both halves are leftovers
of the Chakra→Tailwind port, so EVERY InputGroup in the app is unpadded; the
header is just the one you look at every day. Patched at the primitive
(patches/@[email protected]) rather than hand-tuning padding at one call
site: the offset is the measured width of the actual adornment, so it stays
right when the adornment changes, and FilterInput / revoke's SearchInput / the
showcase are fixed by the same change. Measured at 1440x900: the icon overlapped
the text by 24px and the kbd chip by 52px; both are now 0.
The measurement also moves to a layout effect and stops blanking value and
placeholder on first render. That blanking was inert while the guard swallowed
the clone; with the clone live it flips the field from controlled to
uncontrolled on mount, which React warns about and which can eat a keystroke.
ClearButton spread `className` over its own visibility classes, so a caller
passing one clobbered `invisible` — the header showed a clear button on an empty
field. Merged instead, at the component.
Placeholder is now "Search or ask anything", and Cmd/Ctrl-K opens an assistant.
Nothing in this repo, @luxfi/ui or @hanzo/gui ships a command palette or chat
(no cmdk, no palette, one dialog primitive), so this is the smallest thing that
works: the existing Dialog, one global listener, and a server-side proxy that
holds the credential. The key is deliberately not NEXT_PUBLIC_, so a deployment
without one gets a dialog that says exactly that instead of failing silently.
Off unless NEXT_PUBLIC_AI_ASSISTANT_ENABLED is set — Ctrl+K is the browser's
address-bar shortcut and claiming it to show an error would be worse than not
claiming it.
Validators read 5 on a network running 20. useCurrentValidators asks ONE
P-Chain, the one this deployment points at, but Lux, Hanzo, Zoo and Pars are
each sovereign L1s with their own P-Chain and their own validator set — measured
2026-08-06, five each, with four disjoint NodeID sets. The registry gains the
node origin per chain; absence of it is the signal that a chain has no publicly
reachable node (SPC and Osage are in-cluster only), and those are reported
`unavailable` rather than counted as zero. Stake is deliberately NOT summed:
each chain bonds its own currency.
The proxy resolves `?chain=` through the registry and never from the request, so
it cannot be pointed at an arbitrary origin. The new envs are registered with
the envs-validator and documented in ENVS.md — the schema rejects unknown
NEXT_PUBLIC_* variables and runs at container start, so an undeclared one stops
the pod from booting.
Co-authored-by: Hanzo Dev <[email protected]>
The search fix (be64271) has been sitting on main unpublished because CI on
this forge could not produce an image. Three independent faults, each hidden
behind the one in front of it.
1. The credential. .github/workflows/build-lux.yml logged in to ghcr.io with
secrets.GITHUB_TOKEN. On Hanzo Git that is a Hanzo Git token and ghcr.io has
never heard of it, so every run died in about a minute with
Get "https://ghcr.io/v2/": denied: denied
The build was not queueing forever — it ran, and it failed. The pair that
works here is GHCR_USER / GHCR_TOKEN, the org secrets luxfi/node already
publishes with.
2. The lockfile. d319792b took react-icons out and put @hanzogui/lucide-icons-2
in, and moved @luxfi/ui to 7.4.9, but pnpm-lock.yaml was never regenerated —
it still specified react-icons and @luxfi/ui 7.3.2. The Dockerfile installs
with --frozen-lockfile, so the image build could not have succeeded even with
a valid registry credential:
ERR_PNPM_OUTDATED_LOCKFILE ... don't match specs in package.json
Regenerated against the committed package.json; `pnpm install
--frozen-lockfile` is green.
3. The stub. f3833dfc pointed four Turbopack resolve aliases at
./stubs/empty.js and the file was never added. It exists on the author's
disk, so it builds there and nowhere else. Added.
2 and 3 are the same failure twice: a commit landed with a piece missing, and
because CI could not run, nothing said so. That is the actual cost of a broken
pipeline — not the delay, the silence.
CI moves to .hanzo/workflows, the one path this forge reads, mirroring
luxfi/node:
release.yml gates on the unit suite, builds, proves the image is a runnable
build whose baked NEXT_PUBLIC_GIT_TAG equals the tag being
published, pushes, then proves the manifest resolves. v1.1.14 and
v1.1.15 are package.json bumps with no image behind them; the
inverse — an image whose footer misreports its own version — is
the one that misleads in production, and both are now unpublishable.
ci.yml the unit suite on push and PR.
build-lux.yml is deleted rather than repaired. Besides the dead credential its
second job deployed by driving kubectl straight at the cluster, and those
objects are reconciled by hanzo-cd — an imperative apply is drift by
construction. Deployment is a tag bump in luxfi/universe plus a CD sync; CI's
job ends at a published, resolvable image.
lint:tsc is deliberately not gated: it is red on main on its own account
(TS2304 across ui/**/*.pw.tsx, plus the unrelated vite/ scratch app). It needs
a fix, not a permanently-red job standing in for one.
Version 1.1.15 -> 1.1.16, the patch above the last published tag v1.1.13.
Co-authored-by: Hanzo Dev <[email protected]>
styles/tokens.css was a 494-line FORK of @luxfi/ui/tokens.css, which is how it
drifted its own browns: --color-status-warn #B45309 / #FBBF7A and the whole
Chakra orange + yellow badge ramp (#FEEBCB #FFFAF0 #DD6B20 #FFFFF0 #D69E2E
#FEFCBF #B7791F #C05621 #7B341E #744210 #FAF089). It imports the umbrella now
and adds nothing of its own.
next.config.js hand-listed SEVEN of the engine's 55 packages in
transpilePackages and deduped none of them, which is how this app's store holds
seven physical copies of @hanzogui/popper (3.0.0, 3.0.1, 3.0.2 x3, 7.3.0 x2). A
context published at module scope is per-copy, so PopperAnchor read the empty
default and called refs.setReference on undefined once per tooltip trigger. It
uses withLuxUi now — the one place that wiring lives.
47 files reached for Tailwind's chromatic ramps directly (108 occurrences).
They resolve through tokens now, under short semantic names registered in the
@theme block: text-good / text-bad / text-warn, where warn is ACHROMATIC —
attention in Lux is the loudest neutral, not a hue. The Quasar certificate
tooltip stops naming colours it no longer paints ("BLS (green)", "Corona
(blue)", "ML-DSA (purple)") — copy that describes a palette has to change with
it.
Measured on the rendered pages: warm 0%, tinted-neutral 0.0003%, mean chroma
0.004 (/) and 0.003 (/txs) at 1440x900, 0.000 at 390x844, both modes; zero
uncaught setReference errors.
Two pre-existing defects surfaced because lint-staged now lints these files and
are fixed here rather than skipped: `Box` was used but never imported in
AddressEntity.pw.tsx, and eight lines already exceeded the 160-column limit.
Co-authored-by: Hanzo Dev <[email protected]>
TypeScript 7 reports TS2882 for a stylesheet side-effect import with no type
declaration; TS 5.x accepted them silently. Bundlers resolve stylesheets through
their own loaders, so the import never reaches the TypeScript module resolver —
this declares them legitimate rather than giving them a shape.
MEASURED, not assumed: this repo reports TS2882 under tsc 7 without the file and
zero with it. Repos where a framework already ships ambient CSS declarations
(Next's next-env.d.ts) were left alone — the declaration is inert there and was
not added.
Co-Authored-By: Hanzo Dev <[email protected]>
react-icons was declared and imported nowhere — a whole second icon catalogue
(20+ families, its own vintage of Lucide among them) sitting in the dependency
list waiting for someone to reach for it. Dropped.
In its place, the package the glyphs are actually generated from:
@hanzogui/lucide-icons-2, dev-only, the set @hanzo/gui standardises on.
`pnpm icons:sync` regenerates ./icons from it and rebuilds the sprite, so
refreshing the set is one command and the provenance is a dependency, not a
memory.
Co-authored-by: Hanzo Dev <[email protected]>
Drops "subnet" from the P-chain surface, which is not our vocabulary:
useSubnets -> useNets (useSubnets.ts deleted)
PChainSubnet -> PChainNet
GetSubnetsResponse -> GetNetsResponse
One "subnet" deliberately survives — `subnetID` in lib/api/pchain/wire.spec.ts.
That is the UPSTREAM P-chain wire field, read through a raw
Record<string,string> cast, so renaming it would break response parsing. The
rule is about our own language, not about lying to the wire.
Also adds deploy/k8s/explore-fe/ingress-mainnet.yaml. explore.lux.network is
served by the Next.js deployment explore-fe-lux (ghcr.io/luxfi/explore), not
the Go `explorer` binary, which only backs api-explore.lux.network. This
replaces drift where the plain Ingress pointed explore.lux.network at
explorer:80 — the Go binary's embedded, stale build.
Two pre-commit gates had to be satisfied, both legitimate:
- eslint max-len: three className lines (BridgePage 230 chars, ChainRow 188 x2)
exceeded the 160 limit; wrapped.
- cspell: `platformvm` flagged unknown. It is the P-chain VM's actual name, so
it is added to cspell.jsonc `words` rather than renamed in code. (Note for
the next person: .cspell-words.txt is listed under ignorePaths — it is a file
excluded from checking, NOT the dictionary. Adding a word there does nothing.)
Verified: tsc --noEmit reports ZERO errors in every file this commit touches and
zero stale-symbol errors, so the rename resolves completely. The project's 223
pre-existing errors are all in untouched files and unchanged by this commit.
Two root causes broke the vitest unit suite (6 suites failing → 29/29 green):
1. @growthbook/growthbook-react was intentionally removed from package.json in
the registry-cleanup commit (888ef29) and is unused anywhere in the app, but
the shared test render helpers (vitest/lib.tsx, playwright/TestApp.tsx) still
wrapped children in a dead <GrowthBookProvider>. Vite failed to resolve the
missing module, killing 5 suites that use the helper. Fix: remove the dead
import + wrapper (the provider held no growthbook instance and no feature
flags are consumed — it was a no-op). Do not re-add a deleted dependency.
2. tests/e2e/live-explorers.spec.ts is a Playwright live-infra E2E spec (imports
@playwright/test, hits live explorer URLs). It matched the vitest include glob
**/*.spec.ts and crashed the run. Fix: exclude **/tests/e2e/** from vitest —
those run under the Playwright runner (pnpm test:pw*), not the unit suite.
Also normalized pre-existing over-indentation in TestApp.tsx return block.
Before: 6 failed / 24 passed suites. After: 29/29 suites, 267/267 tests green.
Co-authored-by: Hanzo Dev <[email protected]>
v1.1.11 landed the three money values (Reward/Value/Fee) at 4.54:1 and
killed the overflow + Menu-button clip, but the network-utilization %
mapped to --color-badge-bright-green-fg (#25855A) measured 4.39:1 on the
real #FAFAFA card background (not pure white) -- 0.11 under bar.
Add dedicated semantic status tokens (--color-status-good/warn/bad) with
darker light-theme shades (#1A7A47 / #B45309 / #B91C1C -> 5.1 / 4.8 / 6.1
:1 on #FAFAFA) and light shades for dark theme, and point
getNetworkUtilizationParams at them. All four home-page values now clear
4.5:1 on every brand.
Co-authored-by: Hanzo Dev <[email protected]>
Three write-once-deploy-4 root causes from the 4-res audit:
1. Horizontal overflow (454px @1280, 38px @1920). The shared layout
Container forced 'min-w-[100vw] lg:min-w-[fit-content]', promoting the
whole page to its ~1734px max-content width so stat/block cards clipped
past the viewport. Make it a fluid column ('w-full min-w-0') and add
'min-w-0' to MainColumn so the flex chain shrinks and inner
overflow-x-auto regions scroll internally -> document.scrollWidth ==
innerWidth at every breakpoint.
2. White-on-white values (block Reward / tx Value / tx Fee /
network-utilization%). These passed legacy Chakra dot-tokens
('text.secondary', 'green.600'...) straight into an inline CSS 'color',
which browsers drop as invalid -> the text inherited white. Add one
resolveColorToken() helper (dot-token -> var(--color-*)), apply it in
SimpleValue (the value chokepoint for Reward/Value/Fee), and point
getNetworkUtilizationParams at real theme vars (bright badge fg shades,
all >=4.5:1 on the light card bg).
3. Broken 'Menu' button: the wallet/settings button stacked a 20px icon
over its label in a bare div inside a 32px overflow-hidden button (the
'51px in 32px' clip that read as a stray hamburger at lg+). Lay the
icon+label out horizontally (flex items-center gap-2, sized icon).
Co-authored-by: Hanzo Dev <[email protected]>
Two mobile-QA defects across the block explorers.
Defect 1 (org explorers showed goerli stub data): api-explore.{zoo,hanzo,
pars}.network served the self-signed INGRESS DEFAULT CERT (no cert-manager
Ingress for those SNIs), so browsers rejected every FE fetch and react-query
fell back to the goerli stubs behind stuck skeletons. FE wiring + backend were
already correct (all 6 chains served real per-chain data via the existing
IngressRoutes). Fix: dedicated letsencrypt-prod Ingress per org api host ->
explorer-unified:8090, mirroring api-explore-lux-network. Certs issued, all
three now ssl_verify=0, real data flows. Also source per-org brand identity
(NETWORK_NAME/currency) in the configmaps so titles render the right brand.
Defect 2 (mobile 390px detail pages: dropped labels, horizontal clip, tab
overlap): three shared-UI fixes.
- Footer: was the real horizontal-overflow driver — inline gridTemplateColumns
minmax(auto,470px) never collapsed on mobile. Now grid-cols-1 on mobile.
- DetailedInfo.ItemValue: add min-w-0 + mobile wrap so long address/hash values
stop forcing the page wider than the viewport (desktop unchanged).
- AdaptiveTabsList: @luxfi/ui Tabs silently discard Chakra style props
(overflowX/w/flexShrink/...), so express mobile scroll + shrink-0 as
className. Tab strip now scrolls instead of collapsing/overlapping.
Co-authored-by: Hanzo Dev <[email protected]>
Three UI-correctness fixes surfaced by visual inspection of explore.lux.network:
- DetailedInfo (block/tx/address detail pages): every field label + value
rendered white-on-white because the grid inherited the @luxfi/ui Tamagui
`t_dark` theme color while the page is in light mode. The two most-used
drill-downs were unreadable. Anchor the container to
--color-text-primary and labels to --color-text-secondary so text follows
the app color mode instead of the stray Tamagui theme.
- StatsWidget (home metric cards): the value had no explicit color and
inherited the same Tamagui white -> near-invisible "Total blocks /
transactions / addresses". Anchor to --color-text-primary.
- Validators dashboard + Network Overview: dropped the "Connected" (0/5) and
"Avg Uptime" (0.0%) widgets/columns. platform.getCurrentValidators reports
connected=null and uptime=0 for every validator on the public RPC (the API
node does not track peer uptime), so those numbers were fabricated-looking
zeros. Only chain-verifiable metrics (count, stake, delegation fee,
delegators) remain -- real data, no fake numbers.
Co-authored-by: Hanzo Dev <[email protected]>
The zoo/hanzo/pars brand deployments had been crashlooping at 0 ready since
June: their brand images bake NETWORK_NAME/currency/colors, but the runtime
envs-validator requires NETWORK_NAME in the rendered env and the brand
configmaps never set it — so every pod restart died on "NEXT_PUBLIC_NETWORK_NAME
is a required field". The lux configmap already carries SKIP_ENVS_VALIDATION for
the identical OIDC-placeholder reason; add it to the three brand configmaps
(mainnet + testnet) so they boot on their baked brand identity. Restores the
three brand explorers and lets the just-corrected chainId (pars 494949) + /v1
RPC configmap values take effect.
Co-authored-by: Hanzo Dev <[email protected]>
ChainSwitcher put onClick={handleToggle} on the PopoverTrigger's button, which
double-toggled against PopoverTrigger's own open/close handling (net: stays
closed) — the switcher never opened, so no chains were selectable. Removed the
redundant onClick + unused handleToggle, matching the working popover pattern
(UserProfileDesktop). PopoverTrigger now drives open state via onOpenChange.
Co-authored-by: Hanzo Dev <[email protected]>
v1.1.4 changed useProvider to return null (react-query v5 forbids undefined)
but only switched the lib/web3 hooks. Four UI consumers kept
'const { data: { wallet } = {} } = useProvider()'; '= {}' defaults only
undefined, not null, so null data threw 'Cannot destructure property wallet
of {} as it is null' -> whole-page error boundary. Fixed to
'useProvider().data ?? {}' in ChainWidget, MultichainEcosystemsTableItem,
NetworkAddToWallet, AddressAddToWallet (+ wrap a pre-existing 169-char line).
Co-authored-by: Hanzo Dev <[email protected]>
- wallet: brandFamilyChains derives sibling networks (Lux mainnet/testnet/devnet)
from the multi-tenant chain registry so connect/add presents EVERY registered
network for the brand, not just the env chain; dedupe by id.
- login/menu: useProvider queryFn returns null (react-query v5 forbids undefined)
+ callers switched from '= {}' destructure default to '?? {}' so a null result
no longer crashes the wallet hooks (the busted-menu root cause).
- manifest: fix Pars chainId 7070 -> 494949 (regression) and drop the forbidden
'Subnet' term from sovereign L1 network names (-> '<Brand> Network').
- Validators page + /api/pchain proxy present; rebuild makes the live 5-validator
view visible on explore.lux.network.
Co-authored-by: Hanzo Dev <[email protected]>
The Tooltip/Popover (@luxfi/ui → @hanzogui/tooltip → @hanzogui/[email protected],
hard-pinned) crashed with 'Cannot read properties of undefined (reading
setReference)' (×189, every row) under React 19 → Next error boundary 500 once
real indexer data renders. Force the fixed 3.0.6 patch via pnpm override.
- Rename all @blockscout/* npm packages to @luxfi/* aliases (150+ files)
- Override blue accent colors to gray for monochrome theme
- Fix dropdown/menu/popover fonts to use Geist Sans
- Change Latest Blocks to horizontal scrolling layout
- Show dashes instead of zeros for validator stats on error
- Fix GHA deploy step to use correct deployment name
- LatestBlocks: full-width layout instead of fixed 280px sidebar
- Stats: 4-column grid on desktop, 2-column on mobile
- NetworkOverview: fallback known L1 chains when P-chain API unreachable
- NetworkOverview: expose isError from useCurrentValidators
- P-chain hooks: add retry:2 for resilience
- UserProfile: use address_hash instead of id for display
- Replace middleware.ts with proxy.ts (unused in production Docker)
- Add toolkit/chakra/separator re-export
- tsconfig: jsx react-jsx, formatting cleanup
- dev.sh: disable turbopack (stability)
@libp2p/webrtc imports @ipshipyard/node-datachannel/polyfill which
requires native C++ compilation. Stub provides ESM+CJS exports for
both main and polyfill subpaths with no-op WebRTC classes. Also
ignores pnpm-lock.yaml in cspell and copies stubs dir in Dockerfiles.
- Replace yarn v1 with pnpm 10.11.0 across all build configs
- Move misplaced deps to devDependencies (cspell, esbuild, @openzeppelin/contracts, @next/bundle-analyzer, @types/papaparse, @types/react-scroll)
- Remove stale @types/[email protected] override — use correct React 19 types (fixes 22 type errors)
- Convert yarn resolutions to pnpm.overrides
- Update Dockerfile, Dockerfile.lux, CI workflows, and all shell scripts
- Add .npmrc with shamefully-hoist for Next.js compatibility
- Add outputFileTracingRoot to next.config.js
* envs and feature setup
* home page layout
* cross-chain txs page
* cross-chain transfers page
* cross-chain transfers on tx page
* cross-chain messages on tx page
* cross-chain tx page
* cross-chain tx details token transfers
* stats and counters for lists
* migrate to new API version
* add swagger
* mobile lists
* placeholder views on address page
* tests
* fix adaptive tabs initial state when there is only one tab in the list
* tweaks
* bug fixes
* fix tests
* update screenshots
* migrate to new api version
* add chain label tooltip, copy button to txs in lists and same address highlight
* list views on address page
* fix tests
* fix links to current address in the list views
* update tooltips
* update tables layout
* review fixes
* fix spelling
* update screenshots
* update screenshot
* basic login button implementation
* refactoring and add dynamic auth button to the other pages
* connect wallet without authenticating an user
* login and logout user in blockscout account
* refactoring and RPC overrides
* authenticate user with connected wallet
* add ENS to profile button
* override evm chains and fix contract write with waas wallet
* disconnect wallet and fix docker build
* add dynamic imports to components that use web3 wallet packages
* refactor user profile directory
* clean up
* tweaks
* update pw ci script
* fix "connect wallet" button behavior
* handle linking email to profile with dynamic provider
* fix test
* async loading web3 hooks and remove dynamic loading from components that use them
* fix pw tests
* add account navigation in horizontal layout when auth provider is dynamic
* fix address verification modal
* review fixes
* spelling fixes
* essential dapps config: switch to concurrent request and and add worker termination
* fix rollbar error logging and enable it for server errors
* fix for tokens request on address page
* update generator script for multichain config
* fix hot contracts interval select
* update screenshot
* update screenshots
* pass chainId to trackTransaction for essential dapps
* fix vertical alignment in hot contracts table
* bump next.js to v15.5.10
---------
Co-authored-by: isstuev <[email protected]>
Co-authored-by: Max Alekseenko <[email protected]>
* add EmptyState component to toolkit and use it in the stats page
* remove EmptySearchResult component
* tweaks
* refactor ComingSoon component
* fix toolkit build
* rollback changes in main preset