3881 Commits
Author SHA1 Message Date
zeekay cd39baedd0 feat(zen-models): add creative open foundations and proprietary research models 2026-10-16 19:55:14 -07:00
zeekay d4e0db1673 feat(analytics): docs send through @hanzo/event and the tag manager; the GA4, Pixel, Insights and analytics scripts leave every layout; the one consent banner 2026-09-30 07:15:43 -07:00
zeekay f1d620130c Make the docs CTA "Try Hanzo", to hanzo.ai/login
Every conversion CTA reads "Try Hanzo" and goes to sign-in; payment comes
after it. The landing's close now does that in place of "Choose plan".

It no longer fires plan_clicked: it is not a plan choice, and the page view
and the $click that <Hanzo> autocaptures already record it. That leaves the
EVENTS re-export in @hanzo/docs-analytics with no caller, so it goes too.
2026-09-30 02:38:21 -07:00
zeekay a38d96c672 Load no vendor tag on docs.hanzo.ai
GA4 and the Meta Pixel went into the root layout's <head> and into
@hanzo/docs-analytics, loaded on every page before any consent was asked,
beside a second and third Hanzo stream with their own keys. A vendor tag
loads only through the @hanzo/event tag manager, after consent, so both go,
and the docs report on the one @hanzo/event stream again.

This reverts 81733ecc4a and 6f0e2b2533.
2026-09-30 02:38:17 -07:00
zooqueenandHanzo Dev 81733ecc4a feat(analytics): add GA4 (G-15XZEQ6G9S), Meta Pixel, Hanzo Insights, and interaction tracking
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-30 00:39:12 -07:00
zeekay bb38ff54ad Send docs visitors to hanzo.ai, not hanzo.id, and count the plan click
The landing's "Sign up free" opened hanzo.id/signup, and "Sign in" opened
/login, whose authorize step sent the reader to hanzo.id too. No surface
sends a visitor there. Sign-in is hanzo.ai/login now (the /login address is
a redirect rule), sign-out clears the session in this browser instead of
carrying the reader to the issuer's end-session page, and the conversion
CTA reads "Choose plan" and opens hanzo.ai/pricing.

The click fires plan_clicked {source: 'docs'} on the client that counts the
page views, named from the @hanzo/events catalog that @hanzo/docs-analytics
now re-exports.

The ingest key comes from @hanzo/event's keyring, resolved from the page's
host, rather than a KMS value inlined at build. docs.hanzo.ai resolves the
hanzo org's key, the one the rest of the funnel reports under, and a sibling
docs site on a host no brand claims reports nothing rather than filing its
visitors under hanzo. The deploy resolves the same key, asks /v1/event
whether it names a project, and greps the export for it.
2026-09-29 21:44:35 -07:00
zeekay ad888ecc50 Serve search and chat at /v1, gate any /api path
Claude-Session: https://claude.ai/code/session_011i2AG9nwpiKUCVxAdDzYuU
2026-09-29 19:40:17 -07:00
zeekay 1186dc6ffa Build the docs without Tailwind, Radix or base-ui
Nothing apps/docs renders names a utility class, a Radix primitive or a
base-ui part any more, so the build stops carrying them.  global.css is
plain CSS: the faces, the ground, the color-scheme the code tokens read
through light-dark(), and the shell footer.  postcss.config.mjs goes
with the Tailwind plugin, and the app drops tailwindcss and its postcss
plugin and animate plugin, tailwind-merge, class-variance-authority,
every @radix-ui package, @hanzo/docs-base-ui, @hanzo/docs-ui and
lucide-react.

The MDX pipeline drops twoslash, the auto type table and ts2js, which
existed to emit base-ui and Radix parts and which no page uses, and the
graph view's d3 and force-graph go with the graph.  base-ui's shiki
stylesheet no longer answers .t_dark; the docs were its only reason to.

The framework packages stay: each still has consumers among the other
apps and examples.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-27 15:33:28 -07:00
zeekay 0d7c74917d Draw the app's own pages with @hanzo/gui
What was left of Tailwind and base-ui in apps/docs was the pages around
the docs: the front page, the hero and product sections on /docs, the
catalogs, search, feedback, the 404, the blog and the sign-in screens.
Each is a gui part on the theme now, and the app source carries no
utility class, no Radix and no base-ui import.

The front page follows the reader's theme instead of forcing a dark
subtree, and its API Reference card pointed at /reference, which is not
a route; it goes to /docs/openapi.  Search is one gui dialog opened
from the rail or with Cmd-K, over the same static index, with the jump
and the ask-an-agent rows it had.  The feedback control sends a
docs.feedback event on the one telemetry stream; it used to hand the
reader's answer to a stub that logged it to their own console.

The blog sits in the docs shell on the same page frame, and a post no
longer prints its title twice.  The registry build, the fork's preview
components, the unused AI-search dialogs, the API page and the tree's
icon plugin go: nothing rendered them, and the icons were an SVG tree
serialised into every page.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-27 15:23:16 -07:00
zeekay f11eb49dc2 Draw the docs chrome with @hanzo/gui
The rail, the tree, the bar, the outline and the page header were
Fumadocs' base-ui layout: Tailwind utilities over a CSS grid, with the
width decisions made twice, once in classes and once in a JS media
query.  They are gui stacks on theme tokens now, with every breakpoint a
media prop, so a phone hydrates the markup the export wrote.  Atomic
rules ship as hoisted <style> tags and <html> carries t_dark, so a page
is styled before any script runs.

The theme has one authority, the t_dark/t_light class that gui's
NextThemeProvider writes before paint; <Hanzo> follows it and the
Tailwind dark variant answers it.  The corner dock that covered content
on phones is gone; theme and help sit at the foot of the rail.

A body's leading "# heading" is dropped at compile time, since the
header already prints the frontmatter title: /docs/sdks and
/docs/openapi said their names twice.  The /docs hero stops printing a
second title.

The rail narrows by tree path, so an operation page opens the reference
it belongs to, and the drawer carries the top nav a phone had no way to
reach.

LLM.md carries the four-phase plan and the inventory it is measured by.

Claude-Session: https://claude.ai/code/session_01HuMDfgFssPUptYUzgZ6Cp8
2026-09-27 14:15:45 -07:00
zeekayandHanzo Dev 57d30248ce Format the breadcrumb search and the browser suite
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-25 23:58:53 -07:00
zeekayandHanzo Dev 7134deb49b A page ships the page tree without its fallback, and an operation page breadcrumbs as its product
The fallback listed the 2,400 operation pages no meta lists, and every page carried it five times over: 16 GB of the 19 GB export. The browser used it only for an operation page's breadcrumb, which now comes from the product page above it.


Co-authored-by: Hanzo Dev <[email protected]>
2026-09-25 23:58:52 -07:00
zeekayandHanzo Dev 2f45e64d6c On this page sits at the top of the right rail
The rail is a grid with a definite height and auto rows, and a grid stretches
auto rows to fill its height, so a short table of contents was spread down the
whole rail: "Use Agent" alone at the top, "On this page" a third of the way
down, its entries two thirds down. content-start packs the rows at the top.


Co-authored-by: Hanzo Dev <[email protected]>
2026-09-25 23:58:51 -07:00
zeekayandHanzo Dev 5a779c7088 Pricing reads come from api.hanzo.ai
cloud computes the catalog in Go and serves it at /v1/pricing, so the
docs apps and the zen-models package read the endpoint directly rather
than a host that forwards to it. The Base page links to the pricing page
a reader can actually open.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-16 23:53:10 -07:00
zooqueenandHanzo Dev e0e2e2d4ae Record the versions tsdown actually bundled
These lines are written by the build, and the catalog has moved since they were
last written. Committing the build's own answer keeps the record true instead of
showing a version that is no longer inside the tarball.


Co-authored-by: Hanzo Dev <[email protected]>
2026-09-15 16:30:33 -07:00
zooqueenandHanzo Dev ac095d444a A package that ships tsx needs the types its tsx names
@hanzo/docs-gui publishes src/*.tsx and declared no react types, so @hanzo/docs
— which re-exports it, and whose build runs tsc over what it re-exports —
stopped at "Could not find a declaration file for module 'react'". A consumer
compiling our source compiles our imports; the types belong here.


Co-authored-by: Hanzo Dev <[email protected]>
2026-09-15 16:30:33 -07:00
zooqueenandHanzo Dev 75e59da996 Name content-type, which negotiator 1.1.0 brought with it
The catalog moved negotiator to 1.1.0, which parses media types through
content-type. tsdown bundles negotiator into this package and refuses to bundle
a dependency nobody declared, so every build of @hanzo/docs-core has failed
since — and with it every build of the docs site, which is why docs.hanzo.ai has
served the same export since the 4th.


Co-authored-by: Hanzo Dev <[email protected]>
2026-09-15 16:30:32 -07:00
zeekayandHanzo Dev cddb1b418e Finish the sentence case sweep
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-09 00:01:55 -07:00
zeekayandHanzo Dev 85c835b7cd Merge github/main
Keeps the thirty-two files migrate/nemo: Data Designer maps to gym synth
deleted. That commit says it edits one guide and removes LICENSE,
Dockerfile, Makefile, turbo.json, renovate.json, the k8s manifests, the
changesets and every screenshot — 39,655 lines under a subject about
naming `gym synth`. Without turbo.json the monorepo has no build.

The lockfile stays too. hanzo.yml gates on `pnpm install
--frozen-lockfile`, which needs one, and the root `version` script ends
by writing one.

waku moves off `>=1`, which matches nothing published — 1.0.0-rc.0 is a
prerelease — back to the `^0.26.0 || ^0.27.0` the remote carries.

The create-app templates keep this side's Zen wiring, which is the later
of the two, and gui-docs loses the last file each side left behind.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 16:32:20 -07:00
zeekayandHanzo Dev be5c7e8e15 State a version once
A version written into a manifest is written into every manifest that wants it,
which is how eslint came to exist in a hundred and four forms across this estate
and how a package came to resolve twice at run time. The catalog moves the number
out of the manifest: a manifest names the package, the workspace names the
version.

Four are held below their latest because the newer release cannot be used, and
the reason is recorded rather than the version alone: @noble/hashes 2 renamed
every subpath the web3 stack imports, @splinetool/runtime 2 depends on a package
that was never published, @dynamic-labs 5.4.1 the same, and react-resizable-panels
3 points its types condition at the bundle.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 16:21:17 -07:00
zeekayandHanzo Dev 16e3157e98 One version per package, on the current release
Every dependency on a @hanzo/@luxfi/@zooai package now names the same version
across the repo: a catalog entry where pnpm supports one, an exact pin where it
does not, and a range only where a peer genuinely means one.

pnpm itself is pinned too — the estate ran eighteen different versions, five of
them too old to read a catalog.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 16:21:03 -07:00
e0734077e2 Scaffolded apps are born on Zen too
packages/create-app/.gitignore lists `template`, so every scanner that honours
ignore files walks straight past nine templates that are tracked in git and
shipped by @hanzo/create-docs. Four Next templates declared a face in the root
layout and two react-router ones linked a hosted font CDN from `links()`, which
means a freshly scaffolded docs site started life on someone else's typeface
and a third-party request on every route.

Same shape as the apps: `@import '@hanzo/font/css'` at the top of the template's
own sheet, one :root block naming Zen and Zen Mono as --font-sans / --font-mono,
and @hanzo/font in the template's dependencies. The waku template named no
family at all and fell through to the browser default, so it gets the same
mount rather than being left the one scaffold without a face.

(cherry picked from commit 734765bcf6fa78f395bd368e137f122367f6e02d)

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-03 23:51:23 -07:00
zeekay 59d618aa38 feat(docs): update @hanzo/docs to @hanzo/gui and remove shadcn/tailwind/radix/base
- Introduce @hanzo/docs-gui using @hanzo/gui native components
- Remove dependencies on shadcn, tailwind, radix-ui, and base-ui
- Connect @hanzo/docs ui re-exports to @hanzo/docs-gui
- Provide pure, self-contained Tabs, Card, Callout, Steps, Accordion, CodeBlock, and TypeTable
2026-09-03 19:19:12 -07:00
zeekayandHanzo Dev 2a5e4a8e5a docs analytics: the event client the provider actually reads
@hanzo/docs-analytics hands the provider {product, host, ingestKey, enabled};
the lock resolved @hanzo/event to 0.3.1, whose provider reads only
config.transport — so every docs site shipped a client with no transport and
sent nothing, while the build's own check saw the key in the bundle and
passed. Measured on docs.hanzo.ai: the only beacons were Cloudflare's. 0.3.36
reads those fields and builds the dsn from them.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 20:22:35 -07:00
zeekayandHanzo Dev 56faa8e370 Merge remote-tracking branch 'forge/main'
# Conflicts:
#	apps/bot-docs/package.json
#	apps/dev-docs/package.json
#	apps/docs/package.json
#	apps/flow/package.json
#	apps/platform/package.json
#	apps/team/package.json
#	apps/visor/package.json
#	examples/base-ui/package.json
#	examples/content-collections/package.json
#	examples/marble-cms/package.json
#	examples/next-min/package.json
#	examples/next/package.json
#	examples/obsidian/package.json
#	examples/openapi/package.json
#	examples/proposals/package.json
#	examples/python/package.json
#	packages/create-app/template/+next+hanzo-docs-mdx/package.json
#	pnpm-lock.yaml

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-29 13:38:17 -07:00
zeekay f50ba007b5 Merge remote-tracking branch 'origin/main'
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-29 13:28:54 -07:00
zeekay 68782399f8 fix(type): a scaffolded docs site starts on Zen
The four next+hanzo-docs-mdx templates each built an Inter binding from
next/font/google and put it on <html>, so every site generated from them was
born fetching a face from a host we do not control and shipping it as the
estate's type.

They take Zen from @hanzo/font, which the template's package.json now declares
so the scaffold installs. The binding is the face — there is no local alias to
keep in agreement with what it holds.
2026-08-28 16:53:23 -07:00
zeekay 7218bcd39c fix(type): every app and every template a customer generates starts on Zen
Thirteen shipped apps and every scaffold in packages/create-app pulled their
face from Google at request time — Inter through next/font/google in the
examples and the four Next templates, Geist and Geist_Mono in the apps, and in
the seven react-router roots a bare <link> to fonts.googleapis.com. That last
one never even applied what it fetched: nothing in those trees set a family, so
the browser downloaded Inter, spent two preconnects on it, and rendered
system-ui. So `create-app` handed a customer a new docs site whose first act was
a third-party font request for a face that is not ours.

The apps read the family back through --font-sans and --font-mono, which are
next/font's handles rather than names of anything, so those follow the binding
over to --font-zen-sans and --font-zen-mono. Eight of them had only ever bound
the mono and were leaning on Tailwind's default for the text face; they now name
both, or swapping the binding would have dropped the body copy to system-ui
without a word.

Zen and ZenMono are built faces, not constructors — subsetting, display: swap
and fallback metrics are already in them — so the call and its options go away.
2026-08-28 11:30:07 -07:00
github-actions[bot] c4000470bc chore(zen-models): sync pricing from pricing.hanzo.ai
Automated daily pricing sync. Changes detected and changeset created.
2026-08-28 16:47:34 +00:00
zooqueen 9bd4887a6a Remove the docs auth module
689 lines under radix-ui/src/auth: an OIDC login/callback/logout handler
set, a session cookie, JWT middleware and a page-tree access filter. It
imports jose, which the package does not depend on, so it has never
compiled. tsdown has no entry matching it, dist has never contained it,
the exports map does not name it, and the only importer is its own test.

It is also the wrong shape and the wrong home: sessions, tokens and gates
belong to IAM, and a documentation component library is not where an auth
middleware lives. Nothing consumes it, so nothing breaks.

Recover with: git checkout 955dd5989 -- packages/radix-ui/src/auth
2026-08-27 17:05:18 -07:00
zooqueen be06951f9c A separator's class can be a function of its state
Base UI passes component state to className when the caller gives it a
function. SelectSeparator handed that argument straight to cn(), which
takes class values, so a function className would have been stringified
into the class list. Takes the state form tabs.tsx already uses.
2026-08-27 17:05:18 -07:00
zooqueen 71d9d14041 i18n names the types core owns
src/i18n.tsx asked contexts/i18n for Translations, which contexts/i18n
imports from src/i18n -- a cycle, and the name is declared right below
the import. Meanwhile TranslationValue, TranslationsAPI and
TranslationsAPIExtension moved to @hanzo/docs-core/i18n and were left
unimported, so both packages had been referencing a vocabulary they no
longer named. Nine type errors in radix-ui, six in base-ui.
2026-08-27 17:05:18 -07:00
zooqueen e158f03c4d The registry declares only what the compiler reads
Both registry files carried the same de-branding leftover: Registry
imported twice, once from @hanzo/docs-cli/build and once from
fuma-cli/compiler. The first is `export * from` the second, so it was the
same type under the same name.

They also set env and onResolve. Neither is on the Registry interface and
fuma-cli's runtime never reads either -- zero references in its dist --
so both were inert. onResolve called resolveExternal, which nothing
exports; had anything reached that line it would have thrown. The two
copies disagreed about the argument anyway: base-ui's named docs-ui,
radix-ui's named base-ui, its own alternative rather than itself.

Removing them takes five type errors off each package and changes no
generated output.
2026-08-27 17:05:18 -07:00
zooqueen edf3387993 Delete the home client no route reaches
layouts/home/client.tsx names seven modules that do not exist --
parseLayoutProps, renderTitleNav, NavigationMenuRoot, three shared
toggles and utils/renderer -- and accounts for fifteen of base-ui's
twenty-seven type errors. It is not in the exports map, tsdown has no
entry for it, layouts/home/index.tsx does not import it, and radix-ui has
no counterpart. Nothing has ever built it.
2026-08-27 17:05:17 -07:00
zooqueen 3adfc568a8 Drop the react import nothing calls
useEffectEvent was named in eleven files that never call it. Ten were in
radix-ui alone, against a base-ui copy that upstream keeps byte-identical
to it, so an unused name was the entire difference between the two; the
eleventh, toc/index.tsx, carried it in both. Restoring the ten from
base-ui makes each pair identical again.

The symbol is real and React 19.2 exports it -- it is used, twice, in
thirteen other files. These eleven just never reached for it.
2026-08-27 17:05:17 -07:00
zeekay f2a9cbea55 Merge remote-tracking branch 'origin/main' 2026-08-27 08:17:33 -07:00
zeekay 2c1ed50279 chore(docgen): 3.1.2 — readFile takes its encoding, codeblock states its default 2026-08-27 08:16:15 -07:00
zeekay af364b12d1 docs: the collapsed rail's corner is one control, not a mark beside a button
A collapsed rail drew two things in the top-left: the rail's own control showing
a sidebar glyph, and the layout title beside it showing the mark — two targets,
one of which was not a target. They are one now. The mark is what is there at
rest, because it is what a reader recognises at that size; the glyph replaces it
under the pointer, which is the moment the control has something to say about
what it does. Both layers sit on the same spot and cross-fade, so the button
never changes size and the corner never reflows.

`sidebar.mark` is the node, supplied by the caller. Reaching for the `navTitle`
slot instead is the obvious shortcut and is wrong twice: it is a link to the
site root, so an anchor lands inside a button and wins the click — the control
would navigate home rather than open the rail — and it answers "what fits the
space", where the space here is always the collapsed one.

Measured in a browser across the whole cycle: collapsed, one mark on screen at
x=16..52 and the glyph at opacity 0; hovered, they swap; clicked, the rail
expands and the URL does not move. The nav keeps its rhythm either way — 32px
after an open rail, 36px after the collapsed control.
2026-08-23 18:04:18 -07:00
zeekay 6e5ffde46f the front door and the docs chrome state their tracks, on @hanzo/ui 2026-08-23 11:31:52 -07:00
zeekay 660342d729 the docs shell names the row that absorbs the leftover, and the footer reflows on its column 2026-08-23 10:59:31 -07:00
zeekay 2d1fa7338b A nested tab row is a refinement, not a second widget
Picking SDK opened a bordered, filled box inside the bordered, filled box that
offered it — a frame in a frame, with the six languages reading as a separate
control bolted underneath the surface row.

One level down is the SAME choice refined: which language, having picked the
SDK. So it shares the parent's frame — no border, no fill, no margin — and its
triggers sit at the RIGHT of the row, smaller, where a secondary choice belongs.

Nesting is read from the context the parent already provides, so nothing has to
be passed and no caller changes. No negative offset to tuck the row into the
header: that would assume a header height, and the header grows whenever a
label wraps.
2026-08-22 18:23:25 -07:00
zeekay 6b658ef7ff The destination owns the rail, and a section is a thing you open
The sidebar was one list of the whole site: 35 always-visible entries, the
same on every page, so which surface you were in was invisible and every
destination scrolled through everyone else's pages.

Two changes to the one renderer every layout shares.

A separator now OWNS the siblings that follow it, so a section collapses as
a unit and only the section holding the page you are reading is open. The
tree is flat on disk either way — grouping at render is what keeps the rail
to a handful of rows without moving 35 files.

A folder at the top of the tree is a place the nav can send someone, so a
reader inside one sees that destination and nothing else, with a way back
out. Read off the tree's own shape rather than a list of destinations kept
beside it: a second list would disagree with the tree the first time
somebody added a folder.

Measured on the production build at 1280x880: /docs is 10 rows, /docs/openapi
11, neither scrolls; opening a domain takes it to 27 and it still does not.
Reading /docs/cli opens Reference and leaves the other six closed.

Leaf rows lose their icons — depth already says where a row sits, and a glyph
per row is what made the column unscannable. The chevron stays; it is the one
glyph saying something the text does not.

Nine sections become seven, "Get started" becomes the pages under it rather
than a name repeating its first entry, and the API key control gains a short
twin in the top bar for the two states where the rail is not on screen: a
collapsed rail, and mobile, where it is a drawer. Both read one hook, so they
cannot disagree about who is signed in.

Also: the surface-gap table leaves the generated pages, and contributing/style
writes down the house rules and the traps that have actually cost us answers.
2026-08-22 13:16:41 -07:00
zeekay 575d3d06e0 A heading is not a row, and the tree now says so without being read
Both were sentence-case text at one size, each carrying an icon, so the only
thing separating a section from a link was indentation you had to measure. The
headings are the half that changes: uppercase, tracked, smaller, muted, and no
icon — the label register every docs tree uses for a thing you cannot click.
Rows keep their icons, and the four that lacked one now have one, so "has an
icon" means "is a row" with no exceptions to remember.

Three names said the same word three times. `Integrate` held `Integrations`,
while `Integrations & Extensions` sat under Surfaces — all three wearing the
Puzzle glyph. They are not one thing: `apps` is where Hanzo INSTALLS (browser,
editor, desktop, design, business), so it is Extensions; `integrations` is what
POINTS AT Hanzo (agno, crewai, dspy, autogen), so it is Frameworks. The section
keeps its verb and takes the Plug, and each row now names what it is rather than
what its neighbour is.

`Programs > Startup Program` was the same defect one section down; the page is
Startups. Enso is a proper noun and is capitalised, and its mark is a circle,
which is what an ensō is — a sparkle said "AI" about the one thing here that
does not need telling.

The landing rail starts OPEN, like every other route. It started collapsed on
the argument that a reader who typed the domain has not asked for a table of
contents — but the front door is exactly where somebody does not know what is
here, and a collapsed rail answers that by showing nothing. It also cost the
page its wordmark, since the sidebar is what renders it.

And search is offered once. The sidebar carries the field, the collapsed rail
carries a button beside its own toggle; the third one on the far right was the
same action in a third shape.
2026-08-22 12:12:19 -07:00
zooqueen b3e6a2478d the markdown twin every page already offered
Every docs page renders Copy Markdown and View Markdown / Open in ChatGPT /
Claude / Cursor, and every one of those buttons fetched a 404. The renderer that
produces the text sat in lib/get-llm-text.ts with zero importers, and proxy.ts
negotiated content to a route that cannot exist under output: 'export'. The
feature read as broken rather than missing, which is the worse of the two.

It could not be fixed where it was written: `${page.url}.mdx` is not a route this
app can express. The twins move to their own namespace, /llms.mdx/docs/<slugs>/
content.md, which IS expressible, so generateStaticParams prerenders one file per
page — necessary because hanzoai/static computes nothing at request time.

llms-full.txt lands beside llms.txt for the other half of the trade: the index is
a table of contents, so an agent reading it pays a request per page it wants,
while this is one request for everything, already processed to the same text the
twins serve.

Two source fixes were needed to make any of it export, and neither is where the
symptom pointed:

sanitize-mdx.ts already owned the invariant — an unresolvable specifier fails the
WHOLE build — but scoped it to the import header. Docusaurus declares a partial
where it is used, so ported pages import from mid-prose and every header was
already clean. Widened to every top-level ESM import outside a fenced block:
2,062 ported imports dropped, the three surviving @site lines are inside jsx
fences and correctly kept as samples.

packages/mdx adapter.ts: when a ported page fails MDX compilation the loader
emitted a fallback module without _markdown, so getText('processed') threw at
export time complaining about includeProcessedMarkdown, which was enabled. The
missing export moved the crash and blamed the wrong setting; any route walking
the corpus died on the first of 157 such pages.

Measured: exit 0, 5935/5935 static pages, llms-full.txt 17.4 MB over 2953
entries, and 2953 content.md twins — exactly one per docs page. export.require
gains all three so losing them fails the build loudly instead of silently
reverting to 404s.
2026-08-20 13:00:09 -07:00
zooqueen ce11448232 the radix fork gets the same switcher alignment
The two UI packages are parallel forks, so a change to one alone diverges them —
same rule the docs grid note states. Radix keeps its own
--radix-popover-trigger-width, which is correct there; what it did not have is the
stated alignment and the icon column that survives an option with no icon.
2026-08-19 21:15:57 -07:00
zooqueen a59a25fbfe the mark obeys its box, and the switcher reads down one column
The wordmark's H arrives as markup with a viewBox and no width, height or class,
so at size-5 it painted past its 20px: sideways across the row's gap-2.5, which
is why the mark and 'Hanzo Docs' read as one glued word, and downward past its
own centre, so items-center was centring a box the mark had already left. Sizing
it to the box makes the gap and the centring apply to what you see.

The switcher's options now state their alignment instead of inheriting it, and
the icon keeps its column when an option has none — a collapsing slot moved that
option's label a whole icon-width left of its neighbours.

Also: two base-ui copies of that list asked for w-(--radix-popover-trigger-width).
That variable is Radix's; this build is Base UI, where the anchor's width is
--anchor-width, so the width simply never resolved. The third copy already had it
right, which is how the other two kept it wrong.
2026-08-19 21:15:57 -07:00
zeekayandzooqueen b28b82b214 Docs state the two API keys cloud mints, and the build proves it
docs.hanzo.ai documented three key types. Cloud mints two.
cloud.APIKeyPrefixes is pk- and sk-; hk- and hz- have never existed and
an unrecognized spelling is refused at the door, so a reader's first call
failed asking for a credential nobody can issue. 149 files carried one.

sk- was the subtler half: present, and described as an org-level provider
credential when it resolves to the USER. A find-and-replace would have
left that confidently wrong, so the pages that made a further claim -- a
scope, a third family, a list of recognized prefixes -- were rewritten
rather than swapped.

Derived, not restated. openapi-doc gains Document.keys, read out of the
/v1/keys prose the Go handler writes and zipdoc lifts into the document.
gen-key-types renders that as an MDX fragment api-keys.mdx includes, so
the table nobody may type is not typed. secretKey(doc) replaces the
literal three generators each kept -- which is how three GENERATED pages
also came to teach hk-.

check-keys then holds every page to it, both directions: a prefix the
document does not carry fails the build, and a class api-keys.mdx does
not teach fails it too. Renaming pk- in cloud now stops the build in six
places instead of publishing a stale page.

Also measured against the live API and cluster, and corrected: /v1/keys
does mint keys over HTTP (403 unauthenticated, not absent); Tasks is
/v1/tasks on the one host, not gRPC elsewhere; the KMS example path
404s; and four KMSSecret manifests could not apply -- two named groups
with no controller, and their spec was External Secrets' shape, not the
CRD's five fields.

Sidebar sits flush left. The docs grid was five columns centring a band,
with the sidebar area spanning the leading gutter, so the nav began
176.5px in at 1920 and 496.5px in at 2560 -- inside its own bordered
card. Three columns now: rails pinned, page between them. Measured at
/docs/api-keys before -> after: 1920 176.5/232/1052/268/176.5 ->
232/1405/268, 2560 496.5/232/1052/268/496.5 -> 232/2045/268, nav at x=0.
Phone 390 and tablet 820 unchanged (0/375/0 and 232/573/0, drawer opens,
no page overflow); desktop collapse and restore verified.
2026-08-19 21:13:49 -07:00
zooqueen 647fb3e865 retire the hk- key prefix: sk- is what the gateway verifies
Cloud verifies two key shapes, pk- and sk-, and IAM has minted sk-
since v1.33.9. Nothing mints hk-, so every curl, env var and SDK
snippet carrying it handed the reader a credential that resolves to
no principal.

Server-side examples become sk-. The one publishable case is the
Console tracing client, whose constructor field is publicKey; that
becomes pk-.

Where a page taught the taxonomy rather than just used it, the prose
is rewritten rather than character-swapped. api-keys and
getting-started listed "API Key (hk-)" and "Secret Key (sk-)" as two
prefixes; after the retirement that is one row, and scope -- not
prefix -- is what separates a project key from an org key.

apps/spec is generated from hanzoai/HIPs, which carried the same
claim; fixed there. The vendored openapi document has no hk-, so the
pin is untouched.
2026-08-19 21:08:34 -07:00
zeekayandzooqueen 13abc4145f Remove shadcn integration; convert commerce docs examples to @hanzo/gui
packages/shadcn (@hanzo/docs-shadcn) existed to emit
"npx shadcn@latest add <component>" snippets into rendered docs, and
examples/next-shadcn existed to demo it. A docs page that emits an install
command for a DOM-only component library puts that library in the reader's
app, which is the same defect as depending on it here. Nothing else in the
workspace imported either one, so both are deleted rather than repointed.

The commerce recipes and storefront pages taught the same thing more quietly:
every code block a reader copies was styled with tailwind utility classes.
Tailwind classes and raw <div>/<button>/<select> are DOM-only, so a storefront
built from these pages could never run on iOS or Android. They now use
@hanzo/gui components with style props — XStack/YStack instead of flex and
grid classes, Card/Button/Input/Select/Form instead of bare elements, theme
tokens ($color2, $borderColor, $red10) instead of gray-500 and red-500. The
product grid becomes a wrapping XStack because Tamagui targets flexbox on
native, where CSS grid does not exist.

zen5.mdx hand-rolled a highlighted box out of tailwind on fumadocs CSS vars;
it uses the framework's own <Callout>, which is what that box already was.

Authored-docs tailwind goes 17 files / 467 class tokens -> 0. What the counter
still reports is not tailwind: Bootstrap classes in a Thymeleaf template in the
Java/Spring IAM guide, custom CSS class names paired with <style> blocks in the
IAM login-customization guide, one CHANGELOG entry, and content/docs/projects/**,
which is gitignored and regenerated by sync-project-docs.ts from upstream repos.
2026-08-19 21:07:54 -07:00
github-actions[bot]andzooqueen 8be96559d0 chore(zen-models): sync pricing from pricing.hanzo.ai
Automated daily pricing sync. Changes detected and changeset created.
2026-08-19 21:07:54 -07:00