271 Commits
Author SHA1 Message Date
zeekay 26a0d9dd0a @hanzo/[email protected]
Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-27 14:54:24 -07:00
zeekay 9b1769c7d2 The booker starts with the attendee a page already knows
Booker and BookingForm take prefill, the name, email and notes the
details step starts with. hanzo.ai's sales page sends its visitor to
cal.hanzo.ai with the answers they gave, and nobody types them twice.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-27 14:50:44 -07:00
zeekayandHanzo Dev acdf9136fa A section path may carry no whitespace, control character or backslash
A browser strips a tab out of a URL before it parses it, so /<tab>/host arrived
as //host: another site, drawn as this app's own section.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-25 01:59:12 -07:00
zeekayandHanzo Dev ba57d99f89 The frame draws only sound doors, mounts the scope once, and acts only on plain ids
A section points at a path of the app or an https address on another host;
a protocol-relative, script or bare address is not drawn, and the home mark
falls back to the app's root. The scope renders in exactly one place, the bar
or the phone's sheet, so there is one selection and one set of reads. A
notification is marked read or archived only when its id is a single plain
segment, and a reason the server names is looked up in a Map. The scope's
keys are per workspace, not per person, and the README says the host clears
them when the person changes. `.auth/` is ignored.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-25 01:52:44 -07:00
zeekayandHanzo Dev faed55df10 One frame for the signed-in app: Frame, Account, Scope, Notifications
hanzo.ai and the Hanzo App each drew their own copy of the workspace shell,
and the copies had drifted. The frame is here now, once: the rail of
sections, the sidebar with the workspace switcher at its top and the account
at its foot, the bar, the pane, and the column beside it that a room fills
through Beside.

The rail is the sidebar shut on a laptop (an explicit toggle kept per device,
never hover) and the tab bar on a phone. The far-left strip that stands beside
an open sidebar is opt-in through `strip`, off by default. The account menu is
Profile, Notifications, Appearance, Security, Sign out, and carries no
balance. Scope puts project and environment in the bar, kept per workspace
and never carried across one. Notifications reads the reader's own through
the host's call; the package holds no credential.

The frame follows the host's theme, so its inks are the host's own rungs
(FRAME in theme.ts). Its layout lives in shellStyles.ts beside the chrome's,
injected by an insertion effect so the grid is in place before first paint.

@hanzogui/shell 8.3.19

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-25 01:37:19 -07:00
zooqueen 6f965b7885 The menu plane is opaque, because a plane inside the filtered bar cannot blur the page
Every plane hangs inside the header, and the header carries backdrop-filter,
which makes it a backdrop root: a descendant's own blur samples only what the
bar paints, never the page. So the 72% glass plane showed the hero straight
through the menu in Chromium and WebKit alike. The plane is the opaque panel
now, the bar wears that same ground while a plane or the sheet hangs from it,
and the veil dims deep enough to recede without its blur. The scrolled bar is
still glass, where the blur is real. No API change.

@hanzogui/shell 8.3.18
2026-09-24 16:45:24 -07:00
zeekayandClaude Opus 5.5 3123592875 Send events with the keys that name live projects
@hanzo/event 0.3.39 and 0.3.40 carry a Hanzo key whose project no
longer exists, so the ingest door answers every beacon from gui with
403 ingest_key_unknown. 0.3.41 carries the current keys. The telemetry
package asks for it, and an override lifts the copy @hanzo/observe
pins to the same version.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-24 10:26:19 -07:00
zooqueenandHanzo Dev 31ad1f039e Team is its own repo now
apps/team moved to hanzo-apps/team with its history, and builds there
against the published @hanzo/gui. It leaves the workspace list, the
lockfile, and the Written-at-Hanzo list in every NOTICE (ten names
become nine).


Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 15:53:45 -07:00
zeekayandHanzo Dev f606295c76 Name the legal center in the footer's legal bar
The bar linked Privacy, Terms and Cookies. The Acceptable Use Policy, the
Data Processing Addendum and the subprocessor register — what a procurement
review asks for first — were reachable only from inside another document.
hanzo.ai/legal indexes all of them; the bar now names it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 01:48:01 -07:00
zeekayandHanzo Dev 2684a60de5 Name a click by the component that drew it
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 13:35:28 -07:00
zeekayandHanzo Dev 15278400b1 Give tests time for the compiler to start
The first extraction in a test file esbuild-bundles the config and every
component package and then requires the result — seconds of real work,
once per worker, charged to whichever test asks first. Vitest's 5s
default was never a budget for that, so three static-tests failed by
timeout on an idle machine.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 13:03:39 -07:00
zeekayandHanzo Dev 2013e0ca86 Flatten themes without cloning them first
transformConfig deep-cloned the whole config and then replaced every
theme variable with its value, so the clone built roughly 300k objects
that were discarded in the same pass. Measured on the default config
(900 themes x 362 variables), the same transform: 3502/6722/5005ms
before, 202/241/29ms after, and the emitted hanzogui.config.json is
byte-identical. It was most of a cold compiler start.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 13:03:39 -07:00
zeekayandHanzo Dev c0da5afbca Format the five manifests the check rejects
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-07 23:48:04 -07:00
zeekayandHanzo Dev a13f8eebe7 Keep the ground neutral under every tint
A tint named an accent and then painted the page with it, so yellow put the
section on an olive ground and orange on a brown one — the complaint the
ramp was rebuilt to answer, reached by the other route. The ground and the
section surface now come off the neutral scale under all nine stops, which
measures as zero distance from the untinted ground on both schemes, and a
hue reaches the swatch, the demo, the ink and the stored accent instead.

That settles the rung too. With one ground per scheme rather than nine,
every stop is its solid step on dark, and only the light ground's nearness
to the bright end still pulls green and blue down one and orange two.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-07 23:36:00 -07:00
zeekayandHanzo Dev d1a39698ee Give the ramp every hue and boot it dark
Yellow and orange were dropped for failing 3:1 against the light ground at
one uniform rung, which was a fact about the rung. Each stop now names the
rung that shows it on each scheme, and every dot carries an ink hairline —
the edge that draws white on white, black on black, and the yellow that no
rung can both darken and keep yellow. White, grey and black join as stops;
grey still clears the accent, and none of the three washes the page, since
an absolute palette under the other scheme's ink is a page with its text
turned off.

Dark is the default a reader who has named neither scheme gets: the meta
hands the UA dark rather than the machine's own answer, and the setting
that meant "ask the machine" is dropped, since the pre-paint script and the
app resolved it differently and the page turned light a frame after booting.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-07 23:35:59 -07:00
zeekayandHanzo Dev de1199a687 Read one menu column at a time, two by two when narrow
Hovering a column lights it and steps the others back, and below the
rail's width the four sections are peers rather than one tall column
with three stubs under it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-07 23:13:16 -07:00
zeekay ab754ba686 Every published package carries the license it is under
151 packages of Tamagui-derived code are on npm with no license text in
them. Checked four tarballs at 8.3.5 — @hanzogui/core, @hanzogui/web,
@hanzogui/shell and @hanzo/gui — and not one contains a LICENSE file.

MIT asks for one thing: "The above copyright notice and this permission
notice shall be included in all copies or substantial portions of the
Software." Shipping the code without it does not meet that.

The cause is mechanical rather than deliberate. npm includes a LICENSE
that sits in the package directory, and ours sits at the monorepo root,
where it reaches no tarball. So the license is now in each of the 153
publishable packages, and NOTICE beside it — that one npm will not take on
its own, so 152 manifests name it in `files`.

Verified by dry-run pack: 1.3kB LICENSE, 2.8kB NOTICE.

Already-published tarballs are immutable, so this corrects on the next
publish and not before.
2026-09-07 23:06:05 -07:00
zeekayandHanzo Dev 61e3c561a7 Restore the Tamagui attribution this repository dropped in August
This repository is Tamagui, continued. Its git history runs unbroken through
tamagui/tamagui mainline, and every package in the workspace except ten is
upstream's work carried under the MIT license the LICENSE file already
preserves.

A NOTICE saying so was added in July (61758e3cbc, 51bc03d148) and removed in
August by 2bb4f45f1f, which also rewrote LICENSE and stripped the README of
any statement of descent. Nothing about the facts changed in between, so the
attribution comes back, deliberately and in the house form: NOTICE carries the
whole of it, exact rather than approximate, and names the ten packages written
at Hanzo; the README carries one line of lineage pointing at it.

Five packages published under the @hanzogui scope still listed Nate Wienert as
"author", which claims he wrote a Hanzo package rather than crediting him for
Tamagui. That is worse than no credit. Their author is now Hanzo AI and the
derived-work credit lives in NOTICE, where it can say what it actually means.

LICENSE is untouched.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-07 23:02:05 -07:00
zeekayandHanzo Dev 954d41e603 Retire Cloud and Team as products of their own
The platform is what they were, so the menus name it once and the column
under it lists what the platform actually offers.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-07 22:57:40 -07:00
zeekayandHanzo Dev e233a1e78f Let the Platform column name what the platform offers
Dev and Platform are the door, not entries beside the things behind it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-07 22:47:53 -07:00
zeekayandHanzo Dev 6b0765cbda @hanzogui/shell 8.3.14
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-07 22:44:01 -07:00
zeekayandHanzo Dev 89d2f40176 Let the platform stand for what it runs
Cloud and Team are what the platform does rather than products beside
it, so one name carries all three and one door opens the console.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-07 22:41:40 -07:00
zeekayandHanzo Dev 3e6e277b08 Make every tint stop read on both grounds
Three of the seven stops were yellow, which over the dark ground is an
olive, and the swatches sat in a Group, whose zeroed radii cut a straight
edge through each selection ring. The ramp now sweeps one arc of the
wheel and every stop is measured: the dimmest clears 3:1 against each of
the seven grounds the page can paint, on both schemes. Picking one writes
the accent through @hanzo/appearance, so the choice outlives the visit.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-07 19:48:14 -07:00
zeekayandHanzo Dev c9c37c0ca8 Read the API reference from the shipped types
The framework was documented in hand-written MDX, which drifts the moment a
signature changes. The declarations each package emits already say what it
exports; a page that quotes them says the same thing and cannot say anything
else.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-07 18:57:28 -07:00
zooqueen fe3e8a3417 shell: the wordmark sits inside the gutter, not on it
The brand pill pads by 12 and handed all 12 back, which put the NAME itself
exactly on the page's column. That is right for body text and wrong for a
wordmark: set in bold beside the window's edge it reads as flush, and the
gutter looks like it was forgotten.

Four of the twelve are kept. The lit pill still grows outward into the
gutter, and the name now sits a hair inside the column instead of on it.
2026-09-07 10:05:39 -07:00
zeekay 2de0604a52 v8.3.5 2026-09-07 02:06:21 -07:00
zeekay 2510c51f33 Let the shell tests read the bar and the estate as they are
The bar and its drape spread the glass recipe now, and the public estate
names eight doors; both suites still asserted the earlier shape.
2026-09-07 01:49:59 -07:00
zeekay 3eed379d13 Format the shell registry and settle the lockfile 2026-09-07 01:42:06 -07:00
zooqueen e5ff6f0228 shell: the bar and its plane are one glass — 8.3.13
An open menu is a layer over the page: the plane wears GLASS, the grounded bar
wears the same, so from the top of the screen to the bottom of the menu there is
one ground, one blur and no edge.
2026-09-06 21:32:07 -07:00
zooqueen 5df897d849 shell: eight doors, named as the products are — 8.3.12
App, Bot, CLI, Cloud, Dev, IDE, Platform, Team, each with the line its product
page says under the name. Chat and Base stay products and leave the estate
menu. Every console address is platform.hanzo.ai.
2026-09-06 21:20:56 -07:00
zooqueen 9f5ea05ef6 shell: the console surface lives at platform.hanzo.ai — 8.3.11
The surface record names the host a reader is sent to, and that host is
platform.hanzo.ai. 8.3.11 carries the built dist beside the source.
2026-09-06 21:14:41 -07:00
zooqueen ede5a99ad0 shell: the console is platform.hanzo.ai
Every door that opened console.hanzo.ai opens platform.hanzo.ai — the same
console, simplified for the people who use it — and the menu row wears the
product's name, Hanzo Platform.
2026-09-06 21:13:32 -07:00
zooqueen 8698edcdf5 shell: the estate menu names the seven products, in order
App, Bot, CLI, Dev, IDE, Platform, Team — then Cloud and Chat. CLI and Platform
become flagship products (hanzo.ai/cli and platform.hanzo.ai both serve), and
Team is released rather than beta, so a signed-out reader meets all seven.

The order is stated once, where the menu derives it, so the product table stays
a table of what exists and a new product sorts to the end until it is ranked.
2026-09-06 21:12:09 -07:00
zooqueen 72fa7aed30 The footer mark closes the row, and the brand says it opens
The bottom bar reads left to right as the page does: whose it is, what
governs it, then the mark closing the column.

The brand wears the chevron every other trigger in the bar wears. It was
the one control that opens a menu and did not say so, so a reader looking
for the products read past it.
2026-09-06 21:10:44 -07:00
zeekay f37a62f7ce Set gui.hanzo.ai on the Hanzo design system
Every Hanzo surface reads @hanzo/design, @hanzo/tokens,
@hanzo/appearance and @hanzo/font; this site set its own type, so its
header, ramp and ground drifted from hanzo.ai and a reader's appearance
settings reached nothing here. Gui's $n ladder now resolves through the
design ramp, dark is the default, the chrome stays grey while the
landing sections keep their tints, and everything below the fold loads
as it nears the viewport. getFontSize reads the px fallback of a var()
size, which is what keeps icon sizing numeric once sizes live on a ramp.
2026-09-06 19:35:02 -07:00
zeekay b424fd5871 v8.3.4 2026-09-06 18:54:23 -07:00
zeekay 513b36b7f6 Set gui.hanzo.ai in monochrome
The default tint ramp is gray at every stop, the header carries the
wordmark alone at its left edge, and the mark closes the page.
2026-09-06 18:13:49 -07:00
zeekay c4074a9dd0 v8.3.3 2026-09-06 13:48:41 -07:00
zeekay edc88c0670 Track the runner fixture's ambient declarations 2026-09-06 13:20:22 -07:00
zeekay c183da5389 Drop webpack from the stack
The loader and the Next plugin only existed to drive webpack; Next runs
on Turbopack and Vite has its own plugin. kitchen-sink and the
integration app serve through vite, the optimizer suite drops the
webpack name, and its fixture packages join the workspace so a fresh
checkout links @my/ui.
2026-09-06 08:17:57 -07:00
zeekay 4423eacee9 A provider remount is not a new page
setTelemetry(undefined) deleted @hanzo/event's page-client registry — the map
that makes "one stream per page" true. It reads as test hygiene, and that is
where it came from, but setTelemetry(undefined) is also what TelemetryProvider
calls from its cleanup effect: every unmount, every consent change, every prop
change that rebuilds the client.

So the guarantee this package exists to hold was being dropped by this package.
The next mount gets a client that has never seen the page and counts the view
again — one page load, two pageviews on the wire, with nothing on either row to
say they came from different clients. owner.test.tsx is entirely about not
emitting twice and could not see it, because the delete happened inside the very
call the suite used to reset between tests.

The reset belongs to the page, so it belongs to the package that owns the
registry: @hanzo/event 0.3.40 names it resetClients(), and the suites call it in
beforeEach, where "each `it` is its own page load" is true and says so.

Pins the double as a test: mount, unmount, mount, expect one pageview.
2026-09-05 15:17:28 -07:00
zeekay 84a6a7fad7 Take the publishable key from the domain when nobody supplies one
PUBLISHABLE_KEY is a BUILD-time variable, and a static export is built once and
served on every brand — so a build-time key is either absent or wrong. Absent
is what the fleet actually has: hanzo.ai's is commented out in .env.example and
both Zoo sites shipped with none, which means every one of those surfaces
mounts a client that collects and sends nothing. An analytics client that
reports nowhere looks exactly like one that reports fine.

A brand's domain already names its org, and an org has exactly one publishable
key. @hanzo/event has derived it from the hostname since org.ts was written,
for precisely this case, and nothing ever called it. Now the key falls back to
`keyForPage()` after config and env, so a page served from a domain we
recognise reports correctly with nothing configured, and one served from a host
no brand claims still reports NOWHERE — filing one brand's visitors in
another's project is the defect the derivation exists to prevent.

Bound once, not twice. `describeTelemetry` restated the same expression to
answer `hasIngestKey`, so the moment this line gained a third source the two
disagreed about the same client: it had a key and its own description said it
did not. That is what the new test caught first.
2026-09-05 15:00:49 -07:00
zeekay 5a07cee3b1 v8.3.2 2026-09-05 03:11:37 -07:00
zeekayandHanzo Dev dab83dc050 Run the optimizer's native pass under Node again
Node's require never selects a package's react-native condition and
cannot load an ES module whose sources say a bare require, so the
extractor resolves that entry itself and translates such a file to
CommonJS as it loads. Directories are walked with the filesystem's
glob, negative numbers become AST through valueToNode, the swallowed
load error is named under DEBUG, and the optimize fixture is its
source again with the suite restoring it after each run.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-05 02:00:02 -07:00
zeekayandHanzo Dev af24fc040a Emit the native build as CommonJS
Native sources say a bare require, which Metro provides and an ES
module cannot; marking dist/native CommonJS like dist/cjs lets Node
load it as written, so the optimizer's native pass runs again. The
.cjs sibling answers there too, the CLI imports its commands, the
loader names its css file by the emitted name, and the extractor
tolerates a dependency whose tsconfig extends a preset it lacks.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-05 00:14:46 -07:00
zeekayandHanzo Dev 97529f7dab Alias react-native-web-lite to the .js the emit writes
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 23:42:41 -07:00
zeekayandHanzo Dev 37fc897be5 Hand the extractor's require by its local name
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 23:32:21 -07:00
zeekayandHanzo Dev 839db7eb7b Name a vendored .jsx module by the .js the emit writes
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 23:24:05 -07:00
zeekayandHanzo Dev 9366f98ade Read the shell's own imports as written in its source tests
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 23:13:19 -07:00
zeekayandHanzo Dev 44a5d08e34 Name every relative import and drop the root-level shims
A relative import without its extension survives the emit unchanged
and nothing but a bundler can load it, and the .cjs and directory
shims at package roots pointed at emits no build produces. The exports
map is the one entry; the worker each emit runs is the worker of its
own kind.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 22:46:29 -07:00