5139 Commits
Author SHA1 Message Date
zeekay af68fa15fb v8.4.2 2026-10-22 18:56:01 -07:00
zeekay d9ee6a066b v8.4.1
Claude-Session: https://claude.ai/code/session_01BLLMyBFSvAfCt7ZGqYuiEz
2026-10-22 15:45:13 -07:00
zeekay af461eb5e4 v8.4.0
Claude-Session: https://claude.ai/code/session_01BLLMyBFSvAfCt7ZGqYuiEz
2026-10-22 13:02:19 -07:00
zeekay db64dea69d Stacks lay their children on tracks; Wrap is where wrapping lives
`tracks` is a CSS track list along a stack's own axis, so the parent
sizes its children and none of them carries a flex prop:

  <YStack tracks="auto 1fr auto">   header, body that fills, footer
  <XStack tracks="240px 1fr">       a fixed rail and the page

On web the stack is a grid: a YStack's tracks are its rows (row flow,
one column as wide as the stack), an XStack's its columns. A prop the
author names wins over all of it. Alignment keeps its flex meaning: on
a vertical stack grid's axes swap, so propMapper renames every key it
emits (items -> justifyItems, justify -> alignContent, content ->
justifyContent, and a direct child's self -> justifySelf), which holds
in media, pseudo and variant styles alike. A child learns its parent's
axis from ComponentContext and clears it for its own children.

On native the stack stays flex and each child takes its track as flex
longhands: Nfr is flex N, a length is a fixed basis, anything else is
content-sized.

The compiler leaves a tracks stack and its direct children to the
runtime, which is where both are decided.

Wrap is a row that wraps, the one place content-sized wrapping lives.

All opt in: a stack without tracks renders as it did. Tested in
components-test (web and native), static-tests (the deopt) and a
Playwright run of the integration app that measures the boxes in dev
and in the compiled production build. TracksDemo is in the demos app
and on the stacks docs page.

Claude-Session: https://claude.ai/code/session_01BLLMyBFSvAfCt7ZGqYuiEz
2026-10-22 11:36:30 -07:00
zeekayandClaude Opus 5.5 a5ce56a558 Every home section clips its backdrop to the page
The responsive band runs 1000px past its container, so once that
section mounted the document was 1390px wide under a 390px screen again.
Clipping only the hero left every other backdrop free to do the same.
The clip moves into TintSection: each section is full-bleed, so its
edge is the page edge, and nothing on screen changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-24 10:35:50 -07:00
zeekayandClaude Opus 5.5 d999e0c2a0 Clip the home page's lamps and grid to the page
The hero's three lamps and its grid reach past the viewport on purpose,
and only the clip on html and body kept them out of sight. The document
itself was 960px wide under a 390px screen and 1790px under 1440.

The glow group now spans the page and clips its own lamps, and the hero
section clips its grid at the page edge. Nothing on screen moves; the
layout is as wide as the screen.

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 56d01f0e58 Read the reading weight from the font that draws it
497 is a point on Zen's variable axis, published by @hanzo/font as the
book preset alongside the faces it describes. It was reached through a
package that only restated it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 23:53:11 -07:00
zeekayandHanzo Dev e75d231b0c Wait for the page before timing the tests
Playwright was told a port, so it started the suite as soon as the socket
opened and turbopack compiled the route inside the first test. A test's
time also covers its hooks, so that first test paid for launching chromium
on top of the two dev-server navigations every test makes.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 13:03:39 -07:00
zeekayandHanzo Dev d504ef37b2 Hold the page to one gutter at every width
The responsive section measured itself against a second container. Its
breakpoint ruler rode Container — the docs page's measure and gutter —
nested in the ContainerLarge every other section stands on, so a marker
labelled 500 stood 522px from the mock it measures, and below sm a
hand-fitted translate put the mock 25px left of its own heading, off the
page. Both take the section's own box now.

The preview strip scrolled to an arithmetic copy of its padding and gap
that had drifted from them by one gap per stop, so the ninth colour sat a
whole card off centre. It asks the layout instead.

A stop reaches the swatch, the demo, the ink and the stored accent, never
the ground, and a lamp carried past the hero is the ground by another
route — the wash still under the player row. The glow stays at the hero.

Two footer columns had no basis to size from once stacked and drew over
the next, nine swatches could not fit 390 in one row, and a title glued
to its last word was split mid-word instead.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 03:53:08 -07:00
zeekayandHanzo Dev 9361713291 Show one preview per colour, not two
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 00:28:53 -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
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 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 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
zeekay 2de0604a52 v8.3.5 2026-09-07 02:06:21 -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 02c920b06d Leave the optimizer fixtures unbuilt 2026-09-06 08:21:47 -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 5a07cee3b1 v8.3.2 2026-09-05 03:11:37 -07:00
zeekayandHanzo Dev 6390349a3a Let the docs app's tests resolve react-native to the web build
The grid test asserts the element's own style, which is what the rule
sets; the computed value is jsdom's initial one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-05 02:24:03 -07:00
zeekayandHanzo Dev bad164a516 Give the turbopack suite time to spawn the CLI
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-05 02:08:57 -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 2e287908bf Expect the two errors the rebuilt declarations raise again
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 22:51:25 -07:00
zeekayandHanzo Dev bfe539744a Type the tab wrappers and variant spreads as the packages declare them
React passes ref as a prop, and a forwardRef over props with an index
signature drops every named prop through Omit; the font extras are
optional in a spread function's config.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 21:34:25 -07:00
zeekayandHanzo Dev 278ded5f22 Refuse a CommonJS emit that declares what the wrapper provides
A top-level `const require` is legal in a script and a syntax error
inside Node's CommonJS wrapper, which is where the emit actually runs,
so the local createRequire alias is now `need` and the runner names
any file that declares require, module, exports, __filename or
__dirname.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 21:24:26 -07:00
zeekayandHanzo Dev 42bd70986b Ship source, ESM, CommonJS and types from each package
Every published manifest now states the same exports: types, the
native emit where the package builds one, ESM under import and
CommonJS under require, with src, types and dist in files. The nine
packages that read import.meta do so in one here.ts each, with a
here.cjs.ts sibling for the CommonJS emit; the stragglers on bun,
esbuild, plain tsc or hand-written shims build with the runner like
everything else, and the root-level .cjs shims that pointed at emits
no build produces are gone, as is the vite-plugin-cjs mirror.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 21:07:06 -07:00
zeekay 834a904e48 Format with oxfmt 2026-09-04 09:04:51 -07:00
zeekay e4b4e50911 Name the siblings a build reads
turbo orders a build by declared dependencies, and a forced rebuild found
three packages typechecking against siblings they never named, one of them
through a subpath the types package no longer exports.
2026-09-04 09:04:41 -07:00
zeekayandHanzo Dev d5e94b61d7 Rename the host-bind serve script off a named platform
serve:railway just binds 0.0.0.0 (any container host, not a Railway
particular); call it serve:host and update the CI wrapper's three
call sites to match. The health-check comment drops the same
name-drop. RAILWAY_DEPLOYMENT_ID in setup.server.ts is Railway's own
env var and stays — it's the actual boot-time production guard.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-04 02:11:54 -07:00
zeekay 0129f23dc0 Remove the store from the docs site
Supabase auth, Stripe, purchase modals, Bento and Takeout promotion, the
consulting link and the vendor's talk images were the upstream business,
not documentation. The header, home page, theme page and quick nav lose
the account and promotion surfaces; the content loses the paid gating.
2026-09-03 18:02:51 -07:00
zeekay 894b27e674 Build the docs site from the tsgo output
The browser bundle answered process.env.GUI_TARGET with an empty object,
so every web check failed after hydration and the page lost its classes;
the compiler plugin used to define it. reanimated leaves the manifest,
which is what routed the whole build through a Babel pass that Babel 8
cannot load, and one's inline sourcemap: false yields to a config hook.
2026-09-03 18:02:50 -07:00
zeekay fdb08b51a7 Type-check the packages the old build skipped
esbuild emitted without looking, so a driver could return an animated
style the hook's type did not admit, read a styleState the core passes
but never declared, and widen a snapshot field on the way through a
helper. The Ink screen asked for props Ink never had, and the solito
test was already commented out of its index.
2026-09-03 18:02:50 -07:00
zeekay 5bbac5e492 Augment @hanzo/gui, the module that exists
Every config augmented a module named hanzogui, which no package
provides, so TypeScript declared a fresh ambient module and the
shorthands never reached a component's props: 187 errors in demos alone.
2026-09-03 18:02:50 -07:00
zeekay de489abd1e Resolve every sibling package through node_modules
The project files inherited the root paths map, so a package's typecheck
read its siblings' source instead of their published declarations and
failed on files that were never its own. An ESM-only manifest now names
no dist/cjs path under any condition, the native emit names its renamed
map, and the runner has a test of its own again.
2026-09-03 18:02:50 -07:00
zeekay 6ad5a7f8fd Restore what the strip took from the docs site
The store is wired into the header and the home page, so deleting the files
that import it removed the site. Those files are back; a HeadAnchor variant is
named menu, because gui now has a grid style prop that shadows a variant of
that name; and the community page, which imports a component this repository
never held, is gone. The store's own files remain until their importers stop
importing them.
2026-09-03 17:04:45 -07:00
zeekay 4afa47a701 Strip the upstream store and its auth from the docs site
Stripe, Supabase, Bento, Takeout, seats, licenses and coupons came with the
fork of the site and sold nothing here. Identity is Hanzo IAM and there is no
store, so the forty-four routes, the purchase flow and the theme-history
persistence go, and what remains is a docs site.
2026-09-03 16:48:05 -07:00
zeekay 773ba06393 Apply the token once
The credential rode along as the last argument of every operation —
fetchRooms(token), fetchMessages(id, space, token), sendMessage(id, space,
text, token) — so who you are and what you are doing were stitched
together at every call site, and two modules each built their own client
against the same address. Applying the token once returns the operations
already bound to it: team(token).rooms(), .messages(id, space), .say(id,
space, text).

Transport and reading were the other pair. Billing mixed calling the route
with deciding what a balance looks like, which it has to do because
/v1/billing/balance states no response schema — so the reading moves to
money.ts, where it is a function from an untyped record to a Wallet and
can be read without the request around it.

Eleven lines shorter, and each file answers one question.
2026-09-02 15:07:43 -07:00
zeekay 698b2ff854 every Hanzo dependency is the version npm serves
Ranges drift once nothing moves them: a caret that has not been rewritten
since it was written pins whatever was current that day, and an exact pin
cannot even take a patch. Both are updated here.

One line per package, so a surface reads the same components, the same
tokens and the same face as every other one.
2026-09-01 16:22:28 -07:00
zeekay e9df65ab10 the face is Zen, and it ships from @hanzo/font
One family. Font stacks name Zen and fall back to the native faces, tokens
name the role rather than a face, and the packages that used to supply a
face now point at @hanzo/font.

Self-hosted @font-face blocks are removed rather than renamed: rewriting the
family in one would leave a src pointing at a file this repo no longer ships.
@hanzo/font carries the binaries and declares the faces, so the local copies
go with the declaration that used them.

Comments naming a face we no longer use are deleted, not reworded.
2026-09-01 14:05:29 -07:00
zeekay 2c7650a617 Say where the app talks to in one place
The API base, the issuer and the client id were three literals in three
files, so pointing the app at a cloud on localhost meant editing all of
them — which is how one of them stays edited. config.ts states each once,
defaulting to production, and vite replaces them at build time.

A `define` rather than envPrefix and import.meta.env: this app builds for
native too, where that form is not reliable — the build already warns
about it. A define is textual, so it lands the same way in both, and an
unset variable leaves the production default standing.
2026-09-01 09:02:40 -07:00
zeekay 5cf54adc6b Read the team surface through the generated client
The app stated its own base URL, its own routes and its own Authorization
header, which is a second copy of a contract the SDK already publishes —
and the copy is the one that goes stale. hanzoai carries the routes, the
models and the bearer header, so team.ts is now the four calls the screens
make and nothing else.

Billing moves with it, off the per-product billing.hanzo.ai host and onto
the one endpoint. Its balance reduction stays: that operation states its
route without a response schema, so the shape really is the caller's
problem and pretending otherwise would need a cast.

Every generated field is optional because the document marks none of them
required, so a room missing either half of its address is skipped rather
than linked to a URL that cannot resolve.

The OIDC fallbacks named three paths that answer 404, two of them under an
/api/ prefix IAM has not served in a long time. They are only read when
discovery is down — the one moment nothing else can cover for them.
2026-09-01 01:38:08 -07:00
zeekay b24e4cb023 Give the Team app its rooms
hanzo.team was served by a Mattermost fork whose apex has been 502ing, and
the surface replacing it had login and a wallet but nothing to talk in —
so dropping the server would have left the product with no conversation at
all. The cloud team API has answered /rooms and /rooms/:id/messages the
whole time; this reads them.

vite.config named guiPlugin, which the plugin has never exported. Every
other app in the repo says hanzoguiPlugin. That typecheck error is why
this app built nowhere, and it is why hanzo.yml can now declare the site.
2026-09-01 01:22:26 -07:00
zeekay c19d6842ef Give every package one version
Two thousand and twenty-one packages carried more than one spec across this
estate — eslint alone in a hundred and four forms — and a package resolved twice
is two copies at run time, which is how react-hook-form came to report "two
different types with this name" from files nobody had edited. None do now.

The number lives in the catalog: a manifest names the package, the workspace
names the version. Where a manifest cannot reach a catalog it still carries the
one version, because the table is the source either way.
2026-08-29 18:13:47 -07:00