Removing Vercel analytics left the site posting to /v1/event with no
credential, and every batch came back 403 — telemetry replaced with
telemetry that ingests nothing.
The door does not trust the request Host on purpose, because a Host
header is spoofable, so a static page proves its org by carrying a
publishable key. pk_ keys are write-only and HMAC-verified with no
database hop, which is what makes one safe inside a public bundle. The
build supplies it, so each deployment reports as the org that built it,
and a build without one stays inert instead of posting rejects.
Verified against prod: POST /v1/event with a pk_ returns
{"accepted":1,"dropped":0} on both the header and beacon paths.
The docs site pulled in RainbowKit, which bundles a WalletConnect
connector. That connector phoned pulse.walletconnect.org on every page
load — third-party telemetry from a documentation page — and it could
not have worked anyway: the project id fell back to the literal
'YOUR_PROJECT_ID'.
The browser wallet already speaks EIP-1193, so wagmi's injected
connector is all the identity demo needs: the extension is the only
party in the flow, with no bridge service and no vendor id. ConnectWallet
replaces RainbowKit's modal using our own Button.
wagmi and viem stay — they are how the page reads and writes contracts.
Verified: a clean build exports 8,879 files and the shipped JS contains
no walletconnect reference.
On 0.4.1 every HTML route answered 301 -> /index.html, so the internal
filename showed up in the address bar and in the URLs Next derives for
route prefetches.
The site was on Cloudflare Pages as a direct upload — no git connection,
last deployed 2026-03-28 — while a GitHub Pages workflow ran on every
push against a repo that has Pages disabled (the API 404s). Two deploy
paths, neither of which shipped what main said.
One way now: a Dockerfile builds the static export and serves it with
ghcr.io/hanzoai/static, exactly like every other Hanzo static site, on
our own runners and our own ingress.
Along the way, drop what those two vendors left behind:
- output:'export' was gated on GITHUB_ACTIONS || CF_PAGES, so a build
anywhere else silently produced no export at all. The site is a static
export wherever it is built.
- basePath pointed at a '/react-sdk' GitHub Pages subdirectory that is
not this project.
- highlight-code keyed off GITHUB_ACTIONS to pick server vs client
highlighting; it means 'is this a production export', so it says that.
- pages/api/components returned a JSON blob and 404s in production,
because a static export cannot serve an API route. The live index is
the generated /api/registry/components.json.
.gitignore contradicted itself: line 8 ignored the lockfile while the
lockfiles block declared 'pnpm-lock.yaml - needed for CI/CD, do not
ignore'. With no lockfile in the repo and 'pnpm install
--no-frozen-lockfile', every CI run resolved whatever was newest, so the
Pages deploy has been red since 2026-07-22 on '@x402/*' — optional lazy
imports inside a dependency that a newer release started pulling in and
nothing pinned.
This lockfile is the resolution the app builds green on locally
(verified: full next build, 312+ static pages, exit 0).
link-workspace-packages=true, so the app resolves @hanzo/event to
pkgs/event no matter what the range says, and its exports point at dist/
— which only exists once the package is built. The workflow already
builds pkgs/ui for the same reason; event needs the same step. Declare
the dep by published range like every other @hanzo/* dep in the app.
@hanzo/analytics (pkgs/capture) is the superseded duplicate and is gone
from the tree, so depending on it would not resolve. @hanzo/event is the
one telemetry client — one Event type, one door (POST /v1/event). Its
host already defaults to the one edge, so the site needs no config
beyond naming itself.
ui.hanzo.ai is a static export on GitHub Pages, not Vercel, so
@vercel/analytics injected /_vercel/insights/script.js — which 404s and
is then refused as text/html, on every page. It was also a second way to
do a thing we already own: pkgs/capture ships @hanzo/analytics.
Route both call sites through one client (lib/analytics.ts): the mount
sends pageviews, trackEvent sends events. Neither knows the vendor —
they name what happened and the client owns where it goes.
Two consumer-caught blockers in 8.0.7:
1. gui-native "Missing theme". The gui Popover's SheetController re-roots the trigger
subtree and reads the theme from React context; on gui-native hosts
(@hanzogui/react-native-web-lite, theme in context, no CSS-class fallback) that throws
at MOUNT. FIX: drop the gui Popover entirely — every menu now rides ONE Portal path
(the ContextMenu approach that already worked on gui-native):
- menu/FloatingMenu.tsx — shared floating primitive: gui Portal + PortalTheme
(re-applies the captured theme inside the portal) + anchor positioning (trigger rect
or cursor point) + edge-flip + dismiss + roving keys.
- DropdownMenu, ContextMenu, SelectMenu (now a thin DropdownMenu), ComboBox all use it.
No Popover, no Sheet, no re-root.
Verified on a Vite + rnw-lite harness (theme in React context, NO root theme fallback):
NEW DropdownMenu + SelectMenu render correctly themed through the Portal, dark + light.
2. Next 16 flight-loader parse error. 8.0.7 shipped raw `.ts` with inline
`export { X, type Y }`; Next's flight-client loader parses node_modules `'use client'`
modules WITHOUT TS and choked on `type`. FIX: ship a COMPILED dist —
- tsup → ESM (.js) + CJS (.cjs), every dep external, all 11 subpath entries.
- tsc → .d.ts (dist). scripts/add-use-client.mjs stamps `'use client'` on every output
(tsup banner misses split chunks). CSS copied.
- package.json main/module/exports repointed at dist; files=[dist].
`node --check` passes on all 28 compiled files; the 8.0.7 offender is now valid JS with
the TS `type` stripped — the flight-loader parse error is structurally impossible.
pnpm typecheck: 0 · pnpm test: 19/19 · pnpm build: exit 0.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The @hanzo/gui v5 config omits longhand style aliases that have a shorthand, so
the strict pkg/ui build (createGui augmentation) rejected backgroundColor/alignItems/
justifyContent/minWidth/maxHeight/paddingHorizontal/paddingVertical/marginVertical/
flexShrink/borderRadius/userSelect. Convert the menu primitives + the Popover.Content
shells to the config vocabulary (bg/items/justify/minW/maxH/px/py/my/shrink/rounded/
select). Runtime is unchanged (Tamagui accepts both) — this is the published 8.0.7 code.
pnpm typecheck: 0 errors · pnpm test: 19/19 · pnpm build: exit 0 (48 d.ts emitted).
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
@hanzo/ui/product gains the shared menu primitives every Hanzo surface
(app/chat/desktop) needs, all on @hanzo/gui/Tamagui, all on ONE item spec so
menus are pixel-identical across the fleet.
- menu/items.tsx — the ONE spec: MenuPanel ($color2 surface, hairline, radius-12,
pad-4), MenuItemView (h30, px8, gap8, 16px icon slot left, 13px label, right
affordance shortcut/check/chevron; hover/focus/press → purple accent-soft;
selected → check+accent; disabled muted; optional 2nd-line description),
MenuSeparatorView (1px hairline, 4px margin), MenuLabelView (11px uppercase),
renderMenuItems. Geometry literal px on the 8-grid; colour theme-adaptive tokens
+ brand purple via var(--hanzo-accent[-soft]).
- menu/DropdownMenu.tsx — click menu on gui Popover (bottom-start, allowFlip,
useControllableState).
- menu/ContextMenu.tsx — right-click menu on gui Portal, cursor-positioned (fixed),
edge-flip, dismiss on outside/Escape/scroll/resize/blur.
- menu/portal-theme.tsx — PortalTheme: captures useThemeName() at the trigger and
re-applies <Theme name> INSIDE portaled content, so menus render correctly through
a portal under a nested <Theme> (light+dark). Fixes GAP 1 (desktop "Missing theme").
- menu/roving.ts — shared Arrow/Home/End/Escape keyboard nav.
- SelectMenu + ComboBox — adopt the shared spec + PortalTheme fix (DRY; identical rows).
- ThemeToggle — framework-agnostic: controlled via theme + onToggle/onThemeChange
(NO framework dep, for Vite/Tauri/Express); uncontrolled falls back to the OPTIONAL
@hanzogui/next-theme via ThemeToggleNext (lazy, ErrorBoundary→DOM), so console/Next
stays backward-compatible and non-Next hosts build.
- package.json — 8.0.7; @hanzogui/next-theme added as an OPTIONAL peer.
Verified against the real published @hanzo/gui@7.3.0: product source type-checks
clean; a Vite harness renders DropdownMenu + ContextMenu + SelectMenu + ThemeToggle
through portals under a nested <Theme name="dark"/"light"> — panels correctly themed
(t_dark rgb(20,20,20) / t_light rgb(247,247,247)) while root stays light, no errors.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Makes @hanzo/gui (Tamagui) / React Native and Tauri desktop emit the SAME
canonical Events as the web — same semantic hierarchy, privacy gate, and ONE front
door (POST /v1/event via @hanzo/event).
- '.' (React/Tamagui): ObserveProvider + ObserveScope compose the semantic path
from the React tree (no DOM); useObserve().press/changeText/event/screen wrap
RN/Tamagui handlers; useEventStream for playback
- './tauri' (react-free): bindTauri runs the @hanzo/observe DOM engine in the
webview + forwards Tauri native events; @tauri-apps/api is a runtime-optional peer
- pure emit/semantic/redact core reuses @hanzo/observe wireProps/labelFor/
sensitiveKey so native and web are byte-identical on the wire
19 tests green (semantic/emit/tauri + React mount via react-dom/client).
Reuses the framework-agnostic @hanzo/observe engine; binds it to Svelte idioms:
- createObserver(client) — bootstrap capture in a root +layout (idempotent, SSR-safe)
- observe action — use:observe={{ name }} stamps a stable component name the engine
labels interactions with; { private } / { view } opt out / into a subtree
- stream store — a Svelte-readable live window of interactions for session playback
Emits through @hanzo/event to POST /v1/event. 7 tests green; svelte is an optional
peer (action + store satisfy Svelte contracts structurally).
@hanzo/observe — watches every click/input/nav/visibility across the tree,
annotates each with a semantic hierarchy (component path / role / data-testid /
aria) auto-derived from the DOM as a small JSON-LD document, and emits it through
@hanzo/event to the ONE front door (POST /v1/event).
- framework-agnostic engine ('.'): Observer + annotate + redact + Stream
- React default experience ('./react'): ObserveProvider + useEventStream (session
playback) + useObserver
- privacy-first: input values withheld by default, sensitive fields always
redacted, data-hz-private subtree opt-out; fail-soft throughout
28 tests green (annotate/redact/stream/observer).
The 0.3.0 CJS bundle went to dist/index.js, which Node parses as ESM under
"type": "module" — require('@hanzo/event') threw "exports is not defined in
ES module scope". Emit CJS as .cjs (ESM stays .mjs) and map each exports
condition to its own types. No API change. Bumps 0.3.0 -> 0.3.1.
Repoint @hanzo/event onto Hanzo Cloud's single ingestion front door
(POST /v1/event, body {batch:[Event,...]}), replacing the deprecated
/v1/analytics + /v1/tracker split and the interim /v1/ingest key door.
One door, one wire, for cookie / bearer / publishable-key auth alike.
Errors now reach the error-tracking lens. The batched Event wire carries
`type`, which cloud folds to event_type='error' (foldException +
canonicalType); the exception rides the top-level `error` field, lifted
into properties.$exception. The four-field {event,distinctId,time,
properties} array has no `type`, so it can never be lensed as an error —
the batched wire is the one that lights up web + product + error.
Publishable keys authenticate directly on /v1/event now: Authorization:
Bearer pk_ on fetch, ?ingest_key=pk_ on a headerless unload beacon.
Delete the orphan @hanzo/analytics@0.1.0 thin dup (pkgs/capture, a
leftover of the @hanzo/capture -> @hanzo/event rename that nothing
imports) so the repo carries exactly one telemetry client.
VERSION 0.2.0 -> 0.3.0.
ONE framework-agnostic surfaces.data module (id doubles as icon key) is now the
single source every launcher consumes. Adds hanzo.bot + hanzo.chat, collapses the
redundant cloud/console pair, renders a distinct per-surface icon, and omits the
current surface (no self-link). Publishes 8.0.6.
afd5f82f renamed the dir but a bad `git add` pathspec dropped the content edits,
so pkgs/event shipped as @hanzo/capture@0.1.1 and `pnpm --filter event` matched
nothing (nothing published). This lands the real change: name @hanzo/event@0.2.0,
the captureError/captureException surface, auto error handlers, React ErrorBoundary,
Exception type, and the 6 error-capture tests (28/28 green locally).
Rename the capture SDK to @hanzo/event and fold error tracking into it, so ONE
client emits every kind of event — pageview/event/identify/group AND errors — on
one batched stream. The server lenses that one stream into product analytics, web
analytics, and error tracking (insights/analytics/sentry.hanzo.ai). Subsumes
@sentry: no second SDK, no second pipe.
- New 'error' EventKind + Exception type; WireEvent carries the exception.
- Analytics.captureError()/captureException(): normalize any throwable, emit a
type:'error' event, flush at once (a crash may unload the page). Never throws
back into the app.
- Auto-capture (config captureErrors, default on, browser-only): window.onerror +
unhandledrejection → error events. The drop-in @sentry replacement.
- React ErrorBoundary (./react): reports render errors React swallows before
window.onerror sees them — the React half of the replacement.
- 0.1.1 → 0.2.0. 28/28 tests pass (6 new error-capture specs), tsup build green.
Consumers (app/chat/console/hanzo.ai/operator) migrate @hanzo/capture →
@hanzo/event next; @hanzo/capture stops shipping new versions.
Live verification against prod found two contract mismatches the offline build
could not see:
- the fleet bound to ZERO rows: /v1/deploy/applications serves argoproj-shaped
items (metadata.name, spec.source.*, status.{sync,health}.status,
status.summary.images[]) but the adapter read only the flat native keys.
normalizeDeployApp now reads BOTH wires, flat first, then the nested fields.
- the resource tree 404d: the wired route is applications/:name/resource-tree;
the shorter :name/tree belongs to an unregistered handler.
Live now: 78 applications (69 Healthy / 9 Degraded), env chips, detail with the
real APP->DEPLOYMENT->POD topology, 0 page errors, 0px mobile overflow. The live
payload is pinned as a regression fixture. 38/38 unit, e2e desktop+mobile green.
A native Hanzo CD app (Vite + React 19) that replaces the ArgoCD React fork:
fleet list, app detail with the live resource tree, sync/rollback, over cloud
/v1/deploy. Mobile-first, Geist, small mark, 245KB (the fork was ~18MB).
Ships as a static build to the s3://cdn/cd plane cd.hanzo.ai already serves.
Red review of the cd.hanzo.ai app found three real defects in the SHARED
components (so this hardens the console surfaces too):
- tree: buildResourceGraph seeded `children` lazily per node, so any tree that
listed a child before its parent — or any ownerRef cycle — dereferenced
undefined and threw during render. K8s object lists are not topologically
sorted, so this fired on ordinary data and, with no error boundary, blanked
the whole dashboard. Pre-seed every id before the edge pass.
- health/sync: the substring fallback up-guessed a BAD state to a GOOD one
(NotReady/unavailable -> Healthy, notsynced -> Synced). An incident shown
green is worse than one shown Unknown, so the positive up-guess is gone;
unrecognized folds to Unknown. foldSync also normalizes separators so the
canonical out-of-sync folds at the source.
- diff: lineDiff always built the full O(n*m) LCS matrix; a large ConfigMap
(reachable — not Secret-excluded) froze the tab. Size-guard to a block diff.
Also: an app-level ErrorBoundary so one render throw can never white-screen the
dashboard, client log caps + array-shaped log tolerance, a Secret-kind skip in
the tree, and vitest no longer globs the Playwright specs.
Tests assert the fixed behavior: 35/35 unit (incl. the adversarial fuzz suite
flipped from codifying the bugs), 42/42 gitops, 2/2 e2e desktop+mobile.
Replace the ArgoCD React fork (deploy/ui, webpack argo-cd-ui) with a focused,
mobile-first CD dashboard built on the shared Hanzo component packages over the
native cloud CD plane (/v1/deploy). Its job stays: see every operator App CR with
stats · sync · health · resource tree · logs · sync/rollback.
- Vite + React 19 STATIC SPA → dist/ (index.html + login.html + CNAME + assets),
published to the existing s3://cdn/cd static plane cd.hanzo.ai already serves
(ingress staticFiles/spaMode; /v1/deploy peeled to cloud). No ingress/backend
change.
- @hanzo/gitops (framework-free ArgoCD-replacement views): GitopsAppList for the
fleet; GitopsSyncPanel + GitopsAppTree + GitopsNodeInfo + GitopsRollbackDialog
composed for the app detail (lazy per-node /resource + /logs). Resolved via Vite
path alias off the built dist so it builds without a full monorepo install.
- src/lib/adapt.ts maps the real /v1/deploy DTOs into the @hanzo/gitops
view-models, reusing the package's foldHealth/foldSync (strips [-_] so the
hyphenated 'out-of-sync' folds to OutOfSync, not Unknown).
- Auth: the admin-console PKCE login.html (ported) → hanzo_iam_token cookie the
cloud binary validates (SuperAdmin gate); same-origin credentialed /v1/deploy.
- Lean self-contained topbar (Geist, small mark, env scope) in the interim; the
@hanzo/ui-shadcn shared shell + @hanzo/canvas map are registry-install-gated
follow-ons (the map already ships green in hanzoai/console).
- Verify: tsc 0; vitest 8/8 (adapter + the sync-fold fix); playwright render +
mobile (no horizontal body scroll at 390).
One selector for hanzo.app/chat/console: family groups (Enso, Zen,
Anthropic, OpenAI first), cmdk search, premium marks, context hints,
fetchModelCatalog off /v1/models. catalog.ts pure + SSR-safe.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Adds the one model selector for every Hanzo app to the existing ./models
subpath: hanzo.chat-style family grouping in a compact Radix Popover + cmdk
Command combobox (grouped sections, family headers, premium markers, context
suffixes, keyboard nav, type-to-filter search over 12 models). Monochrome,
dark-first, data-agnostic.
- catalog.ts: ModelCatalogEntry, familyOf, groupModelsByFamily, isChatModel,
filterChatModels, fetchModelCatalog (pure, SSR-safe, no caching/state)
- ModelSelector.tsx: ModelSelector + ModelSelectorProps
- wired through src/models/index.ts; version 5.8.0 -> 5.9.0 (minor, additive)
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The Button always rendered two JSX children (a loading-spinner slot + children).
Under asChild, Comp is a Radix Slot which calls React.Children.only on that
2-element array and throws 'expected to receive a single React element child',
crashing the consuming tree. A slotted button can't host an injected spinner
anyway (the child replaces the button), so with asChild we now pass children
through as the single child Slot requires. Non-asChild loading behavior is
unchanged. This kills the whole <Button asChild> crash class for consumers
(hanzo.app hit it on /projects and /dev).
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Document the canvas/wallet/network/billing/dashboard/usage/gitops/data subpaths
(thin re-exports of their home packages, optional peers) + the pkg/ui publish
caveat, so consoles import every recent component from the single @hanzo/ui.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Complete the one-surface pattern the @hanzo/ui/gitops subpath started: add thin
re-export subpaths so a console imports every recent component from the single
@hanzo/ui entry while each lives once in its home package (zero duplication,
optional peers — only pulled when the subpath is used):
@hanzo/ui/canvas -> @hanzo/canvas (ProjectCanvas, ServiceNode, DeployTimeline, EnvSwitcher, ServiceDetailDrawer, ServiceStatusBadge)
@hanzo/ui/wallet -> @hanzo/ui-shadcn/wallet (WalletMenu, injectedEvmAdapter [EIP-1193], walletAvailable, ensureEvmNetwork)
@hanzo/ui/network -> @hanzo/ui-shadcn/network (NetworkSwitcher, useNetwork, configureNetworks, HANZO_NETWORKS)
@hanzo/ui/billing -> @hanzo/ui-shadcn/billing (CreditModal)
@hanzo/ui/dashboard -> @hanzo/dashboard
@hanzo/ui/usage -> @hanzo/usage (UsageMeter, UsageProviderCard, UsageDashboard)
Together with the existing ./gitops and ./data subpaths that is the 8 newest
component kits reachable from @hanzo/ui. Bump @hanzo/ui 8.0.2 -> 8.0.3.
Publish the two lagging homes via ARC (pkgs/* -> publish.yml):
@hanzo/canvas 0.2.0 -> 0.2.1 (npm was behind at 0.1.0)
@hanzo/gitops 0.1.0 -> 0.1.1 (first publish)
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Thin subpath so a console can import from '@hanzo/ui/gitops' while the code lives
once in @hanzo/gitops. Optional peer dependency — only pulled when the subpath is
used.
demo/demo.tsx server-renders the sync panel + resource tree + node drill-in
(diff tab) + applications list into a self-contained demo/index.html for visual
review. Reproduce command in the file header.
happy-dom + Testing Library render proofs: tree draws a card per resource with
SVG edges + collapse hides pods + selection fires; node panel shows manifest and
switches to diff/events/logs; diff classes add/del + tallies; app list filters by
search. 42 tests green (33 pure + 9 render).
Table/grid of applications with name/project, sync + health badges, revision,
source and age. Search across name/project/namespace/source, multi-select
health + sync filters, sortable columns (name/health/sync/age via the pure
ranks). Data-prop-driven; row-open callback.
Publishing is now a single step: bump a package's version in its
package.json, merge to main, and publish.yml publishes the changed
@hanzo/* package to npm.
Remove the changeset machinery (.changeset/, changeset-version.js) and
the redundant publish paths — release.yml (version-PR bot),
npm-publish.yml (manual dispatch), publish-on-tag.yml (tag trigger),
prerelease*.yml (betas) — leaving publish.yml as the only path. Drop the
@changesets deps and the `changeset version` scripts; refresh the
CONTRIBUTING and LLM docs.
* @hanzo/analytics: shared product-analytics capture client
Tiny batched client emitting pageview/event/identify/group to Hanzo Cloud
(/v1/analytics + /v1/tracker) — never to insights-capture directly. First-touch
UTM/referrer/refCode attribution persisted and attached to every event;
beacon-on-unload; dual cookie/bearer auth; SSR-safe; tenant is stamped
server-side (never sent by the client). Ships a framework-agnostic core and a
@hanzo/analytics/react provider + hooks, plus the shared EVENTS/GOALS/COHORTS
vocabulary. 22 unit tests + tsup build (cjs/esm/dts) green.
* rename @hanzo/analytics -> @hanzo/capture (name collision)
The intended name @hanzo/analytics is already a LIVE, hanzoai-owned npm package
(team-manager's, latest 0.6.4, an incompatible providers/Analytics API).
Publishing this new capture client under that name would move the 'latest' tag
onto a different codebase and confuse/break bare + latest consumers. Renamed to
the free, single-word @hanzo/capture, which also matches cloud's 'capture plane'.
One-line revert if the owner prefers to supersede team-manager instead.
---------
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The intended name @hanzo/analytics is already a LIVE, hanzoai-owned npm package
(team-manager's, latest 0.6.4, an incompatible providers/Analytics API).
Publishing this new capture client under that name would move the 'latest' tag
onto a different codebase and confuse/break bare + latest consumers. Renamed to
the free, single-word @hanzo/capture, which also matches cloud's 'capture plane'.
One-line revert if the owner prefers to supersede team-manager instead.
Zero-consumer AI-usage duplicates removed so <UsagePanel> in @hanzo/usage is the
single source: @hanzo/ui product usage kit (UsageDashboard/UsageMeter/
UsageProviderCard) and @hanzo/ui-shadcn billing usage-panel. Verified no
remaining references across pkg/pkgs/apps; the panel now lives at
@hanzo/usage/panel (gui/Tamagui) with the DOM <UsageDashboard> at /react.
(Phase-A intent from feat/usage-panel-phase-a, applied onto current main — that
branch sits on the retired v8 line and is not itself mergeable.)
Tiny batched client emitting pageview/event/identify/group to Hanzo Cloud
(/v1/analytics + /v1/tracker) — never to insights-capture directly. First-touch
UTM/referrer/refCode attribution persisted and attached to every event;
beacon-on-unload; dual cookie/bearer auth; SSR-safe; tenant is stamped
server-side (never sent by the client). Ships a framework-agnostic core and a
@hanzo/analytics/react provider + hooks, plus the shared EVENTS/GOALS/COHORTS
vocabulary. 22 unit tests + tsup build (cjs/esm/dts) green.
Typed field system (26 types) → record table / card / detail, on @hanzo/gui
(web + native + desktop), shorthand style props, zero Tailwind. The universal
object/field/record/view core for any Base-backed CRM, CMS, or commerce app.
Registry-dispatched (add a type = one registerField call). Ships TS source
(zero-build internal package). tsc --noEmit clean against @hanzo/gui 7.2.2.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
A new source-only @hanzo/gui package: a pannable/zoomable board of service
nodes (ProjectCanvas over @xyflow/react) with live status (ServiceStatusBadge),
metric sparklines (MetricSparkline), deploy timelines (DeployTimeline), an
environment switcher (EnvSwitcher), and a tabbed service detail drawer
(ServiceDetailDrawer) — plus the ServiceNode card and SourceRef/ReplicaPill
primitives. Presentational + data-prop-driven; brand/white-label aware via the
design tokens (semantic status palette overridable per brand). Pure folds
(status normalization, layered graph layout, relative time) are unit-tested
(17 tests). React-free logic re-exported at @hanzo/canvas/pure so data mappers
and their tests never pull in JSX/xyflow.
Co-authored-by: hanzo-dev <dev@hanzo.ai>
* fix(ui-shadcn): self-referencing imports use the package's own name
The @hanzo/ui -> @hanzo/ui-shadcn rename (name freed for v8) left 29 files
importing themselves via the OLD name, breaking tsc/dts and making dist
resolve against npm @hanzo/ui@8.x at runtime. Self-reference by own name
resolves correctly under any install name (including npm: aliases).
* feat(network,wallet): the ONE hanzo.network selector + wallet menu
<NetworkSwitcher/> + <WalletMenu/> at @hanzo/ui-shadcn/{network,wallet} —
the shared network/wallet standard for desktop, app, chat, team, console.
- Network = (env, label, networkID, evmChainID, rpcEndpoint, apiEndpoint):
sovereign L1, networkID === evmChainID; envs mirror the hanzo CLI
(mainnet 36963 / testnet 36964 / devnet 36965 / local 31337; one
api.hanzo.ai across public envs; per-env rpc.hanzo[-test|-dev].network).
- Selection persists env name only; endpoints always re-resolve from code.
- WalletAdapter seam keeps custody per-surface (desktop lux-wallet PQ HD,
web injected EIP-1193 — non-custodial, no key material, no storage).
- 20 vitest cases; v5.7.2.
* feat(network,wallet): menuSide prop — menus open upward from footers (v5.7.3)
* fix(network): align to genesis-canonical chain IDs (v5.7.4)
The published 5.7.3 mirrored pre-reconcile CLI values. cli#3 + console#139
(both merged) fixed the canonical set to match genesis
(lux/genesis/configs/hanzo-*). Align EXACTLY to console main
src/lib/network.ts + cli main src/commands/network.rs:
testnet 36964 -> 36962 (rpc.hanzo-test.network -> rpc.testnet.hanzo.network)
devnet 36965 -> 36964 (rpc.hanzo-dev.network -> rpc.devnet.hanzo.network)
local 31337 -> 1337 (:9650/ext/bc/C/rpc -> :9630/v1/bc/C/rpc)
Sovereign L1 networkID === evmChainID preserved. mainnet 36963 unchanged.
The shared component is the ONE place — it MUST match genesis.
vitest 254/254; tsup + tsc dts green.
---------
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
* feat(@hanzo/data): Twenty-grade record views (table/board/detail/editors) — clean-room
Bring the Hanzo Base data-app layer to Airtable/Twenty-class polish, 100% original
(no Twenty code — GPL kept at arm's length; Twenty observed as a running-UI reference only).
New in pkg/data (published as @hanzo/data@1.2.0):
- RecordsView shell: table <-> board switch, search, filter builder, sort builder,
board group-by, optional saved views — one ViewConfig, applied via pure view/logic.
- DataTable (Twenty-grade): click-to-sort headers, drag-resize + drag-reorder columns,
row selection + select-all, inline cell editing, pagination, hover-open, honest states.
- BoardView: kanban grouped by a select/status/boolean/relation field; drag-between-lanes
emits the record patch (optimistic, reverts on failure).
- RecordDetail: titled, inline-editable panel + related slot; RecordForm gains fieldOptions.
- Field editors upgraded: searchable select dropdown w/ color chips, month-grid calendar,
relation record-picker, file upload, validated JSON, sliding boolean toggle.
- Pure logic (sort/filter/search/group/paginate/view) — gui-free, 37 unit tests, exported
on subpaths (@hanzo/data/{table,board,view}/logic).
- Self-contained primitives (Menu/CheckBox/Toggle/Calendar) on the minimal proven gui surface.
- gui.config.ts + gui.d.ts so the package type-checks the v5 shorthands standalone.
@hanzo/ui/primitives/bases/data re-exports @hanzo/data (the bases surface convention).
tsc --noEmit clean; vitest 37/37.
* feat(@hanzo/ui): v8 unified lib on @hanzo/gui — product + record (@hanzo/data) layers
The one cross-platform, presentational, host-agnostic, clean-room component
library. Product/app layer (charts, metrics, page headers, status tags, empty
states, combobox, slide-over, toasts, drag-reorder, field rows, marks) at
'@hanzo/ui'; metadata-driven record layer composed from @hanzo/data at
'@hanzo/ui/data'; calm dark-first tokens + motion vocabulary. Web + native + desktop.
Retires the shadcn @hanzo/ui (5.x) → @hanzo/ui-shadcn; this gui-based line
carries the name forward at 8.0.0. tsc --noEmit clean, vitest 12/12.
Manifest: CONSOLIDATION.md.
* refactor(ui): retire shadcn @hanzo/ui → @hanzo/ui-shadcn (name freed for v8)
Rename the legacy shadcn/Radix line (pkgs/ui, 5.7.0) to @hanzo/ui-shadcn so the
gui-based unified library can carry the @hanzo/ui name forward at v8. Code is
untouched — only the package name changes; the published @hanzo/ui@5.7.1 stays
on npm for external ^5.x consumers.
Internal workspace consumers keep resolving the shadcn line with ZERO source
edits via workspace aliases (@hanzo/ui → @hanzo/ui-shadcn):
- app (@hanzo/ui-web): dependency workspace alias
- commerce: devDependency workspace alias (source imports @hanzo/ui/*),
peer stays @hanzo/ui>=5.0.0 for external consumers
- checkout, agent-ui: peer ranges unchanged (no source imports)
Proven: app + commerce node_modules/@hanzo/ui resolve to @hanzo/ui-shadcn@5.7.0.
Drive-by (unblocks workspace install/CI): pkgs/data pinned the now-unpublished
@hanzogui/config@7.2.2 — patch-forward to 7.3.0 (published latest, same major,
matches pkg/ui).
* build(ui,data): emit compiled .d.ts — retire raw-.tsx type surface
@hanzo/ui@8.0.1 + @hanzo/data@1.2.1: types/exports now point at flat
compiled declarations (tsc -p tsconfig.build.json → types/), matching
@hanzo/gui@7.3.0. src/gui-env.d.ts bakes the GuiCustomConfig augmentation
into each package's own compilation so shorthand props resolve internally —
consumers no longer type-check vendor .tsx (the 171-phantom-error/hoisted-
linker fragility).
---------
Co-authored-by: Hanzo AI <ai@hanzo.ai>
Co-authored-by: hanzo-dev <dev@hanzo.ai>
The ONE cross-platform AI-usage surface every Hanzo app (console, desktop,
app, chat) renders: a labeled rate-limit bar with % left + honest reset
countdown, a per-provider quota card (session/weekly/extra windows, spend,
history sparkline) mirroring the Codex menu card, and a dashboard grid with a
totals header. Composes existing Charts.Sparkline + Metric idioms on @hanzo/gui
primitives only; presentational, host-agnostic, web + native + desktop.
Bumps @hanzo/ui 8.0.0 -> 8.0.1.
Presentational, self-contained credit/top-up modal any Hanzo app
(console2, hanzo.app, billing.hanzo.ai) can mount with its own data +
handlers. All data/handlers injected via props (cents-based, commerce
balance shape); no network calls, no app coupling.
- Two distinct buckets: non-cash trial credit vs. real prepaid money,
with a combined breakdown + explicit total.
- Welcome celebration state when a new user's trial credit just landed.
- Top-up affordance (preset amounts + custom) that calls the injected
onTopUp(amountCents); Square/HUSD payment flow stays in the caller,
optionally rendered via children. Reuses the existing handler-prop
pattern (onAddFunds / SquareCardForm) rather than duplicating it.
- Reuses the package radix Dialog primitive for focus trap, escape,
overlay + aria; styled with the billing semantic tokens.
- Exported from the billing barrel; 8 vitest cases.
Co-authored-by: hanzo-dev <dev@hanzo.ai>
Drop 'LivingOverview'/'LineChart'/'BarChart'/'BarRows' from prose comments;
match the de-branded single-word exports. No code change (typecheck still clean).
Documents every export + usage snippet, maps each back to its console2 source,
the generalizations made, and the exact per-component swap plan for consuming
@hanzo/dashboard back in console2 (follow-up pass). Tidy a double import.
Augment GuiCustomConfig in BOTH @hanzogui/web and @hanzogui/core, and declare
@hanzogui/web + @hanzogui/core as devDeps so the 'declare module' targets resolve
under pnpm strict nesting (npm-flat installs like console2 hoist them; pnpm does
not). Result: tsc --noEmit clean (0 errors) and tsc build emits dist cleanly.
- landing/: Landing (Hero/Metrics/Samples/Rail) + pure link logic — brand/docs
via LandingConfig props (no host-app config coupling)
- pipeline/: pure pipeline.ts (de-branded stage model) + small stages-driven
Pipeline component; de-branded CSS classes (hz-pipe-*)
- src/index.ts: clean single-word public API
No other-company brand names anywhere; single-word exports per naming guidance.
Rename the legacy shadcn/Radix line (pkgs/ui, 5.7.0) to @hanzo/ui-shadcn so the
gui-based unified library can carry the @hanzo/ui name forward at v8. Code is
untouched — only the package name changes; the published @hanzo/ui@5.7.1 stays
on npm for external ^5.x consumers.
Internal workspace consumers keep resolving the shadcn line with ZERO source
edits via workspace aliases (@hanzo/ui → @hanzo/ui-shadcn):
- app (@hanzo/ui-web): dependency workspace alias
- commerce: devDependency workspace alias (source imports @hanzo/ui/*),
peer stays @hanzo/ui>=5.0.0 for external consumers
- checkout, agent-ui: peer ranges unchanged (no source imports)
Proven: app + commerce node_modules/@hanzo/ui resolve to @hanzo/ui-shadcn@5.7.0.
Drive-by (unblocks workspace install/CI): pkgs/data pinned the now-unpublished
@hanzogui/config@7.2.2 — patch-forward to 7.3.0 (published latest, same major,
matches pkg/ui).
The one cross-platform, presentational, host-agnostic, clean-room component
library. Product/app layer (charts, metrics, page headers, status tags, empty
states, combobox, slide-over, toasts, drag-reorder, field rows, marks) at
'@hanzo/ui'; metadata-driven record layer composed from @hanzo/data at
'@hanzo/ui/data'; calm dark-first tokens + motion vocabulary. Web + native + desktop.
Retires the shadcn @hanzo/ui (5.x) → @hanzo/ui-shadcn; this gui-based line
carries the name forward at 8.0.0. tsc --noEmit clean, vitest 12/12.
Manifest: CONSOLIDATION.md.
- Typography: fontSans -> Basel Grotesk via next/font/local (self-hosted,
Book 400 + Medium 500, --font-basel-sans); keep fontMono = Geist Mono.
Point both tailwind configs' sans at var(--font-basel-sans). Basel replaces
Geist Sans as the default; DM Sans/Figtree/Inter stay optional .theme-*
variants only, never defaults.
- DESIGN.md: the single source of truth for the shared Hanzo look — canonical
typography (Basel + Geist Mono), the sidebar toggle icon (lucide PanelLeft),
sidebar/panel specs (16rem width, border-border, monochrome hover/active),
and the true-black dark palette (#000 canvas / #0a0a0a surface / white-10
borders / #ededf1 text).
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The @hanzo/products snapshot (the shared-registry fallback + commerce seed + the
source docs' gen-services-nav derives from) had drifted behind the canonical console
registry (hanzoai/console src/lib/products/registry.tsx). Ten customer products that
shipped in recent console waves were absent from the snapshot, so docs coverage +
the derived services nav under-counted the real product set.
Purely additive — the existing 92 rows are byte-identical; ten rows appended, each
at the end of its category run (diff is insertions only):
Observe: open-edition, analytics Data: records Platform: apps
Apps: crm, cms, erp, helpdesk, accessibility, templates
Fields derived to match the existing snapshot shape + the package maps:
brandColor = defaultColorKey(id) (curated pin or FNV-1a hash), iconKey = the
registry icon component, slug=id, route=/id, docsUrl=/docs/services/<id>,
apiPath /v1-prefixed, brands derived from category. All package invariants hold
(icon-drift guard over the 1760-icon vocab, swatch keys, 10 canonical categories,
/v1 apiPaths, unique slug==id). Count assertion + doc-comment 92 -> 102.
vitest 90/90, tsc --noEmit clean.
The single source of truth for the Hanzo product taxonomy/icons/colors that
console, docs, site, and pricing all derive from — committed on the CTO-locked
10-category cut so every surface groups by ONE axis.
CATEGORY_ORDER (13 -> 10): AI · Compute · Data · Network · Security · Observe ·
Platform · Web3 · Apps · Commerce. The three cuts:
- Training -> AI (finetuning, kubeflow — AI training)
- Dev -> Platform (cli, sdks, api, integrations, ide, desktop, api-keys —
the platform's developer surface)
- Settings -> removed (settings/team/profile are account/avatar-menu items,
not products — dropped from the catalog grid)
Snapshot: 92 products (95 - 3 dropped); every row's brands[] re-derived from
category (never hand-authored — the drift-killer). Sovereign brand scope
(lux/zoo/pars) follows the merge: Web3 · Network · Security · Platform, so the
chains keep CLI/SDKs/API keys; account settings move to the avatar menu.
types/categories/brands/docs/snapshot + LLM.md updated; tests assert the new
canonical set (CATEGORY_ORDER.length === 10, none of Training/Dev/Settings
remain, sovereign+hanzo-only scopes partition the 10). 90 tests green,
tsc --noEmit clean, tsup build OK.
The @hanzo/data field registry had Displays for all 24 types but Inputs for only
~15 — records were read-mostly. Fill the gaps so a Base record is FULLY editable
in table/detail (the CRM/CMS foundation):
- RelationInput — single/to-many record picker over host-injected candidate
options (metadata.options), falls back to raw-id entry when none injected.
- FilesInput / LinksInput — add/remove chip lists.
- JsonInput — parses on change, keeps text on invalid so typing isn't lost.
- FullNameInput (first/last) + AddressInput (street/city/state/zip) — composite
sub-field editors.
- relation metadata gains options + maxSelect; files gains accept + maxSelect.
Only the true system types (uuid/position/actor) stay display-only. Built on the
same @hanzo/gui primitives + FieldInputProps as the existing inputs (cross-
platform, shorthand style). registry.test.ts asserts every non-system type now
has an Input (mocking @hanzo/gui). 11/11 tests pass, tsc clean.
Co-authored-by: z <z@zeekay.io>
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The @hanzo/data field registry had Displays for all 24 types but Inputs for only
~15 — records were read-mostly. Fill the gaps so a Base record is FULLY editable
in table/detail (the CRM/CMS foundation):
- RelationInput — single/to-many record picker over host-injected candidate
options (metadata.options), falls back to raw-id entry when none injected.
- FilesInput / LinksInput — add/remove chip lists.
- JsonInput — parses on change, keeps text on invalid so typing isn't lost.
- FullNameInput (first/last) + AddressInput (street/city/state/zip) — composite
sub-field editors.
- relation metadata gains options + maxSelect; files gains accept + maxSelect.
Only the true system types (uuid/position/actor) stay display-only. Built on the
same @hanzo/gui primitives + FieldInputProps as the existing inputs (cross-
platform, shorthand style). registry.test.ts asserts every non-system type now
has an Input (mocking @hanzo/gui). 11/11 tests pass, tsc clean.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Typed field system (26 types) → record table / card / detail, on @hanzo/gui
(web + native + desktop), shorthand style props, zero Tailwind. The universal
object/field/record/view core for any Base-backed CRM, CMS, or commerce app.
Registry-dispatched (add a type = one registerField call). Ships TS source
(zero-build internal package). tsc --noEmit clean against @hanzo/gui 7.2.2.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The repo-level NPM_AUTH_TOKEN (2026-03-29) shadowed the org token and was
expired → 'npm error 401 Unauthorized' on every tag publish. Converge on the
org-level NPM_TOKEN (2026-06-18, the one @hanzo/iam published 0.13.0 with).
One token, one way.
@hanzo/ui becomes the PRESENTATION layer over @hanzo/iam's mechanism. Atomic,
composable, knows nothing of token exchange — it just starts a login per method.
- <SignIn providers={[...]}> composes <PasswordForm> + one <SocialButton> per
provider + <Web3Connect>. providers is the ONLY app-level knob.
- <SocialButton provider> delegates to startIamLogin with the provider knob
(rides as &provider on /v1/iam/oauth/authorize); apps never register per-app
Google/GitHub clients. Composition escape hatch: inject onLogin (e.g. the
@hanzo/iam SDK's startLogin) so @hanzo/ui stays dependency-light.
- buildIamAuthorizeUrl adds the provider knob to iam.ts, mirroring the SDK.
- IAMLoginButton de-hexed: brand via CSS tokens (bg-primary, border-input, …),
not literals. Every atom is monochrome/brand-neutral by construction.
- demo/sign-in-demo.tsx: the CONFIGURATION layer — zero auth code, wires
<SignIn> to the @hanzo/iam SDK (startLogin + loginWithPassword).
- auth.test.tsx (10 cases, vitest+happy-dom): authorize URL carries PKCE-S256 +
provider hint for google/github/web3, no /api/; <SignIn> renders a method per
provider; rendered markup has zero hex (brand-neutral); <SocialButton>
delegates to the injected starter with the provider knob.
- declare happy-dom (vitest env, was referenced but undeclared).
Co-authored-by: zeekay <z@zeekay.io>
The zero-dep auth components hand-rolled the OAuth authorize URL against the
legacy '/login/oauth/authorize' path WITHOUT PKCE, and the shell hook fetched
userinfo from '/api/userinfo'. Both violate HIP-0111 (canonical paths are
'/v1/iam/oauth/*'; PKCE S256 is always required).
- new auth/iam.ts: one canonical helper (IAM_OIDC_PATHS mirroring @hanzo/iam's
OIDC_PATHS; startIamLogin() does authorization-code + PKCE-S256 to
/v1/iam/oauth/authorize). One way to start a login.
- IAMLoginButton + AuthGuard: route through startIamLogin() instead of
hand-rolling a PKCE-less authorize URL on the legacy path.
- useHanzoAuth: /api/userinfo -> /v1/iam/oauth/userinfo.
@hanzo/ui stays dependency-light, so iam.ts mirrors the SDK's path contract
byte-for-byte rather than pulling in @hanzo/iam; identical endpoints, PKCE on.
Co-authored-by: z <z@zeekay.io>
The mod-key glyph was computed during render via isMacOS() (window.navigator),
so the static-export SSR produced 'Ctrl' while the macOS client hydrated '⌘' on
the same <span>, tripping React error #418 (hydration text mismatch) on every
page (CommandMenu is in the global SiteHeader).
Start modKey from the SSR-stable 'Ctrl' and upgrade to the platform glyph in a
client-only mount effect, so SSR and first hydration render agree. Verified via
CDP: pre-fix decoder showed args[]=text with the exact Ctrl->⌘ text node.
Co-authored-by: Hanzo CTO <ai@hanzo.ai>
* refactor: decouple @hanzo/ui from hanzogui
Make @hanzo/ui shadcn-only — no hanzogui/Tamagui coupling.
- remove primitives/bases (gui/admin/svelte/vue) re-exports
- drop @hanzogui peerDependencies + peerDependenciesMeta entries
- add check-no-hanzogui guard, wired into build
- fix latent NodeJS.Timeout types to keep the build green
* refactor: remove duplicate @hanzo/brand from the monorepo
@hanzo/brand is owned by the standalone hanzoai/brand (the npm-canonical
source); this monorepo's copy was unused and had drifted.
- delete pkg/brand
- add check-no-brand-pkg guard (wired into `check`) so it can't reappear
* refactor(ui): stop importing from the app; use own cn util
pkg/ui pulled `cn` from @/lib/utils (../../app) — an inverted dependency on
the consuming app. Point the animation components at pkg/ui's own cn and
drop the @/app, @/registry, @/lib tsconfig path aliases.
* fix(ui): type errors; stop tracking the root lockfile
- ModelCard: lucide-react dropped the Github icon → use Code
- drawer: annotate DrawerTrigger/DrawerClose (radix type portability)
- untrack root pnpm-lock.yaml; CI → --no-frozen-lockfile
* release: bump @hanzo/ui to 5.7.0
* refactor: rename pkg/ → pkgs/, merge packages/ into it
standardize on 'pkgs/': move pkg/* and packages/* into pkgs/.
Update workspace globs, CI, scripts, and config references accordingly.
The TypeScript counterpart of github.com/hanzoai/go-sdk/metering — the one
way every Hanzo product meters usage to commerce (the billing source of
truth) so everything can be paid for, not just the LLM/cloud path. Shares
an identical wire contract with the Go client.
- Metering class composes the existing Commerce client (no HTTP dup):
authorize() pre-request balance gate (fail-closed by default; 402 vs 503),
record() post-request usage write. tierAware gates on effectiveAvailable
(prepaid + included plan allotment).
- S2S auth: Authorization: Bearer COMMERCE_SERVICE_TOKEN (KMS-sourced) +
X-IAM-Org-Id. Metering.fromEnv() for canonical env wiring.
- identityFromHeaders(): reads gateway-minted X-User-Id/X-Org-Id.
- client.ts: getTier() + custom-headers support on request/getBalance/
addUsageRecord (DRY enablers for the S2S org header).
- 14 contract tests (mock fetch) mirroring the Go suite; isolated tsc clean.
- exports: ./metering ; index re-exports ; version 7.6.1 -> 7.6.2.
Refs universe task #28.
Co-authored-by: Antje Worring <worringantje@gmail.com>
The Hanzo cross-app console (the shared header / app switcher rendered by
@hanzo/ui across console, platform, billing, chat, etc.) is driven by a
single canonical registry in navigation/hanzo-shell/types.ts. The
bootnode-powered chain orchestration surface at web3.hanzo.ai was live but
absent from that registry, so it never appeared in the switcher.
Add "Web3" as an Infrastructure-category app, threaded through every
structure that enumerates the registry so the list stays orthogonal:
- DEFAULT_HANZO_APPS: the static hanzo.ai default list.
- OrgDomains type + all four ORG_DOMAINS maps (hanzo, lux, zoo, pars):
white-label by org domain — web3.hanzo.ai / web3.lux.network /
web3.zoo.ngo / web3.pars.network.
- getAppsForOrg(): the org-aware URL builder.
- AppSwitcher APP_GROUPS: place "web3" in the Infrastructure section
(between cloud and storage) so it renders grouped, not under "Other".
No icon is set — the switcher renders label + description only; every
existing entry is icon-less, so this matches the one established pattern.
Description: "Deploy & manage blockchain validators across Bitcoin,
Ethereum, Solana, Lux, and any Lux-derived L1".
Co-authored-by: zeekay <z@zeekay.io>
Reverts violations of the durable rule that current-state docs belong
in LLM.md and history belongs in git log. Removed files were session
handoffs, agent-style "complete success" / "1000%" reports, dated
audit dumps, and stub NOTES.
sync-forks.yml.disabled and market-overview-alt.mdx.disabled are fossilized
copies left over from earlier work. Stale .disabled files in a tracked tree
are dead code that lives forever; either re-enable or delete.
Brand policy: do not reference Tamagui by name on disk. The product is
@hanzo/gui v7 (Hanzo GUI). Internal workspace umbrella is `hanzogui`
(lowercase). Source code imports `from 'hanzogui'`. NPM publish:
@hanzo/gui.
This commit replaces "Tamagui v7" → "Hanzo GUI v7" / "@hanzo/gui v7"
in pkg/ui/BASES.md and pkg/ui/src/primitives/bases/{gui,svelte,vue}/
header docstrings + placeholder error messages.
Brand policy: do not reference Tamagui by name on disk. The product is
@hanzo/gui v7 (Hanzo GUI). Internal workspace umbrella is `hanzogui`
(lowercase). Source code imports `from 'hanzogui'`. NPM publish:
@hanzo/gui.
This commit replaces "Tamagui v7" → "Hanzo GUI v7" / "@hanzo/gui v7"
in pkg/ui/BASES.md and pkg/ui/src/primitives/bases/{gui,svelte,vue}/
header docstrings + placeholder error messages.
Add framework-base re-exports under @hanzo/ui/primitives/bases/* so
consumers can swap framework backends without changing imports:
- bases/admin → @hanzogui/admin (Tamagui v7 admin chrome, canonical)
- bases/gui → hanzogui (Tamagui v7 primitives umbrella)
- bases/svelte → throws (placeholder until Svelte port lands)
- bases/vue → throws (placeholder until Vue port lands)
Source-of-truth files stay in ~/work/hanzo/gui/ — this package only
re-exports. Component names are identical across bases by contract,
so swapping a base is a one-line import change in consumer code.
Adds @hanzogui/admin, @hanzogui/lucide-icons-2, hanzogui as optional
peer deps. See pkg/ui/BASES.md for the full doc.
Add framework-base re-exports under @hanzo/ui/primitives/bases/* so
consumers can swap framework backends without changing imports:
- bases/admin → @hanzogui/admin (Tamagui v7 admin chrome, canonical)
- bases/gui → hanzogui (Tamagui v7 primitives umbrella)
- bases/svelte → throws (placeholder until Svelte port lands)
- bases/vue → throws (placeholder until Vue port lands)
Source-of-truth files stay in ~/work/hanzo/gui/ — this package only
re-exports. Component names are identical across bases by contract,
so swapping a base is a one-line import change in consumer code.
Adds @hanzogui/admin, @hanzogui/lucide-icons-2, hanzogui as optional
peer deps. See pkg/ui/BASES.md for the full doc.
@hanzo/brand was a cross-org registry (hanzo + lux + zoo + pars all
bundled). Brand belongs per-org:
Zoo → @zooai/brand (github.com/zooai/brand)
Lux → @luxfi/brand (github.com/luxfi/brand)
Liquidity → @partner/brand (github.com/partner/brand)
Delete lux/zoo/pars blocks from orgs.ts, narrow OrgId to 'hanzo',
narrow index.ts exports. 200-line reduction.
No callers broken — nothing in ~/work/{hanzo,lux,zoo,liquidity}
imports @hanzo/ui/brand currently (verified via grep). This is
dead cross-org code being removed.
@hanzo/brand was a cross-org registry (hanzo + lux + zoo + pars all
bundled). Brand belongs per-org:
Zoo → @zooai/brand (github.com/zooai/brand)
Lux → @luxfi/brand (github.com/luxfi/brand)
Delete lux/zoo/pars blocks from orgs.ts, narrow OrgId to 'hanzo',
narrow index.ts exports. 200-line reduction.
No callers broken — nothing in ~/work/{hanzo,lux,zoo}
imports @hanzo/ui/brand currently (verified via grep). This is
dead cross-org code being removed.
pkg/gui/ was a Tamagui subtree living in hanzoai/ui — the 57
@hanzogui/* components (button, card, dialog, popover, switch, and
the supporting primitives) belong alongside the rest of the
@hanzogui/* engine in hanzoai/gui, not here. History for these
packages was preserved via git filter-repo.
- Deleted pkg/gui/ (57 packages, 744 files)
- Removed "pkg/gui/*" entry from pnpm-workspace.yaml
- Deleted scripts/publish-gui.ts — legacy ad-hoc publisher
hardcoded to pkg/gui and an ancient version string
- Narrowed .github/workflows/publish.yml from "@hanzo/*|@hanzogui/*"
to "@hanzo/*" so this repo no longer tries to publish Tamagui
components
- Removed stale "gui/ GUI component packages (@hanzogui/*)" line
from LLM.md
Replace the server-side CardForm (which calls /card/tokenize and gets
503 due to PCI compliance) with SquareCardForm which uses the Square
Web Payments SDK for client-side card tokenization.
The Square sourceId token is passed as _sourceToken on the PaymentMethod
object so consuming apps can send it to commerce's payment-methods
endpoint for real $1 pre-auth card verification.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The chat API route (app/api/chat/route.ts) imports @ai-sdk/openai-compatible
and ai but neither was declared in app/package.json. Also skip puppeteer
Chrome download in .npmrc since it's only used for optional screenshot
capture and its postinstall failure breaks pnpm install in CI.
- /api/chat: streaming RAG chat about UI components (zen-coder-flash)
- /api/search: proxy to Hanzo Cloud search-docs with publishable key
- lib/search.ts: search config (cloud backend, pk-hanzo-ui-search-2026)
Same pattern as docs.hanzo.ai AI search. Users can chat on the site
and ask questions about components.
Changed imports from v4 names (Group, Separator) to v3 names
(PanelGroup, PanelResizeHandle) to fix build errors in consumers
using react-resizable-panels v3. Updated peer dep to ^3.0.0.
Bump @hanzo/ui to 5.5.1.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
The gui-* packages were imported from hanzo/gui but 34 underlying
utility/core packages (gui-build, gui-core, gui-helpers, gui-web, etc.)
were not included in the workspace. Changed their workspace:* refs to
the published 2.0.0-rc.29 versions so pnpm can resolve from registry.
- Upgrade CTA now renders first (above usage card) when not subscribed
- "No usage yet" instead of "— tokens" when zero usage
- "Credits available" with balance instead of "$0.00 API spend"
- "Start using Hanzo AI to see stats" subtitle for zero-usage state
Unified app switcher is the single source of truth for cross-app navigation.
All services now accessible from every Hanzo app via HanzoHeader.
- DEFAULT_HANZO_APPS: 26 apps across 7 groups (Core, AI, Observability,
Infrastructure, Apps, Business, Resources)
- OrgDomains: white-label domain mapping for all 4 orgs (hanzo, lux, zoo, pars)
expanded from 7 to 27 fields
- getAppsForOrg(): returns org-aware URLs for all 26 apps
- AppSwitcher: grouped 2-column grid with section headers, scrollable
Add separate tsup entry point for navigation/hanzo-shell so the
wildcard package export ./navigation/* resolves to a real dist file.
Bumps version to 5.3.40.
New auth components for unified IAM integration:
- IAMLoginButton: "Sign in with Hanzo" button that initiates OAuth flow
- AuthGuard: wrapper that redirects to IAM when unauthenticated
- Re-exports useHanzoAuth, UserOrgDropdown, HanzoUser/HanzoOrg types
Adds ./auth and ./auth/* subpath exports to package.json.
Runs 228 tests checking that every href in docs.ts config resolves to
an actual MDX file. Catches 404s before deployment without needing a
running server. Reports orphaned MDX files not in nav config.
- Move pkg/auth and pkg/auth-firebase to deprecated/ (excluded from workspace)
- Remove @hanzo/auth from commerce and checkout peerDependencies
- Remove useAuth() from payment-step-form; contact form defaults to empty strings
- Update pnpm lockfile
Production auth is now handled entirely by IAM (hanzo.id). The legacy
auth package is preserved in deprecated/ for reference.
Adds @hanzo/ui/models export with data-agnostic model UI components
that work across hanzo.ai, zen-docs, and any other Hanzo site:
- ZenModelLike/ModelFamilyLike interfaces (structural compatibility
with @hanzo/zen-models, no cross-package dependency needed)
- ModelCard: rich clickable card with status badges, spec, action buttons
- ModelTable: static table view with pricing and context columns
- ModelLibrary: full catalog with family sections and filter toggle
- ZenEnso: animated SVG enso circle logo component
Fix ./models ESM export path (dist/models/index.mjs not dist/src/models/).
Shared package for generating OpenGraph/social images across all Hanzo
sites. Supports 6 layout variants (page, model, code, stat, split,
minimal) with inline styles for next/og ImageResponse compatibility.
Includes HANZO_AI_THEME and HANZO_INDUSTRIES_THEME brand presets.
- Remove duplicate tsup entries: primitives-export and primitives/index
both compiled same primitives/index-standard.ts → save 2×436K=870K
- Enable minify:true; safe because 'use client' banner is added post-build
via onSuccess hook, not as source directive
- Update ./primitives export in package.json to point to ./dist/index.mjs
- Result: dist 10M → 2.6M, index.mjs 436K → 314K (minified)
Published as @hanzo/ui@5.3.36
Commerce is THE client for the Commerce API. No aliases, no backwards
compat wrappers. import { Commerce } from '@hanzo/commerce'
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
CommerceClient is the canonical Commerce API client. @hanzo/commerce/billing
re-exports as BillingClient for backwards compat.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
- billing.ts: canonical billing client for Commerce API
- Exports at @hanzo/commerce/billing
- Fix workspace:* peerDeps (resolve to version ranges for npm compat)
- Fix npm-publish workflow to use NPM_TOKEN secret
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Props-driven org/project/environment switcher used across all
Hanzo services. Supports single-org display, multi-org dropdown,
project selector, and environment badge.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
2026-02-17 20:38:07 -08:00
8856 changed files with 175483 additions and 289302 deletions
<textx="378"y="322"font-family="Inter,system-ui,sans-serif"font-size="30"fill="#ffffff"opacity=".66">React component library for AI applications</text>
name:npm-package-hanzoai-ui@${{ steps.package-version.outputs.current-version }}-pr-${{ github.event.number }}# encode the PR number into the artifact name
# Hanzo UI Consolidation — one library, all polish, no duplication
**Goal.** RIP the fragmentation (`@hanzo/data` vs `@hanzo/ui` vs in-console duplicates) into ONE canonical, cross-platform, presentational, clean-room library: **`@hanzo/ui`, built on `@hanzo/gui`**. Preserve every polished component + token; zero loss; no fork.
**Status.**
- **Step 1 — Audit:** complete (this document; four source trees read file-by-file).
- **Step 2 — Establish `@hanzo/ui` + migrate the stable foundation:** **DONE & GREEN** (`pkg/ui` is now a real package; `tsc --noEmit` = 0 errors, `vitest` = 12/12).
- **Step 3 — Console re-point:** **STAGED, not merged.** Gate is split (backend live, app lanes in-flight) — see [Step 3](#step-3--console-re-point-staged).
**One decision needs CTO sign-off before publish** — see [Naming / version](#naming--version--the-one-decision).
@hanzo/data (pkg/data) the metadata-driven RECORD layer: fields → records → views
│ (RecordsView, DataTable, BoardView, RecordDetail, field editors)
▼
@hanzo/ui (pkg/ui) ◄──── THE ONE LIBRARY. Two orthogonal concerns, one package:
• product/app layer → import { … } from '@hanzo/ui'
• record layer → import { … } from '@hanzo/ui/data' (re-exports @hanzo/data)
```
`@hanzo/data` stays the **source of truth** for the record layer; `@hanzo/ui`**composes** it (`export * from '@hanzo/data'` on the `./data` subpath) rather than copying — so the CMS/ERP/Help app lanes that consume `@hanzo/data` today keep working untouched, and there is exactly one home. This mirrors how the gui base wraps `hanzogui`.
Both layers own a `DataTable` (product = generic typed `<T>` list; data = field-driven record grid). Keeping the record layer on the `./data` subpath keeps each name unambiguous — no rename, no collision.
---
## The preserve-and-merge manifest
Legend: **✅ migrated** (in `pkg/ui`, GREEN) · **↪ re-exported** (composed from `@hanzo/data`) · **⏳ staged** (Step 3, after app lanes land) · **🔧 pending prop-injection** (app-coupled; decouple before it can live in a presentational lib) · **🏠 stays in console** (app glue, not a UI component).
### A. Record layer — `@hanzo/data` → `@hanzo/ui/data` ↪
Source: `pkg/data/src/*` (v1.2.0, clean-room, already published). Surfaced through `@hanzo/ui/data` and `@hanzo/ui/primitives/bases/data`. Nothing re-implemented.
The brief's premise (`globals.css``t_dark`/`t_light` token blocks) is **corrected**: `t_dark`/`t_light` are `@hanzo/gui` (Tamagui) theme **class names**; the calm values live in three real places, all preserved:
| OKLCH shadcn-compatible palette (soft-charcoal `oklch(0.145 0 0)`, `--radius: 0.5rem`, indigo sidebar accent) | `pkgs/ui/style/hanzo-default-colors.css`, `@hanzo/tokens` | unchanged (legacy shadcn line); the gui line uses the hex tokens above |
| **Motion vocabulary** — `hz-fade-up` (FadeIn), `hz-collapse`/`hz-slide`/`hz-fade` (shell/drawer), `hz-drag-item`, `hz-skeleton`/`hz-pulse`/`hz-row-in`; all `prefers-reduced-motion`-guarded | `console2/app/globals.css` (motion section) | **`@hanzo/ui/styles/motion.css`** (verbatim) — `import '@hanzo/ui/styles/motion.css'` once at app root |
### D. Generic DocType renderer → `@hanzo/ui` ⏳ (Step 3, FINAL — active lane)
Source: `console2/src/components/doctype/*`. **Actively edited by the CMS/ERP/Help lanes (untracked).** Dependency-**injected** (`client: FrameworkClient` prop) — not `~/lib/api`-coupled — so the real move-blockers are `~/lib/framework/{types,fields}` + the `ui/*` primitives (now in `@hanzo/ui`) + `@hanzo/data` (now `@hanzo/ui/data`).
### E. LivingOverview → `@hanzo/ui` ⏳ (Step 3 — active lane)
Source: `console2/src/components/products/overview/living/*` (untracked/modified). Pure/presentational parts portable; app-glue concentrated in `registry.ts` (live API clients).
| Part | Class | Step-3 disposition |
|---|---|---|
| `motion.ts`, `hooks.ts`, `logic.ts`, `config.ts` (types) | pure | → `@hanzo/ui` (unit-tested; count-up, poll clock, unit formatting) |
| `tiles.tsx`, `LivingOverview.tsx` | presentational (reuse `ui/Charts` verbatim) | → `@hanzo/ui` (once `config.ts`'s one `~/lib/products/registry``ProductIcon` type is swapped for `@hanzo/ui`'s `IconLike`) |
| `adapters.ts`, `registry.ts` | glue (map REAL `/v1` sources → `OverviewData`) | **stay in console** |
### F. Framework / Base-data libs → stay in console 🏠
`src/lib/framework/*` (DocType wire contract + `FrameworkApi` client + pure `fields.ts` mapper to `@hanzo/data`) and `src/lib/base-data/*` (Base REST client + schema→field mapper) are **app data-access glue**, not UI. They stay in the console. Their pure mappers (`docTypeToFields`, `baseCollectionToFields`) could later publish as a small `@hanzo/base-data` adapter, but they are not UI components and are out of scope for `@hanzo/ui`.
### G. App-coupled console components → 🔧 pending prop-injection (NOT yet in the lib)
These import app `~/lib`/session/router/branding and must be made presentational (inject props) before they belong in a host-agnostic lib. Kept in console for now; the injection contract is specified so the eventual lift is mechanical.
| Component | Coupling | Inject to lift |
|---|---|---|
| `BackendStateCard` (`BackendState.tsx`) | `~/lib/api``ApiError` | accept a numeric `status` (or shared error shape) instead of `ApiError` |
| `BrandLogo` | session + live IAM `organization()` + `~/config` + branding | `orgName`, `logoUrl` (or a `loadOrgLogo` loader), brand mark/name as props |
- **One decouple fix:** `product/EmptyState.tsx` no longer imports `~/lib/products/registry`; it uses the local `IconLike` type — the last host coupling in the product tree is gone.
**Green:**`tsc --noEmit` = 0 errors (strict); `vitest run` = 12/12 (`combobox/filter`, `Reorder`). Every product component is host-agnostic (imports only `react`, `@hanzo/gui`, `@hanzogui/*`, `@hanzo/data`, relative).
## Clean-room guarantee (preserved)
`pkg/data` audited for GPL/Twenty contamination: **none.** No GPL, no copied license headers/SPDX, no Twenty entity/decorator architecture. The field/record model is an independent `FieldType` union + `FieldDefinition` + runtime `Map` registry (records are plain `Record<string, unknown>`). "Twenty" appears **only as benchmark prose** ("Airtable/Twenty-class polish, clean-room"). License: BSD-3-Clause. `@hanzo/ui` inherits the same posture.
---
## Naming / version — DECIDED: `@hanzo/ui@8`, shadcn retired to `@hanzo/ui-shadcn`
**Decision:** the gui-based unified library takes the `@hanzo/ui` name **forward at `8.0.0`** (aligning with the "Hanzo Cloud 8.x" umbrella; major = breaking re-platform). The legacy shadcn/Radix line (`pkgs/ui`, v5.7.0) is **retired by renaming** to `@hanzo/ui-shadcn` (never hard-deleted) — it stays fully alive under the new name, freeing `@hanzo/ui` for v8. Precedent: `pkg/data@1.2.0` already superseded `pkgs/data@1.1.0` under the same name.
This is **DONE (repo-local):**`pkg/ui/package.json` is `@hanzo/ui@8.0.0`, GREEN. The rest is a coordinated, **sequenced** retire — because publishing `@hanzo/ui@8` (a different, gui-based API with no shadcn `Button/Card/Dialog`) under the name ~20 repos consume for shadcn primitives will BREAK any consumer that resolves to `@8`. So order matters:
**Blast radius (measured across `~/work/hanzo`).** Declared deps on `@hanzo/ui` in ~20 repos. Most pin `^5.x` (semver-safe from an `@8` bump): paas, chat, platform, app, hanzo.ai, o11y, docs, mdx, hanzobot, ui-repo `pkgs/checkout``^5.3`, `pkgs/agent-ui``^5.0`. **Would break on `@8`:**`hanzoai/identity/app` (`"latest"`), ui-repo `pkgs/commerce` (`>=5.0.0`); `app/` uses `workspace:^`. The `ui.hanzo.ai` docs app + `pkgs/{commerce,checkout,agent-ui}` import the shadcn line internally.
**Publish reality.**`.github/workflows/publish-on-tag.yml` publishes **`pkgs/ui`** (the shadcn line) as `@hanzo/ui` on a `v*` tag — it does not reference `pkg/ui`. So a tag push today publishes shadcn, not v8. Publishing v8 needs the workflow rewired to build/publish `pkg/ui`. `npm` is not authed locally (`npm whoami` empty) — per house rules, publish goes through **CI (self-hosted runners, canonical org `NPM_TOKEN`)**, not a local `npm publish`.
**Safe sequence to fully land v8 (each step reversible until the tag push):**
1. ✅ `pkg/ui` = `@hanzo/ui@8.0.0`, GREEN (done, committed to a branch).
2. Rename `pkgs/ui` name → `@hanzo/ui-shadcn`; update the internal ui-repo consumers (`app/`, `pkgs/{commerce,checkout,agent-ui}`) + `check-no-hanzogui`/registry scripts that reference it.
3. Migrate external consumers off `@hanzo/ui`→`@hanzo/ui-shadcn` (start with the break-risk ones: `identity``latest`, `commerce``>=5`; the `^5` pins are safe to migrate at leisure). ~20 repos — do as a tracked sweep or flag per-repo.
4. Rewire `publish-on-tag.yml` (and `release.yml`) to build + publish `pkg/ui` as `@hanzo/ui@8`, and `pkgs/ui-shadcn` as `@hanzo/ui-shadcn`.
5.`npm deprecate '@hanzo/ui@<8' 'moved to @hanzo/ui-shadcn; @hanzo/ui@8+ is the @hanzo/gui-based unified lib'`.
6. Tag `v8.0.0` → CI publishes `@hanzo/ui@8.0.0`. Verify `npm view @hanzo/ui@8.0.0`.
**Gated on the user's direct go-ahead** (irreversible / globally-visible / ecosystem-wide): steps 2–6 — the `pkgs/ui` rename, the ~20-repo consumer sweep, the CI rewire, `npm deprecate`, and the `v8.0.0` tag push that triggers publish. These were not executed autonomously.
---
## Step 3 — console re-point (STAGED)
**Gate (per the brief):** cloud `/v1/framework/modules` live **AND** console CMS/ERP/Help native.
- ✅ Backend: `/v1/framework/modules[/:module[/install]]` is **implemented + wired** (`cloud/clients/framework/framework.go:71-73`, HIP-0106 order 129, bound in `subsystems.go:130`, real tests).
- ⏳ Console: `CmsModule.tsx`, `ErpModule.tsx`, `HelpModule.tsx`, `components/doctype/` are **untracked/in-flight** on `feat/console-native-cms`.
→ Gate is **split**, so the re-point is **staged, not applied.** I deliberately did **not** touch `console2`'s working tree (it holds the lanes' uncommitted work — editing it would collide and risk loss, the opposite of the goal).
**Ready-to-apply re-point (mechanical, once the lanes land + merge):**
1.`console2` deps: keep `@hanzo/gui`, `@hanzo/data`; add `@hanzo/ui` (`^6.0.0`). (`@hanzo/data` may stay as a direct dep or be dropped in favor of `@hanzo/ui/data` — both resolve to the same source.)
-`app/globals.css` motion block → `import '@hanzo/ui/styles/motion.css'` (keep base resets local).
- Move the 5 app-coupled components (§G) into the lib only after applying their inject-props contract; until then they stay local.
3. Verify **identical render**: `tsc --noEmit` + `vitest` + `next build` green, then headless-Playwright the live pages pixel-same (the polish is byte-identical, so parity is expected).
**Report:**`@hanzo/ui` is the ONE unified library — product layer + record layer (`@hanzo/data`) + charts + calm tokens + motion, all on `@hanzo/gui`, presentational, cross-platform, clean-room, GREEN. Console re-point is **ready to apply once the CMS/ERP/Help lanes land**; it was not merged to avoid colliding with that in-flight work.
## Follow-ups (semver-minor, with visual e2e)
- Collapse the preserved Sparkline/Donut variants to one each (`MetricSparkline`→`Sparkline`, `DonutRing`→`Donut`) once call-sites are proven identical.
- Lift §G's five components after applying their prop-injection contracts.
- Consider publishing the pure `framework`/`base-data` mappers as a small `@hanzo/base-data` adapter (not UI).
Thanks for your interest in contributing to ui.hanzo.com. We're happy to have you here.
Thanks for your interest in contributing to ui.shadcn.com. We're happy to have you here.
Please take a moment to review this document before submitting your first pull request. We also strongly recommend that you check for open issues and pull requests to see if someone else is working on something similar.
If you need any help, feel free to reach out to [@hanzo](https://x.com/hanzoai).
If you need any help, feel free to reach out to [@shadcn](https://twitter.com/shadcn).
## About this repository
@@ -12,7 +12,9 @@ This repository is a monorepo.
- We use [pnpm](https://pnpm.io) and [`workspaces`](https://pnpm.io/workspaces) for development.
- We use [Turborepo](https://turbo.build/repo) as our build system.
-We use [changesets](https://github.com/changesets/changesets) for managing releases.
-Releases are semver-driven: bump a package's `version` in its `package.json` and
merge to `main`. CI (`.github/workflows/publish.yml`) detects the version change
and publishes that package to npm. One step, one source of truth.
## Structure
@@ -20,28 +22,25 @@ This repository is structured as follows:
This workflow ensures that you are running the most recent version of the registry and testing the CLI properly in your local environment.
## Documentation
The documentation for this project is located in the `www` workspace. You can run the documentation locally by running the following command:
The documentation for this project is located in the `v4` workspace. You can run the documentation locally by running the following command:
```bash
pnpm --filter=www dev
pnpm --filter=v4 dev
```
Documentation is written using [MDX](https://mdxjs.com). You can find the documentation files in the `apps/www/content/docs` directory.
Documentation is written using [MDX](https://mdxjs.com). You can find the documentation files in the `apps/v4/content/docs` directory.
## Components
We use a registry system for developing components. You can find the source code for the components under `apps/www/registry`. The components are organized by styles.
We use a registry system for developing components. You can find the source code for the components under `apps/v4/registry`. The components are organized by styles.
```bash
apps
└── www
└── v4
└── registry
├── default
│ ├── example
│ └── ui
└── new-york
└── new-york-v4
├── example
└── ui
```
@@ -157,7 +141,7 @@ When adding or modifying components, please ensure that:
1. You make the changes for every style.
2. You update the documentation.
3. You run `pnpm build:registry` to update the registry.
3. You run `pnpm registry:build` to update the registry.
## Commit Convention
@@ -196,9 +180,9 @@ If you have a request for a new component, please open a discussion on GitHub. W
## CLI
The `hanzo-ui` package is a CLI for adding components to your project. You can find the documentation for the CLI [here](https://ui.hanzo.com/docs/cli).
The `shadcn` package is a CLI for adding components to your project. You can find the documentation for the CLI [here](https://ui.shadcn.com/docs/cli).
Any changes to the CLI should be made in the `packages/cli` directory. If you can, it would be great if you could add tests for your changes.
Any changes to the CLI should be made in the `packages/shadcn` directory. If you can, it would be great if you could add tests for your changes.
Two lanes, one trigger each. No changesets, no version-PR bot.
All packages updated to support **React 19.2.0**:
## 1. `pkgs/*` — auto-publish on version bump (`publish.yml`)
-`@hanzo/ui` - v5.1.1
-`@hanzo/auth` - Latest
-`@hanzo/commerce` - Latest
-`@hanzo/brand` - Latest
-`@hanzo/react` - v1.0.0
Bump a package's `version` in `pkgs/<name>/package.json` and merge to `main`.
`.github/workflows/publish.yml` detects the changed public `@hanzo/*` package,
builds it, and publishes to npm (repo secret `NPM_TOKEN`). Patch bumps only
(`x.y.z` → `x.y.z+1`).
## Publishing Methods
## 2. `pkg/ui` — `@hanzo/ui@8`, the v8 lane (maintainer flow)
### 1. Automatic Publishing (Tag-based)
When you push a git tag starting with `v` (typically matching @hanzo/ui version), the workflow automatically checks all packages and publishes any with new versions:
`pkg/ui` (with `pkg/data`) is the modern cross-platform library on `@hanzo/gui`.
It publishes from the package directory (`prepack` builds the `types/`):
```bash
# Tag with @hanzo/ui version (workflow checks all packages)
git tag v5.1.1
git push origin v5.1.1
```
**What happens:**
1. Tests run (pkg/ui and pkg/react)
2. All 5 packages build
3.**Automatic version detection:**
- Checks each package's current version in package.json
- Queries npm to see if that version already exists
- Only publishes packages with new versions not on npm
4. GitHub release created (only if packages were published)
**Example workflow output:**
```
📦 Checking @hanzo/ui@5.1.1
⏭️ Already published - skipping
📦 Checking @hanzo/auth@2.5.5
🚀 Publishing to npm...
✅ Successfully published @hanzo/auth@2.5.5
📊 Publishing Summary
✅ Published: 1 package(s)
⏭️ Skipped: 4 package(s)
```
This approach means you:
- Only need to tag once (with @hanzo/ui version)
- Don't need to track which packages need publishing
- Can bump any package version and it auto-publishes on next tag
Accessible and customizable components for React, Vue, Svelte, and React Native. **Built on shadcn/ui with multi-framework support, 3D components, AI components, and advanced features.**
**The React component library for AI applications.**Accessible, customizable primitives for React, Vue, Svelte, and React Native — built on shadcn/ui, extended with AI, 3D, animation, and commerce components, and a single typed import surface.
@@ -6,4 +6,4 @@ We will investigate all legitimate reports and do our best to quickly fix the pr
Our preference is that you make use of GitHub's private vulnerability reporting feature to disclose potential security vulnerabilities in our Open Source Software.
To do this, please visit the security tab of the repository and click the "Report a vulnerability" button.
To do this, please visit the security tab of the repository and click the [Report a vulnerability](https://github.com/shadcn-ui/ui/security/advisories/new) button.
description: Contact form sections with validation, multiple fields, and submission handling.
---
## Overview
Contact form blocks provide complete form layouts for collecting user inquiries. They include input validation, loading states, and success confirmation.
## Features
- **Form Validation**: Built-in HTML5 and custom validation
- **Multiple Fields**: Name, email, subject, and message inputs
- **Loading States**: Visual feedback during form submission
- **Success Message**: Confirmation after successful submission
- **Responsive Design**: Adapts to all screen sizes
- **Accessible**: Semantic form elements with proper labels
## Usage
```tsx
import ContactBlock from "@/registry/default/block/contact"
description: Call-to-action sections for driving user conversions and signups.
---
## Overview
CTA (Call-to-Action) blocks are designed to drive user engagement and conversions. They feature prominent buttons, compelling copy, and focused layouts that guide users toward a specific action.
## Features
- **Conversion-Focused**: Designed to maximize click-through rates
- **Responsive Design**: Adapts to all screen sizes
- **Multiple Layouts**: Centered, split, and banner variations
- **Button Variants**: Primary, secondary, and ghost CTA styles
- **Background Options**: Solid colors, gradients, and image backgrounds
## Usage
```tsx
import CTABlock from "@/registry/default/block/cta"
description: Frequently asked questions sections with expandable accordions and search.
---
## Overview
FAQ blocks present commonly asked questions in organized, expandable layouts. They use accordion patterns for progressive disclosure and optional search filtering.
description: Feature grid and list sections for highlighting product capabilities.
---
## Overview
Feature blocks display product capabilities in organized grid or list layouts. They combine icons, headings, and descriptions to communicate value propositions clearly.
## Features
- **Grid Layouts**: 2, 3, or 4 column responsive grids
- **Icon Support**: Lucide icons or custom SVGs
- **Responsive**: Stacks to single column on mobile
- **Customizable**: Flexible content and styling options
- **Accessible**: Semantic HTML with proper heading hierarchy
## Usage
```tsx
import FeaturesBlock from "@/registry/default/block/features"
description: Full-width hero sections for landing pages with headlines, CTAs, and imagery.
---
## Overview
Hero blocks are viewport-sized sections designed for the top of landing pages. They feature bold typography, call-to-action buttons, and visual elements to capture user attention.
## Variations
### App UI Showcase
<ComponentPreview name="showcase-app-ui-01" />
A hero section showcasing your app's UI with a centered layout and product screenshot.
### E-commerce Showcase
<ComponentPreview name="showcase-ecommerce-01" />
A hero section designed for e-commerce with product imagery and shopping CTAs.
### Marketing Showcase
<ComponentPreview name="showcase-marketing-01" />
A bold marketing hero section with large typography and conversion-focused design.
## Features
- **Responsive Design**: Adapts to all screen sizes
- **CTA Buttons**: Primary and secondary call-to-action buttons
- **Visual Elements**: Support for images, gradients, and animations
- **Typography**: Large, bold headlines with supporting text
- **Customizable**: Easy to modify colors, text, and layout
description: Statistics and metrics sections with counters, charts, and key figures.
---
## Overview
Stats blocks display key metrics and achievements in visually impactful layouts. They feature animated counters, comparison figures, and trend indicators.
## Features
- **Animated Counters**: Number animations on scroll into view
- **Multiple Layouts**: Grid, inline, and featured stat variations
- **Trend Indicators**: Up/down arrows with percentage changes
- **Responsive**: Adapts from multi-column to single column on mobile
- **Icons**: Support for metric-specific icons
- **Customizable**: Easy to update values and labels
## Usage
```tsx
import StatsBlock from "@/registry/default/block/stats"
description: Team member grid and list sections with photos, roles, and social links.
---
## Overview
Team blocks showcase team members with profile photos, names, roles, and social links. They support multiple layout variations for different page contexts.
## Features
- **Grid Layout**: Responsive grid with team member cards
- **Profile Photos**: Avatar images with fallback initials
- **Role Display**: Job titles and department labels
description: Testimonial sections with user quotes, ratings, and social proof.
---
## Overview
Testimonial blocks display customer reviews and social proof in visually appealing layouts. They support avatars, star ratings, company logos, and quote formatting.
## Features
- **Multiple Layouts**: Grid, carousel, and featured testimonial styles
- **Avatar Support**: User photos with name and title
- **Star Ratings**: Visual rating display
- **Company Logos**: Brand association for B2B testimonials
- **Responsive**: Adapts from multi-column to single column on mobile
- **Animated**: Optional entrance animations and carousel transitions
## Usage
```tsx
import TestimonialsBlock from "@/registry/default/block/testimonials"
description: Beautiful, composable chart components built on Recharts.
---
## Overview
@hanzo/ui provides a set of chart components built on top of [Recharts](https://recharts.org/). The chart components are designed to be composable and work with Tailwind CSS theming.
## Installation
<Tabs defaultValue="cli">
<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">
```bash
npx @hanzo/ui@latest add chart
```
</TabsContent>
<TabsContent value="manual">
<Steps>
<Step>Install recharts:</Step>
```bash
pnpm add recharts
```
<Step>Copy the chart components into your project.</Step>
<ComponentSource name="chart" />
</Steps>
</TabsContent>
</Tabs>
## Components
The chart package includes the following components:
- **ChartContainer** - Responsive container that handles theming and sizing
- **ChartTooltip** - Styled tooltip component
- **ChartTooltipContent** - Content renderer for tooltips
- **ChartLegend** - Chart legend component
- **ChartLegendContent** - Content renderer for legends
## Chart Types
- [Area Chart](/docs/charts/area) - For showing trends over time
- [Bar Chart](/docs/charts/bar) - For comparing categorical data
- [Line Chart](/docs/charts/line) - For showing continuous data
- [Pie Chart](/docs/charts/pie) - For showing proportional data
- [Radar Chart](/docs/charts/radar) - For multivariate data comparison
- [Radial Chart](/docs/charts/radial) - For circular data visualization
description: Customizable tooltip component for all chart types.
---
## Overview
The chart tooltip component provides a consistent, themed tooltip across all chart types. It integrates with the chart config system for automatic label and color resolution.
## Usage
```tsx
import { ChartTooltip, ChartTooltipContent } from "@hanzo/ui"
description: Provider and hook for RTL/LTR text direction support.
---
## Overview
The Direction component provides RTL (right-to-left) and LTR (left-to-right) text direction support through a React context provider and hook. Useful for internationalization and bidirectional text layouts.
## Usage
```tsx
import { DirectionProvider, useDirection } from "@hanzo/ui"
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.