Files
docs/LLM.md
zeekay 2e28c2ebdc
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
Hanzo CI/CD / test-amd64 (push) Successful in 11m54s
Hanzo CI/CD / cicd (push) Failing after 9m5s
CI/CD / cicd (push) Failing after 21m3s
Draw every code block in the Hanzo code theme
The theme is codeTheme from @hanzo/ui/core: Dracula on dark, GitHub
Light on light, the one every Hanzo surface draws. lib/shiki.ts and each
app's source.config.ts import it rather than naming a theme, so vesper
and github-dark are gone. @hanzo/ui moves to 8.27.83, the first release
that carries it.

A block's code area paints the theme's own ground and ink from the pre
shiki wrote, #282a36 in the dark, rather than the site's panel.

The docs home drew its hero and its eight domain snippets as plain text,
and so did the models page's fallback request. The hero highlights its
three doors in a server part (components/hero.tsx); the snippets and the
fallback are Example props in the MDX. No highlighter reaches the
browser.
2026-10-04 23:41:47 -07:00

463 lines
26 KiB
Markdown

# Hanzo Docs Framework
Fork of [Hanzo Docs](https://github.com/hanzoai/docs) with all packages renamed to `@hanzo/docs-*` namespace.
## How this ships
**Runbook: [`RELEASE.md`](./RELEASE.md).** One lane:
push -> github.com/hanzo-docs/docs origin; GitHub Actions is OFF here
-> git.hanzo.ai/hanzoai/docs a pull mirror; sync + dispatch (RELEASE.md)
.github/workflows/deploy.yml the forge reads this directory natively
-> pnpm build --filter=docs NEXT_EXPORT=1 -> apps/docs/out
-> hanzoai/ci .github/actions/site Sites project `docs`
-> s3://hanzo-sites/hanzo/docs what docs.hanzo.ai serves
The host is a route, not a workload: `Host(docs.hanzo.ai)` -> Middleware
`docs-static` -> that S3 prefix, declared in hanzoai/universe
`charts/app/values/hanzo/static-sites.yaml`. Nothing is pinned and nothing rolls;
the publish IS the deploy.
The other workflows in `.github/workflows/` run on the same forge: `lint.yml` and
`test.yml` on pull requests, `release.yml` on push (the npm packages),
`sync-zen-pricing.yml` daily, `deploy-cloud.yml` for `apps/cloud` (Sites project
`hanzo-cloud`), and eight `deploy-*-docs.yml` that still `wrangler pages deploy`
their app. Each wrangler one is the ONLY deploy of its host, so it stays until
that host has a Sites project: give the app `output: 'export'`, publish a slug,
move the DNS, delete the workflow.
**The image lane is cold.** `ghcr.io/hanzoai/docs`, the root `Dockerfile`,
`hanzo.yml`'s `images:` and the `docs` app in universe served this host before
the Sites plane did. The pod still runs and no router names it. Building or
pinning it publishes nothing.
**The export gate is in `scripts/check-export.sh`**, which the deploy runs on
`apps/docs/out` and the Dockerfile runs in its build stage. This site fails by
exporting nothing — a valid layer, a valid image, a 404 — and `apps/<app>/
export.require` names the sections that must not silently vanish (`docs/studio/`
is a submodule; a checkout that does not recurse drops it while page count and
nav stay green).
## Canonical model — the one way to do docs
Full ADR: `apps/docs/content/docs/contributing/docs-architecture.mdx` (rendered
at docs.hanzo.ai/docs/contributing/docs-architecture). Summary:
- **docs.hanzo.ai is the hub.** ONE Fumadocs build (`apps/docs`) → ONE Sites
project `docs`. The federated per-section deploy is **retired** (stale
origins 530'd). Do not reintroduce per-section builds.
- **IA is two levels of separator, and one word each.** The ROOT sidebar
(`content/docs/meta.json`) is nine sections a developer walks in order — Get
started · Concepts · Capabilities · Reference · Build with · Surfaces ·
Programs · Integrate · Open source. Inside Capabilities
(`content/docs/openapi/meta.json`, generated) is the second level: the nine
DOMAINS — Identity & Trust · Intelligence · Data · Streams · Observability ·
Commerce · Platform · Applications · Chain — from `apps/docs/openapi-specs/
capabilities.yaml`, which is tracked HERE and is the one editorial decision
left in this repo. Both levels are icon-bearing separators
(`---[Icon]Name---`, resolved by `lucideIconsPlugin`), NOT folder moves —
files stay flat so URLs/openapi back-links don't break.
**Counts are never written twice.** How many capabilities there are is a fact
about cloud's document, printed by the generator on `/docs/openapi`; authored
prose links to it rather than restating a number that goes stale the next time
cloud ships. `check-capabilities` is what holds the taxonomy, the document and
the pages to one set.
**No stutter.** A folder's own `index.mdx` is already its landing page — the
tree builder resolves it before it reads `pages`. Naming `index` in `pages`
demotes the page to a child of the folder carrying the folder's own name, so
the sidebar reads `Agents > Agents`; 191 folders named it and 188 of them
carried the same title, so that is how many rows said it twice. Same rule one
level down: a folder wrapping exactly ONE page is a folder that should not
exist, which is why a single-tool MCP product is published as a page and not a
directory. **API reference and
SDKs are separated surfaces** (`/docs/openapi`, `/docs/sdks`) reached via the
top nav (`lib/nav.ts`), deliberately NOT in the root
sidebar descent. The landing (`app/(landing)/page.tsx` — its own route group,
because it carries the docs chrome from `components/shell.tsx` while
`(home)` keeps the marketing chrome for /blog) + docs home
(`content/docs/index.mdx`) lead with the decentralized spine ("the AI cloud
you can run yourself"); `content/docs/network.mdx` = self-host/hanzo.network,
`content/docs/architecture/philosophy.mdx` = the engineering pedagogy. Landing
breadth stats trace to real sources: the domains from `capabilities.yaml` and
the capability count from cloud's document, 157 models
(`ai/conf/models.yaml`), 706 connectors (`cloud/clients/automations/catalog/
catalog.json`), 6 SDK langs (`openapi/CLAUDE.md`). Count from data, never invent.
- **One canonical home per doc.** Content enters the single build via exactly one
of three orthogonal mechanisms, chosen by *kind*:
1. **Authored** (first-party prose) → MDX under `content/docs/<section>/`.
Small/core sections live in-repo; team-owned sections live in a
`hanzo-docs/<team>` content repo mounted as a **git submodule** at
`content/docs/<team>/`. Exemplar: `hanzo-docs/studio-docs` → `content/docs/studio/`.
2. **Generated** (never hand-written, `.gitignore`d) → API reference from
`hanzoai/cloud`'s `openapi.yaml` at the release `openapi-specs/.spec-lock`
pins, via `scripts/sync-openapi.sh` + `scripts/gen-openapi-pages.ts`
(source-derived: an endpoint's sentence is written next to its handler and
travels here unaltered); SDK reference from the ZAP SDK generator into
`content/docs/sdks/<lang>/`. Every reader of the document goes through
`scripts/openapi-doc.ts`, which owns the path — no script names the file.
A generated MDX **fragment** is the third shape here, for a fact an authored
page needs mid-sentence: `scripts/gen-key-types.ts` writes
`generated/key-types.mdx` and `content/docs/api-keys.mdx` pulls it in with
`<include>`. It sits outside `content/` because source.config globs
`**/*.mdx` there and would give a fragment its own URL — and the path is
relative to the INCLUDING file, so from `content/docs/` it is
`../../generated/`. One directory short renders the page body empty with
the frontmatter and chrome intact, which looks like a working page.
3. **Ported** (upstream OSS, mirrored with attribution, **tracked**) →
`content/docs/projects/<upstream>/`. Port, don't re-author. Carry upstream
LICENSE + NOTICE. GPL stays GPL. This is a committed snapshot, not build
output: `scripts/sync-project-docs.ts` refreshes it on demand
(`pnpm --filter docs sync:projects`) and you read the diff before it ships,
the same contract `openapi-specs/` has. It used to be BOTH — gitignored and
re-mirrored by every build, so a build rewrote 965 committed files, put 298
repos' docs on disk that production never had, and died on 54 imports
(`@site/...`) no alias resolves. The builder alone skipped the sync, which
meant the command a person runs and the command that ships the site
compiled different sites.
- **Repos:** `hanzo-docs/docs` = framework + hub (canonical; `hanzoai/docs`
redirects here). `hanzo-docs/<team>` = authored content only, NO framework.
`hanzoai/cloud` `openapi.yaml` = the API source of truth; `hanzoai/openapi`'s
`hanzo.yaml` was a projection of it on its own clock and this repo no longer
reads it. All docs live in the `hanzo-docs`
org: the hub at `hanzo-docs/docs`, each team's content at `hanzo-docs/<team>`.
- **Standalone vs hub:** default is a hub section. Standalone deploy only if ALL
of: ≈150+ pages or fast OSS-upstream churn, independent versioning, direct
audience. Standalone runs its own copy of this framework; the hub links out,
never copies.
- **Serving:** the Sites plane — `s3://hanzo-sites/hanzo/docs`, served by the
ingress staticFiles middleware on `Host(docs.hanzo.ai)`. The sibling hosts
below are still CF Pages `hanzo-docs` (token from KMS, never hard-coded). No
nginx/caddy, and no pod for a static export.
**Known dedup debt (rollout, not done):** the `apps/*-docs` legacy apps
(base-docs, bootnode-docs, bot-docs, cloud, dev-docs, dns-docs, flow, gui-docs,
insights-docs, platform, pulsar-docs, spec, tasks-docs, team, visor, zen-docs,
zt-docs) predate the unified `content/docs/` model — migrate their content into
`content/docs/<section>/` (or a `hanzo-docs/<team>` submodule) then archive the
app.
## Funnel
- **No page sends a visitor to hanzo.id.** Sign-in is `https://hanzo.ai/login`
(`components/account.tsx`; `/login` is a rule in `public/_redirects`).
Sign-out clears the session in this browser and navigates nowhere.
- **The conversion CTA reads "Try Hanzo"** and opens `https://hanzo.ai/login`;
payment comes after sign-in (`components/landing.tsx`). It sends no event of
its own: it is not a plan choice, and the page view and click cover it.
- **Tracking is `@hanzo/docs-analytics`**: page views (first load and each route
change) to `api.hanzo.ai/v1/event`, and `<Hanzo>` (@hanzo/ui) autocaptures
`$click` onto the same client. The key is the one `@hanzo/event`'s keyring
resolves from the page's host: `docs.hanzo.ai` is the hanzo org's, `localhost`
resolves none. An event this site names itself comes from the @hanzo/events
catalog. The cold image lane's `PUBLISHABLE_KEY` build arg is no longer read
by any app.
## Build guards, and why prose needs them
`scripts/pre-build.ts` ends with checks that fail the build. The first two exist for
the same reason: a generated page cannot be wrong about the API, an authored one
can, and a reader cannot tell which kind of page they are on.
- **`check-endpoints.ts`** — no page may name a `/v1/…` route on `api.hanzo.ai`
that the document does not serve.
- **`check-keys.ts`** — no page may spell an API key a way cloud does not mint,
and `api-keys.mdx` must teach every class the document carries. Both
directions, so renaming, adding or removing a key class in cloud stops the
build here instead of publishing a stale page.
- **`check-zen.ts`** — a Zen page (`models/index.mdx`, `models/zen*`) names no
upstream base model outside a `data-upstream` element. Two places carry one:
the Architecture value (the loader string from the weights' own config) and
the License & attribution section. The names live in `apps/docs/zen.upstream`
and only there; title and description are read too, since they become the
meta and OG tags.
The key classes come from `Document.keys` (`openapi-doc.ts`), read out of the
`/v1/keys` prose — cloud's own Go doc comments, lifted by zipdoc. `secretKey(doc)`
is the one accessor generators use for "the key a server presents"; three of them
had it as a literal, which is how three GENERATED pages came to teach `hk-`.
**What was wrong:** docs.hanzo.ai documented three key types — `hk-` "API Key",
`sk-` "Secret Key", `hz-` "Widget Key". Cloud mints two (`cloud.APIKeyPrefixes`:
`pk-`, `sk-`) and refuses anything else, so a reader's first call failed asking
for a key nobody can issue. `hk-`/`hz-` were on 149 files. Note the shape of the
mistake: `sk-` was *present* and *described wrongly* (as an org-level provider
credential; it resolves to the USER), so a find-and-replace would have left the
page confidently wrong. Cloud's `apps/platform/secretshape.go` still lists `hk-`
in its secret-DETECTION table — harmless, it only scans, but it is where the
invention came from.
## Branch Convention
- **`main`** — Production branch. A push here builds the export and publishes it
to the Sites plane, so landing on `main` IS publishing (`RELEASE.md`). All
Hanzo work lands here.
- **`dev`** — Tracks upstream `Hanzo Docs/dev`. Used for upstream sync merges only.
- **`upstream`** remote — points to `hanzoai/docs`
## Architecture
pnpm workspace monorepo with turbo. Two apps, 24 packages, 24 examples.
```
~/work/hanzo/docs/
├── apps/
│ ├── docs/ # Main docs site (hanzoai.github.io/docs)
│ └── zap-docs/ # Zap protocol docs
├── packages/
│ ├── core/ # @hanzo/docs-core - source loading, search, i18n
│ ├── mdx/ # @hanzo/docs-mdx - MDX processing, collections
│ ├── base-ui/ # @hanzo/docs-base-ui - headless UI (@base-ui/react)
│ ├── radix-ui/ # @hanzo/docs-ui - full UI with Radix primitives
│ ├── openapi/ # @hanzo/docs-openapi - OpenAPI docs generation
│ ├── typescript/ # @hanzo/docs-typescript - auto type tables
│ ├── twoslash/ # @hanzo/docs-twoslash - TypeScript code hints
│ ├── cli/ # @hanzo/docs-cli - scaffolding & customization
│ ├── story/ # @hanzo/docs-story - component stories
│ ├── tailwind/ # @hanzo/docs-tailwind - Tailwind CSS utils
│ ├── press/ # @hanzo/docs-press - minimal setup
│ ├── hanzo-docs/ # @hanzo/docs - unified wrapper re-exporting all
│ ├── content-collections/ # @hanzo/docs-content-collections
│ ├── mdx-remote/ # @hanzo/docs-mdx-remote - remote MDX
│ ├── obsidian/ # @hanzo/docs-obsidian - Obsidian vault adapter
│ ├── python/ # @hanzo/docs-python - Python docgen
│ ├── doc-gen/ # @hanzo/docs-docgen - doc generation
│ ├── create-app/ # @hanzo/docs-create-app - project scaffolding
│ ├── create-app-versions/ # version tracking for create-app
│ ├── shared/ # shared utilities
│ ├── stf/ # @hanzo/docs-stf (upstream dependency)
│ ├── mdx-runtime/ # @hanzo/mdx-runtime
│ ├── eslint-config-custom/ # shared ESLint config
│ └── tsconfig/ # shared TypeScript config
└── examples/ # 24 example apps (Next.js, Astro, React Router, etc.)
```
## Package Naming Convention
One brand: every workspace package publishes under `@hanzo/docs-*`. The
rename rule (applied when merging from the upstream fork) drops the upstream
prefix and re-scopes to `@hanzo/docs-`:
- `<basename>` (unscoped upstream) → `@hanzo/docs-<basename>`
- `@<scope>/<basename>` (scoped upstream) → `@hanzo/docs-<basename>`
Canonical workspace packages and their paths:
| Hanzo name | Path |
|------------|------|
| `@hanzo/docs-core` | packages/core |
| `@hanzo/docs-mdx` | packages/mdx |
| `@hanzo/docs-ui` | packages/radix-ui |
| `@hanzo/docs-base-ui` | packages/base-ui |
| `@hanzo/docs-openapi` | packages/openapi |
| `@hanzo/docs-preview` | packages/preview |
| `@hanzo/docs-typescript` | packages/typescript |
| `@hanzo/docs-twoslash` | packages/twoslash |
| `@hanzo/docs-cli` | packages/cli |
| `@hanzo/docs-story` | packages/story |
| `@hanzo/docs-tailwind` | packages/tailwind |
| `@hanzo/docs-language` | packages/language |
| `@hanzo/docs-local-md` | packages/local-md |
| `@hanzo/docs-sanity` | packages/sanity |
| `@hanzo/docs-vite` | packages/vite |
| `@hanzo/docs-basehub` | packages/basehub |
| `@hanzo/docs-mdx-remote` | packages/mdx-remote |
| `@hanzo/docs-stf` | packages/stf |
| `@hanzo/create-docs` | packages/create-app |
| `@hanzo/docs-create-versions` | packages/create-app-versions |
**Important**: `packages/radix-ui` publishes as `@hanzo/docs-ui` (Radix
variant). The `base-ui` variant (`@hanzo/docs-base-ui`) uses `@base-ui/react`
instead of Radix.
**External deps kept verbatim** (real upstream npm packages, NOT renamed):
`fuma-cli`, `fuma-content`, `@fumari/json-schema-ts`, and the third-party
search adapters (typesense / trieve) documented under `apps/docs`.
## Upstream Sync
Remote `upstream` points to the upstream fork. Local `dev` tracks `upstream/dev`.
To merge upstream changes:
```bash
git checkout dev && git pull upstream dev
git checkout -b merge-upstream-YYYY-MM-DD main
git merge dev
# Resolve conflicts, then re-apply the package rename (table above)
# Merge into main when ready
```
After merge, re-apply the rename with the canonical script
(`scripts/rebrand-packages.mjs` — masks the external KEEP-list, then maps
each upstream name to its `@hanzo/docs-*` form).
## Key Patterns
### Source Config (`source.config.ts`)
```typescript
import { defineConfig, defineDocs } from '@hanzo/docs-mdx/config';
```
### Source Loader (`lib/source.ts`)
```typescript
import { docs } from '@/.source';
import { loader } from '@hanzo/docs-core/source';
export const source = loader({
baseUrl: '/docs',
source: docs.toHanzoDocsSource(), // API name kept from upstream
});
```
### The chrome is @hanzo/gui (apps/docs)
Every docs page sits in `components/shell.tsx`: the rail (`rail.tsx` — brand,
search, `tree.tsx`, account and theme at the foot), and a column holding the bar
(`bar.tsx`) and the page (`page.tsx` — breadcrumb, title, description, the
"Use agent" menu `agent.tsx`, body, previous/next; `toc.tsx` beside it). All of
it is gui stacks and text on theme tokens; no class names.
- **Widths are media props, never JS.** `$max-md`, `$xl` compile to CSS media
rules, so the markup a phone hydrates is the markup the export wrote. Rail
264px, bar 56px, article 880px with the outline 240px beside it from 1280px;
below 768px the rail is a Sheet opened from the bar.
- **First paint is styled with no script.** gui emits each component's atomic
rules as `<style precedence>` during SSR, React hoists them, and `<html>` ships
`t_dark`. Check it: render any page with JavaScript off.
- **One theme authority: `t_dark`/`t_light` on `<html>`.** gui's
`NextThemeProvider` (`@hanzogui/next-theme`) writes it before paint and
`<Hanzo theme>` follows it (`app/provider.tsx`). The only CSS that reads it is
`color-scheme` in `app/global.css`, which is what picks the code palette.
- **Muted text is `lib/ink.ts`.** gui's grey ramp is not symmetric (light step 10
is #333), so muted names step 10 dark / 9 light. Chrome text measures >= 7.5:1
in both themes.
- **gui v5 is shorthand-only.** `bg p px items justify rounded minW maxH t z`
exist; `w`/`h` do NOT (use `width`/`height`) — an unknown prop is dropped
silently and the box takes its content's size.
- **One title per page.** The header prints the frontmatter `title`;
`lib/remark-title.ts` drops a body's leading `# heading`.
- **Page bodies are gui too.** MDX hands each markdown tag to
`components/mdx.tsx`; blocks are spaced by `Body`'s gap, never their own
margins, so a list inside a callout keeps its own rhythm. Running text sets
`whiteSpace: normal` (gui text keeps whitespace, markdown hard-wraps). Tables
keep the browser's table layout through `style.display`. Code tokens carry
`color: light-dark(…)` (`lib/shiki.ts`), so `color-scheme` picks the palette
and no stylesheet knows the theme. Steps are the `Steps`/`Step` parts
(`lib/remark-parts.ts` renames remark-steps' marked divs).
- **One code theme: `codeTheme` from `@hanzo/ui/core`** (Dracula dark, GitHub
Light light), the same on hanzo.ai, platform and ui.hanzo.ai. `lib/shiki.ts`
passes it to rehype-code and `Example`; every app's `source.config.ts` imports
it, never a theme literal. A block's code area paints the theme's own ground
and ink from the `pre` shiki wrote (`#282a36` dark, `#fff` light). A client
part never runs shiki: it takes the code already highlighted — `snippet` and
`request` are `<Example lang code />` props in the MDX, and the hero is
`components/hero.tsx`, a server part that highlights its three doors.
- **The tree narrows by path, not address.** `lib/tree.tsx` resolves an
operation page (not in the tree) to its product page, so the rail opens the
reference it belongs to.
## Build
```bash
pnpm install # Install deps
pnpm build # Build all packages + apps
pnpm dev # Dev server
# Individual packages
pnpm build --filter @hanzo/docs-core
pnpm build --filter @hanzo/docs-ui
```
Build tool: `tsdown` (all packages except `hanzo-docs` wrapper which uses `tsup`).
## Compatibility
- Next.js 15-16+ with App Router
- React 19+
- `apps/docs` renders no Tailwind; the framework packages still ship it ("Moving onto @hanzo/gui")
- pnpm 10+
## Moving onto @hanzo/gui — the plan
House rule: `@hanzo/gui` is the only UI framework (it renders on web, iOS and
Android; Tailwind classes and Radix/Base UI are DOM-only). docs.hanzo.ai is being
moved onto it in four phases, each shipped to main when verified live.
**Inventory of what docs.hanzo.ai renders** (Tailwind's own scanner, counting only
tokens Tailwind would emit CSS for, in class contexts; `content/docs/projects/**`
excluded — it is upstream's):
| Area | Tailwind tokens / files | Radix files | Base UI files |
|---|---|---|---|
| `apps/docs` app+components+lib, before phase 1 | 2,189 / 52 | 2 | 0 |
| `apps/docs` after phase 1 | 1,906 / 37 | 2 | 0 |
| `apps/docs` after phase 3 | 0 / 0 | 0 | 0 |
| `packages/base-ui` (@hanzo/docs-base-ui) | 2,774 / 76 | 0 | 10 |
| `packages/radix-ui` (@hanzo/docs-ui, via openapi/twoslash/typescript) | 2,568 / 71 | 9 | 0 |
| `packages/openapi` | 794 / 23 | 3 | 0 |
| `packages/story` / `twoslash` | 212 / 4, 3 / 1 | 1, 1 | 0 |
| authored MDX (`content/docs`, mostly code samples) | 63 / 56 | 0 | 0 |
Plus the CSS: `app/global.css` imports `tailwindcss`, the base-ui, openapi,
story and twoslash presets, and `tailwindcss-animate`.
**What replaces what.** Layout, rail, bar, outline and page header: our own gui
stacks (done). Drawer: `@hanzo/ui` Sheet. Menus: `@hanzo/ui` DropdownMenu.
Folds: plain state + gui stacks. Icons: `@hanzogui/lucide-icons-2`. Theme:
`@hanzogui/next-theme`. Tree/TOC logic stays in `@hanzo/docs-core` (headless).
| Phase | Scope | Replaces | Status |
|---|---|---|---|
| 1 | Chrome on every page: rail, tree, bar, outline, page header, search trigger, account, theme | base-ui `DocsLayout`/`DocsPage`/sidebar/toc, next-themes, the corner dock | shipped (f11eb49dc2) |
| 2 | Page bodies: markdown's elements (`components/mdx/prose.tsx`), the parts content names — Callout, Cards, Tabs, Steps, Accordion, Files, code blocks and code tabs (`blocks.tsx`, `tabs.tsx`, `code.tsx`) — and `Example`; content imports none of them; ported and studio pages reach them through the `parts` aliases in next.config | Tailwind typography, base-ui MDX components, shiki.css, the HoverCard link preview (a `title` now) | shipped |
| 3 | App pages and app parts: landing (on the theme now, no forced dark), hero, product sections, provider strip, catalogs, search (`search.tsx` + `finder.tsx`, ⌘K), feedback (a real `docs.feedback` event, not a console stub), 404, blog (in the shell, on the page frame), sign-in screens | base-ui search dialog, `RootProvider`, HomeLayout, the registry build and the dead preview / AI-search / API-page modules | shipped |
| 4 | The build: `app/global.css` is plain CSS (faces, ground, `color-scheme`, the shell footer); no `@import 'tailwindcss'`, no presets, no `postcss.config.mjs`; `apps/docs` no longer depends on `tailwindcss`, `@tailwindcss/postcss`, `tailwindcss-animate`, `tailwind-merge`, `class-variance-authority`, any `@radix-ui/*`, `@hanzo/docs-base-ui`, `@hanzo/docs-ui`, `lucide-react`, or the build plugins that emitted their parts (twoslash, auto type table, ts2js) | — | shipped |
**No framework package was deleted, because none is dead.** `packages/base-ui`,
`radix-ui`, `tailwind`, `story`, `twoslash`, `typescript`, `openapi` and the
`packages/gui` stub each still have consumers in `apps/*`, `examples/*` or the
`@hanzo/docs` wrapper. They leave when those apps move onto the gui parts in
`apps/docs/components/` — lift those into a package first, so every docs app
renders one set.
**Known gap in @hanzo/ui (8.27.18):** its `.d.ts` augments `@hanzogui/web`
without depending on it, so in this workspace the augmentation lands on the
hoisted 8.1.1 copy while `@hanzo/gui` 8.3.5 uses 8.3.5 — every gui shorthand
fails typecheck here (runtime is correct; `ignoreBuildErrors` is on). The fix is
@hanzo/ui declaring `@hanzogui/web`/`core` as dependencies of the same train.
**Out of scope when counting:** `content/docs/projects/**` is a snapshot of
upstream repos. The Java/Spring IAM guide uses Bootstrap in a Thymeleaf template
and the IAM login-customization guide uses its own `<style>` blocks — not Tailwind.
**`@zenlm/ui` ships classes that style nothing** (`packages/zenlm-ui`: Tailwind
utilities, no CSS, no Tailwind dep); `apps/zen-docs` pins the published tarball.
Converting it to gui style props fixes it by construction.
### Baseline when touching this repo
`pnpm types:check` is **not green and never was**: 50 of 72 tasks fail at rest.
`npx vitest run` is 13 failed / 203 passed. Judge a change by the *delta* against
those sets, not by a clean run you will never get. And `docs#types:check` failing
tells you nothing about whether a page renders — MDX compile errors do not surface
in tsc. `zen5.mdx` typechecked clean while the loader skipped the entire page
(`<50ms` in a table parses as a JSX tag; fence such values in backticks). Run the
app and look at it.
`remark-math` is on, so two `$` in one paragraph or list item typeset the text
between them as math: `migrate/stripe.mdx`'s "floor $1 and ceiling $5,000"
renders as KaTeX on the live site. Write each amount as `\$0.021` where a
paragraph holds two; a table cell, a code span and frontmatter are each their
own context and need no escape.
## Landing Apps (Moved Out)
Landing page apps were moved to their ecosystem repos:
- Hanzo apps → `~/work/hanzo/apps/`
- Lux apps → `~/work/lux/apps/`
- Zoo apps → `~/work/zoo/apps/`
`apps/zap-docs` and `apps/liquid` are gone; their deploy workflows were deleted
because they built directories that do not exist. See `apps/` for what is here.