Commerce is called at /v1, the indexer at /v1/indexer, and the operator
serves /v1/clusters. The console's unused fleet helpers called a /api
base nothing serves; they are gone. The legacy Quart env files named an
app that no longer exists.
Claude-Session: https://claude.ai/code/session_011i2AG9nwpiKUCVxAdDzYuU
A static export has no host when it prerenders, so BrandProvider renders the
tree only once the browser has resolved the host, pages that show the brand
are client components, the static head names no brand, and the title and
favicon come from the host. The chain page keeps its static params and
metadata on the server and renders its body on the client, with RPC endpoints
on the brand's API origin at /v1/rpc/{chain}/{network}.
Hanzo's API origin is web3.hanzo.ai itself. The node fleet view, its landing
strip and its nav link belong to the brand that operates a fleet, which is
Lux alone. llms.txt, one file for every host, names no brand and points at
each host's own /docs/api.
Marketing copy states no certification the company does not hold and no
unmeasured uptime, latency, volume or response-time figure. Every internal
link points at a page that exists, every catalog chain is prerendered, and
the routes test holds page bodies to zero dead links.
Co-authored-by: Hanzo Dev <[email protected]>
The @hanzo/gui engine writes --background onto :root and paints <body> from
it with an unlayered rule for prefers-color-scheme, which outranks every
Tailwind layer, so a visitor on a light OS got a light page under dark text.
The app's ground token is --ground, and an unlayered `html body` rule sets
the ground and ink of the scheme the theme provider chose. The d3 charts
read the same token.
Co-authored-by: Hanzo Dev <[email protected]>
hanzo.yml declares the one image, ghcr.io/hanzoai/bootnode:<version>-web,
built from the repository root with console/Dockerfile and named by the
console's own package version, and gates it on the console's unit tests
and typecheck. .hanzo/workflows/cicd.yml runs hanzoai/ci build.yml@v1 on
push to main.
The console manifest names every dependency from the pnpm catalog, and the
catalog carries the versions this code builds against: @luxfi/ui 7.4.3 with
@hanzo/gui 7.3, next 16.2, TypeScript 5.9 and the Zen faces from
@hanzo/font 1.8.0. `pnpm install --frozen-lockfile` and `next build` both
pass. The unused geist and lucide-react entries and the npm lockfile are
gone, tests report in TAP so the pipeline can count them, and the README no
longer calls this repository a retired copy.
Co-authored-by: Hanzo Dev <[email protected]>
ghcr.io/hanzoai/static served only linux/amd64 from v0.5.1 through 0.5.14,
so an image built on it has no manifest an arm64 node can pull. 0.5.15 is
the same server source published as an index over both architectures.
Co-authored-by: Hanzo Dev <[email protected]>
A version written into a manifest is written into every manifest that wants it,
which is how eslint came to exist in a hundred and four forms across this estate
and how a package came to resolve twice at run time. The catalog moves the number
out of the manifest: a manifest names the package, the workspace names the
version.
Four are held below their latest because the newer release cannot be used, and
the reason is recorded rather than the version alone: @noble/hashes 2 renamed
every subpath the web3 stack imports, @splinetool/runtime 2 depends on a package
that was never published, @dynamic-labs 5.4.1 the same, and react-resizable-panels
3 points its types condition at the bundle.
Every dependency on a @hanzo/@luxfi/@zooai package names the same version across
the repo: a catalog entry where pnpm supports one, an exact pin where it does
not, and a range only where a peer genuinely means one.
pnpm itself is pinned too — the estate ran eighteen different versions, five of
them too old to read a catalog.
luxd serves /v1 only — server/http/server.go pins baseURL="/v1" — so every
/ext call in this repo was a silent no-op. The fleet collector's _rpc()
returns None on non-200, so the lux.cloud dashboard rendered nodes_rpc_ok:0,
version:null and every node rpc_reachable:false while the fleet was in fact
running luxd/1.36.2 (mainnet), 1.36.24 (testnet), 1.36.25 (devnet).
Fixing the path alone would not have helped: the collector addressed each
validator by a hardcoded public LoadBalancer IP, and there are now zero
LoadBalancer Services in lux-mainnet/testnet/devnet. Those IPs time out. So
node addressing is no longer configured — it is derived from the cluster on
every collection, and the one way to reach a validator is the lux-k8s API
server's pod proxy, since luxd RPC is cluster-internal.
That collapses the old MODE A / MODE B split into a single path: read the CR
for the declared shape, the pods for phase/readiness/IP, then each pod's
JSON-RPC through the proxy. One credential authenticates both. Deleting the
endpoint ConfigMap and its 15 baked-in IPs removes the failure mode where
infrastructure moves and a dashboard quietly reports zeros.
Probes move to /v1/health/liveness, not /v1/health: a degraded-but-running
validator answers /v1/health with 503, so probing it would restart nodes that
are merely behind. Any fleet from charts/lux could never have gone Ready.
LuxNode.external_ip becomes .ip — with no public per-pod endpoint, the honest
value is the pod IP.
Avalanche endpoints keep /ext; that is their API, not ours.
Measured, with controls: pod-proxy POST /v1/info -> 200 on all 15 pods;
/ext/health -> 404; /zzz/health -> 404; nonexistent pod -> 404. A test asserts
every probe path is under /v1 and fails if /ext returns.
Co-authored-by: Hanzo Dev <[email protected]>
Two duplications, both removed.
1. The header's account cluster was an email in a <span> and a Sign out button.
It is now <OrgSwitcher/> + <UserMenu/> from @luxfi/ui — the same controls the
MPC dashboard and lux.market render, reading the same IAM verbs
(get-account, get-organizations, get-organization-projects), with the same
phone sheet, which this bar had no equivalent of at all. Signed out they
offer sign-in rather than showing an empty list that reads as an account with
no organizations.
2. lib/brand.ts held a SECOND host→brand table. It kept what is genuinely its
own — logos, taglines, palettes, footer links, the ecosystem launcher, the
API origins that answer — and now takes issuer, client id and IAM host from
@luxfi/ui/white-label, which is the same resolver every Lux surface already
uses. Two copies of one fact is two things to get wrong, and these two DID
disagree: this table had Zoo on zoolabs.id (right) and the shared one on
id.zoo.network (an alias whose own discovery document names zoolabs.id, so a
client on it rejects every token). The shared table is fixed; this file no
longer has an opinion to be wrong about.
The bundler wiring for the gui engine comes from `withLuxUi` — the one place it
lives, rather than a hand-rolled transpile list per app.
`next build` passes (static export, every route emitted). `pnpm test` 26/26,
including the assertion that lux.cloud + lux-cloud + https://lux.cloud/auth/callback
— the one login that works today — is unchanged.
Co-authored-by: Hanzo Dev <[email protected]>
74 files imported `lucide-react` directly, pinned ^1.26.0, while @luxfi/ui —
which this console renders alongside — resolves 1.27. Two Lucide copies from two
resolutions in one bundle is how a set stops being one set. Two more glyphs were
not from any icon library at all: the node list drew its network in/out arrows as
the literal characters "↓" and "↑", and the chain onboarding selector drew its
tick as "✓". Those are font glyphs — they take the text metrics, not the icon
box, so they sit on a different baseline and scale with the font, not `size-*`.
One source now: `@luxfi/ui/icons`, Lucide as @hanzo/gui standardises on it and
@luxfi/ui themes it. `lucide-react` is out of package.json, so per-surface drift
is no longer expressible; @luxfi/ui comes in at 7.4.3, which is where `./icons`
lives. The three text glyphs are ArrowDown / ArrowUp / Check.
All 121 distinct icon names this console uses were checked against the 6014
symbols @luxfi/ui/icons exports before the rewrite — zero missing, with a
negative control. `next build` is clean: 78 routes, Next 16.2.11 + Turbopack.
The d3 chart roots (node-globe, node-map, globe-view, donut-chart, fleet-map)
keep their <svg>: those are plots, not glyphs. The command menu's "↑↓" stays —
it is a keyboard legend inside <kbd>, and it means the keys.
Co-authored-by: Hanzo Dev <[email protected]>
/v1/iam is guarded — a dashboard probe there shows IAM as down when it is up.
/healthz answers 200 unauthenticated. Userinfo is /v1/iam/oauth/userinfo in
the deployed route table; there is no /v1/iam/userinfo.
Co-authored-by: Hanzo Dev <[email protected]>
Hanzo standard (hanzoai/openapi): every route is /v1/<service>/<resource>.
The /api/ dialect is dead on the deployed IAM (iam v1.33.24 serves 154
paths, all under /v1/iam). Prefix only; route spelling unchanged.
Co-authored-by: Hanzo Dev <[email protected]>
Three dashboard surfaces fetched a RELATIVE /api/v1/... path. That cannot
resolve in a static export — there is no server at that origin — so the
observability panel, the services grid and the AI chat were dead on every
deployed host. It also violates the v1-only rule: the prefix is /v1, never /api.
They now call the host-derived origin at /v1/...
Deleted app/api/health/route.ts, which emitted out/api/health — an /api/ path
SHIPPED INSIDE the artifact.
The deeper fix is the duplication. lib/fleet.ts kept a PRIVATE copy of the
"brand apiUrl, else build-time default" resolver. A second private copy of
exactly that logic, in lib/auth.tsx, shadowed its own import and called itself —
infinite recursion that killed every checkAuth/login/register on any host
classified as local, i.e. all of local development. That copy is already gone;
this removes the other one. apiBase now lives once, exported from lib/api.ts
beside getApiUrl, and fleet.ts imports it.
Left alone deliberately: the /api/v1 strings in app/products/mpc are curl
examples for a CUSTOMER's own host, not calls this app makes.
Console compiles clean.
Co-authored-by: Hanzo Dev <[email protected]>
Names the two halves for what they are. The repo already holds both (12407e9 merged
bootnode/bootnode into this history); this makes the frontend directory say so, so the layout
reads the way the owner describes the system: backend and frontend separate, one repo.
git mv throughout — 187 renames, history preserved. Every path reference follows: hanzo.yml
build context and dockerfile, compose.yml, compose.prod.yml, start.sh, LLM.md,
infra/k8s/web-deployments.yaml, infra/cloud/deploy.sh, and the Dockerfile's own COPY paths.
Image and container names (bootnode-web, the -web tag suffix) are deliberately NOT renamed here:
those are a deploy contract the App CR and ingress bind to on port 3001, and breaking them is a
separate, staged change.
Console builds green after the move; the Go tree builds.
Co-authored-by: Hanzo Dev <[email protected]>