Files
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

26 KiB

Hanzo Docs Framework

Fork of Hanzo Docs with all packages renamed to @hanzo/docs-* namespace.

How this ships

Runbook: 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, .gitignored) → 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:

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)

import { defineConfig, defineDocs } from '@hanzo/docs-mdx/config';

Source Loader (lib/source.ts)

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

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.