- scripts/check-no-hardcoded-brand.mjs: fails if a brand literal (Hanzo/Zoo/Lux/Shinkai) appears in user-facing i18n copy; brand must come from {{appName}}/brandConfig
- net-i18n: 62 brand literals -> {{appName}}; tidy 2 stray 'shinkai' comments
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Read brandConfig.logo inside OnboardingLayout (was a module-level useBrand()
frozen on the default brand, forcing visor.svg's Hanzo "H" onto Zoo/Lux).
Drop the broken right-column hero preview; tagline is now opt-in via
brandConfig.tagline (empty default — removes the old hardcoded "the way, the
truth, the life"). Welcome title uses {{appName}} so no brand name leaks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Four root causes blocked the one/vxrn (rolldown) build of apps/gui.hanzo.ai:
1. tsconfig project references: single-level pkgs/*/tsconfig.json used ../../
(depth-2) refs that resolved to /work/core/* and /work/ui/gui instead of
pkgs/core/* and pkgs/ui/hanzogui. Fixed the depth (../) and the stale
gui->hanzogui rename across 9 tsconfigs.
2. @hanzogui/recipes deep subpaths (component/*) were UNLOADABLE: the no-recipes
path still pulls the showcase tree, which imports @hanzogui/recipes/component/*
via named re-export / named import / namespace member access. The old
virtual-module stub could not satisfy statically-validated named exports.
generate-recipes-proxy.mjs now scans the app and emits concrete per-subpath
stub modules with the exact named exports (hooks return a proxy object so
destructuring is safe); vite.config aliases each deep subpath (longest-first)
to its stub ahead of the catch-all @hanzogui/recipes alias.
3. @react-navigation version skew: expo-router pulled bottom-tabs@7.16.2 which
imports NavigationProvider (only in native >=7.2.5), conflicting with one's
~7.1.33 peer. Pinned bottom-tabs to 7.10.1 and bumped native 7.1.9->7.1.34
(latest 7.1.x), keeping the @react-navigation family internally consistent.
4. Stale gui->hanzogui imports: SelectDemo.tsx and TestSelect.tsx imported from
gui/linear-gradient instead of hanzogui/linear-gradient.
Validated locally: bun run build:app completes (build complete, full dist emitted).
The config-bundler did require("gui") for each entry in
gui.build.ts components, but no workspace package is named gui
(the components package is hanzogui, pkgs/ui/hanzogui). This
threw Cannot find module \x27gui\x27 -> Must provide components in
the gui-extract step of build:prod (static extract), so gui
never published an image. Rename the stale tamagui->gui leftover
to hanzogui in components and the matching optimizeDeps include.
The gui.hanzo.ai site (the only app shipped in the GHCR image) runs on
one@1.12.5, whose vxrn@1.12.5 self-imports vxrn/worker. kitchen-sink and
sandbox pull one@1.15.10 -> vxrn@1.15.10, which bun hoists to the root
node_modules/vxrn. vxrn@1.15.10 dropped the ./worker export, so the
hoisted copy breaks vxrn@1.12.5s self-referential import:
Package subpath ./worker is not defined by exports in vxrn/package.json
Pin the whole one/vxrn family to 1.12.5 via root overrides so a single
vxrn (with ./worker) exists in the tree. One version, no hoist collision.
Add .github/workflows/build.yml that builds+pushes ghcr.io/hanzoai/gui on
v* tags via the canonical hanzoai/.github docker-build reusable workflow
on the self-hosted arcd fleet (amd64+arm64, no GitHub-hosted runners).
Wires the existing working Dockerfile (gui.hanzo.ai, bun build, serves on
:3000) to GHCR. Semver-only tags. The npm publish-gui*.yml workflows are
unaffected.
Co-authored-by: hanzo-dev <z@hanzo.ai>
Replaces non-canonical scale-set / org-prefixed labels with the
existing labels every arcd host registers with. Matches evo for
amd64 and spark for arm64. No new labels added.
release.ts --force-publish-all crashed packing unrelated @hanzo_network/*
packages. Replace with publish-gui-surface.mjs which publishes only the
@hanzogui/* closure + hanzogui + @hanzo/gui alias, rewriting workspace:*
to concrete versions and publishing the npm pack tarball directly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes broken 7.0.0 publish where @hanzogui/* primitives shipped without
dist/, breaking standalone @hanzo/gui installs. Builds then runs
release.ts --republish --force-publish-all using the same KMS->secret
NPM_TOKEN resolution as publish-gui.yml.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI OOM'd bundling the app-as-SDK. Skip minify (consumers minify in their own
build), skip compressed-size reporting, target esnext, and raise heap to 12GB.
Build ~89s, no OOM. (Lib output is unminified by design; downstream apps minify.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace retired self-hosted labels with native host arcd daemons:
evo (linux/amd64), spark (linux/arm64).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The monorepo relied on hoisting and never declared many externals; a clean CI
install (bun, no hoisting guarantees) failed to resolve them when bundling
@hanzo/ai. Declare them on the owning packages (net-ui +49, net-artifacts +33,
net-state, net-i18n, net-message-ts, …). @hanzogui/* workspace deps untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- net-i18n: useChain/@hanzo_network/chain-config -> useBrand/brand-config
(chain-config was renamed to brand-config; this import was never updated).
- loader: drop now-unused @ts-expect-error (TS2578) in GuiPlugin.ts.
Pre-existing breakage that gated the @hanzo/ai publish.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10 leaf TS packages (recipes, net-brand-config, examples, *-test) lacked a
committed tsconfig.json, so the workspace build died with ENOENT on the first
one (pkgs/recipes). Add them on the standard pattern (extends root + composite).
Pre-existing breakage, gates the @hanzo/ai publish.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- net-brand-config bundles ZERO brands: registry (registerBrands) + injection
(setBrand/useBrand/getBrand); add company + machinesEnabled to BrandConfig.
- Brand type: drop 'hanzo'|'zoo'|'lux' union -> string (open/extensible).
- Delete hardcoded BRAND/CHAIN consts -> proxy the active brand (16 call sites
unchanged, now read the live brand).
- isMachinesEnabled: config field, not a hardcoded brand check.
- Examples each ship their own brand.config.ts (apps do the same downstream).
- Neutral test (fixture brands, no bundled assumptions).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace hardcoded-hanzo useHanzoLogin (hanzo.id, placeholder clientId, shipped
client secret, wrong oauth-deep-link event) with brand-neutral useIamLogin that
reads the active brand's iam config (baseUrl/clientId/redirectUri/callbackEvent)
from @hanzo_network/brand-config and uses public-client PKCE. Adding a brand is
now a config entry; nothing is hardcoded to one brand.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per-brand iam used placeholder clientIds not registered in Hanzo IAM. Domains
(hanzo.id/lux.id/zoolabs.id), redirect URIs, and callbackEvents were already
correct. Pairs with desktop redirects added to IAM init_data.json.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `@hanzo/ai/desktop` (vite.desktop.config.ts → dist-desktop): same converged
app as the web build, but it EXTERNALIZES @tauri-apps (14 modules) instead of
aliasing them to web shims — so a desktop consumer gets the REAL native APIs
(window/fs/dialog/updater/node-sidecar), full parity with the standalone forks.
This is the reusable foundation for converging the desktops locally: a desktop
shim is `import HanzoAI, { getBrand } from '@hanzo/ai/desktop'; <HanzoAI
{...getBrand()}/>` (VITE_BRAND selects hanzo/zoo/lux), loaded by the repo's Tauri
shell (which provides @tauri-apps + the node-manager handlers). hanzo/zoo have
full shells; lux's bespoke shell needs those handlers added.
build now emits both dist (web) + dist-desktop; exports['./desktop'] added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Prep for `npm publish @hanzo/ai` (the @hanzo scope):
- Drop the `@hanzo_network/* : workspace:*` runtime deps — they're bundled into
the self-contained dist (aliased to src at build), and workspace:* can't be
installed by consumers. Real 3rd-party deps stay.
- `publishConfig.access = public` (scoped package).
- Ship a hand-authored, self-contained public-API `dist/index.d.ts`
(types/public.d.ts; the build copies it). Raw tsc would emit a broken tree of
internal/@hanzo_network imports for a bundled lib.
Verified: `npm pack --dry-run` → valid tarball with dist + index.d.ts.
Note: 14 MB (bundles streamdown→shiki's ~200 grammars); a follow-up can
externalize shiki to slim it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add `test:web` to @hanzo/ai so the existing Checks `unit-tests` job
(`turbo run test:web`) includes the 63 SDK unit/regression tests
(verified: `turbo run test:web --filter=@hanzo/ai` → 63 passed).
- Replace the hardcoded /home/z libsodium alias in web/vite.config.ts with a
__dirname-relative resolve so the web build works off this machine (CI).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every console error/warning from the running web app, fixed and guarded by the
e2e (which now asserts the console stays clean):
- react-query "Query data cannot be undefined": web host `invoke` returns null
(not undefined) for no-op native calls.
- "isPermissionGranted is not a function": complete the notification host shim
(isPermissionGranted/requestPermission/sendNotification via the browser API).
- CORS on direct :36900/v1/engine/models: route the hanzo-engine client through
a same-origin `/engine-api` vite proxy (set VITE_ENGINE_BASE_URL=/engine-api).
- "ws://localhost:1501 failed": the app derives ws port = httpPort+1; add
local-runtime/ws-proxy.js (:1501 → node WS :3701).
- 401 /v1/node/available_models: gate useGetLLMProviders on auth — it mounted
during onboarding and fired with an empty Bearer.
- framer-motion `motion()` deprecation → `motion.create()` (2 sites).
Tests: unit 63/63, e2e 2/2 (now also asserts no CORS / "is not a function" /
react-query-undefined / available_models / :1501 in the console). Remaining: a
rare intermittent static-asset 404 + a node-side embedding warning — neither
affects chat (documented in CHAT.md).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Establish the test harness and lock in every bug found this session as a
regression test (TDD going forward — see TESTING.md):
- vitest.config.ts: jsdom + the web build's alias graph (@/, @tauri-apps→host
shims, @hanzo_network→src), streamdown stub, @testing-library/jest-dom setup.
`bun run --cwd pkgs/ai test` → 61 tests green (4 new + the 7 pre-existing).
- src/__tests__/ regressions:
• brand-store — useBrand/getBrand are plain getters callable outside render
(the invalid-hook-call that blocked the web app)
• host-shims — getCurrentWindow().emit etc. exist; host adapter web↔tauri
• resizable — react-resizable-panels v3 API present + net-ui renders
• no-missing-imports — critical screens import every <Component> they use
(the dropped Box/Boxes/Coins lucide imports)
- e2e/chat.e2e.test.ts (vitest.e2e.config.ts): Playwright drives the real
browser — onboarding smoke + connect→register→send→assert zen-coder reply.
Skips cleanly when the local runtime isn't up. `test:e2e` → 2 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The web app now chats with zen-coder-24b through a real hanzo-node, fully in the
browser (Playwright e2e: "Reply with exactly: E2E CHAT OK" → zen-coder generated
exactly that). Fixes uncovered by driving the real UI:
- main-layout.tsx: restore missing lucide imports Box/Boxes/Coins (the chat
screens crashed with "Box is not defined").
- react-resizable-panels: the merge pulled v4 (Group/Separator), but the app
uses the v3 API (PanelGroup/PanelResizeHandle) — pin ^3.0.2 in net-ui so the
chat conversation view renders.
- web/vite.config.ts: proxy /v1 (the node's API is /v1/node/*, 135 endpoints) in
addition to /v2 + /ws, so the browser reaches the node same-origin (no CORS).
local-runtime/ holds the reproducible inference wiring (see CHAT.md):
- responses-proxy.py: the node's OpenAI provider POSTs /v1/engine/responses, but
hanzo-engine serves the Responses API at /v1/responses — rewrite the path
(+ enable_thinking:false). This was the "AI Provider API Error" root cause.
- run-node.sh: a standalone /v1 node wired to the engine + embeddings. Uses
127.0.0.1 everywhere (localhost → IPv6 ::1 → instant connection failure).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reconciles the 140 upstream commits with the @hanzo/ai SDK (pkgs/ai +
pkgs/net-*, purely additive). Only bun.lock conflicted; regenerated fresh from
the merged package.json. The markdown dep skew that this exposed is fixed by
the prior commit (shiki@^3 override + dev streamdown stub).
Verified post-merge: fresh `bun install` exit 0, turbo build passes, web prod
build (rollup, 13MB) renders the Hanzo onboarding screen, dev server renders.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The committed lock had a latent skew that only surfaced on a clean `bun install`
(earlier session renders used an ephemeral node_modules):
- shiki: `@vxrn/mdx` pins shiki 1.3.0 *exact*; bun hoisted it over
`@streamdown/code`'s `shiki@^3` (no `engine/javascript` export → build fails).
Fix: `overrides.shiki = ^3.19.0`. Deterministic; @vxrn/mdx is docs-only, not
in the app graph. This alone makes the PROD and LIB builds (rollup) clean —
rollup resolves the rest of the nested micromark stack correctly.
- micromark: a v1↔v4 generation skew (16 v1 consumers from the remark/MDX docs
stack vs 1 v2 consumer = streamdown). The two APIs (util@1 subpath exports vs
util@2 root) must coexist nested, and rollup handles it — but esbuild's DEV
pre-bundle flattens the bare `micromark-util-symbol` to the hoisted @1
("Missing '.' specifier"). Fix: a DEV-ONLY (command==='serve') stub of the
streamdown stack (streamdown-stub.tsx); the prod build uses the real packages
so chat markdown/code/math/mermaid are unaffected in production.
Verified clean: web dev server renders the onboarding screen, web prod build
(rollup, 13MB) renders, self-contained lib dist builds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an env-configurable same-origin dev proxy (/v2 + /ws → VITE_NODE_API/
VITE_NODE_WS) so the browser reaches a hanzo-node without CORS: the app's
nodeAddress becomes its own origin and vite forwards to the node.
CHAT.md documents the verified state: the zen engine answers on :36900
(zen-coder-24b), and a healthy node (:2000, v1.1.20) already has the zen_engine
provider wired to it. Remaining blocker is API-version alignment — the app
client speaks /v1/node/*, the healthy node serves /v2/* only, and the
/v1-compatible desktop node (:3690) is currently hung. Recipe for both paths
included.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make @hanzo/ai consumable as a published APP-as-SDK so each app collapses to
a ~10-line shim (the luxfi/exchange model, for AI):
- Lib build now bundles EVERYTHING (the app, net-* libs, @tauri-apps host
shims, all 3rd-party deps) and externalizes ONLY react/react-dom as peers.
Previously every dep was external, so a consumer installing just @hanzo/ai
was missing libsodium/framer-motion/radix/etc. (pinned libsodium's CJS build
in the lib config too, now that it's bundled rather than external).
- Re-export getBrand/getBrandFromHostname from the package, so a shim needs no
separate brand package for the default case:
import HanzoAI, { getBrand } from '@hanzo/ai'
- Shims (hanzo/zoo/lux/web) aligned to the proven pattern: brand is a build
axis (VITE_BRAND), platform an injected prop (host={tauriHost}), app is
@hanzo/ai. Added a runnable web shim (index.html + vite.config) that imports
@hanzo/ai from its built dist providing only react.
Verified: the external web shim (only react/react-dom provided, everything
else from dist) renders the full Hanzo onboarding screen on :1503.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The web app threw `Cannot read properties of null (reading 'useContext')`
(invalid hook call) and never painted. Root cause was NOT React duplication
from a dep: 7 migrated app modules imported `useBrand` from the `@hanzo/ai`
PACKAGE name, which resolves to the built `dist/index.js`. That pulled the
whole minified library (a second React + a second copy of the app) back into
the source graph — a module cycle with a dual React dispatcher.
Fix: the injectable brand store lives in `@hanzo_network/brand-config`
(`setBrand`/`useBrand`, plain module getters — not hooks — so the app's
non-component call sites are safe), aliased to src so there is ONE copy and
no cycle. The 7 app files now import `useBrand` from there; `@hanzo/ai`'s
`brand-context` just re-exports it. Rule: app code never imports the
`@hanzo/ai` package by name.
Also completed `src/host/window.ts` to the full Tauri Window surface
(`emit`/`listen`/`once`/`onCloseRequested`/geometry) — the missing `emit`
was the next crash after the cycle was broken. Production web entry cleaned
up with a real ErrorBoundary.
Verified: dev server on :1500 paints the real Hanzo onboarding screen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The entire shinkai-fork AI app (280 files) + its 7 @hanzo_network/* libs,
lifted into @hanzo/ai as <HanzoAI {...brand} host={}/> — brand is a prop,
platform is an injected HostAdapter (web / tauri / expo). One unified surface
for hanzo/zoo/lux desktop + mobile + web (hanzo.chat). Library build green
(2.8MB) and production web build green (14MB). Shims in examples/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Auth: @supabase/{ssr,supabase-js,auth-js} → @hanzo/iam (server JWT
validation via JWKS, browser session via IAM SDK). `features/iam/{server,client}`
expose `authenticate(req)` + `useIamSession()`. `useSupabaseClient` is now
a thin compat shim so the login + auth + user.tsx flows keep compiling.
- DB: supabaseAdmin.from(...) → @hanzo/base via a small adapter at
`features/db` that mimics the .from/.select/.insert/.update/.delete shape
used across ~60 files. supabaseAdmin re-exports the adapter so legacy
call sites keep working; storage moved to `features/db/storage` (bucket
`bento` → `recipes`).
- Subscriptions: stripe.* → commerce.subscriptions.* via a typed REST
client at `features/commerce/client`. cancel-subscription,
remove-subscription-item, upgrade-subscription, create-subscription,
add-team-seats, enable-v2-renewal, handle-failed-payment-subscription
rewritten as commerce pass-throughs.
- Deleted: app/api/stripe/webhook+api.ts — commerce owns the webhook
surface; gui should never terminate Stripe traffic.
- V2 checkout, admin impersonate, test-user fixture: replaced with
501-not-implemented stubs + TODO(supabase-rip). These flows now belong
in commerce + @hanzoai/pay + IAM admin assume-identity.
- Stripe UI: StripePaymentModal (1447 lines) and AddTeamMemberModal
collapsed to thin redirect stubs that bounce to commerce.hanzo.ai/checkout.
- Deps: remove @supabase/ssr, @supabase/supabase-js, @stripe/react-stripe-js,
@stripe/stripe-js, stripe. Add @hanzo/iam ^0.9.4, @hanzo/base ^0.2.0.
Drop matching entries from vite.config optimizeDeps + ssr externals.
- Rename SUPABASE_AUTH_SUCCESS postMessage event → HANZO_AUTH_SUCCESS.
Build status: bun install clean, `tsc --noEmit` zero app errors, 40/40
vitest tests passing.
TODO(supabase-rip) markers flag the remaining deep-rewrite work
(V2 purchase flow, OG image upload, admin user impersonation, IAM admin
user lookup for unclaim flows). Each one is a clean handoff to a sibling
service rather than a hidden coupling.
Both subpaths reference dist files the build doesn't produce:
- ./react-native-web/*: no src/react-native-web.{ts,tsx} exists, gui-build
has nothing to bundle. Consumers use @hanzogui/react-native-web
(scoped pkg) instead.
- ./native-test had `react-native: ./dist/test.native.js` but
bundle-native.mjs only emits test.cjs. Drop the dead
react-native condition — the require+default both correctly point
at test.cjs.
Smoke-test was correctly catching this. Forwards-only fix per
"no backwards-compat shims" — if anyone was depending on the broken
subpath, they were getting MODULE_NOT_FOUND already.
The previous npm-install-based smoke fails on first release because
hanzogui's @hanzogui/* workspace deps aren't on npm at the new version
yet (chicken-and-egg — we publish hanzogui umbrella first, primitives
publish after via bun release). \`--silent\` also hid the actual error
making attempt 4's failure opaque.
New shape: tar -xzf the tgz, then check tarball contents directly:
- name === 'hanzogui' (literal token static extractor scans for)
- no leftover 'workspace:*' specs (pack step must rewrite all of them)
- every ./path referenced in exports/main/module/types exists in tarball
Catches: broken exports map, wrong main/module paths, missing files
in `files` glob, Gui→gui rename leaks, workspace:* not rewritten.
Doesn't catch: actual runtime resolution / peer dep mismatch — those
are validated by downstream consumers after publish, not here.
Two fixes to unblock release/gui-v7.3.0 publish attempt 3 (which
failed at the KMS auth step because KMS_CLIENT_ID/SECRET aren't
configured on the hanzoai/gui repo secrets, and HANZO_API_KEY isn't
either).
1. Add fallback chain in token resolution:
KMS Universal Auth → KMS HANZO_API_KEY → direct repo secret
NPM_TOKEN. Keeps the KMS path as the preferred future state while
unblocking publishes today using the repo-secret NPM_TOKEN that
release.yaml + the rest of the org's workflows already use.
2. Fix the Publish step's working-directory: pkgs/ui/gui →
pkgs/ui/hanzogui. The umbrella package lives at pkgs/ui/hanzogui
(Artem's prefix-drop commit renamed the file/identifier content but
not the directory itself). pkgs/ui/gui is an orphan dir holding
just dist/ + types/ — untracked, can be deleted later.
Last batch of flatten side-effects: tsconfig.json `references` arrays
in packages at pkgs/{compiler,core,ui}/<X>/ that had `"path":
"../../packages/<Y>"` (correctly resolving to pkgs/packages/<Y> in the
old layout). After flatten those targets are at pkgs/<Y>, so the path
needs to be `"../../<Y>"`.
10 tsconfig.json files updated:
- pkgs/ui/hanzogui/, ui/tabs/, ui/input/, ui/focus-scope/
- pkgs/core/web/
- pkgs/compiler/{loader,next-plugin,static,static-tests,vite-plugin}/
This was the root cause of the CI failure on release/gui-v7.3.0
attempt 2 — vite-transform's tsconfig resolver looked up
`/home/runner/_work/gui/gui/pkgs/packages/element` (stale path) when
bundling the hanzogui umbrella's native target. Local repro: same
error; same fix; `bun run build` for pkgs/ui/hanzogui now succeeds.
Side-effects of the pkgs/packages/* → pkgs/* flatten that
broke the CI publish:
1. **tsconfig depth (18 files)**: packages at pkgs/packages/X/tsconfig.json
had \`"extends": "../../../tsconfig.json"\` pointing 3 levels up to the
monorepo root. After flatten they're at pkgs/X/tsconfig.json (2 levels
deep) — need \`"../../tsconfig.json"\`. Failure surface: gui-build
crashed with "TS5083: Cannot read file '../../../tsconfig.json'" on
every flattened package (cli-color was just the first one turbo hit).
2. **bun.lock regen**: lockfile still referenced pkgs/packages/* paths
throughout the workspace map. \`bun install\` regenerated it cleanly
(652 ins / 1433 del). Required so CI's \`bun install\` doesn't try
to resolve stale paths.
Now \`bun run build\` succeeds for cli-color (was the first failure in
the turbo build order on the release/gui-v7.3.0 CI run).
Companion to hanzoai/admin@fd68af5 (filter-repo extraction, history
preserved). admin no longer ships from this monorepo.
Removed:
- apps/admin-{agent,auto-stub,base,base-go,bot,commerce,tasks}/
- pkgs/ui-admin/ (became pkgs/admin/ in hanzoai/admin)
- package.json workspaces: drop ./apps/admin-{base,bot,commerce,tasks}
- .github/workflows/publish-gui.yml: drop @hanzogui/admin
typecheck/pack/publish steps, narrow smoke-test to gui only, retitle
@hanzogui/admin@7.3.0+ now publishes from hanzoai/admin via its own
release pipeline (separate npm publish lane). Existing 7.3.x and below
on npm continue to resolve fine; new versions just come from a
different repo.
ONE WAY ONLY: hanzoai/gui = primitives, hanzoai/admin = admin SPAs +
shell. Each repo owns one concern.
@hanzogui is the canonical scope for all GUI surface — recipes is GUI
content, so it moves under @hanzogui. Also fixes lingering
repository.directory paths from the earlier flatten that still said
pkgs/packages/{get,recipes} (they should have updated alongside the
mv but git did not detect the rename because the file content had
diverged too much for the heuristic).
- pkgs/recipes/package.json: name @hanzo/recipes → @hanzogui/recipes
- pkgs/recipes/{build.ts,readme.md}: rebrand log line + docs
- pkgs/get/package.json: dep @hanzo/recipes → @hanzogui/recipes
- pkgs/get/src/commands/{add,init,list,pick}.ts: 4 import sites
- scripts/seed-recipes.ts: server-side seed import
- pkgs/{get,recipes}/package.json: repository.directory pkgs/packages/X → pkgs/X
ONE WAY ONLY: 'pkgs' is the canonical name for any directory holding
packages. The redundant 'pkgs/packages/' sub-level is dropped — every
package now lives at pkgs/<name>/ (with categorical sub-groups
pkgs/compiler/, pkgs/core/, pkgs/ui/ unchanged since they are
sub-systems, not just 'packages').
- mv pkgs/packages/* → pkgs/* (24 packages)
- rm pkgs/packages/hanzogui-dev-config (orphan: only had dist/ artifacts,
no source; gui-dev-config is the canonical @hanzogui/dev-config)
- package.json workspaces: drop pkgs/packages glob, add ./pkgs/*
- tsconfig.json: rewrite paths + 15 references entries
- apps/{demos,kitchen-sink,gui.hanzo.ai}/tsconfig.json: reference paths
- .github/workflows/publish-gui.yml: directory walk list
- pkgs/{get,recipes}/package.json: repository.directory
bun.lock will regenerate on next install.
ONE WAY ONLY: lean CLI installer lives under the @hanzogui scope alongside the rest of the GUI primitives. The redundant "gui-" prefix is dropped (already in the scope name).
- mv pkgs/packages/gui-get → pkgs/packages/get
- package.json: name @hanzo/gui-get → @hanzogui/get, bin gui-get → hanzo-gui-get (matches @hanzogui/build's hanzo-gui-build), repository.directory updated
- src/constants.ts: CLI_NAME → hanzo-gui-get
- build.ts: log line rebrand
- readme.md: rewrite around @hanzogui/get + hanzo-gui-get binary
Removed: empty pkgs/packages/bento-get scaffold (orphan from old Tamagui Bento fork).
Companion actions outside this commit:
- hanzogui/get GitHub repo archived (was the renamed bento-get fork, now dead)
- @hanzogui/bento-get to be deprecated on npm pointing at @hanzogui/get
- Publish @hanzogui/get@7.3.0 via 'bun release' (manual, requires npm auth)
Minor bump covering the TAMA→GUI rip + Hanzogui→Gui file/identifier
rename. Public API (`@hanzogui/*` namespace, exported `GuiProvider`/
`createGui`/`Gui`/`Text`/`View`/`styled`/...) unchanged from 7.0.0
on npm; this release brings source artifacts in line with what was
already shipped in dist and removes all upstream brand references.
Internal-only breaking surface: compiler env vars (`TAMAGUI_TARGET`,
`TAMAGUI_IS_SERVER`, etc.) renamed to `GUI_*`. Affects only consumers
piping flags into the static extractor.
Continuation of the chore/rip-tama sweep. The earlier pass left
9 compromises that violated the zero-tolerance directive; this
removes them and brings the tree to 0 tamagui refs (verified
via `git ls-files | xargs grep -l tamagui` excluding deleted).
Removed:
- .tamagui cache-dir entries from .gitignore/.dockerignore/.oxfmtrc.jsonc
and three sub-gitignores (apps/sandbox, apps/tests/next-turbopack,
templates/{expo-router,remix}). No backwards-compat for the old cache dir.
- scripts/rename-from-tama.sh migration script (no migration path).
- pkgs/ui/switch/src/Switch.draggable-tmp scratchpad with @tamagui/* imports.
- assets/banners.sketch binary (uneditable; designer can redo from
current branding if needed).
- @take-out/cli devDependency + its upgradeSets entry (third-party
CLI whose transitive @tamagui/constants was the only tamagui ref
in bun.lock).
Renamed (asset metadata):
- SVG <title>/<g id="tamagui-words"> in gui-words.svg / logo-white.svg
→ "hanzogui-words" (Sketch artboard names left over from the fork).
- favicon.svg "Tamagui"/"tamagui" → "hanzogui".
- iOS bundle ID `dev.tamagui.kitchensinkgo` → `ai.hanzo.kitchensinkgo`
in Info.plist and project.pbxproj.
LICENSE files (~120, all packages):
- Replaced explanatory line "Portions of this software are derived
from Tamagui, originally licensed under..." with "Portions of
this software are derived from upstream code originally licensed
under...". Copyright lines (Wienert, Gallagher, Facebook, Radix,
Pullara, Framer, WorkOS) preserved verbatim per MIT terms — those
are legal attribution, not brand references.
Regenerated:
- Root bun.lock after dropping @take-out/cli. 1 package removed,
zero tamagui refs remain.
- templates/expo-router/bun.lock deleted; the template uses
workspace:* deps and consumers regenerate on first `bun install`.
Verified: `git ls-files | xargs grep -lE 'tamagui|TAMAGUI|Tamagui'`
returns no results.
Eradicate every upstream-fork token from source. Scope is the published
hanzogui ecosystem — the artifacts on npm (@hanzogui/*@7.0.0) are already
clean; this sweep brings the source tree to parity.
Categories handled (in sed precedence order to avoid token collisions):
- URL rewrites: github.com/tamagui/tamagui -> github.com/hanzoai/gui,
tamagui.dev -> gui.hanzo.ai
- Package namespace: @tamagui/X -> @hanzogui/X (imports, package.json deps,
__tests__ fixtures, build scripts)
- Env vars: NEXT_PUBLIC_IS_TAMAGUI_*, IS_TAMAGUI_*, DEBUG_TAMAGUI_*,
TAMAGUI_* (TARGET, IS_SERVER/CLIENT, ENVIRONMENT, OPTIMIZE_THEMES,
HEADLESS, DEBUG, KEEP_THEMES, PACKAGE_EXPORTS, PRO_SECRET, etc.) all
collapse to GUI_* prefix (short, matches @hanzogui/ namespace ergonomics)
- Vite envPrefix: ['TAMAGUI_'] -> ['GUI_'] in pkgs/compiler/vite-plugin
- PascalCase identifiers: Tamagui -> Hanzogui (any remaining
TamaguiCustomConfig, TamaguiInternalConfig, etc. in source)
- Lowercase strings/comments/log labels: tamagui -> hanzogui
- Filename: scripts/rename-from-tamagui.sh -> scripts/rename-from-tama.sh
(and the script's internal rules were rewritten to be self-consistent —
it still consumes upstream tamagui sources and produces hanzogui output)
- Dockerfile.local ARG names: ARG IS_TAMAGUI_DEV -> ARG IS_GUI_DEV,
ARG TAMAGUI_PRO_SECRET -> ARG GUI_PRO_SECRET, etc.
- .env.development: NEXT_PUBLIC_IS_TAMAGUI_DEV -> NEXT_PUBLIC_IS_GUI_DEV
- bun.lock regenerated via bun install
Decisions on edge cases:
- .gitignore / .dockerignore / .oxfmtrc.jsonc / templates: kept .tamagui
entries alongside the new .hanzogui entries as safety so legacy cache
directories from older installations are still ignored. Net additive.
- pkgs/packages/build/__tests__/fixtures/{simple,watch}-package: the fixture
+ builder (hanzogui-build.js) + integration.test.ts all reference
TAMAGUI_TARGET symmetrically (test asserts builder DCE-eliminates it).
Renamed all three to GUI_TARGET — test contract is preserved end-to-end.
- pkgs/compiler/static/src/helpers/requireHanzoguiCore.ts: file was already
renamed; internals already use HanzoguiPlatform / @hanzogui/core. No-op.
- plans/toast2.md: renamed to use hanzogui (no longer tracking upstream
tamagui issues — this fork is independent).
- assets/banners.sketch: binary Sketch file (zip archive). Skipped — same
treatment as svg/plist/pbxproj. Internal XML may still contain "tamagui"
but it's only visible inside the design tool.
- bun.lock: 2 remaining @tamagui/constants references come from third-party
transitive (@take-out/helpers depends on it). Cannot rewrite upstream
package metadata.
- LICENSE files (root + per-pkg): preserved unchanged. MIT attribution to
Tamagui Inc is a legal requirement.
- .git/config upstream remote: untouched (per task spec).
- pkgs/ui/switch/src/Switch.draggable-tmp: scratchpad, left alone.
Smoke tests:
- bun install: clean (lockfile regenerated, 1 package installed)
- @hanzogui/constants: builds in 302 ms
- @hanzogui/web (200+ source files): builds in 4187 ms
- @hanzogui/vite-plugin: builds in 997 ms
- @hanzogui/static: builds in 2547 ms
Residual count after sweep:
- git grep -ci tamagui (excluding LICENSE, lockfiles, binaries, draggable-tmp,
rename script, intentional .gitignore safety entries): 0 source matches.
158 files changed.
Two parallel streams of work landing together:
• main: 74 commits — code/ → pkgs/+apps/+templates/ restructure, admin-base
SPAs scaffolded, bento-get → gui-get rename, license/version 7.2.x bumps,
@hanzogui/core type refactor (drop "Hanzogui" prefix), CI/build hardening,
workspace:* resolves at pack time.
• sync-upstream-2.0.0: 11 commits — wholesale upstream tamagui 2.0.0 sync
over the (then-current) code/ tree, plus 10 fix-ups for types/, tsconfig,
stale .d.ts artifacts, dep cleanup, hanzogui-build clean:build workaround,
@tamagui/types → @hanzogui/types rename.
Conflict resolution doctrine: main is canonical for STRUCTURE (pkgs/ wins
over code/). Sync's fixups for types/tsconfig were largely superseded by
main's restructure work. Where both branches touched the same package.json
the published 7.2.3 from main wins (sync's 7.0.0 was already pushed to npm
and is now historical).
Resolved:
• 1544 UD (sync deleted demos/sandbox/gui.hanzo.ai/starters; main kept
those + restructured them) → kept main's restructured versions
• 268 UA (both branches added the same paths with different content,
typically lucide-icons backwards-compat shims) → kept main's
• 175 UU (package.json version bump conflicts + 2 tsconfigs) →
kept main's canonical 7.2.x + main's pkgs/ paths
• 2 DU (main deleted files sync had modified) → accepted deletions
Net: the value Artem captured on sync-upstream-2.0.0 was the npm
publish of 7.0.0, which already shipped. Main's 7.2.3 supersedes it
and is the forward path. This merge closes the branch cleanly so the
history shows both streams converged rather than the sync branch being
abandoned.
when i overwrote this package.json earlier from upstream's tarball,
its version got reset to 2.0.0. sync-gui-versions.ts skipped it on
the first pass because its name was still @tamagui/types (chicken-
and-egg: filter requires the @hanzogui/ prefix). re-running after
the name rename catches it.
prior committed .d.ts files had drifted from current source over
multiple syncs. a fresh clean+force build emits 14 types differently
from what was committed — these are the accurate versions.
also reverts the floating --skip-types + custom clean:build workaround
from 6c78e319d / beb6f741e. the original premise (hanzogui-build
drops '| null' from FloatingOverrideContext) only held when stale
types existed in types/ as the build was running. with a clean
types/ at build start, hanzogui-build emits the type correctly. no
workaround needed.
hanzogui-build emits FloatingOverrideContext type as
React.Context<UseFloatingOverrideFn> instead of
React.Context<UseFloatingOverrideFn | null>, breaking every package
that imports it. workaround: build script uses --skip-types so the
committed (correct) types stay in place. but clean:build defaults
to wiping both dist/ AND types/ — override it here to wipe only
dist/ so the publish wave preserves our type fix.
types-dir copy script earlier matched code/core/types as a directory
named 'types' and overwrote its package.json with upstream's
@tamagui/types name. subsequent rename pass only touched .ts/.d.ts,
not .json. bun install couldn't resolve @hanzogui/types@workspace:*
because nothing in the workspace exposed that name.
ran the rename rules across all package.json under code/ to belt-and-
suspenders catch any others.
several packages had committed types/ files that drifted from source
over previous syncs. replaced our types/ contents with upstream v2.0.0's
(rename-translated) so they match current source.
@hanzogui/floating: hanzogui-build incorrectly drops '| null' from the
inferred type of FloatingOverrideContext on regeneration. set its
build script to --skip-types so the (now-correct) committed type is
preserved instead. real fix would be in hanzogui-build itself; this
unblocks the publish.
build status: 163/163 packages build successfully.
restored scripts/rename-from-tamagui.sh (was missing from earlier
commits).
ships 86 .d.ts files alongside .js source that don't exist upstream;
they confuse esbuild's loader inference. mark private until the
package's vendored react-native subtree builds cleanly with the
current hanzogui-build flow.
earlier sed deletion of the @hanzogui/demos paths alias nuked the
bottom half of compilerOptions. restored noImplicitAny: false,
downlevelIteration: true, module: System, moduleResolution: node,
strict + strictNullChecks, target: es2020, etc. — verbatim from
upstream tamagui v2.0.0 root tsconfig.json (only paths differ to use
hanzogui-renamed aliases).
upstream patches deposited compiled outputs into src/ alongside the
real .ts sources; esbuild treated both as inputs and hit output-path
collisions. removed every .js and .d.ts in src/ where a .ts or .tsx
sibling exists; kept 176 standalone .js/.d.ts files that look like
hand-written source.
One mount-prefix knob per app, matches the server-side BASE_API_PREFIX.
Default per-app: /v1, /v1/tasks, /v1/agents, /v1/bot. Override at
deploy via VITE_API_PREFIX. IAM stays a fixed sibling at /v1/iam.
* admin-base: lib/api.ts gained API_PREFIX + apiPath(); every page +
vite.config define block wired through. Logo wiring now respects
VITE_BRAND_MARK_URL / VITE_BRAND_NAME / VITE_BRAND_SUBTITLE so
white-label deployments override without code changes.
* admin-tasks / admin-agent / admin-bot: same pattern. Each lib/api.ts
exports ROOT = '/' + (VITE_API_PREFIX ?? '/v1/<app>'). Pages now
build URLs as `${ROOT}/...` instead of hardcoding `/v1/<app>/...`.
* BrandMark: HanzoMark now renders the CANONICAL block-H (five SVG
paths, no font, no Geist text) on a BLACK chip — matches
~/work/hanzo/logo/dist/hanzo-favicon.svg exactly. Hanzo Red is for
brand accents only, never the logo background.
* admin-base/public/favicon.svg fixed to the same canonical artwork.
End-to-end OIDC sign-in against the embedded /v1/iam now lands on the
collections page without leaking 401/403/PKCE-replay errors:
* lib/api.ts, src/pages/*: every fetch path moved from /api/* to /v1/*
to match the Hanzo route convention. base no longer serves /api/.
* lib/api.ts: Authorization header is now standard OAuth2 `Bearer <jwt>`.
The platform middleware accepts only Bearer; raw tokens 401'd.
* pages/Callback.tsx: gate handleCallback() on a module-scoped Set of
consumed `code` params so React.StrictMode's intentional double-mount
doesn't try to redeem the same single-use code twice.
* vite.config.ts: dev proxy now forwards /v1/* (was /api/) to
VITE_BASE_URL or :8090. WebSocket survives the same path.
Decomplect: the env.ts cross-runtime shim was an "easy" bridge over
Vite + Metro that braided two unrelated platforms. Each platform now
reads env its native way — admin-base uses `import.meta.env.VITE_*`
directly; the Expo sibling (admin-base-go) owns its own
`process.env.EXPO_PUBLIC_*` strategy. One way per platform.
- src/env.ts : deleted (Function-shim was incidental
complexity over `import.meta.env`).
- src/App.tsx : BRAND_*/APP_VERSION read directly from
`import.meta.env.VITE_*` (Vite inlines).
- src/pages/Login.tsx : redirect-only PKCE. Click "Sign in" →
`iam.signinRedirect()` → IAM owns the
credential UI. No `<IamLogin>` form.
SERVER_URL: `VITE_IAM_SERVER_URL`, then
same-origin `/v1/iam`. Removed the
`/api/iam` fallback.
- src/pages/Callback.tsx : new — handles `/_/callback`, calls
`iam.handleCallback()`, persists token,
navigates to original target.
- src/main.tsx : route `/callback` → `<Callback />`.
- package.json : bump `@hanzo/iam` ^0.9.4 (OIDC-only).
App.tsx imports NavigationContainer from @react-navigation/native but
the dep was missing from package.json — the metro resolver would have
failed on first bundle. Use ^7.1.9 to match what the workspace already
resolves transitively. Peers react-native-screens and
react-native-safe-area-context are already declared.
Lockfile drift comes from running 'bun install' against the
post-license-bump package.json baseline.
apps/admin-base/src/ is consumed by both apps/admin-base (Vite SPA) and
apps/admin-base-go (Expo iOS/Android/web) via a tsconfig path alias.
Direct import.meta.env reads crash Metro's CJS parser; direct process.env
reads aren't inlined by Vite. env.ts hides the fork behind a single try/
catch-guarded module so the shared source compiles under both runtimes.
Audit found one VITE_* read (APP_VERSION); replaced in App.tsx. Vite
define still inlines 0.4.0 into the bundle.
apps/admin-base is the Vite SPA admin UI for Hanzo Base. This new
sibling boots the same source tree through Expo so the admin can
ship as native iOS (.ipa), Android (.apk/.aab), and a Metro-built
PWA — one screen tree, three runtimes.
Layout matches apps/kitchen-sink-go (the proven mobile pattern):
- package.json expo + react-native + @hanzogui workspace deps
- app.json bundle id ai.hanzo.base.admin, scheme hanzobase://
- App.tsx imports App from ../admin-base/src via tsconfig
alias '@hanzo/base-ui/*' — zero src duplication
- index.js expo.registerRootComponent
- metro.config.js hoist node_modules from workspace root, hot-reload
monorepo-wide
- babel.config.js hanzogui babel plugin for tree-shake
- tsconfig.json paths alias for @hanzo/base-ui → ../admin-base/src
- README.md run instructions + env contract
Status: scaffold. Next mechanical step is a Vite→Expo env shim
(replace import.meta.env.VITE_* with process.env.EXPO_PUBLIC_* — or
a thin env.ts re-export in apps/admin-base/src/) before this boots.
- 171 package.json files bumped from 102.0.0-rc.41-hanzoai.1 / 2.0.0-rc.41-hanzoai.5 / 2.0.0-rc.0-1769885482630 → 2.0.0
- 194 package.json files set author=Hanzo AI <dev@hanzo.ai> (was missing or Nate Wienert object form)
- 194 package.json files set license=BSD-3-Clause (was missing or MIT)
- 164 per-package LICENSE files replaced with canonical BSD-3-Clause text
preserving original upstream MIT copyright notices (Tamagui/Radix/Framer/WorkOS/etc.) per MIT terms
- Root LICENSE now BSD-3-Clause with Hanzo AI, Inc. as copyright holder
The 102 prefix was a fork-marker that drifted from upstream Tamagui's 2.0.0-rc.x
release cadence; this lands @hanzogui/* at a clean v2.0.0 baseline.
npm pack does not resolve workspace:* specifiers. The previous publish
flow shipped tarballs with literal `workspace:*` deps which break every
consumer at install time — this was the root cause of @hanzo/gui@7.0.0
landing as a 33 KB husk on npm.
- Build a workspace name -> on-disk-version map upfront.
- Pack hanzogui and @hanzogui/admin from a temp copy of each package
with workspace specs rewritten to the actual versions on disk.
- Pack the @hanzo/gui alias from a clone of the hanzogui tmpdir so we
inherit the same resolved deps and only swap `name`.
Bump umbrella 7.2.1 -> 7.2.2; the 7.2.1 tag was published with the
unfixed pack step.
- Add explicit files field to 15 packages that lacked one (latent bug
if dist/ ever gets stripped by an .npmignore default).
- Bump hanzogui umbrella 7.2.0 -> 7.2.1.
- Extend publish-hanzogui workflow to also republish the umbrella as
@hanzo/gui after the canonical hanzogui+admin publishes succeed. Same
dist tree, only the package name differs. Skips if version already on
npm so re-runs are idempotent. Static extractor still keys on the
literal 'hanzogui' import token; the @hanzo/gui alias is purely a
consumer ergonomics shim.
- HanzoMark.tsx: single source of truth for the 7-path canonical H
mark (viewBox 0 0 67 67, sourced from ~/work/hanzo/logo/src/logos.ts).
App.tsx brand block + favicon.svg both use it now; the old stylized
H rectangle from @hanzogui/admin BrandMark is overridden.
- useSettings hook (src/stores/settings.ts) reads /v1/tasks/settings
and exposes capability predicates (canWriteNamespace,
canAddSearchAttribute, workerHeartbeatsEnabled, archivalEnabled,
workerStopSupported). Defaults to permissive so legacy responses
never over-disable.
- Inverse buttons: worker stop, identity revoke, search-attr delete,
deployment unset-current, batch terminate confirm.
- Write-action gates: Start workflow, New schedule, Start batch,
Add search attribute, Namespace metadata edits, Worker heartbeats
empty state, Archival disabled-state explainer.
Completes the rename from commit c5ce0a3779 — the new gui.config.ts
files were already staged in that commit; this finalizes by removing
the old files.
Workflow Typecheck step previously ran `bun run typecheck --filter=...`
which forwards --filter into tsc (TS5072: Unknown build option).
Now runs `bun run typecheck` directly inside pkgs/ui-admin (the only
publish target with a typecheck script — hanzogui umbrella's types
are validated transitively via every consumer + the static-extractor
pipeline).
Build step now skips the redundant filtered rebuild — postinstall
already ran a full SKIP_TYPES=1 build of all 169 workspace tasks,
which is the actual gate. This step just sanity-checks the two
publish dist trees exist before pack.
Version bump: hanzogui 7.1.0 → 7.2.0 (minor, public RN type rename
in @hanzogui/core: RNHanzoguiViewNonStyleProps → RNViewNonStyleProps,
RNHanzoguiTextNonStyleProps → RNTextNonStyleProps). @hanzogui/admin
0.3.1 → 0.4.0 (minor, paired with the consumer-side rename).
Also fixes pkgs/ui/hanzogui/package.json repository.directory which
still pointed to the pre-rename code/ui/hanzogui path.
Turbo forwards everything after `--` to every leaf task. Apps run
`tsc -b && vite build` — vite chokes on --skip-types (CACError:
unknown option). hanzogui-build already honors SKIP_TYPES env var,
and tsc/vite ignore unknown env. Same skip semantics, no flag leak.
admin-bot: drop unused Workflow icon; rename <Badge tone> →
<Badge variant> (canonical primitive uses variant); 'neutral' →
'muted', 'danger' → 'destructive' to match StatusVariant enum;
drop fontFamily="$mono" (default v5 config has no $mono token —
falls through to body which is system-mono in monospace contexts).
admin-base: same $mono drop in SettingsTokens.
admin-commerce: wrap Text in YStack so textAlign moves to a layout
container — Text (RNTextComponent) doesn't accept layout props.
admin-agent: rewrite 5 pages (Members, ApiKeys, Observability, Orgs,
Billing) against the canonical @hanzogui/admin API: EmptyState →
Empty, ErrorBanner → ErrorState, MetricCard → SummaryCard,
DataTable {header,render} columns → {key,label,flex} + renderRow +
rowKey + emptyState. The scaffold was written against an older
draft API and never converged.
@hanzogui/admin: Breadcrumbs aria-hidden="true" → aria-hidden
(boolean attr, the IconStyleProps shape rejects string).
@hanzogui/lucide-icons-2: codegen-inject "types" field into all
1760 icon entries in package.json so consumers' tsc resolves the
.d.ts (not the .mjs) under moduleResolution=bundler.
Renames RNHanzoguiViewNonStyleProps → RNViewNonStyleProps and
RNHanzoguiTextNonStyleProps → RNTextNonStyleProps (the user-named
target). Internal aliases RNHanzoguiView/RNHanzoguiText → RNViewComponent/
RNTextComponent at the View/Text export cast. The HanzoguiComponent +
HanzoguiElement + Hanzogui*Config public types stay (renaming those
breaks every npm consumer outside this workspace and is out of scope
for this hop).
Regenerates types/*.d.ts across 60+ pkgs/ui/* + pkgs/packages/* +
pkgs/core/* — the structural d.ts emit picks up the new names from
the renamed source. No back-compat alias; one-way rename.
Public API change → minor bump (covered separately in version commit).
Drops the redundant brand-stutter from local config filenames. Static
extractor still uses .hanzogui/ as its temp-dir name (that's an internal
implementation detail of the extractor, not a public surface). All
vite.config.ts, tsconfig.json include arrays, and src/main.tsx import
specifiers now reference 'gui.config' / 'gui.config.ts'.
51 strict-tsc errors across pkgs/ui-admin/src/ closed:
- editable={false} (8 sites in iam/policy/{AdapterEdit,AuthzEditor,
EnforcerEdit,ModelEdit,PermissionEdit}) → readOnly/disabled. Hanzo
GUI v7 dropped the editable prop on Input.
- theme="active" (3 sites in iam/resources/{FormEdit,ServerEdit,
TicketEdit}) → tokenized variant or removed prop.
- animation="..." prop on YStack/XStack (2 sites in PermissionEdit)
→ enterStyle/exitStyle (config-driven in v7).
- paddingLeft/minWidth/maxHeight/minHeight longhand props on Stack
(8 sites across primitives/{SearchInput,WorkflowFilters,WorkflowsTable}
+ iam/federation/{LdapSync,WebhookEdit} + iam/policy/AuthzEditor)
→ pl/minW/maxH/minH shorthands.
- Missing icon .d.ts for 7 deep-imports (CircleX, Power, RotateCcw,
HelpCircle, ArrowDown, ArrowUp, etc) → ambient declare module shims
in src/lucide-icons.d.ts.
- @types/react-is added to devDependencies for test/iam/identity/
pages.test.tsx.
- tz() helper called with raw ISO string in 4 federation list pages
→ wrapped with new Date(...).
- Misc: TextArea/Input event handler shape, Select<T> generic,
discriminated union narrowing, FlexAlignType normalization.
Tests: 209/209 across 29 files. Typecheck: 51 → 0.
Unblocks: external @hanzogui/admin consumers can re-enable tsc -b
(Liquidity superadmin had to drop tsc -b in c3715369; can flip back
once npm publish lands via release/hanzogui-v* tag — workflow
shipped at f15200b869).
- New workflow at .github/workflows/publish-hanzogui.yml runs on
release/hanzogui-v* tag pushes only (manual version bumps don't
ship until a tag lands).
- NPM_TOKEN sourced from KMS via Universal Auth (kms.hanzo.ai
/api/v3/secrets/raw at /publish env=prod). No long-lived NPM token
in GitHub secrets — same pattern as the cf-credentials pull in
hanzo/login deploy.
- Workflow runs typecheck + build:js (filtered to hanzogui +
@hanzogui/admin), npm pack both packages, then smoke-tests both
tarballs from a /tmp consumer (catches broken exports/files
manifests before publish), then npm publish --access public --tag
stable.
- Bump hanzogui to 7.1.0, @hanzogui/admin to 0.3.1.
- @hanzogui/admin gets dual-mode exports (source for workspace
consumers via 'source' / 'default'; dist for npm consumers via
'import' / 'require') and includes both src + dist in the
published tarball.
5-page operator chrome that mirrors apps/admin-tasks:
- Orgs (list every org the caller has access to)
- Members (per-org membership, IAM-backed)
- ApiKeys (hk-* keys, secret returned once at creation)
- Billing (prepaid balance + month spend, hanzo-commerce backed)
- Observability (webhook destinations)
Built on @hanzogui/admin chrome (AdminApp, Sidebar, TopBar,
DataTable, MetricCard, EmptyState, ErrorBanner) — same shape
admin-tasks uses. Vite SPA, base /_/agents/.
Lives ALONGSIDE the existing 79k LOC product UI in
~/work/hanzo/agent/control-plane/web/client (deck.gl workflow
viz, react-flow editor, xterm console). Different mount:
- product UI → '/' (web/client/dist)
- admin chrome → '/_/agents/' (admin/dist, this app)
Stole the react-native-svg vite plugin alias from admin-tasks.
New SPA at apps/admin-bot/ mirroring apps/admin-tasks shape:
@hanzogui/admin chrome (AdminApp + Sidebar + TopBar) wrapping 13
pages — chat, overview, channels, instances, sessions, usage, cron,
agents, skills, nodes, config, debug, logs.
Wire shape types in src/lib/api.ts match the bot gateway HTTP
surface (/v1/bot/*). useFetch from @hanzogui/admin handles transport
and SWR cache. base path /_/bot/ matches the canonical embed mount
across all hanzo binaries.
This SPA replaces the legacy Lit "openclaw-control-ui" at
~/work/hanzo/bot/ui/. Per the Node-runtime exception in
HANZO_BINARY.md, hanzo-bot ships the bundle by syncing dist/ into
its dist/control-ui/ via scripts/sync-admin-ui.sh — no //go:embed,
just Node static.
vite build: 1058 modules → 599 kB JS / 7 kB CSS / 1 kB HTML
(185 kB JS gzipped). Total dist/ = 604 kB.
Brand-neutral (Hanzo brand title "Hanzo Bot"); white-label by
swapping mark + title at the @hanzogui/admin chrome layer.
Wires Collections / Records / Logs / Settings (SMTP, rate limits, tokens)
on @hanzogui/admin DataTable + useFetch + AdminApp shell. Replaces the
react-query + Tailwind ui-react bespoke. Output is consumed by base via
//go:embed against apps/admin-base/dist after scripts/sync-admin-ui.sh.
Login / Signup / ForgotPassword now compute `captchaRequired` from the
config and refuse to submit until a token is produced — closing the
"undefined widget = bypass" hole. Signup and ForgotPassword grew first-
class CaptchaConfig + CaptchaWidget props matching Login.
TokenEdit and KeyEdit lift server-issued blobs into local view consts
so the source no longer contains `value={draft.accessToken}` /
`value={draft.accessSecret}` even on the read-only CopyField path —
keeps the regression-test invariant tight.
Adds `test/iam/secret.test.tsx` covering all 8 secret-bearing edit
pages with banned-binding assertions and save-payload destructure
checks, and `test/auth/captcha.test.tsx` pinning the canSubmit gate
on Login / Signup / ForgotPassword. The narrower
`federation/secret.test.tsx` is replaced by the bucket-spanning suite.
TokenEdit rendered `accessToken`, `refreshToken`, and the OAuth `code`
in editable textareas / inputs and POSTed them straight back on save.
KeyEdit POSTed the full draft including the server-minted `accessKey`
and `accessSecret`. Both let an attacker with DOM access freeze a
stolen credential in place. Render every server-issued blob through
read-only `<CopyField>` and strip the field from the save payload so
the backend remains the sole authority for these values.
LdapEdit and SyncerEdit submitted `password: ''` (and `sshPassword: ''`)
when the operator left the field empty. The IAM backend treats empty
strings as "store this value", so a save that only edited (say) the
search filter would silently destroy the bind credential. Match the
WebhookEdit secret pattern: omit the field from the payload entirely
when no fresh value was typed.
The Casbin adapter edit form bound `value={draft.password}` directly,
echoing the server-stored bind password back into a `secureTextEntry`
input. That value would round-trip on save and overwrite the stored
secret with itself (or worse — leak through DOM inspection). Move to
`passwordEdit` write-only state; omit the field from the payload when
blank so the stored password stays untouched.
ProviderEdit and AppEdit echoed `draft.clientSecret` straight back into
the form input, so the upstream Casdoor regression — server returns the
encrypted secret, UI rebroadcasts it on save — was reproduced. Move both
to a `clientSecretEdit` write-only state: never hydrated, only sent when
non-empty, omitted from the payload otherwise so the server keeps the
stored value.
Drop the back-compat re-export from primitives/index.ts. PageShell is
chrome (it lives in shell/), not a primitive. The 9 IAM identity pages
that imported it from ../../primitives now import from ../../shell —
the only canonical location.
One way to import. Zero compatibility shims.
- test/setup.ts: add window.matchMedia shim. @hanzogui/select reads it
at module load — without the shim every test importing a component
that touches Select crashes with "matchMedia is not a function".
- vitest.config.ts: add resolve.alias for `react-native` →
`react-native-web` and `react-native-svg` → `@hanzogui/react-native-svg`
(mirrors the admin-tasks Vite build aliases). Hanzo GUI primitives
pull RN imports that need the web shim under jsdom.
- src/auth/{Login,Signup,ForgotPassword}.tsx: replace bare-dir
`'@hanzogui/lucide-icons-2/icons'` with explicit per-icon deep
imports (`/icons/Eye`, `/icons/EyeOff`). Same idiom as the rest of
the bucket; bare-dir spec isn't a real subpath in the package
exports map.
Result: 91/100 tests green, 15/16 files green. Remaining 9 failures
all in test/iam/identity/pages.test.tsx — render-shape test that
mocks `hanzogui` but the pages also use `@hanzogui/admin` primitives
(DataTable etc.) which reach through the mocks. Not a regression in
the port; the test pattern itself is brittle and will be rewritten
to match the static import-shape style used in policy/pages.test.tsx.
Correct the previous rename direction. The clarified rule:
- @hanzogui/* = gui internals (umbrella, deep packages, admin chrome,
config, lucide, vite-plugin)
- @hanzo/* = product-surface packages
`@hanzogui/admin` (the shared admin chrome) STAYS under @hanzogui — it's
a gui internal, not a product surface. The consumer SPA was the only
mis-classified one: renaming it from `@hanzogui/admin-tasks` to top-level
`@hanzo/tasks` so it shares a namespace with the binary `tasksd` and the
domain `tasks.hanzo.ai`. Source imports of `@hanzogui/admin` reverted
across 30 files; package.json deps updated; READMEs cleaned.
Bundle output is byte-identical to the v2.49.0 tag — only naming.
Rename the published-package identity for the admin chrome from
@hanzogui/admin to @hanzo/admin and the consumer SPA from
@hanzogui/admin-tasks to @hanzo/admin-tasks. The workspace internal
umbrella stays `hanzogui` (Tamagui static extractor scans for that
literal import specifier — Vite aliases don't reach it), and that's
fine: it's tooling-internal, not part of any public surface.
Versioning: tag v2.49.0 to track upstream temporalio/ui v2.49.x.
Past v3.x.x tags were a mistake — they bumped majors for internal
UI rewrites instead of tracking upstream's semver. Going forward,
admin-tasks tracks temporalio/ui's major.minor.
Other:
- vite.config.ts: alias react-native-svg -> @hanzogui/react-native-svg
to keep Rolldown from choking on fabric/codegen native components.
- @hanzogui/vite-plugin pinned back to workspace:* — the npm v7.0.0
publish ships an empty stub that does no extraction.
Closes 75% of upstream Temporal Web UI parity (was 55%) by landing the
five highest-leverage shape gaps Playwright identified.
FIX-1: Per-tab counts on workflow detail
TabSpec gains optional count(wf) reader. History/Pending/Workers/
Relationships/Nexus render <Badge variant="muted"> next to the label.
FIX-2: SavedViewsRail on Workflows list
New code/ui-admin/src/shell/SavedViewsRail.tsx (reusable across
every admin app — kms might want similar saved searches). Six
system views: All / Task Failures / Running / Parent / Today /
Last Hour. Free-text query edits drop the active view. No custom
views storage yet (defer to v3.3).
FIX-3: DataTable primitive — real headers always
New code/ui-admin/src/primitives/DataTable.tsx. Renders headers
+ empty body row with copy when 0 rows. Schedules / Batches /
Deployments ported. Columns now match upstream order:
Schedules: Status / ID / Workflow Type / Recent / Upcoming / Spec
Batches: Status / Job ID / Operation / Start / Close
Deployments: Deployment Name / Build ID / State / Deployed At
FIX-4: Input + Result panels on workflow detail Summary
Side-by-side PayloadPanel cards reading wf.input / wf.result with
collapsible JSON, "No input" / "Workflow still running" fallbacks.
FIX-5: Event timeline strip on workflow detail
New code/admin-tasks/src/pages/workflow-tabs/HistoryStrip.tsx.
Fetches /v1/tasks/.../history and renders horizontal flex bars
colored by event family (started=sky, completed=green, failed=red,
canceled=amber, signaled=violet, scheduled=slate). Each bar a
<Link> to events/:id with native title tooltip.
Bundle delta: 693.33 → 700.90 kB raw / 206.35 → 208.25 kB gzip
(+7.57 kB raw / +1.90 kB gzip for 5 features = ~1.5 kB per fix).
lucide-icons.d.ts — added AlertTriangle / Calendar / GitBranch
ambient module decls (TS strict refused without).
Pre-existing TopBar.tsx Tamagui prop typing errors confirmed
unrelated via git stash diff. Out of scope per "don't expand scope".
Closed 70% of upstream Temporal Web UI parity gap. Page count went
14 → 22 (out of upstream 45). All 11 workflow detail tabs now match
upstream's tab list 1:1.
New tab panes (src/pages/workflow-tabs/):
PendingActivitiesPane.tsx — engine-gated empty state (worker SDK
runtime hint)
WorkersPane.tsx — engine-gated empty state
QueriesPane.tsx — interactive query runner; non-stack
types return 501 surfaced inline
JsonPane.tsx — shared by Memo + Search Attributes,
JSON tree of describe.memo / .searchAttrs
UserMetadataPane.tsx — engine-gated, summary+details strings
RelationshipsPane.tsx — parent-child workflow graph (engine
partial: parent if known, no children
tracked yet)
NexusLinksPane.tsx — engine-gated, outbound nexus links
New top-level pages:
Activities.tsx — namespace-level activity inspector empty state
Archival.tsx — replaced disabled placeholder with real explanation
page about engine-gated archival
WorkflowDetail.tsx refactored into a controlled-tab shell. 9 sibling
react-router routes mount the same component with different tab prop
(react-router-dom@7 lacks first-class optional segments).
main.tsx new routes:
/workflows/:id/call-stack
/workflows/:id/pending-activities
/workflows/:id/workers
/workflows/:id/query
/workflows/:id/memo
/workflows/:id/search-attributes
/workflows/:id/user-metadata
/workflows/:id/relationships
/workflows/:id/nexus-links
/namespaces/:ns/activities
/archive
Sidebar: Activities now wired (Zap icon), Archive enabled.
Bundle delta: 674.74 → 693.33 kB raw / 201.70 → 206.35 kB gzipped
(+18.59 raw / +4.65 gzip for 9 new components, ~2 kB per pane).
lib/api.ts — searchAttrs typed Record<string, unknown> | null so
JsonPane can iterate keys safely.
lucide-icons.d.ts — added Zap + ArrowUpRight ambient module decls.
Engine-gated tabs render honest empty states with worker-SDK-runtime
hints; faking data would be lying. The wire is correct, the empty
state is the truthful answer until activity tracking + worker
registration ship in the engine layer.
Vite was pre-bundling `hanzogui` and `react` but leaving `@hanzogui/core`,
`@hanzogui/helpers-icon`, `@hanzogui/sizable-context`, `@hanzogui/themes`,
`@hanzogui/web`, and `@hanzogui/lucide-icons-2` un-bundled. Each
unbundled module loaded as its own ESM unit at request time → multiple
React Context instances for theme state.
Symptom: TamaguiProvider (from `hanzogui` umbrella) populated
context A; <Text> from helpers-icon's themed() icon wrapper looked
up context B and got null. "Looked for theme (component: Text), but
no parent theme context was found."
Fix: optimizeDeps.include lists every @hanzogui/* package the app
touches. Confirmed via `ls node_modules/.vite/deps/`:
@hanzogui_core.js
@hanzogui_helpers-icon.js
@hanzogui_lucide-icons-2.js
@hanzogui_sizable-context.js
@hanzogui_themes.js
@hanzogui_web.js
hanzogui.js
All single instances now. Production build was always fine (Rolldown
single-pass). resolve.dedupe alone wasn't enough — Vite's dev-time
request resolver needs the explicit pre-bundle hint to fold every
import path through the same compiled module.
Vite was serving two parallel hanzogui module graphs in dev:
admin-tasks resolved hanzogui from its own node_modules, while
@hanzogui/admin (a workspace dep that admin-tasks imports for
Sidebar/TopBar/etc.) resolved its OWN copy from a nested path.
Each graph carried its own TamaguiProvider context. Components
rendered through the @hanzogui/admin chrome couldn't see the
provider mounted in admin-tasks's main.tsx and threw "Can't find
Hanzogui configuration" on every Icon/Text/Stack mount.
Fix: vite.config.ts resolve.dedupe forces ONE module instance for
react, react-dom, react-native-web, hanzogui, @hanzogui/core,
@hanzogui/web, @hanzogui/themes, @hanzogui/use-element-layout.
Production build was unaffected (Rolldown dedupes via the same
optimizeDeps include[] that was already there) — bug was purely
the Vite dev server's request-time module resolution.
Rewrote all 14 files importing from `@hanzogui/lucide-icons-2` umbrella
to use per-icon deep paths `@hanzogui/lucide-icons-2/icons/<Name>`.
before: import { Plus, Layers } from '@hanzogui/lucide-icons-2'
after: import { Plus } from '@hanzogui/lucide-icons-2/icons/Plus'
import { Layers } from '@hanzogui/lucide-icons-2/icons/Layers'
Bundle delta: 199.4 KB → 199.6 KB gzip (NO win today). Once the
hanzogui workspace move enabled the static extractor, lucide was
already tree-shaken — Scientist's −73 KB projection was for the older
`disable: true` build path. Keeping the deep-imports anyway as
structural insurance: future icons can't accidentally pull the whole
1761-icon set if a new contributor copies an `import { X } from '@hanzogui/lucide-icons-2'`
back in.
`src/lucide-icons.d.ts` (in both packages) — ambient module decls for
the per-icon paths. Auto-generated from `git grep` of icon usage;
documented regeneration path in the file's header.
32 unique icons used across both packages. Each one declared with
`any` typing — Tamagui-typed icons are accepted regardless.
Reviewer SHIP-WITH-FIXES verdict on v0.1: separation/composition right,
patterns layer dead-on-arrival, dup SummaryCard, 0 tests. All 5 fixes
landed before commerce/kms lock in the wrong API.
FIX-1 DELETE patterns/ — <ListPage>/<DetailPage> were exported but
zero pages used them; the eight list pages diverge in shape (filters,
saved views, badges, counts) and reifying a one-size-fits-all
abstraction would lock in mistakes. Primitives are the unit of
reuse; pages compose them. patterns/ folder removed; barrel updated.
FIX-2 De-dup SummaryCard — admin-tasks/pages/TaskQueueDetail.tsx
re-implemented the same SummaryCard locally. Now imports from
@hanzogui/admin. Removed 30 LOC + unused H2 import.
FIX-3 Split tz from format — new data/tz.ts (getTz/setTz/TZ_KEY/Tz)
so TopBar's chrome import no longer drags humanTTL/badgeColors.
format.ts imports getTz from ./tz. Both re-exported via data/index.
FIX-4 Tests — vitest jsdom config, setupFiles for Node 25 localStorage
shadowing, 7 tests pass: format (4) + useFetch (2 — generation
cancellation + ApiError throw, no network) + tz (1).
FIX-5 v0.2.0 bump + README rewrite — surface map drops patterns/,
adds Tests + Changelog sections.
Verification:
bun run typecheck (admin-tasks) — clean
bun run test (ui-admin) — 7/7 passed
bun run build (admin-tasks) — clean, 674 KB raw / 199 KB gzip
http://localhost:5174/_/tasks/ — 200, renders
Pre-existing 47 hanzogui-prop-shape errors in src/primitives/*+shell/*
are orthogonal to this PR (long-form prop names rejected by v102
generic; tracked separately).
Moved ~/work/hanzo/tasks/ui-tamagui-spike/ into code/admin-tasks/ as
@hanzogui/admin-tasks workspace package. Inside the workspace,
@hanzogui/static can resolve @hanzogui/core from .hanzogui/ temp dir
correctly — extractor now emits the static CSS layer Tamagui needs to
populate the theme registry at runtime.
Bundle delta (admin-tasks/dist):
JS: 1,511,832 → 674,728 (−55%)
Gzip JS: 294,962 → 199,380 (−32%, UNDER 200 KB target)
CSS: 0 → 9,104 (extracted, was runtime CSS-in-JS)
Total: 1.5 MB → 680 KB
Per-component extractor logs visible in `bun run build` output (e.g.
"Workflows · 47 found · 42 opt · 34 flat"). All 26 components run
through the optimizer cleanly.
Type widening in code/ui-admin/src/shell/Sidebar.tsx — IconComponent
relaxed from `ComponentType<{ size?, color?: string }>` to
`ComponentType<any>` so Tamagui-typed `color: ColorTokens` from
@hanzogui/lucide-icons-2 props pass through. Same widening applied
in admin-tasks/src/pages/Support.tsx.
Workspace integration:
package.json — workspaces[] now includes ./code/ui-admin and
./code/admin-tasks
bun.lock — both packages link as workspace:* refs
Smoke test: `bun dev` at code/admin-tasks/ serves
http://localhost:5174/_/tasks/ with React-refresh, Vite client,
all source modules transform clean.
Embedded tasksd binary UI at ~/work/hanzo/tasks/ui/ is UNTOUCHED —
remains React/shadcn for the embedded form factor. admin-tasks
deploys as a standalone web app at tasks.hanzo.ai when ready.
Pre-existing blocker resolved: @hanzogui/vite-plugin's stale dist/
(April 13, before guiPlugin → hanzoguiPlugin rename) required
`bun run build:js` at gui root before the extractor would load.
The postinstall hook only checks for *existence* of dist/, not
freshness. Filed as separate concern — not gating this commit.
Required by tooling that does require.resolve('@hanzo/gui/package.json')
to find the installed location of the package. The published v4.3.2 already
exposes this; this aligns the workspace local copy.
exposed this gap during Docker builds.)
- scripts/release.ts: add --from-feature-branch flag that bypasses
the main-only branch check and skips the implicit `git pull
--rebase origin main`, so releases can run from non-main branches
- scripts/sync-gui-versions.ts: repoint ROOT_PACKAGE at
code/ui/hanzogui/package.json (code/ui/gui/ was removed in the
audit) and widen glob to code/**/package.json so all @hanzogui/*
outside code/{ui,core}/*/ get synced (compiler/, packages/,
starters/, tests/, top-level workspaces — 40 packages previously
missed)
- code/gui.hanzo.ai/package.json: mark @hanzogui/site private so
the docs site is excluded from the publish set; it has no dist/
output (only generated CSS) and was failing release.ts dist-check
- .gitignore + .oxfmtignore: add **/.hanzogui/ to stop tracking and
format-checking the static-compiler cache (1MB+ generated config
bundles, regenerated by `hanzogui generate-css` and the Vite/Next
plugins)
Aligns all 181 @hanzogui/* packages + the hanzogui umbrella onto
a single version train matching upstream Tamagui rc.41. The
-hanzoai.N suffix leaves room for hanzoai-specific patches
between upstream syncs.
Also patches scripts/release.ts:
- Fixes stale ./code/ui/gui path to ./code/ui/hanzogui (umbrella
was renamed during the rebrand)
- Adds --tag <name> flag for custom dist-tag publishes
Full build and types build pass.
Applies the rebrand rename pass to code/gui.hanzo.ai and moves
its config layout to match upstream's docs site.
- Rename @hanzo/gui → hanzogui, Gui* → Hanzogui*,
gui.config/gui.build/gui-prompt → hanzogui.* across the site
- Move hanzogui.config.ts to site root (was in config/)
- package.json build script: gui → hanzogui (CLI bin name)
- Remove stale gui.generated.css duplicate and TWO_NAMESPACES.md
(documented the old namespace split that's now unified)
Full build and types build pass. Missing-config warning is gone.
Replaces the hanzoai/gui code tree with upstream tamagui main
(2.0.0-rc.41-1776626066745) after running a mechanical rename
codemod. Fixes the long-standing split where engine packages
(@hanzogui/* at v4.4.0) had drifted in shape from the component
packages (at v3.0.2), producing type errors like "Property
'children' does not exist on SliderImplProps".
Renames applied uniformly across the tree:
- @tamagui/* → @hanzogui/*
- @tamagui/helpers-tamagui → @hanzogui/component-helpers
- tamagui (umbrella) → hanzogui (unscoped, was @hanzo/gui; clean
break — no shim)
- Tamagui* → Hanzogui* (identifiers + filenames)
- TAMAGUI_HEADLESS → HANZOGUI_HEADLESS
- tamagui.dev → hanzogui.dev
- github.com/tamagui/tamagui → github.com/hanzoai/gui
- tamagui-build → hanzogui-build (CLI)
- tamagui-loader → hanzogui-loader (stays unscoped, matches
upstream)
Preserved 4 hanzoai-only packages:
- code/packages/gui-dev-config (@hanzogui/dev-config)
- code/core/font-geist-mono
- code/core/font-geist-sans
- code/gui.hanzo.ai (@hanzogui/site)
Upstream-sourced substitutions:
- code/tests/next-webpack: replaced hanzoai's stubbed-out
@hanzogui/next-webpack-test with upstream's live
next15-plus-cli-optimize.
Dropped from the sync:
- upstream code/tamagui.dev (docs site) — hanzoai has its own
site at code/gui.hanzo.ai, which was preserved.
Build: `bun run build` succeeds across all 165 workspace tasks.
Follow-ups (not in this commit):
- Consumer migration: @hanzo/gui → hanzogui, Gui* → Hanzogui*
- Publish strategy: hanzogui@2.0.0-rc.41-hanzoai.1 and
@hanzogui/*@2.0.0-rc.41-hanzoai.1 on the `next` npm dist-tag,
leave `latest` pointing at current v4.x until consumers
migrate.
- @hanzogui/site emits a bundling warning; not blocking.
2026-04-20 11:08:46 -07:00
12089 changed files with 1937108 additions and 76983 deletions
Note you need to re-build packages (`bun run build` in the package directory) as you change them, unless you or someone is running a `bun run watch` at root.
FOR LONG RUNNNING DEBUGGING run `bun run watch` in the background its faster and rebuilds all packages.
keep commits to one line, add a trailing "Fixes #" if associated with a GH issue, and start with a convential commit style - UNLESS its a change that shouldn't go into the changelog, in those cases you can do things like `docs: ` or `site: `.
# Hanzo GUI Testing Guide
## Running Tests
### Kitchen Sink Tests
The kitchen-sink package contains the main integration tests for Hanzo GUI components. To run these tests:
1.**Start the web server** (in the background):
```bash
cd apps/kitchen-sink
bun run start:web
```
To open a specific test case in the browser:
```bash
open "http://localhost:9000/?test=YourTestCaseName"
```
Test case names match the file names in `apps/kitchen-sink/src/usecases/` (e.g., `SelectFocusScopeCase`).
To open a component demo:
```bash
open "http://localhost:9000/?demo=Select"
```
Demo names match files in `apps/demos/src/` without the `Demo` suffix (e.g., `Select` for `SelectDemo.tsx`).
2. **Run all web tests** with different animation drivers:
```bash
bun run test:web
```
This uses `run-tests-parallel.ts` which first runs `default` + `webkit` projects sequentially, then runs all four animated driver projects (`css`, `native`, `reanimated`, `motion`) in parallel against a single shared dev server.
3. **Run tests with a specific animation driver**:
```bash
# Using env var + playwright --project flag
cd apps/kitchen-sink
NODE_ENV=test HANZO_GUI_TEST_ANIMATION_DRIVER=css npx playwright test --project=animated-css
# Available projects: animated-css, animated-native, animated-reanimated, animated-motion
```
4. **Run a specific test file**:
```bash
# Using playwright directly
cd apps/kitchen-sink
npx playwright test tests/PopoverFocusScope.test.tsx
# Or with a specific driver
NODE_ENV=test HANZO_GUI_TEST_ANIMATION_DRIVER=css npx playwright test tests/YourTest.animated.test.tsx --project=animated-css
```
5. **Debug tests**:
```bash
bun run test:web:debug
# or
npx playwright test --debug
```
### Test Structure
Tests are located in `apps/kitchen-sink/tests/` and follow these naming conventions:
- `ComponentName.test.tsx` - Standard tests that run ONCE with the default animation driver
- `ComponentName.animated.test.tsx` - Animation-dependent tests that run with ALL animation drivers (css, native, reanimated, motion)
This separation significantly speeds up the test suite since most tests don't need to run 4x across all animation drivers. Only use `.animated.test.tsx` for tests that specifically verify animation behavior across different drivers.
### Writing Tests
When writing tests for focus behavior or component interactions:
1. Use appropriate wait times for animations and focus changes
2. Be aware that `trapFocus` behavior depends on the component's open state
3. Test both trapped and non-trapped focus scenarios
4. Consider browser focus behavior when `trapFocus` is false
### Common Issues
- If tests fail due to timing, add appropriate `waitForTimeout` calls
- For focus tests, ensure elements are visible before testing focus state
- When testing popover/dialog components, wait for animations to complete
## Commit Message Conventions
- Use `site:` prefix (not `fix(site):`) for gui.hanzo.ai changes since they don't go in the changelog
- Use `ci:` prefix (not `fix(ci):`) for CI/workflow changes since they don't go in the changelog
- Keep commit messages to a single line
## iOS Development
See [docs/using-ios.md](./docs/using-ios.md) for iOS native development and Detox testing tips.
## gui.hanzo.ai API Authentication
When making authenticated API calls from the client side in gui.hanzo.ai, always use the `authFetch` helper:
```ts
import { authFetch } from '~/features/api/authFetch'
**Why this matters:** Cookies alone are not reliable for auth in production due to cross-origin/SameSite issues. The `authFetch` helper automatically includes the Authorization header with the user's access token. All payment/subscription endpoints require this.
-`@hanzogui/web` - Universal style library for React.
-`@hanzo/gui` - UI kit that adapts to every platform.
-`@hanzogui/static` - Optimizing compiler that works with `gui-web` and `gui`.
-`gui` - UI kit that adapts to every platform.
-`@hanzogui/static` - Optimizing compiler that works with `@hanzogui/web` and `gui`.
<br />
@@ -32,9 +32,9 @@ It does this with an optimizing compiler that outputs platform-specific optimiza
The entirety of Hanzo GUI works at compile time and runtime, and can be set up gradually, with initial usage as simple as importing it and using the base views and styled function.
We recommend checking out the starters with `npm create hanzo-gui@latest`, they range from a simple learning example to a production-ready monorepo.
We recommend checking out the starters with `npm create gui@latest`, they range from a simple learning example to a production-ready monorepo.
The compiler optimizes most and ultimately flattens a majority of styled components. In the [~500px² responsive browser section](https://gui.hanzo.ai) of the Hanzo GUI website, 49 of the 55 or so [inline styled components](https://github.com/hanzoai/gui/blob/main/code/gui.hanzo.ai/components/HeroResponsive.tsx) are flattened to a `div`. The homepage gains nearly 15% on Lighthouse with the compiler on.
The compiler optimizes most styled components by flattening them to plain `div`s (web) or `View`s (native) at build time. Real-world apps see double-digit Lighthouse improvements.
[Learn more on the website](https://gui.hanzo.ai/docs/intro/introduction).
# The Two-Namespace Problem: `@hanzo/gui` vs `@hanzogui/*`
## Summary
This repo publishes **127 npm packages** under two namespaces:
- **`@hanzo/gui`** (1 package, v4.0.0) — a barrel that re-exports everything
- **`@hanzogui/*`** (126 packages, v3.0.x) — where all the actual code lives
`@hanzo/gui` contains zero implementation. Its `src/index.ts` is entirely:
```ts
export*from'@hanzogui/core'
export*from'@hanzogui/stacks'
export*from'@hanzogui/button'
// ... 50+ more re-exports
```
This design causes serious, reproducible problems for downstream consumers. This document explains the issues, why they happen, and proposes a consolidation plan.
---
## Problems
### 1. pnpm duplicate copies
pnpm uses strict dependency isolation. When a consumer depends on both `@hanzo/gui` (which transitively pulls in `@hanzogui/core@3.0.6`) and `@hanzogui/button@3.0.2` (which also depends on `@hanzogui/core@3.0.6`), pnpm may resolve two physically separate copies of `@hanzogui/core`.
This breaks any library that relies on module identity — which includes the entire Tamagui/GUI styling system. `styled()` called with a component from copy A fails when the style engine is initialized in copy B.
**Symptoms:**
-`TypeError: Cannot read properties of undefined (reading 'staticConfig')`
-`TypeError: Cannot read properties of undefined (reading 'View')`
When Rollup bundles `@hanzo/gui`, it inlines all 50+ `export * from '@hanzogui/*'` re-exports into a single module. The `@hanzogui/*` packages have circular dependencies among themselves (e.g., `@hanzogui/button` → `@hanzogui/core` → `@hanzogui/web` → `@hanzogui/core`). Rollup resolves these into a single flat scope, but JavaScript execution order means some bindings are `undefined` at the time `styled()` is called.
**Symptoms:**
-`styled(Stack, {...})` receives `undefined` instead of the `Stack` component
- Only happens in production builds (dev mode serves modules individually)
- Downstream consumers must add custom Vite plugins to work around this
### 3. Downstream consumers need resolution hacks
Every consumer using pnpm + Vite must add a custom plugin to force `@hanzogui/*` imports to resolve from `@hanzo/gui`'s own `node_modules`:
```ts
// Required in every downstream vite.config.ts
{
name:'resolve-hanzogui',
enforce:'pre',
resolveId(source){
if(source.startsWith('@hanzogui/')){
returnrequire_.resolve(source,{paths:[guiDir]})
}
},
}
```
This is fragile, poorly documented, and needs to be copied into every project that uses `@hanzo/gui`.
### 4. Version drift between packages
Core packages are at 3.0.6, component packages are at 3.0.2–3.0.3. This means `@hanzogui/button@3.0.2` may depend on `@hanzogui/core@3.0.2` while the barrel pulls in `@hanzogui/core@3.0.6`. Even minor version mismatches can cause duplicate copies.
### 5. Publishing overhead
127 separate npm publishes per release. Version coordination across all packages. Any missed package creates a broken dependency graph.
---
## Root cause
The two-namespace design was intended for **tree-shaking on React Native**: importing `@hanzogui/button` avoids pulling in `@hanzogui/dialog`, `@hanzogui/sheet`, etc.
However:
- Modern bundlers (Vite, Rollup, esbuild) tree-shake **named exports** from a single package just as effectively
- The tree-shaking benefit is marginal compared to the cost of 126 separate packages
- The barrel `@hanzo/gui` already defeats the purpose — most consumers use it, pulling in everything anyway
---
## Proposal: Consolidate into `@hanzo/gui`
### What changes
1.**Move all source** from `@hanzogui/*` into `@hanzo/gui` as internal directories
2.**Single entry point**: `@hanzo/gui` exports everything (already does via barrel)
3.**Subpath exports** for config/build-time packages not in the main barrel:
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.