Re-pinned to cloud@573d2d6ed. `standing` arrives at /v1/standing, so the
document carries 116 products and the tree carries 123 capability pages: cloud's
122 GA rows plus `openapi`, which the host serves itself and no plugin row
names. `catalogsync` and `rollingcap` were already gone, and the taxonomy's
Commerce note already said why.
A folder's own index.mdx is ALREADY its landing page — the tree builder resolves
it before it reads `pages`. Naming `index` there demotes the page from BEING the
folder to being a child OF it, under the folder's own name: 191 folders named
it, 188 of them under the same title, so that many rows said it twice. Gone from
four generators and nine authored sections.
The same rule one level down. The MCP door publishes one action-routed tool per
capability, so 80 of 81 products were a directory holding a single same-named
tool beside an index whose whole body was a one-row table pointing at it. One
tool is one page now, at the product's own address: 204 pages to 124. Its titles
come from `Product.title` like every other surface's, so `Cloud MCP` and `CLI`
spell a capability the same way.
A folder draws a chevron whether or not it holds anything, so 219 leaves offered
to open something and opened nothing. They say `collapsible: false`.
Counts are not written twice. Prose claimed 67 capabilities across eight
movements — two stale numbers and a third word for `domains`; it links to the
reference, which counts them. And the landing paints a literal white-on-neutral
palette while `.light` is stored globally, so choosing light on /docs and
clicking the wordmark gave a white headline on a white page: `dark` is scoped to
that page rather than left to a hidden switch.
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.
The Latency column wrote bare "<50ms", "<200ms", "<2s". MDX reads "<5" as the
start of a JSX tag and fails with "Unexpected character 5 before name", and the
loader's response is to skip the whole page: /docs/models/zen5 returned HTTP 200
with the chrome and none of the content. Nothing caught it, because zen-docs
passes types:check either way — tsc does not compile MDX. Verified by rendering:
128KB and no body before, 208KB with every section after. The values are fenced
in backticks, which is also how a latency belongs in a table.
LLM.md gets the measurements rather than a separate report file: tailwind is
LIVE here (21 postcss pipelines, 60 CSS entrypoints, and the published
radix-ui/base-ui tarballs ship "@import tailwindcss"), 39,433 class tokens over
537 source files, and the reason it cannot be patched away is that the UI layer
is two parallel Fumadocs forks that every app consumes. Also recorded: the
radix->@hanzo/gui primitive map with the two that genuinely do not exist
(navigation-menu, scroll-area), that @zenlm/ui ships tailwind classes with no
CSS in its tarball, and that types:check fails 50/72 and vitest 13/216 at rest
so changes are judged on the delta.
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.
Six of the seven readers of the API description still opened
`openapi-specs/hanzo.yaml`. That file is gone — the reference moved to
hanzoai/cloud's `openapi.yaml` and one caller was edited — so the flow pages, the
MCP reference, the interactive loader, both test suites and the build's own guard
all threw ENOENT. Thirteen tests ran where forty-five exist. The path is now a
value in `openapi-doc.ts`, which already called itself the only reader; no script
names the file, so there is no seventh caller to miss next time.
With the guard running again it found what it had been unable to see: seventy-four
routes in authored prose that the API does not serve. `/v1/paas/apps`,
`/v1/visor/machines`, `/v1/mpc/sign`, `/v1/db/query`, `/v1/o11y/scores`,
`/v1/commerce/orders` — each printed as a curl a reader would run and get a 404
from, each sitting on a page that also carried the generated callout pointing at
the real reference. The guard failed only on generated pages, which cannot be
wrong, and merely printed the half that could; it now fails on any of them, and
it governs what it can prove: a route written against `api.hanzo.ai`. A bare
`/v1/iam/acs` in a SAML guide is a path on somebody else's host and holding it to
this description was a category error, not strictness.
Its matching was wrong three ways besides. `{wildcard1}` matched one segment
where it means the rest of the path, so every correct deep path under
`/v1/kms/secrets` read as unknown. A three-segment prefix allowance waved through
anything under a real product. And the document serves the forge at
`/{org}/{repo}`, whose first segment is a parameter, so that one template alone
accepted every route on the site — including the invented ones.
The pages that were an API reference are deleted rather than corrected, because
correcting them recreates the defect: commerce's orders, customers, inventory,
promotions and storefront pages, mpc's, and the seven Bootnode stubs whose own
"full reference" link points at docs.bootno.de. Hand-maintained counts go the
same way — 187 endpoints, 51 namespaces, 147 endpoints, 69 services — kept by
hand beside a reference that computes its own.
`flows.yaml` and `sdks.yaml` stop being fetched from hanzoai/openapi. flows.yaml
names operationIds, and the pinned copy carried the projection's id scheme
(`get_v1_kv_name` where cloud emits `get_v1_kv_by_name`), so three of eleven ids
resolved to nothing and the flow pages could not build at all. A file whose
contents must agree with the document does not get a second upstream.
Two smaller things the same shape. A sibling cloud checkout was installed
unchecked, so a colleague's older tree silently rebuilt the whole reference while
`.spec-lock` still claimed the pinned release; it is digest-checked now like every
other source. And a tool resolving to an operation with no product wrote its page
to the section root — in no folder, linked from no index, invisible to every count
that walks the folders.
The git-runners poll hanzoai/docs off git.hanzo.ai and built it at
2026-08-04T04:35Z (task 31188). The note named hanzo-docs/docs, a different
repo, and reading it as "docs cannot build" sends you hunting for a lane that
already works.
The path in the IA note pointed at app/(home)/page.tsx, which no longer
exists — / lives in its own route group now so /blog and /showcase keep
the marketing chrome.
Co-authored-by: Hanzo Dev <[email protected]>
docs.hanzo.ai could not be republished. This makes every path that could do it
real or explicitly named, and moves the one check that matters somewhere every
path passes through.
The export gate is now in the Dockerfile. This site fails by exporting NOTHING:
the build succeeds, the layer is valid, the push succeeds, the host answers 404,
and no builder notices because from a builder's point of view nothing went wrong.
It used to be gated between build and push in deploy.yml, which only works for
deploy.yml — buildx builds and pushes in a single invocation with no step in
between to hold, and hanzo.yml's `test:` block runs AFTER that push, so neither
could have held it back. scripts/check-export.sh states what a site is, once;
apps/<app>/export.require names the sections that vanish without breaking
anything else. A failed gate means no image exists, so no lane can push past it.
deploy.yml keeps its own call of the same script against the finished image,
which answers the one question the build stage cannot: did the export land in the
IMAGE. Tested against seven exports — healthy, empty, nav-less, under the page
floor, section missing, absent directory, default require-file resolution — under
both dash and busybox ash.
docs/studio/ is the case that motivates the require file. It is a submodule, and
hanzoai/ci checks out without recursing, so on that lane the section silently
disappears while page count and nav both stay green.
.github/workflows/cicd.yml is seven lines importing hanzoai/ci, configured by the
root hanzo.yml. It is workflow_dispatch-only: give this repo two push-triggered
builders for one image and one commit yields two images under two tag schemes,
and the pin names one of them by luck. It names our own runner pool, the same
label the forge lane answers to. It cannot schedule yet, and the reason is not
Actions — Actions is on and the repo is public; every runner we own is registered
to git.hanzo.ai, and GitHub-hosted runners are not something we build on.
RELEASE.md is the runbook: four builders, the credential each needs, and the
exact edit that makes an image live. The finding worth repeating here is that
universe pins a digest beside the tag and the chart renders repo:tag@digest — so
the digest is what gets pulled, and moving the tag alone is a silent no-op.
Verified against the live systems rather than the docs: the pin resolves to the
digest the values file names; no image exists for current main in ANY of the
three tag shapes, so all lanes are cold rather than one quietly working; the
forge repo answers 404 on /actions while another repo on the same forge answers
200; /v1/runner answers 401 with the token configured on the far side, so it is
armed and one credential away.
Co-authored-by: Hanzo Dev <[email protected]>
.github/workflows held 19 files; it holds sync.yml alone now, the canonical
push-refs workflow from universe infra/ci/sync.yml. What it replaced was a
mirror-sync nudge documenting GitHub as upstream and asking the forge to pull.
Thirteen workflows moved into .hanzo/workflows unchanged — byte-identical, so
git records them as pure renames. runs-on is untouched: the forge's runner
answers hanzo-docs-build-linux-amd64. That covers the only checks (lint, test),
the only publisher of the ~30 npm packages (release), the daily pricing commit
(sync-zen-pricing), and the nine sibling static sites, each of which is the
ONLY deploy of its host. Those nine still reach Cloudflare Pages; they move to
images one host at a time, because cf-zones declares only three of the nine and
an App CR with a guessed host is wrong even while inert. LLM.md carries the
list and the steps.
deploy-docs.yml becomes deploy.yml and only builds and pushes. It had patched
`app docs` with kubectl and waited on the rollout, and it built the image from
a heredoc Dockerfile pinned to static:0.4.1 while the repo's own Dockerfile —
the one the fabric builds — pinned v0.5.1. The recipe lives in the Dockerfile
alone now; the workflow runs it, gates the export by reading /public out of the
built image so what is checked is what serves, and pushes only if the gate
passes. ghcr auth is REGISTRY_TOKEN, since a forge-issued GITHUB_TOKEN
authenticates nothing there.
The Dockerfile takes APP, so every static site in the monorepo builds one way
and the nine siblings need no new Dockerfile when they flip.
Deleted four workflows that could not have worked: deploy-liquid-docs and
deploy-zap-docs build apps/liquid and apps/zap-docs, neither of which exists
(liquid's Cloudflare job was already `if: false`), and fix-cf-domain and
setup-gui-domain are dispatch-only one-shots calling the decommissioned
Infisical /api/v3/secrets/raw route.
Also corrects three changesets that named packages absent from the workspace,
which made `changeset version` fail and release.yml publish nothing:
@hanzo/zen-models -> @zenlm/models (sync-zen-pricing.yml wrote that name every
day), and fumadocs-mdx / @fumadocs/content -> @hanzo/docs-mdx /
@hanzo/docs-content per the rename rule in LLM.md.
Co-authored-by: Hanzo Dev <[email protected]>
Ground docs.hanzo.ai in HIP-0127 (V8 · Open Edition). The spine: "the AI cloud
you can run yourself" — one unified binary, hanzo.network is the cloud, category-
first descent by the eight movements, API reference as its own separated surface.
- Sidebar (content/docs/meta.json): flat ~85-item dump with overlapping
ad-hoc labels → the eight canonical movements from openapi/capabilities.yaml
(Identity & Trust · Intelligence · Data · Streams · Observability · Commerce ·
Platform · Applications), as icon-bearing separators (---[Icon]Name---). Files
stay flat — no URL/back-link breakage. openapi/sdks/api removed from the root
descent; they remain the dedicated top-nav surfaces.
- Top nav (components/layouts/shared.tsx): add Docs + Network; API and SDKs kept
as separated surfaces.
- Landing (app/(home)/page.tsx): hero → decentralized spine; ecosystem grid →
the eight movements (category-first); breadth stats bar + models/SDKs/MCP
panels making the full surface visible. Every number traces to a source:
67 caps / 8 movements (capabilities.yaml), 157 models (ai/conf/models.yaml),
706 connectors (cloud/.../catalog.json), 6 SDK langs (openapi/CLAUDE.md).
- Docs home (content/docs/index.mdx): 69-services pitch → the run-it-yourself
spine + the eight-movements grid.
- New content/docs/network.mdx: hanzo.network / self-host — one binary, BYO GPU
or K8s, mine on any device (TEE/CC), free for public+OSS, the language seam is
the wire not FFI.
- New content/docs/architecture/philosophy.mdx: the engineering pedagogy —
one-and-one-way, decomplect, one contract → N SDKs → thin CLIs, ZAP-native.
- architecture/index.mdx: "ten categories" → the eight movements; link
philosophy + network.
Content review only — do not merge yet.
* docs: redirect /docs/{startups,deploy} → real pages (QA 404 fix)
getting-started references /docs/startups + /docs/deploy but those slugs 404
(the mdx pages didn't survive an earlier merge). Redirect to the pages that
carry the content: getting-started ($5/startup on-ramp) + services/paas (deploy).
* docs: unified docs architecture — hub + hanzo-docs/<team> submodule pattern
Ratify docs.hanzo.ai as the single unified-build hub and define the one way
content joins it, by kind:
- authored -> content/docs/<section> (in-hub or hanzo-docs/<team> submodule)
- generated -> openapi + ZAP SDK generators, gitignored, never hand-written
- ported -> sync-project-docs mirror + upstream attribution
Changes:
- ADR as a rendered docs page (content/docs/contributing/docs-architecture.mdx)
+ nav entry; LLM.md canonical model + dedup/rollout debt.
- Wire hanzo-docs/studio-docs as the reference exemplar: git submodule at
content/docs/studio/ (was a single tracked file). deploy-docs.yml checkout
now fetches submodules recursively.
- Security: cf-pages-deploy.mjs no longer hard-codes a Cloudflare API token;
it is required from env/KMS and fails fast (token must be rotated).
Standalone-vs-hub criteria, API-ref-from-openapi, and SDK-slot wiring are all
documented in the ADR. Aggregation proven: 38 API-ref pages generated from 36
openapi specs; studio section resolves from the submodule on disk.
---------
Co-authored-by: hanzo-dev <[email protected]>
- Replace docs.hanzo.ai with hanzoai.github.io/docs across 65+ files
(metadata, package.json homepages, READMEs, examples, templates, content)
- Update twitter creator from @fuma_nama to @hanzoai
- Update baseUrl to use GitHub Pages URL instead of Vercel env var
- Update RSS feed base URL
- Add GitHub Actions workflow for building docs
- Add LLM.md with detailed technical documentation for AI assistants
- Add build-all-docs.sh and dev-all-docs.sh scripts for multi-brand docs
- Add setup-fork.sh for creating new brand forks
- Fix sponsor page null tier error with optional chaining
- Update README with multi-brand documentation