@nyuchi/mzizi-mcp 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -39
- package/dist/auth/cors.d.ts +33 -0
- package/dist/auth/cors.d.ts.map +1 -0
- package/dist/auth/cors.js +110 -0
- package/dist/auth/cors.js.map +1 -0
- package/dist/auth/front.d.ts +8 -0
- package/dist/auth/front.d.ts.map +1 -1
- package/dist/auth/front.js +10 -0
- package/dist/auth/front.js.map +1 -1
- package/dist/auth/mcp-api-handler.d.ts +1 -1
- package/dist/auth/mcp-api-handler.d.ts.map +1 -1
- package/dist/auth/mcp-api-handler.js +4 -3
- package/dist/auth/mcp-api-handler.js.map +1 -1
- package/dist/auth/provider-options.d.ts +16 -0
- package/dist/auth/provider-options.d.ts.map +1 -0
- package/dist/auth/provider-options.js +21 -0
- package/dist/auth/provider-options.js.map +1 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/floors.d.ts +117 -0
- package/dist/floors.d.ts.map +1 -0
- package/dist/floors.js +103 -0
- package/dist/floors.js.map +1 -0
- package/dist/generated/registry-pin.d.ts +8 -1
- package/dist/generated/registry-pin.d.ts.map +1 -1
- package/dist/generated/registry-pin.js +3 -1
- package/dist/generated/registry-pin.js.map +1 -1
- package/dist/generated/registry.d.ts +2 -2
- package/dist/generated/registry.js +198 -198
- package/dist/generated/registry.js.map +1 -1
- package/dist/generated/skills.d.ts +1 -1
- package/dist/generated/skills.js +13 -13
- package/dist/generated/skills.js.map +1 -1
- package/dist/http.d.ts +14 -4
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +38 -54
- package/dist/http.js.map +1 -1
- package/dist/origin.d.ts +68 -0
- package/dist/origin.d.ts.map +1 -0
- package/dist/origin.js +141 -0
- package/dist/origin.js.map +1 -0
- package/dist/rate-limit.d.ts +1 -1
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +3 -2
- package/dist/rate-limit.js.map +1 -1
- package/dist/server.d.ts +47 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +14 -8
- package/dist/server.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +115 -48
- package/dist/tools.js.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +25 -11
- package/dist/worker.js.map +1 -1
- package/package.json +2 -2
package/dist/generated/skills.js
CHANGED
|
@@ -12,24 +12,24 @@ export const BUNDLED_SKILLS = [
|
|
|
12
12
|
{
|
|
13
13
|
"name": "mzizi-language",
|
|
14
14
|
"category": "language",
|
|
15
|
-
"description": "
|
|
16
|
-
"body_mdx": "# Writing and checking Mzizi\n\n**Mzizi is a general-purpose programming language, built to make Rust better, the way\nTypeScript makes JavaScript better** (`CHARTER.md` v0.4, \"Mzizi: a general-purpose\nprogramming language\"). How: no borrows, lifetimes or ownership in the language you write\n(RFC-0001 §1.8), with the harness at the core, the layer an agent reads. \"Makes Rust better\"\nis the goal Phase 0 measures, not a result. Only a backend `service` lowers to Rust today; no\ncomponent does.\n\nRust is its platform, the way JavaScript is TypeScript's: Mzizi is designed to lower to Rust (RFC-0001 §5), and Mzizi\nRoots is its component model, the way React is JavaScript's. Its goal is to be the language\npeople choose instead of TypeScript, Python and C++: write Mzizi, get Rust underneath. That\nis a goal, not a result. Phase 0 has one goal: to build Mzizi as a programming language,\nmeasured against the best existing language for each kind of task (RFC-0009). Nothing has\nbeen measured against that yet.\nIts syntax, type system and diagnostics are designed for machine authorship.\n\nThe language lives in [`mzizi-dev/mzizi`](https://github.com/mzizi-dev/mzizi) (public). Say\nwhich thing is which when you describe it:\n\n- **The language:** its syntax, type system, semantics, and contracts as a language\n feature.\n- **The harness, the core of Mzizi:** the layer an agent reads and works through. It covers\n the language as an agent sees it (grammar, types, contracts, canonical form), the agent\n protocol (`mz check --agent`, its diagnostics and fixes; RFC-0001 §4), and the plugin\n host that the toolchain, the CLI, the MCP server and plugins attach to natively. Part of\n it exists today, in the toolchain: the `mz check --agent` output this skill teaches. The\n harness as a whole is designed, not built: RFC-0012 (a draft) specifies it, and there is no\n `mz harness` command and no plugin host, so never say it works. It lives in\n `mzizi-dev/mzizi`, the language repository, and the `agent-tools`\n packages (`@nyuchi/mzizi-cli`, `mzizi-mcp`, `fundi`) are its clients. It is not\n `benchmarks/harness/`, which is the Phase 0 benchmark scorer.\n- **The toolchain, which implements and supports the language:** `mz` (the compiler, its\n IR and its NDJSON diagnostics, `mz fix`, `mz contract`, `mz build`), `@nyuchi/mzizi-cli`, the MCP\n server at `mcp.mzizi.dev`, these skills, and the Phase 0 benchmark harness. They are\n meant to attach to the harness. Never call the compiler \"the language\".\n- **The components, which support the language:** Mzizi Roots and the registry\n (`mzizi-dev/mzizi-registry`), the language's UI layer. That is the companion\n `mzizi-roots` skill. Supplying the benchmark's UI tasks is one of their jobs, not what\n they are.\n\nThis skill describes `main` at `cf3a44e` (2026-11-01): 765 tests in the workspace\n(`cargo test --workspace`, 634 of them in the compiler crate) and 33,540 lines in\n`compiler/src`. Where this skill and the code disagree, **the code is the fact** (RFC-0003\n§7.1). Every example below is copied from a file in the repository that passes `mz check`,\n`mz contract` and, for a program, `mz run` there.\n\n## Check the tracker before you claim anything\n\n[`LANGUAGE-TRACKER.md`](https://github.com/mzizi-dev/mzizi/blob/main/LANGUAGE-TRACKER.md) in\nthe language repository is **the one list of what Mzizi can and cannot do** (owner, 2026-09-30).\nIf a capability is not ✅ there, Mzizi does not have it, whatever any other page says, this\nskill included. Its status column is what the code on `main` does, never what an RFC designs.\nRead it before you tell anyone Mzizi can do something, and before you write Mzizi that relies\non a construct this skill does not show.\n\n**In a `program` (RFC-0013, rows C1–C10, all ✅ on `main` since the 2026-10-08 releases):**\nexpressions and operators (arithmetic, comparison with `is`, `and` / `or`, text interpolation\n`\"{x}\"`), bindings (`let`, `var`, assignment), functions with parameters, return types and\nrecursion, control flow (`when` / `else when` / `else`, exhaustive `match`, `when` and `match`\nas values, `for each` over `range(a, to = b)` or a list, `while`, `break`, `continue`, early\n`return`), `int` and `float` with the numeric methods, text methods (`s[i]`, `slice`, `find`,\n`split`, `chars`, `parse_int`, `parse_float`), collections (`list(T)`, `map(K, V)`, `set(K)`,\nbracket literals, indexing that returns an option read with `otherwise`, `in`, the named\nfolds), records with methods, enums with columns, and errors (`result(T, E)`,\n`return error(e)`, prefix `try`, a `match` on a result, and `main` returning a result).\n`mz run` lowers a program to a dependency-free Rust package, builds it with Cargo and runs it.\n\n**Still missing (❌ or partial in the tracker):** modules and imports (P1; one top-level\ndeclaration per file), a standard library (P2), lowering of components (P3 is partial: a\n`service` and a `program` lower, no component does), Rust interop (P6), state and I/O (P8),\nconcurrency (P9), generics and interfaces (P12), a formatter, a language server, `mz test`,\npackage management and a published release (`mzizi-lang-compiler` 0.0.0, `publish = false`).\nErrors are not yet mapped back to `.mz` lines (P5, designed). Do not write Mzizi that needs\nany of these, and do not tell anyone it exists.\n\nTwo pilots have run and neither showed an advantage for Mzizi. The run that tests the\ncharter's kill criterion has not happened, so never write that Mzizi is faster or better.\n\n## What exists, and what does not\n\n**Built and tested, in the toolchain:** the lexer, the recovering parser, the name and type\nresolver, the agent diagnostic protocol (`mz check --agent`), `mz fix`, the content-addressed\nIR, `mz outline`, contract evaluation (`mz contract`), the language harness (`mz harness`,\nRFC-0012: one machine-readable definition of every feature, operator and diagnostic code),\nnine primitives written in Mzizi (`primitives/*.mz`), component, service and program examples\n(`examples/*.mz`, each program with the output `mz run` must print in `examples/<name>.expected`),\nand the Phase 0 benchmark harness and runner. All of it is gated in CI.\n\n**Programs (RFC-0013), built and tested.** A `program` file holds one program with `fn main`.\n`mz check` checks it, and `mz run` lowers it to Rust, builds it and runs it:\n\n```mz\n## Fibonacci numbers by recursion, in RFC-0013's foundation slice (§18.1): functions,\n## calls, `let`, `var`, `when`, integer arithmetic and `print`, and no loops yet.\n## `mz run examples/fib.mz` prints what fib.expected holds; CI compares the two.\nprogram fib\n\n fn main\n print(\"fib(9) is {fib(9)}\")\n let n = 20\n let f = fib(n)\n print(\"fib({n}) is {f}\")\n var total = 0\n total = total + fib(10)\n total = total + fib(11)\n print(\"fib(10) + fib(11) is {total}, and that is fib(12): {total is fib(12)}\")\n print(parity(f))\n end fn main\n\n fn fib(n: int): int\n when n < 2\n return n\n end\n return fib(n - 1) + fib(n - 2)\n end fn fib\n\n fn parity(n: int): text\n when n % 2 is 0 and n > 0\n return \"{n} is even\"\n else\n return \"{n} is odd, or not positive\"\n end\n end fn parity\n\nend program fib\n```\n\n`mz run examples/fib.mz` prints:\n\n```text\nfib(9) is 34\nfib(20) is 6765\nfib(10) + fib(11) is 144, and that is fib(12): true\n6765 is odd, or not positive\n```\n\nErrors are values. A function returns `result(T, E)`, fails with `return error(e)`, and a\ncaller handles the result with `match` or propagates it with prefix `try`:\n\n```mz\n## Checks ages: one function fails with an error, its callers handle it with `match` or\n## propagate it with `try`, and `main` itself returns a result (RFC-0013 §12).\nprogram errors\n\n enum age_problem\n negative say \"is below zero\"\n too_old say \"is above 150\"\n end\n\n fn main: result(none, age_problem)\n print(describe(42))\n print(describe(-3))\n print(describe(200))\n let total = try total_age(20, 22)\n print(\"20 and 22 make {total}\")\n match total_age(20, 151)\n case ok sum\n print(\"20 and 151 make {sum}\")\n case error problem\n print(\"20 and 151 were rejected: {problem} {problem.say}\")\n end\n end fn main\n\n fn check_age(n: int): result(int, age_problem)\n when n < 0\n return error(negative)\n end\n when n > 150\n return error(too_old)\n end\n return n\n end fn check_age\n\n fn describe(n: int): text\n match check_age(n)\n case ok age\n return \"age {age} is fine\"\n case error problem\n return \"{n} is rejected: {problem} {problem.say}\"\n end\n end fn describe\n\n fn total_age(a: int, b: int): result(int, age_problem)\n return try check_age(a) + try check_age(b)\n end fn total_age\n\nend program errors\n```\n\n**The backend slice (RFC-0011), built and tested:** a `service` with HTTP routes and handlers\n(`when`, `header`, `respond`), checked by `mz check`. `mz contract` runs a service in process\nand evaluates its `example` and `ensure` clauses. `mz build` lowers a service to a local\nRust + axum package, which CI compiles, tests and serves. That is **the whole of the lowering\ntoday:** no component lowers, and the package is a local server, with no Workers, WebAssembly\nor Containers target and no deployment. The `mzizi-be` benchmark arm, the probe crate\n`mzprobe` and backend task B1 exist; no backend episode has run.\n\n**Not built:** everything in the \"still missing\" list above, lowering of components,\nrendering, a release and a published binary. A component `.mz` file today is checked and its contract\nevaluated; **nothing runs it.** Do not tell anyone Mzizi \"compiles to Rust\" or is \"production\nready\"; say it is designed to lower to Rust, and that only a service does so today.\n\n**In progress, as design only:**\n\n- **The harness.** RFC-0012 (draft). Only the agent protocol exists (`mz check --agent`,\n `mz fix`, `mz contract`).\n- **Contracts everywhere.** RFC-0010 (draft) extends contracts to functions, handlers,\n services and records. Component contracts (RFC-0006) and service contracts (RFC-0011 §7) are\n evaluated. Of RFC-0010's codes, `MZ0606`, `MZ0611`, `MZ0612` and `MZ0613` exist, for\n services; `MZ0607`–`MZ0610` do not exist in the compiler.\n- **Request bodies** for a service (`body input: <record>` and `reject` blocks, RFC-0011 §4.3)\n are designed and not built. So is state (task B4).\n- **Modules.** There are no imports. Components are referenced by bare name\n (`primitives/confirm_bar.mz` uses `button` and `alert` with no import line). A reference to\n another component's enum (`button_variant.ghost`) is warning `MZ0502`, because it cannot be\n checked until modules exist.\n\n**Results, as they fell.** Two Phase 0 pilots ran on 2026-09-27 and neither showed an\nadvantage for Mzizi. On the frontier model the Mzizi and Dioxus arms tied, with Mzizi about 8%\nfewer transcript tokens. On the ~7B open-weight model, the design target, Mzizi did worse on\nall three metrics. Neither pilot is the kill-criterion run (RFC-0009 §6), and that run has not\nhappened. The pilots were tests inside Phase 0 on a few registry UI components against\nDioxus, not its goal. \"Designed for agents\" and \"aims to replace TypeScript, Python and C++\"\nare accurate; \"faster\" or \"better\" is not.\n\n## Getting `mz`\n\nThere is no release. Build it from source; the compiler crate has zero dependencies:\n\n```bash\ngit clone https://github.com/mzizi-dev/mzizi.git && cd mzizi/compiler\ncargo build --bin mz # ../target/debug/mz\ncargo run --bin mz -- check ../primitives/button.mz\n```\n\n**Never `cargo install mz`.** The `mz` crate on crates.io is an unrelated project, and the\ncompiler crate (`mzizi-lang-compiler`) is `publish = false`.\n\n## The commands\n\n`mz` takes exactly one file per call. `mz check ../primitives/*.mz` is a usage error (exit 2).\nLoop over files instead.\n\n| Command | Does |\n| ----------------------------------- | -------------------------------------------------------------------------------- |\n| `mz check <file>` | human-readable diagnostics |\n| `mz check --agent <file>` | NDJSON: one diagnostic per line, then one summary line. **Target this.** |\n| `mz fix <file>` | applies every `exact` fix in place, in one pass, then re-checks the file |\n| `mz contract <file>` | evaluates the file's `contract` block (only on a file that checks clean) |\n| `mz contract --agent <file>` | the same, as NDJSON; the summary adds `contract_clauses` and `contract_failures` |\n| `mz outline <file>` | the interface only (name, enums, props, contract subjects), as valid Mzizi |\n| `mz hash <file>` / `mz ir <file>` | the content-addressed root hash / every IR node with its hash and path |\n| `mz build <service.mz> --out <dir>` | lowers a service to a Rust + axum Cargo package in `<dir>` (only a clean file) |\n| `mz build <program.mz> --out <dir>` | lowers a program to a dependency-free Rust package in `<dir>` |\n| `mz run <program.mz>` | lowers a program, builds it with Cargo and runs it |\n| `mz harness version` | protocol, language version and the definition's SHA-256 |\n| `mz harness definition --agent` | every language-harness entry (features, operators, codes) as one line of JSON |\n| `mz harness entry <name>` | one entry, e.g. `mz harness entry let` |\n\nExit status: **0** no errors (warnings do not fail), **1** errors or a failed contract\nassertion, **2** a usage or I/O problem. Branch on the status; parse the NDJSON for detail.\n\n`mz run` is the exception, because 1 belongs to the program it runs. It exits with the\nprogram's own status: 0; 1 when `main` returns an error (`MZ0992`); 101 for a trap (`MZ0991`)\nor a panic; 141 when standard output is closed; 128 plus the signal that ended it; **3** when\nthe check failed and nothing ran, or the lowered Rust did not compile (`MZ0990`); and 2 for a\nusage, environment or I/O problem (`--agent` is not accepted, a file that is not a `program`).\n\nOn a **service**, `mz contract` runs the service in process (no lowering, no socket), and the\nsummary adds `contract_tested`, the number of generated requests its `ensure` clauses were\ntested over. `mz outline`, `mz hash` and `mz ir` do not cover services yet and exit 2. `mz\nbuild` on a component exits 2 (\"components do not lower yet\"), and on a file with errors it\nprints them, builds nothing and exits 1.\n\n`check` and `contract` are deliberately separate: a compile error is not a defect, and a\ncontract failure is. Your file is done when **both** `mz check --agent` and\n`mz contract --agent` exit 0.\n\n## The loop\n\n1. Write the file.\n2. `mz check --agent file.mz`. If it exits 0, go to 5.\n3. `mz fix file.mz` to apply every `exact` fix. Read each remaining diagnostic's `say`; apply\n `guess` fixes only after checking them.\n4. Fix everything reported, then go to 2. Errors are independent, so fix them all before\n re-running; do not fix one and re-run to \"see the next\".\n5. `mz contract --agent file.mz`. `MZ0603` means an assertion is false (a defect in the\n component or the contract); `MZ0605` means it could not be evaluated, which **counts as a\n failure**.\n\n## Reading the NDJSON\n\nThis file is wrong on purpose. It is the example in `benchmarks/prompts/mzizi-guide.md`, which\n`benchmarks/prompts/verify-guide.sh` checks against the real compiler.\n\n```mz\n## WRONG ON PURPOSE: three mistakes.\ncomponent tag\n\n enum tag_size\n snug class \"h-13 px-3\"\n end\n\n prop onPick: event(none)\n prop size: tag_size = snug\n\n view\n row\n slot = \"tag\"\n class = \"rounded-full {size.class}\"\n tap = on_pick\n end\n end\n\n contract\n slot is \"tag\"\n tag_size.snug height 52\n end\n\nend\n```\n\n`mz check --agent tag.mz` exits 1 and prints exactly:\n\n```text\n{\"code\":\"MZ0101\",\"severity\":\"error\",\"file\":\"tag.mz\",\"span\":[8,8,8,14],\"say\":\"`onPick` is not snake_case — Mzizi names are snake_case, so write `on_pick`\",\"fix\":{\"span\":[8,8,8,14],\"replace\":\"on_pick\",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0602\",\"severity\":\"error\",\"file\":\"tag.mz\",\"span\":[21,26,21,28],\"say\":\"a contract clause needs a predicate (is / in / contains / not_empty / at_least / uses / min_height / shows), found `52`\",\"fix\":{\"span\":[21,26,21,26],\"replace\":\"is \",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0208\",\"severity\":\"error\",\"file\":\"tag.mz\",\"span\":[24,1,24,4],\"say\":\"top-level blocks close with their name: write `end component tag`\",\"fix\":{\"span\":[24,1,24,4],\"replace\":\"end component tag\",\"confidence\":\"exact\"}}\n{\"summary\":true,\"errors\":3,\"warnings\":0,\"exact_fixable\":3,\"ms\":0}\n```\n\n`mz fix tag.mz` then prints `mz: applied 3 exact fixes; 0 errors (0 exact-fixable) remain`, and\n`mz contract --agent tag.mz` reports `\"contract_clauses\":2,\"contract_failures\":0`.\n\n- `code`, `severity` (`error` or `warning`), `file` (the path you passed), `span`\n `[line, col, end_line, end_col]` (1-indexed, end exclusive) and `say` are always present.\n- `say` quotes the offending source, so you need not re-read the file to understand it.\n- `fix` is optional: put `replace` at `fix.span`. An empty `replace` is a deletion. `exact`\n is safe to apply blind; `guess` has the right location and an inferred replacement.\n- The last line is always the summary.\n\n## Syntax essentials\n\nOne file holds one component, or one service (see \"Services\" below). There is one form per\nintent: if a construct is not shown here or in the RFCs, assume it does not exist. The order\ninside a component is doc lines, `use`, `enum` and `record`, `prop`, `view`, `fn`, `contract`.\n\nA complete primitive, `primitives/badge.mz`:\n\n```mz\n## A small status label. Not interactive, so no touch floor applies.\n## Corpus reference: n2-primitives/badge.tsx.\ncomponent badge\n\n enum badge_variant\n default class \"bg-primary text-primary-foreground\"\n secondary class \"bg-secondary text-secondary-foreground\"\n destructive class \"bg-destructive/10 text-destructive dark:bg-destructive/20\"\n outline class \"border-border text-foreground bg-input/30\"\n ghost class \"hover:bg-muted hover:text-muted-foreground\"\n link class \"text-primary underline-offset-4 hover:underline\"\n end\n\n prop variant: badge_variant = default\n prop label: text\n\n view\n mark\n slot = \"badge\"\n portal = \"https://mzizi.dev/components/badge\"\n variant = variant\n class = \"inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-md border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all {variant.class}\"\n text = label\n end\n end\n\n contract\n every badge_variant class not_empty\n badge_variant.default class contains \"bg-primary\"\n slot is \"badge\"\n end\n\nend component badge\n```\n\n- **Shape.** `component <name>` opens the file and `end component <name>` closes it, repeating\n the name. Every other block (`enum`, `record`, `view`, `contract`, each element, each `when`,\n each `for each`, each `fn`) closes with a bare `end`. Never `end enum` or `end view`.\n- **Comments.** `##` to end of line is the only comment form. `//` and `/* */` are errors.\n- **Names** are `snake_case`. `onRemove` is `MZ0101`, with the snake form as its `exact` fix.\n- **Enums are variant tables.** One row per variant: its name, then `column value` pairs. Every\n variant has every column (`MZ0303` otherwise). A column can carry meaning beyond styling, as in\n `primitives/alert.mz`, where `announce` ties the ARIA role to the severity:\n\n ```mz\n enum alert_variant\n default class \"bg-card text-card-foreground\" announce \"status\"\n destructive class \"bg-card text-destructive\" announce \"alert\"\n warning class \"bg-card text-[var(--status-warning,#FFD740)]\" announce \"alert\"\n info class \"bg-card text-[var(--status-info,#00B0FF)]\" announce \"status\"\n end\n ```\n\n- **Heights come from the class.** A variant's `height` is the pixels its `h-N` or `size-N`\n class renders (`N × 4`). You may leave the `height` column out. A written `height` that\n disagrees with the class is `MZ0313`, and its `exact` fix is the rendered number.\n- **Props.** `prop <name>: <type>`, optionally `= <default>`. The types are an enum name\n (`= default`), `text`, `bool`, `int`, `event(none)`, `event(<type>)`, `list(<type>)`\n (omitted means empty), `option(<type>)` (omitted means none) and a record name. Never\n `option(list(T))` (`MZ0703`).\n- **The view** holds exactly one element tree. An element is a bare word on its own line,\n closed by `end`. Inside it, each line is an attribute `name = value` (the `=` is required),\n a child element, a `when` block, a `for each`, or `nothing`. Strings interpolate with\n `{...}`; there is no `+`.\n- **Conditionals.** `when x` … `end`, `when not x`, `when v is <variant>`, and for an option or\n a list `when x is none` … `else` … `end`. There is no `if` (`MZ0407`, `exact` fix `when`)\n and no `match`. From `primitives/avatar.mz`:\n\n ```mz\n when image is none\n row\n slot = \"avatar-fallback\"\n class = \"flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground {size.text_class}\"\n text = initials\n end\n else\n picture\n slot = \"avatar-image\"\n source = image\n alt = alt\n class = \"aspect-square size-full rounded-full object-cover\"\n end\n end\n ```\n\n- **Lists** render with `for each item in items`, whose first line is its `key = item.<field>`.\n- **Capabilities.** `use motion` if the component animates (`primitives/spinner.mz`). Nothing\n else needs `use`.\n- **Events.** Bind with `tap = on_tap` / `change = on_change`. A parameterless `fn` can `emit`\n an event prop, as in `examples/connectivity_bar.mz`:\n\n ```mz\n fn retry\n emit on_state_change(syncing)\n end\n ```\n\n- **No React carry-overs.** `...props` spreads are `MZ0106` (`exact` fix deletes the line).\n An `as_child` prop is warning `MZ0312`: delete it, and keep what the `as_child = false`\n branch renders. `className` pass-through has no equivalent.\n\n`benchmarks/prompts/mzizi-guide.md` is the complete one-file guide the benchmark gives a model,\nincluding records, `for each` and a React-to-Mzizi table. Read it before writing a component.\n\n## Services (the backend slice, RFC-0011)\n\nA backend program is one file holding one `service`, closed by `end service <name>`. Every\ninner block (`record`, `route`, `fallback`, `when`, `contract`) closes with a bare `end`. The\norder is doc lines, service-level `header` lines, `record`s (and `enum`s), `route`s,\n`fallback`, `contract`. There are no imports, no `main`, no server setup and no port.\n\n`examples/registry.mz`, the example service (22 clauses, all holding; its `ensure` clauses\ntested over 61 generated requests):\n\n```mz\n## A registry of two components: routes, methods, redirects and a JSON 404.\n## RFC-0011's worked example, shaped like the API gateway's `/v1/ui` routes (RFC-0009 §2.4, B1).\nservice registry\n\n header \"x-mzizi-source\" \"fixture\"\n\n record problem\n field error: text\n end\n\n record health\n field status: text\n end\n\n record item\n field name: text\n end\n\n route health_check\n get \"/v1/health\"\n respond 200 json health status \"ok\"\n end\n\n route ui_list\n get \"/v1/ui\"\n respond 200 file \"fixtures/ui.json\"\n end\n\n route ui_item\n get \"/v1/ui/{name}\"\n when name in \"badge\" \"button\"\n respond 200 json item name name\n end\n header \"cache-control\" \"private, no-cache, no-store, max-age=0, must-revalidate\"\n respond 404 json problem error \"Not found\"\n end\n\n fallback\n header \"cache-control\" \"private, no-cache, no-store, max-age=0, must-revalidate\"\n respond 404 json problem error \"Not found\"\n end\n\n contract\n example get \"/v1/health\" body.status is \"ok\"\n example get \"/v1/ui\" status is 200\n example get \"/v1/ui\" header \"content-type\" is \"application/json\"\n example get \"/v1/ui/button\" status is 200\n example get \"/v1/ui/button\" body.name is \"button\"\n example get \"/v1/ui/does-not-exist\" status is 404\n example get \"/v1/ui/does-not-exist\" body.error is \"Not found\"\n example get \"/v1/ui/does-not-exist\" header \"cache-control\" is \"private, no-cache, no-store, max-age=0, must-revalidate\"\n example head \"/v1/ui/button\" status is 200\n example head \"/v1/ui/button\" body is \"\"\n example options \"/v1/ui\" status is 204\n example options \"/v1/ui\" header \"allow\" is \"GET, HEAD, OPTIONS\"\n example delete \"/v1/ui/button\" status is 405\n example delete \"/v1/ui/button\" body is \"\"\n example get \"/v1/ui/\" status is 308\n example get \"/v1/ui/?limit=2\" header \"location\" is \"/v1/ui?limit=2\"\n example get \"/nope\" status is 404\n example get \"/nope\" header \"content-type\" is \"application/json\"\n ensure status in 200 204 308 404 405\n ensure header \"x-mzizi-source\" is \"fixture\"\n ensure when status is 404 then body.error is \"Not found\"\n ensure when status is 405 then header \"allow\" is \"GET, HEAD, OPTIONS\"\n end\n\nend service registry\n```\n\n- **A route** is `route <name>`, then exactly one `<method> \"<pattern>\"` line, then zero or\n more `query <name>: option(<scalar>)` lines, then statements, then `end`. The methods are\n `get`, `head`, `post`, `put`, `patch`, `delete` and `options`, lower case; a second method on\n the same path is a second route.\n- **A pattern** is `/` or `/`-separated segments of lower-case letters, digits and `-._~`,\n or a whole-segment `{name}`, which binds `name` as `text`. No trailing slash, no empty\n segment, no `?`. A literal segment beats a parameter at the same position.\n- **A query parameter is always an option:** `query limit: option(int)`. `option(int)` is\n `-`? then ASCII digits, so `\"0x10\"`, `\"1e2\"`, `\" 7 \"` and `\"1.5\"` are `none`; `option(bool)`\n is exactly `true` or `false`. Bad input is absence, never an error.\n- **A handler has three statements**, and every path through it must end in `respond`:\n\n | Statement | Means |\n | -------------------------------- | --------------------------------------------------------- |\n | `when <cond>` … [`else` …] `end` | a branch; with no `else`, a false condition falls through |\n | `header \"<name>\" \"<value>\"` | set a header on the response this path sends |\n | `respond <status> [<body>]` | send the response; nothing after it on the path runs |\n\n A condition is `p` / `not p` (a `bool`), `p is <literal>`, `p in <literal> …`,\n `p at_least <n>` / `p at_most <n>` (an `int`), or `p is none` … `else` (an option, which is\n narrowed in the `else`). There is no `if`, `==`, `return`, assignment, loop or call.\n\n- **Bodies:** none; `json <record> <field> <value> …` (every field once, a literal or a\n parameter, no punctuation; `application/json`); `text \"<string>\"` (may interpolate a\n parameter as `{name}`); or `file \"<path>\"` (a fixture beside the `.mz` file, embedded at\n build time). A response record's fields are scalars or options of scalars. `204`, `304` and\n `1xx` take no body.\n- **Headers** are lower case. A service-level `header` applies to every response, runtime\n ones included; a route's own `header` for the same name wins. `content-length` and\n `transfer-encoding` belong to the server.\n- **The runtime answers what HTTP defines, so do not write it:** `HEAD` for a `get` route\n (its status and headers, no body); `OPTIONS` (`204` with `allow`, e.g. `GET, HEAD, OPTIONS`);\n any other undeclared method on a matched path (`405` with the same `allow`, no body); and a\n trailing slash or `//` (`308` to the canonical path, query kept, never off-site). A path no\n route matches runs `fallback`, or gets an empty `404` when there is none.\n- **The contract:** `example <method> \"<target>\" <facet> <predicate>` sends one request;\n `ensure [when <facet> <predicate> then] <facet> <predicate>` must hold for every generated\n request. A facet is `status`, `header \"<name>\"`, `body` or `body.<field>`; a predicate is\n `is <value>`, `in <value> …`, `not_empty`, `contains \"<text>\"`, `at_least <n>` or\n `at_most <n>`. `header \"<name>\" is none` asserts absence and `body is \"\"` an empty body.\n `ensure` is **tested** over the generated set, never proven.\n\nThis file is wrong on purpose. It is the example in `benchmarks/prompts/mzizi-be-guide.md`:\n\n```mz\n## WRONG ON PURPOSE: three mistakes.\nservice shop\n\n record problem\n field error: text\n end\n\n route item\n GET \"/v1/items/{id}/\"\n when id is \"1\"\n respond 200 text \"one\"\n end\n end\n\nend service shop\n```\n\n`mz check --agent candidate.mz` exits 1 and prints:\n\n```text\n{\"code\":\"MZ0613\",\"severity\":\"warning\",\"file\":\"candidate.mz\",\"span\":[2,9,2,13],\"say\":\"`service shop` has no `contract` block — its behaviour is unverified (RFC-0010 §8)\"}\n{\"code\":\"MZ0801\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[9,5,9,8],\"say\":\"`GET` is not a method — a route declares one of get, head, post, put, patch, delete, options\",\"fix\":{\"span\":[9,5,9,8],\"replace\":\"get\",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0802\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[9,9,9,26],\"say\":\"`\\\"/v1/items/{id}/\\\"` ends with `/` — patterns have no trailing slash; the runtime redirects `…/` to the path without it\",\"fix\":{\"span\":[9,9,9,26],\"replace\":\"\\\"/v1/items/{id}\\\"\",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0804\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[13,3,13,6],\"say\":\"a path through `route item` reaches `end` without a `respond` — when the `when` on line 10 does not respond on both branches; every path must respond\"}\n{\"summary\":true,\"errors\":3,\"warnings\":1,\"exact_fixable\":2,\"ms\":0}\n```\n\n`mz fix` applies the two `exact` fixes. `MZ0804` needs a decision: add\n`respond 404 json problem error \"Not found\"` after the `when`'s `end`, so the false path\nresponds too.\n\n**Building it.** `mz build registry.mz --out <dir>` checks the file and, with zero errors,\nwrites `Cargo.toml`, `src/main.rs` and the fixtures. The package is its own workspace root,\ngenerated output that is never committed, and depends on `axum =0.8.9` and `tokio =1.53.1`\n(tests add `tower`, `http-body-util` and `serde_json`). Every `example` becomes one\n`#[tokio::test]` (`cargo test` in the package); `ensure` clauses are evaluated by\n`mz contract` only. `cargo run --release --manifest-path <dir>/Cargo.toml` serves\n`127.0.0.1:$PORT` (8080 by default) and prints `listening on http://127.0.0.1:<port>`.\n\n## Contracts\n\nEvery component carries a `contract … end` block: one assertion per line,\n`<subject> <predicate>`, no colon. A file with no contract is warning `MZ0501`. It is checked\nagainst the file's own enums, view attributes and prop defaults (RFC-0006). From\n`primitives/button.mz`:\n\n```mz\n contract\n every button_size height at_least 48\n button_size.default height is 56\n button_size.sm height is 48\n button_variant.default class contains \"bg-primary\"\n slot is \"button\"\n end\n```\n\n| Subject | Means |\n| --------------------------- | -------------------------------------------------- |\n| `every <enum> <column>` | that column in every variant |\n| `<enum>.<variant> <column>` | one cell |\n| `<attribute>` | the root view element's attribute (`slot`, `role`) |\n| `<prop>` | the prop's default (only a prop that has one) |\n| `<element> \"<text>\"` | the element carrying that text |\n| `when <name>` | the `when` branch that mentions that name |\n\nPredicates: `is <value>` (exactly as written: `height is \"52\"` fails against `height 52`),\n`contains \"<s>\"`, `not_empty`, `at_least <n>`, `in \"<a>\" \"<b>\"`, `uses \"--token\"`,\n`uses <component>` (no subject), `shows <element> \"<t>\"` (after `when <name>`) and\n`min_height <n>`. `examples/connectivity_bar.mz` uses the last two:\n\n```mz\n when offline shows button \"Retry\"\n button \"Retry\" min_height 48\n```\n\nAssert what the source guarantees: every interactive size clears 48px, the `slot`, and the key\ncells. An assertion on a prop with no default is `MZ0605` (unevaluable), which fails.\n\n`mz contract` checks nothing rendered. The rendered half is `benchmarks/harness`, which diffs a\n`.mz` component's variants, defaults, touch heights and `data-slot` set against the Rust\nreference. Mzizi Roots components carry the same clause grammar as `pub const CONTRACT` and\ncheck it against their server-rendered markup (see the `mzizi-roots` skill).\n\nThe direction (RFC-0010, draft) is that **everything built carries a contract**: components,\nlanguage functions and backend handlers. The component form and the service form (\"Services\"\nabove) are evaluated today; functions have no contracts because they have no bodies.\n\n## Diagnostic codes you will meet\n\nThe ranges are `MZ01xx` lexing and names, `MZ02xx` blocks and closers, `MZ03xx` declarations,\n`MZ04xx` the view, `MZ05xx` lints, `MZ06xx` contracts, `MZ07xx` types and resolution, and\n`MZ08xx` services and handlers. The full tables are RFC-0001 §4.7, RFC-0006 §8, RFC-0008\n(types), RFC-0010 §6 and RFC-0011 §9. A service reuses `MZ0701`, `MZ0707`, `MZ0708` and\n`MZ0710` for unknown names and un-narrowed options.\n\n| Code | Severity | Written | Repair |\n| ------------------- | -------- | ---------------------------------------------------------------------------------- | ---------------------------------------------- |\n| `MZ0101` | error | a name that is not `snake_case` | `exact`: the snake form |\n| `MZ0105` | error | `list<T>`, `T[]`, `[T]` | `exact`: `list(T)` |\n| `MZ0106` | error | `...props`, `{...props}`, `..attributes` | `exact`: delete (the whole line when alone) |\n| `MZ0204` | error | a block never closed | `guess`: insert the `end`s |\n| `MZ0206`–`MZ0208` | error | a wrong closer (`end view`, a bare `end` for `component`) | `exact`: rewrite the whole closer line |\n| `MZ0303` | error | a variant missing a column another variant has | add the column |\n| `MZ0312` | warning | `prop as_child: bool` (React `asChild`) | `exact` when unread: delete the prop |\n| `MZ0313` | error | `icon class \"size-14\" height 48` | `exact`: the rendered px (`56`) |\n| `MZ0406` | error | `class \"flex\"` (attribute without `=`) | insert `=` (`exact` for known attribute words) |\n| `MZ0407` | error | `if open` | `exact`: `when open` |\n| `MZ0408` | error | an attribute directly under `view` | move it inside the root element |\n| `MZ0409` | error | `span class = \"x\"` (more after an element word) | put the element word on its own line |\n| `MZ0501` | warning | no `contract` block | write one |\n| `MZ0502` | warning | `<other>_enum.variant` from another component | none until modules |\n| `MZ0602` | error | a clause with no predicate (`height 52`) | `exact`: insert `is` (needs an operand) |\n| `MZ0603` | error | **an assertion that does not hold** | fix the component or the clause |\n| `MZ0605` | error | an assertion that cannot be evaluated | assert something the file defines |\n| `MZ0606` | error | an `example` status that an `ensure status in …` rules out | fix one of the two |\n| `MZ0611` / `MZ0612` | error | a service `ensure` / `example` that does not hold | `say` quotes the request that broke it |\n| `MZ0613` | warning | a service with no or an empty `contract` block | write one |\n| `MZ0701` | error | an unknown type (`strng`) | alias or nearest-name fix |\n| `MZ0706` | error | a default that does not fit its type | varies |\n| `MZ0707` / `MZ0708` | error | an unknown name / variant, column or field | nearest-name fix |\n| `MZ0710` | error | an option used without `when … is none … else` | narrow it first |\n| `MZ0711` / `MZ0712` | error | a wrong kind of value / a `when` that does not fit its type | see `say` |\n| `MZ0801` | error | a route not opening with `<method> \"<pattern>\"`, `GET`, a second method line | `exact` for `GET`; else a `guess` |\n| `MZ0802` | error | a bad pattern: trailing `/`, empty segment, `?`, no leading `/` | `exact` where the repair is certain |\n| `MZ0803` | error | two routes with one method and the same pattern | delete or change one |\n| `MZ0804` | error | a handler path that reaches `end` without `respond` | add the `respond` |\n| `MZ0805` | error | a line after `respond` that can never run | `exact`: delete it |\n| `MZ0806` | error | a bad `respond`: no status, not 100–599, a body on `204` | `exact` deletes a body that is not allowed |\n| `MZ0807` | error | a bad `header`: `\"Cache-Control\"`, set twice, server-owned | `exact`: the lower-case name |\n| `MZ0808` | error | a `json` literal with a missing, repeated or ill-typed field (unknown is `MZ0708`) | give every field once |\n| `MZ0809` | error | `query limit: int` | `exact`: `option(int)` |\n| `MZ0810` | error | a `file \"…\"` fixture that does not exist | add the file or fix the path |\n| `MZ0811` | error | a line a service or handler cannot hold (`return`, `=`, `view`) | see `say` (`is` for `=` is `exact`) |\n| `MZ0812` | error | a second `fallback`, or a route or parameter name used twice | rename or delete one |\n\nTwo real outputs, from pilot-2 candidates written by the 7B model and re-checked on `main`\n(`benchmarks/results/2026-09-27-pilot-2/raw/scored/…/mzizi/{button,badge}/seed-1/iter-01/candidate.mz`):\n\n```text\n{\"code\":\"MZ0313\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[16,35,16,37],\"say\":\"`icon` declares `height 48` but its class `size-14` renders 56px (N × 4) — write `height 56`, or drop the column and let the class say it once\",\"fix\":{\"span\":[16,35,16,37],\"replace\":\"56\",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0106\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[15,8,15,16],\"say\":\"`...props` is a spread, and Mzizi has none: a component names each prop it reads and each attribute it sets. `props` has no equivalent — delete the line\",\"fix\":{\"span\":[15,1,16,1],\"replace\":\"\",\"confidence\":\"exact\"}}\n```\n\n## Contributing to the language\n\n- A language change needs an RFC in `design/`. Take the next number not used on `main` or\n claimed by an open PR: RFC-0005 is reserved, and RFC-0011 (handlers), RFC-0012 (the language\n harness), RFC-0013 (the core language) and RFC-0014 (Tier 2, draft) exist, so the next is\n RFC-0015.\n- A PR that adds or changes a language feature adds or updates its language-harness entry in\n `compiler/src/harness.rs` in the same PR: one entry in `FEATURES`, one `Code` per diagnostic\n code with a `trigger`, and tested examples. `cargo test --test harness` is the check.\n- A PR that changes what the language can do updates its row in `LANGUAGE-TRACKER.md` in the\n same PR, with evidence, and adds a `CHANGELOG.md` entry.\n- CI runs `cargo fmt -- --check`, `cargo clippy --all-targets -- -D warnings`, `cargo test`,\n and `mz check` and `mz contract` over every file in `examples/` and `primitives/`, one file\n per call. Its `lowering` job runs `mz build` on `examples/registry.mz`, then `cargo test` and\n one request over a socket against the package, `mzprobe verify` of task B1, and `mz run` of\n every example program with its output diffed against `examples/<name>.expected`. `AGENTS.md`\n in the repo has the exact script.\n- Pull requests are squash-merged through the merge queue, and the squash commit keeps every\n commit message. State what you measured in the commit message.\n- The repo consumes nothing else in the org: no build step, test or script may reach into\n `mzizi-registry` or `agent-tools`.\n\n## See also\n\n- `LANGUAGE-TRACKER.md` (what Mzizi can and cannot do), `CHARTER.md` (v0.4),\n `design/RFC-0001` (syntax and the agent protocol), `RFC-0013` (the core language: programs),\n `RFC-0014` (Tier 2, draft), `RFC-0006` (contracts), `RFC-0008`\n (types, collections, records), `RFC-0009` (the comparison benchmark and the kill\n criterion), `RFC-0010` (contracts everywhere, draft), `RFC-0011` (handlers, the backend\n slice), `RFC-0012` (the language harness, draft), `benchmarks/prompts/mzizi-be-guide.md` (the\n one-file service guide) and `benchmarks/READINESS.md`.\n- The `mzizi-backend` skill: where a Mzizi service stands against the live TypeScript Workers.\n- The `mzizi-roots` skill: the Rust components built to support the language, its component\n model and where a component lowering would target.",
|
|
15
|
+
"description": "Use this skill when writing, checking or fixing Mzizi language source (.mz files): LANGUAGE-TRACKER.md as the one list of what Mzizi can and cannot do, the component syntax (enum tables, props, the view tree, when / for each, records, use motion), the backend service (routes, handlers with when / header / respond, example and ensure contracts; RFC-0011), the `mz check --agent` loop and its NDJSON diagnostics, `mz fix` for every exact fix, `mz contract`, `mz build` (a service to a local Rust + axum package), the diagnostic codes you will meet (MZ0101, MZ0106, MZ0312, MZ0313, MZ0406-MZ0409, MZ0602-MZ0613, MZ07xx, MZ08xx), programs (RFC-0013: fn main, let / var, functions, when / match, loops, int / float, text, list / map / set, records, enums, result with try) checked by `mz check` and run by `mz run`, the language harness, and exactly what is not built yet (modules, a standard library, component lowering, Rust interop, I/O, concurrency, generics, mz test, a release).",
|
|
16
|
+
"body_mdx": "# Writing and checking Mzizi\n\n**Mzizi is a general-purpose programming language, built to make Rust better, the way\nTypeScript makes JavaScript better** (`CHARTER.md` v0.4, \"Mzizi: a general-purpose\nprogramming language\"). How: no borrows, lifetimes or ownership in the language you write\n(RFC-0001 §1.8), with the harness at the core, the layer an agent reads. \"Makes Rust better\"\nis the goal Phase 0 measures, not a result. Only a backend `service` lowers to Rust today; no\ncomponent does.\n\nRust is its platform, the way JavaScript is TypeScript's: Mzizi is designed to lower to Rust (RFC-0001 §5), and Mzizi\nRoots is its component model, the way React is JavaScript's. Its goal is to be the language\npeople choose instead of TypeScript, Python and C++: write Mzizi, get Rust underneath. That\nis a goal, not a result. Phase 0 has one goal: to build Mzizi as a programming language,\nmeasured against the best existing language for each kind of task (RFC-0009). Nothing has\nbeen measured against that yet.\nIts syntax, type system and diagnostics are designed for machine authorship.\n\nThe language lives in [`mzizi-dev/mzizi`](https://github.com/mzizi-dev/mzizi) (public). Say\nwhich thing is which when you describe it:\n\n- **The language:** its syntax, type system, semantics, and contracts as a language\n feature.\n- **The harness, the core of Mzizi:** the layer an agent reads and works through. It covers\n the language as an agent sees it (grammar, types, contracts, canonical form), the agent\n protocol (`mz check --agent`, its diagnostics and fixes; RFC-0001 §4), and the plugin\n host that the toolchain, the CLI, the MCP server and plugins attach to natively. Part of\n it exists today, in the toolchain: the `mz check --agent` output this skill teaches. The\n harness as a whole is designed, not built: RFC-0012 (a draft) specifies it, and there is no\n `mz harness` command and no plugin host, so never say it works. It lives in\n `mzizi-dev/mzizi`, the language repository, and the `agent-tools`\n packages (`@nyuchi/mzizi-cli`, `mzizi-mcp`, `fundi`) are its clients. It is not\n `benchmarks/harness/`, which is the Phase 0 benchmark scorer.\n- **The toolchain, which implements and supports the language:** `mz` (the compiler, its\n IR and its NDJSON diagnostics, `mz fix`, `mz contract`, `mz build`), `@nyuchi/mzizi-cli`, the MCP\n server at `mcp.mzizi.dev`, these skills, and the Phase 0 benchmark harness. They are\n meant to attach to the harness. Never call the compiler \"the language\".\n- **The components, which support the language:** Mzizi Roots and the registry\n (`mzizi-dev/mzizi-registry`), the language's UI layer. That is the companion\n `mzizi-roots` skill. Supplying the benchmark's UI tasks is one of their jobs, not what\n they are.\n\nThis skill describes `main` at `cf3a44e` (2026-11-01): 765 tests in the workspace\n(`cargo test --workspace`, 634 of them in the compiler crate) and 33,540 lines in\n`compiler/src`. Where this skill and the code disagree, **the code is the fact** (RFC-0003\n§7.1). Every example below is copied from a file in the repository that passes `mz check`,\n`mz contract` and, for a program, `mz run` there.\n\n## Check the tracker before you claim anything\n\n[`LANGUAGE-TRACKER.md`](https://github.com/mzizi-dev/mzizi/blob/main/LANGUAGE-TRACKER.md) in\nthe language repository is **the one list of what Mzizi can and cannot do** (owner, 2026-09-30).\nIf a capability is not ✅ there, Mzizi does not have it, whatever any other page says, this\nskill included. Its status column is what the code on `main` does, never what an RFC designs.\nRead it before you tell anyone Mzizi can do something, and before you write Mzizi that relies\non a construct this skill does not show.\n\n**In a `program` (RFC-0013, rows C1–C10, all ✅ on `main` since the 2026-10-08 releases):**\nexpressions and operators (arithmetic, comparison with `is`, `and` / `or`, text interpolation\n`\"{x}\"`), bindings (`let`, `var`, assignment), functions with parameters, return types and\nrecursion, control flow (`when` / `else when` / `else`, exhaustive `match`, `when` and `match`\nas values, `for each` over `range(a, to = b)` or a list, `while`, `break`, `continue`, early\n`return`), `int` and `float` with the numeric methods, text methods (`s[i]`, `slice`, `find`,\n`split`, `chars`, `parse_int`, `parse_float`), collections (`list(T)`, `map(K, V)`, `set(K)`,\nbracket literals, indexing that returns an option read with `otherwise`, `in`, the named\nfolds), records with methods, enums with columns, and errors (`result(T, E)`,\n`return error(e)`, prefix `try`, a `match` on a result, and `main` returning a result).\n`mz run` lowers a program to a dependency-free Rust package, builds it with Cargo and runs it.\n\n**Still missing (❌ or partial in the tracker):** modules and imports (P1; one top-level\ndeclaration per file), a standard library (P2), lowering of components (P3 is partial: a\n`service` and a `program` lower, no component does), Rust interop (P6), state and I/O (P8),\nconcurrency (P9), generics and interfaces (P12), a formatter, a language server, `mz test`,\npackage management and a published release (`mzizi-lang-compiler` 0.0.0, `publish = false`).\nErrors are not yet mapped back to `.mz` lines (P5, designed). Do not write Mzizi that needs\nany of these, and do not tell anyone it exists.\n\nFour pilots have run (two on 2026-09-27, two on 2026-10-29) and none showed an advantage for\nMzizi. The run that tests the charter's kill criterion has not happened, so never write that\nMzizi is faster or better.\n\n## What exists, and what does not\n\n**Built and tested, in the toolchain:** the lexer, the recovering parser, the name and type\nresolver, the agent diagnostic protocol (`mz check --agent`), `mz fix`, the content-addressed\nIR, `mz outline`, contract evaluation (`mz contract`), the language harness (`mz harness`,\nRFC-0012: one machine-readable definition of every feature, operator and diagnostic code),\nnine primitives written in Mzizi (`primitives/*.mz`), component, service and program examples\n(`examples/*.mz`, each program with the output `mz run` must print in `examples/<name>.expected`),\nand the Phase 0 benchmark harness and runner. All of it is gated in CI.\n\n**Programs (RFC-0013), built and tested.** A `program` file holds one program with `fn main`.\n`mz check` checks it, and `mz run` lowers it to Rust, builds it and runs it:\n\n```mz\n## Fibonacci numbers by recursion, in RFC-0013's foundation slice (§18.1): functions,\n## calls, `let`, `var`, `when`, integer arithmetic and `print`, and no loops yet.\n## `mz run examples/fib.mz` prints what fib.expected holds; CI compares the two.\nprogram fib\n\n fn main\n print(\"fib(9) is {fib(9)}\")\n let n = 20\n let f = fib(n)\n print(\"fib({n}) is {f}\")\n var total = 0\n total = total + fib(10)\n total = total + fib(11)\n print(\"fib(10) + fib(11) is {total}, and that is fib(12): {total is fib(12)}\")\n print(parity(f))\n end fn main\n\n fn fib(n: int): int\n when n < 2\n return n\n end\n return fib(n - 1) + fib(n - 2)\n end fn fib\n\n fn parity(n: int): text\n when n % 2 is 0 and n > 0\n return \"{n} is even\"\n else\n return \"{n} is odd, or not positive\"\n end\n end fn parity\n\nend program fib\n```\n\n`mz run examples/fib.mz` prints:\n\n```text\nfib(9) is 34\nfib(20) is 6765\nfib(10) + fib(11) is 144, and that is fib(12): true\n6765 is odd, or not positive\n```\n\nErrors are values. A function returns `result(T, E)`, fails with `return error(e)`, and a\ncaller handles the result with `match` or propagates it with prefix `try`:\n\n```mz\n## Checks ages: one function fails with an error, its callers handle it with `match` or\n## propagate it with `try`, and `main` itself returns a result (RFC-0013 §12).\nprogram errors\n\n enum age_problem\n negative say \"is below zero\"\n too_old say \"is above 150\"\n end\n\n fn main: result(none, age_problem)\n print(describe(42))\n print(describe(-3))\n print(describe(200))\n let total = try total_age(20, 22)\n print(\"20 and 22 make {total}\")\n match total_age(20, 151)\n case ok sum\n print(\"20 and 151 make {sum}\")\n case error problem\n print(\"20 and 151 were rejected: {problem} {problem.say}\")\n end\n end fn main\n\n fn check_age(n: int): result(int, age_problem)\n when n < 0\n return error(negative)\n end\n when n > 150\n return error(too_old)\n end\n return n\n end fn check_age\n\n fn describe(n: int): text\n match check_age(n)\n case ok age\n return \"age {age} is fine\"\n case error problem\n return \"{n} is rejected: {problem} {problem.say}\"\n end\n end fn describe\n\n fn total_age(a: int, b: int): result(int, age_problem)\n return try check_age(a) + try check_age(b)\n end fn total_age\n\nend program errors\n```\n\n**The backend slice (RFC-0011), built and tested:** a `service` with HTTP routes and handlers\n(`when`, `header`, `respond`), checked by `mz check`. `mz contract` runs a service in process\nand evaluates its `example` and `ensure` clauses. `mz build` lowers a service to a local\nRust + axum package, which CI compiles, tests and serves. That is **the whole of the lowering\ntoday:** no component lowers, and the package is a local server, with no Workers, WebAssembly\nor Containers target and no deployment. The `mzizi-be` benchmark arm, the probe crate\n`mzprobe` and backend task B1 exist; no backend episode has run.\n\n**Not built:** everything in the \"still missing\" list above, lowering of components,\nrendering, a release and a published binary. A component `.mz` file today is checked and its contract\nevaluated; **nothing runs it.** Do not tell anyone Mzizi \"compiles to Rust\" or is \"production\nready\"; say it is designed to lower to Rust, and that only a service does so today.\n\n**In progress, as design only:**\n\n- **The harness.** RFC-0012 (draft). Only the agent protocol exists (`mz check --agent`,\n `mz fix`, `mz contract`).\n- **Contracts everywhere.** RFC-0010 (draft) extends contracts to functions, handlers,\n services and records. Component contracts (RFC-0006) and service contracts (RFC-0011 §7) are\n evaluated. Of RFC-0010's codes, `MZ0606`, `MZ0611`, `MZ0612` and `MZ0613` exist, for\n services; `MZ0607`–`MZ0610` do not exist in the compiler.\n- **Request bodies** for a service (`body input: <record>` and `reject` blocks, RFC-0011 §4.3)\n are designed and not built. So is state (task B4).\n- **Modules.** There are no imports. Components are referenced by bare name\n (`primitives/confirm_bar.mz` uses `button` and `alert` with no import line). A reference to\n another component's enum (`button_variant.ghost`) is warning `MZ0502`, because it cannot be\n checked until modules exist.\n\n**Results, as they fell.** The first two Phase 0 pilots ran on 2026-09-27 and neither showed an\nadvantage for Mzizi (two more ran on 2026-10-29, also with no advantage). On the frontier model the Mzizi and Dioxus arms tied, with Mzizi about 8%\nfewer transcript tokens. On the ~7B open-weight model, the design target, Mzizi did worse on\nall three metrics. Neither pilot is the kill-criterion run (RFC-0009 §6), and that run has not\nhappened. The pilots were tests inside Phase 0 on a few registry UI components against\nDioxus, not its goal. \"Designed for agents\" and \"aims to replace TypeScript, Python and C++\"\nare accurate; \"faster\" or \"better\" is not.\n\n## Getting `mz`\n\nThere is no release. Build it from source; the compiler crate has zero dependencies:\n\n```bash\ngit clone https://github.com/mzizi-dev/mzizi.git && cd mzizi/compiler\ncargo build --bin mz # ../target/debug/mz\ncargo run --bin mz -- check ../primitives/button.mz\n```\n\n**Never `cargo install mz`.** The `mz` crate on crates.io is an unrelated project, and the\ncompiler crate (`mzizi-lang-compiler`) is `publish = false`.\n\n## The commands\n\n`mz` takes exactly one file per call. `mz check ../primitives/*.mz` is a usage error (exit 2).\nLoop over files instead.\n\n| Command | Does |\n| ----------------------------------- | -------------------------------------------------------------------------------- |\n| `mz check <file>` | human-readable diagnostics |\n| `mz check --agent <file>` | NDJSON: one diagnostic per line, then one summary line. **Target this.** |\n| `mz fix <file>` | applies every `exact` fix in place, in one pass, then re-checks the file |\n| `mz contract <file>` | evaluates the file's `contract` block (only on a file that checks clean) |\n| `mz contract --agent <file>` | the same, as NDJSON; the summary adds `contract_clauses` and `contract_failures` |\n| `mz outline <file>` | the interface only (name, enums, props, contract subjects), as valid Mzizi |\n| `mz hash <file>` / `mz ir <file>` | the content-addressed root hash / every IR node with its hash and path |\n| `mz build <service.mz> --out <dir>` | lowers a service to a Rust + axum Cargo package in `<dir>` (only a clean file) |\n| `mz build <program.mz> --out <dir>` | lowers a program to a dependency-free Rust package in `<dir>` |\n| `mz run <program.mz>` | lowers a program, builds it with Cargo and runs it |\n| `mz harness version` | protocol, language version and the definition's SHA-256 |\n| `mz harness definition --agent` | every language-harness entry (features, operators, codes) as one line of JSON |\n| `mz harness entry <name>` | one entry, e.g. `mz harness entry let` |\n\nExit status: **0** no errors (warnings do not fail), **1** errors or a failed contract\nassertion, **2** a usage or I/O problem. Branch on the status; parse the NDJSON for detail.\n\n`mz run` is the exception, because 1 belongs to the program it runs. It exits with the\nprogram's own status: 0; 1 when `main` returns an error (`MZ0992`); 101 for a trap (`MZ0991`)\nor a panic; 141 when standard output is closed; 128 plus the signal that ended it; **3** when\nthe check failed and nothing ran, or the lowered Rust did not compile (`MZ0990`); and 2 for a\nusage, environment or I/O problem (`--agent` is not accepted, a file that is not a `program`).\n\nOn a **service**, `mz contract` runs the service in process (no lowering, no socket), and the\nsummary adds `contract_tested`, the number of generated requests its `ensure` clauses were\ntested over. `mz outline`, `mz hash` and `mz ir` do not cover services yet and exit 2. `mz\nbuild` on a component exits 2 (\"components do not lower yet\"), and on a file with errors it\nprints them, builds nothing and exits 1.\n\n`check` and `contract` are deliberately separate: a compile error is not a defect, and a\ncontract failure is. Your file is done when **both** `mz check --agent` and\n`mz contract --agent` exit 0.\n\n## The loop\n\n1. Write the file.\n2. `mz check --agent file.mz`. If it exits 0, go to 5.\n3. `mz fix file.mz` to apply every `exact` fix. Read each remaining diagnostic's `say`; apply\n `guess` fixes only after checking them.\n4. Fix everything reported, then go to 2. Errors are independent, so fix them all before\n re-running; do not fix one and re-run to \"see the next\".\n5. `mz contract --agent file.mz`. `MZ0603` means an assertion is false (a defect in the\n component or the contract); `MZ0605` means it could not be evaluated, which **counts as a\n failure**.\n\n## Reading the NDJSON\n\nThis file is wrong on purpose. It is the example in `benchmarks/prompts/mzizi-guide.md`, which\n`benchmarks/prompts/verify-guide.sh` checks against the real compiler.\n\n```mz\n## WRONG ON PURPOSE: three mistakes.\ncomponent tag\n\n enum tag_size\n snug class \"h-13 px-3\"\n end\n\n prop onPick: event(none)\n prop size: tag_size = snug\n\n view\n row\n slot = \"tag\"\n class = \"rounded-full {size.class}\"\n tap = on_pick\n end\n end\n\n contract\n slot is \"tag\"\n tag_size.snug height 52\n end\n\nend\n```\n\n`mz check --agent tag.mz` exits 1 and prints exactly:\n\n```text\n{\"code\":\"MZ0101\",\"severity\":\"error\",\"file\":\"tag.mz\",\"span\":[8,8,8,14],\"say\":\"`onPick` is not snake_case — Mzizi names are snake_case, so write `on_pick`\",\"fix\":{\"span\":[8,8,8,14],\"replace\":\"on_pick\",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0602\",\"severity\":\"error\",\"file\":\"tag.mz\",\"span\":[21,26,21,28],\"say\":\"a contract clause needs a predicate (is / in / contains / not_empty / at_least / uses / min_height / shows), found `52`\",\"fix\":{\"span\":[21,26,21,26],\"replace\":\"is \",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0208\",\"severity\":\"error\",\"file\":\"tag.mz\",\"span\":[24,1,24,4],\"say\":\"top-level blocks close with their name: write `end component tag`\",\"fix\":{\"span\":[24,1,24,4],\"replace\":\"end component tag\",\"confidence\":\"exact\"}}\n{\"summary\":true,\"errors\":3,\"warnings\":0,\"exact_fixable\":3,\"ms\":0}\n```\n\n`mz fix tag.mz` then prints `mz: applied 3 exact fixes; 0 errors (0 exact-fixable) remain`, and\n`mz contract --agent tag.mz` reports `\"contract_clauses\":2,\"contract_failures\":0`.\n\n- `code`, `severity` (`error` or `warning`), `file` (the path you passed), `span`\n `[line, col, end_line, end_col]` (1-indexed, end exclusive) and `say` are always present.\n- `say` quotes the offending source, so you need not re-read the file to understand it.\n- `fix` is optional: put `replace` at `fix.span`. An empty `replace` is a deletion. `exact`\n is safe to apply blind; `guess` has the right location and an inferred replacement.\n- The last line is always the summary.\n\n## Syntax essentials\n\nOne file holds one component, or one service (see \"Services\" below). There is one form per\nintent: if a construct is not shown here or in the RFCs, assume it does not exist. The order\ninside a component is doc lines, `use`, `enum` and `record`, `prop`, `view`, `fn`, `contract`.\n\nA complete primitive, `primitives/badge.mz`. Its `px-2 py-0.5` and `gap-1` are the corpus\nbadge's, copied unchanged, and are below the spacing floor (14px inset, 7px gaps, 1.5 leading;\nthe `mzizi-design` skill), so they are not a model for new code: a component you write keeps\nthe floor, or carries a `mzizi-spacing-exempt: <reason>` comment:\n\n```mz\n## A small status label. Not interactive, so no touch floor applies.\n## Corpus reference: n2-primitives/badge.tsx.\ncomponent badge\n\n enum badge_variant\n default class \"bg-primary text-primary-foreground\"\n secondary class \"bg-secondary text-secondary-foreground\"\n destructive class \"bg-destructive/10 text-destructive dark:bg-destructive/20\"\n outline class \"border-border text-foreground bg-input/30\"\n ghost class \"hover:bg-muted hover:text-muted-foreground\"\n link class \"text-primary underline-offset-4 hover:underline\"\n end\n\n prop variant: badge_variant = default\n prop label: text\n\n view\n mark\n slot = \"badge\"\n portal = \"https://mzizi.dev/components/badge\"\n variant = variant\n class = \"inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-md border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all {variant.class}\"\n text = label\n end\n end\n\n contract\n every badge_variant class not_empty\n badge_variant.default class contains \"bg-primary\"\n slot is \"badge\"\n end\n\nend component badge\n```\n\n- **Shape.** `component <name>` opens the file and `end component <name>` closes it, repeating\n the name. Every other block (`enum`, `record`, `view`, `contract`, each element, each `when`,\n each `for each`, each `fn`) closes with a bare `end`. Never `end enum` or `end view`.\n- **Comments.** `##` to end of line is the only comment form. `//` and `/* */` are errors.\n- **Names** are `snake_case`. `onRemove` is `MZ0101`, with the snake form as its `exact` fix.\n- **Enums are variant tables.** One row per variant: its name, then `column value` pairs. Every\n variant has every column (`MZ0303` otherwise). A column can carry meaning beyond styling, as in\n `primitives/alert.mz`, where `announce` ties the ARIA role to the severity:\n\n ```mz\n enum alert_variant\n default class \"bg-card text-card-foreground\" announce \"status\"\n destructive class \"bg-card text-destructive\" announce \"alert\"\n warning class \"bg-card text-[var(--status-warning,#FFD740)]\" announce \"alert\"\n info class \"bg-card text-[var(--status-info,#00B0FF)]\" announce \"status\"\n end\n ```\n\n- **Heights come from the class.** A variant's `height` is the pixels its `h-N` or `size-N`\n class renders (`N × 4`). You may leave the `height` column out. A written `height` that\n disagrees with the class is `MZ0313`, and its `exact` fix is the rendered number.\n- **Props.** `prop <name>: <type>`, optionally `= <default>`. The types are an enum name\n (`= default`), `text`, `bool`, `int`, `event(none)`, `event(<type>)`, `list(<type>)`\n (omitted means empty), `option(<type>)` (omitted means none) and a record name. Never\n `option(list(T))` (`MZ0703`).\n- **The view** holds exactly one element tree. An element is a bare word on its own line,\n closed by `end`. Inside it, each line is an attribute `name = value` (the `=` is required),\n a child element, a `when` block, a `for each`, or `nothing`. Strings interpolate with\n `{...}`; there is no `+`.\n- **Conditionals.** `when x` … `end`, `when not x`, `when v is <variant>`, and for an option or\n a list `when x is none` … `else` … `end`. There is no `if` (`MZ0407`, `exact` fix `when`)\n and no `match`. From `primitives/avatar.mz`:\n\n ```mz\n when image is none\n row\n slot = \"avatar-fallback\"\n class = \"flex size-full items-center justify-center rounded-full bg-muted text-muted-foreground {size.text_class}\"\n text = initials\n end\n else\n picture\n slot = \"avatar-image\"\n source = image\n alt = alt\n class = \"aspect-square size-full rounded-full object-cover\"\n end\n end\n ```\n\n- **Lists** render with `for each item in items`, whose first line is its `key = item.<field>`.\n- **Capabilities.** `use motion` if the component animates (`primitives/spinner.mz`). Nothing\n else needs `use`.\n- **Events.** Bind with `tap = on_tap` / `change = on_change`. A parameterless `fn` can `emit`\n an event prop, as in `examples/connectivity_bar.mz`:\n\n ```mz\n fn retry\n emit on_state_change(syncing)\n end\n ```\n\n- **No React carry-overs.** `...props` spreads are `MZ0106` (`exact` fix deletes the line).\n An `as_child` prop is warning `MZ0312`: delete it, and keep what the `as_child = false`\n branch renders. `className` pass-through has no equivalent.\n\n`benchmarks/prompts/mzizi-guide.md` is the complete one-file guide the benchmark gives a model,\nincluding records, `for each` and a React-to-Mzizi table. Read it before writing a component.\n\n## Services (the backend slice, RFC-0011)\n\nA backend program is one file holding one `service`, closed by `end service <name>`. Every\ninner block (`record`, `route`, `fallback`, `when`, `contract`) closes with a bare `end`. The\norder is doc lines, service-level `header` lines, `record`s (and `enum`s), `route`s,\n`fallback`, `contract`. There are no imports, no `main`, no server setup and no port.\n\n`examples/registry.mz`, the example service (22 clauses, all holding; its `ensure` clauses\ntested over 61 generated requests):\n\n```mz\n## A registry of two components: routes, methods, redirects and a JSON 404.\n## RFC-0011's worked example, shaped like the API gateway's `/v1/ui` routes (RFC-0009 §2.4, B1).\nservice registry\n\n header \"x-mzizi-source\" \"fixture\"\n\n record problem\n field error: text\n end\n\n record health\n field status: text\n end\n\n record item\n field name: text\n end\n\n route health_check\n get \"/v1/health\"\n respond 200 json health status \"ok\"\n end\n\n route ui_list\n get \"/v1/ui\"\n respond 200 file \"fixtures/ui.json\"\n end\n\n route ui_item\n get \"/v1/ui/{name}\"\n when name in \"badge\" \"button\"\n respond 200 json item name name\n end\n header \"cache-control\" \"private, no-cache, no-store, max-age=0, must-revalidate\"\n respond 404 json problem error \"Not found\"\n end\n\n fallback\n header \"cache-control\" \"private, no-cache, no-store, max-age=0, must-revalidate\"\n respond 404 json problem error \"Not found\"\n end\n\n contract\n example get \"/v1/health\" body.status is \"ok\"\n example get \"/v1/ui\" status is 200\n example get \"/v1/ui\" header \"content-type\" is \"application/json\"\n example get \"/v1/ui/button\" status is 200\n example get \"/v1/ui/button\" body.name is \"button\"\n example get \"/v1/ui/does-not-exist\" status is 404\n example get \"/v1/ui/does-not-exist\" body.error is \"Not found\"\n example get \"/v1/ui/does-not-exist\" header \"cache-control\" is \"private, no-cache, no-store, max-age=0, must-revalidate\"\n example head \"/v1/ui/button\" status is 200\n example head \"/v1/ui/button\" body is \"\"\n example options \"/v1/ui\" status is 204\n example options \"/v1/ui\" header \"allow\" is \"GET, HEAD, OPTIONS\"\n example delete \"/v1/ui/button\" status is 405\n example delete \"/v1/ui/button\" body is \"\"\n example get \"/v1/ui/\" status is 308\n example get \"/v1/ui/?limit=2\" header \"location\" is \"/v1/ui?limit=2\"\n example get \"/nope\" status is 404\n example get \"/nope\" header \"content-type\" is \"application/json\"\n ensure status in 200 204 308 404 405\n ensure header \"x-mzizi-source\" is \"fixture\"\n ensure when status is 404 then body.error is \"Not found\"\n ensure when status is 405 then header \"allow\" is \"GET, HEAD, OPTIONS\"\n end\n\nend service registry\n```\n\n- **A route** is `route <name>`, then exactly one `<method> \"<pattern>\"` line, then zero or\n more `query <name>: option(<scalar>)` lines, then statements, then `end`. The methods are\n `get`, `head`, `post`, `put`, `patch`, `delete` and `options`, lower case; a second method on\n the same path is a second route.\n- **A pattern** is `/` or `/`-separated segments of lower-case letters, digits and `-._~`,\n or a whole-segment `{name}`, which binds `name` as `text`. No trailing slash, no empty\n segment, no `?`. A literal segment beats a parameter at the same position.\n- **A query parameter is always an option:** `query limit: option(int)`. `option(int)` is\n `-`? then ASCII digits, so `\"0x10\"`, `\"1e2\"`, `\" 7 \"` and `\"1.5\"` are `none`; `option(bool)`\n is exactly `true` or `false`. Bad input is absence, never an error.\n- **A handler has three statements**, and every path through it must end in `respond`:\n\n | Statement | Means |\n | -------------------------------- | --------------------------------------------------------- |\n | `when <cond>` … [`else` …] `end` | a branch; with no `else`, a false condition falls through |\n | `header \"<name>\" \"<value>\"` | set a header on the response this path sends |\n | `respond <status> [<body>]` | send the response; nothing after it on the path runs |\n\n A condition is `p` / `not p` (a `bool`), `p is <literal>`, `p in <literal> …`,\n `p at_least <n>` / `p at_most <n>` (an `int`), or `p is none` … `else` (an option, which is\n narrowed in the `else`). There is no `if`, `==`, `return`, assignment, loop or call.\n\n- **Bodies:** none; `json <record> <field> <value> …` (every field once, a literal or a\n parameter, no punctuation; `application/json`); `text \"<string>\"` (may interpolate a\n parameter as `{name}`); or `file \"<path>\"` (a fixture beside the `.mz` file, embedded at\n build time). A response record's fields are scalars or options of scalars. `204`, `304` and\n `1xx` take no body.\n- **Headers** are lower case. A service-level `header` applies to every response, runtime\n ones included; a route's own `header` for the same name wins. `content-length` and\n `transfer-encoding` belong to the server.\n- **The runtime answers what HTTP defines, so do not write it:** `HEAD` for a `get` route\n (its status and headers, no body); `OPTIONS` (`204` with `allow`, e.g. `GET, HEAD, OPTIONS`);\n any other undeclared method on a matched path (`405` with the same `allow`, no body); and a\n trailing slash or `//` (`308` to the canonical path, query kept, never off-site). A path no\n route matches runs `fallback`, or gets an empty `404` when there is none.\n- **The contract:** `example <method> \"<target>\" <facet> <predicate>` sends one request;\n `ensure [when <facet> <predicate> then] <facet> <predicate>` must hold for every generated\n request. A facet is `status`, `header \"<name>\"`, `body` or `body.<field>`; a predicate is\n `is <value>`, `in <value> …`, `not_empty`, `contains \"<text>\"`, `at_least <n>` or\n `at_most <n>`. `header \"<name>\" is none` asserts absence and `body is \"\"` an empty body.\n `ensure` is **tested** over the generated set, never proven.\n\nThis file is wrong on purpose. It is the example in `benchmarks/prompts/mzizi-be-guide.md`:\n\n```mz\n## WRONG ON PURPOSE: three mistakes.\nservice shop\n\n record problem\n field error: text\n end\n\n route item\n GET \"/v1/items/{id}/\"\n when id is \"1\"\n respond 200 text \"one\"\n end\n end\n\nend service shop\n```\n\n`mz check --agent candidate.mz` exits 1 and prints:\n\n```text\n{\"code\":\"MZ0613\",\"severity\":\"warning\",\"file\":\"candidate.mz\",\"span\":[2,9,2,13],\"say\":\"`service shop` has no `contract` block — its behaviour is unverified (RFC-0010 §8)\"}\n{\"code\":\"MZ0801\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[9,5,9,8],\"say\":\"`GET` is not a method — a route declares one of get, head, post, put, patch, delete, options\",\"fix\":{\"span\":[9,5,9,8],\"replace\":\"get\",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0802\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[9,9,9,26],\"say\":\"`\\\"/v1/items/{id}/\\\"` ends with `/` — patterns have no trailing slash; the runtime redirects `…/` to the path without it\",\"fix\":{\"span\":[9,9,9,26],\"replace\":\"\\\"/v1/items/{id}\\\"\",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0804\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[13,3,13,6],\"say\":\"a path through `route item` reaches `end` without a `respond` — when the `when` on line 10 does not respond on both branches; every path must respond\"}\n{\"summary\":true,\"errors\":3,\"warnings\":1,\"exact_fixable\":2,\"ms\":0}\n```\n\n`mz fix` applies the two `exact` fixes. `MZ0804` needs a decision: add\n`respond 404 json problem error \"Not found\"` after the `when`'s `end`, so the false path\nresponds too.\n\n**Building it.** `mz build registry.mz --out <dir>` checks the file and, with zero errors,\nwrites `Cargo.toml`, `src/main.rs` and the fixtures. The package is its own workspace root,\ngenerated output that is never committed, and depends on `axum =0.8.9` and `tokio =1.53.1`\n(tests add `tower`, `http-body-util` and `serde_json`). Every `example` becomes one\n`#[tokio::test]` (`cargo test` in the package); `ensure` clauses are evaluated by\n`mz contract` only. `cargo run --release --manifest-path <dir>/Cargo.toml` serves\n`127.0.0.1:$PORT` (8080 by default) and prints `listening on http://127.0.0.1:<port>`.\n\n## Contracts\n\nEvery component carries a `contract … end` block: one assertion per line,\n`<subject> <predicate>`, no colon. A file with no contract is warning `MZ0501`. It is checked\nagainst the file's own enums, view attributes and prop defaults (RFC-0006). From\n`primitives/button.mz`:\n\n```mz\n contract\n every button_size height at_least 48\n button_size.default height is 56\n button_size.sm height is 48\n button_variant.default class contains \"bg-primary\"\n slot is \"button\"\n end\n```\n\n| Subject | Means |\n| --------------------------- | -------------------------------------------------- |\n| `every <enum> <column>` | that column in every variant |\n| `<enum>.<variant> <column>` | one cell |\n| `<attribute>` | the root view element's attribute (`slot`, `role`) |\n| `<prop>` | the prop's default (only a prop that has one) |\n| `<element> \"<text>\"` | the element carrying that text |\n| `when <name>` | the `when` branch that mentions that name |\n\nPredicates: `is <value>` (exactly as written: `height is \"52\"` fails against `height 52`),\n`contains \"<s>\"`, `not_empty`, `at_least <n>`, `in \"<a>\" \"<b>\"`, `uses \"--token\"`,\n`uses <component>` (no subject), `shows <element> \"<t>\"` (after `when <name>`) and\n`min_height <n>`. `examples/connectivity_bar.mz` uses the last two:\n\n```mz\n when offline shows button \"Retry\"\n button \"Retry\" min_height 48\n```\n\nAssert what the source guarantees: every interactive size clears 48px, the `slot`, and the key\ncells. An assertion on a prop with no default is `MZ0605` (unevaluable), which fails.\n\n`mz contract` checks nothing rendered. The rendered half is `benchmarks/harness`, which diffs a\n`.mz` component's variants, defaults, touch heights and `data-slot` set against the Rust\nreference. Mzizi Roots components carry the same clause grammar as `pub const CONTRACT` and\ncheck it against their server-rendered markup (see the `mzizi-roots` skill).\n\nThe direction (RFC-0010, draft) is that **everything built carries a contract**: components,\nlanguage functions and backend handlers. The component form and the service form (\"Services\"\nabove) are evaluated today; functions have no contracts because they have no bodies.\n\n## Diagnostic codes you will meet\n\nThe ranges are `MZ01xx` lexing and names, `MZ02xx` blocks and closers, `MZ03xx` declarations,\n`MZ04xx` the view, `MZ05xx` lints, `MZ06xx` contracts, `MZ07xx` types and resolution, and\n`MZ08xx` services and handlers. The full tables are RFC-0001 §4.7, RFC-0006 §8, RFC-0008\n(types), RFC-0010 §6 and RFC-0011 §9. A service reuses `MZ0701`, `MZ0707`, `MZ0708` and\n`MZ0710` for unknown names and un-narrowed options.\n\n| Code | Severity | Written | Repair |\n| ------------------- | -------- | ---------------------------------------------------------------------------------- | ---------------------------------------------- |\n| `MZ0101` | error | a name that is not `snake_case` | `exact`: the snake form |\n| `MZ0105` | error | `list<T>`, `T[]`, `[T]` | `exact`: `list(T)` |\n| `MZ0106` | error | `...props`, `{...props}`, `..attributes` | `exact`: delete (the whole line when alone) |\n| `MZ0204` | error | a block never closed | `guess`: insert the `end`s |\n| `MZ0206`–`MZ0208` | error | a wrong closer (`end view`, a bare `end` for `component`) | `exact`: rewrite the whole closer line |\n| `MZ0303` | error | a variant missing a column another variant has | add the column |\n| `MZ0312` | warning | `prop as_child: bool` (React `asChild`) | `exact` when unread: delete the prop |\n| `MZ0313` | error | `icon class \"size-14\" height 48` | `exact`: the rendered px (`56`) |\n| `MZ0406` | error | `class \"flex\"` (attribute without `=`) | insert `=` (`exact` for known attribute words) |\n| `MZ0407` | error | `if open` | `exact`: `when open` |\n| `MZ0408` | error | an attribute directly under `view` | move it inside the root element |\n| `MZ0409` | error | `span class = \"x\"` (more after an element word) | put the element word on its own line |\n| `MZ0501` | warning | no `contract` block | write one |\n| `MZ0502` | warning | `<other>_enum.variant` from another component | none until modules |\n| `MZ0602` | error | a clause with no predicate (`height 52`) | `exact`: insert `is` (needs an operand) |\n| `MZ0603` | error | **an assertion that does not hold** | fix the component or the clause |\n| `MZ0605` | error | an assertion that cannot be evaluated | assert something the file defines |\n| `MZ0606` | error | an `example` status that an `ensure status in …` rules out | fix one of the two |\n| `MZ0611` / `MZ0612` | error | a service `ensure` / `example` that does not hold | `say` quotes the request that broke it |\n| `MZ0613` | warning | a service with no or an empty `contract` block | write one |\n| `MZ0701` | error | an unknown type (`strng`) | alias or nearest-name fix |\n| `MZ0706` | error | a default that does not fit its type | varies |\n| `MZ0707` / `MZ0708` | error | an unknown name / variant, column or field | nearest-name fix |\n| `MZ0710` | error | an option used without `when … is none … else` | narrow it first |\n| `MZ0711` / `MZ0712` | error | a wrong kind of value / a `when` that does not fit its type | see `say` |\n| `MZ0801` | error | a route not opening with `<method> \"<pattern>\"`, `GET`, a second method line | `exact` for `GET`; else a `guess` |\n| `MZ0802` | error | a bad pattern: trailing `/`, empty segment, `?`, no leading `/` | `exact` where the repair is certain |\n| `MZ0803` | error | two routes with one method and the same pattern | delete or change one |\n| `MZ0804` | error | a handler path that reaches `end` without `respond` | add the `respond` |\n| `MZ0805` | error | a line after `respond` that can never run | `exact`: delete it |\n| `MZ0806` | error | a bad `respond`: no status, not 100–599, a body on `204` | `exact` deletes a body that is not allowed |\n| `MZ0807` | error | a bad `header`: `\"Cache-Control\"`, set twice, server-owned | `exact`: the lower-case name |\n| `MZ0808` | error | a `json` literal with a missing, repeated or ill-typed field (unknown is `MZ0708`) | give every field once |\n| `MZ0809` | error | `query limit: int` | `exact`: `option(int)` |\n| `MZ0810` | error | a `file \"…\"` fixture that does not exist | add the file or fix the path |\n| `MZ0811` | error | a line a service or handler cannot hold (`return`, `=`, `view`) | see `say` (`is` for `=` is `exact`) |\n| `MZ0812` | error | a second `fallback`, or a route or parameter name used twice | rename or delete one |\n\nTwo real outputs, from pilot-2 candidates written by the 7B model and re-checked on `main`\n(`benchmarks/results/2026-09-27-pilot-2/raw/scored/…/mzizi/{button,badge}/seed-1/iter-01/candidate.mz`):\n\n```text\n{\"code\":\"MZ0313\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[16,35,16,37],\"say\":\"`icon` declares `height 48` but its class `size-14` renders 56px (N × 4) — write `height 56`, or drop the column and let the class say it once\",\"fix\":{\"span\":[16,35,16,37],\"replace\":\"56\",\"confidence\":\"exact\"}}\n{\"code\":\"MZ0106\",\"severity\":\"error\",\"file\":\"candidate.mz\",\"span\":[15,8,15,16],\"say\":\"`...props` is a spread, and Mzizi has none: a component names each prop it reads and each attribute it sets. `props` has no equivalent — delete the line\",\"fix\":{\"span\":[15,1,16,1],\"replace\":\"\",\"confidence\":\"exact\"}}\n```\n\n## Contributing to the language\n\n- A language change needs an RFC in `design/`. Take the next number not used on `main` or\n claimed by an open PR: RFC-0005 is reserved, and RFC-0011 (handlers), RFC-0012 (the language\n harness), RFC-0013 (the core language) and RFC-0014 (Tier 2, draft) exist, so the next is\n RFC-0015.\n- A PR that adds or changes a language feature adds or updates its language-harness entry in\n `compiler/src/harness.rs` in the same PR: one entry in `FEATURES`, one `Code` per diagnostic\n code with a `trigger`, and tested examples. `cargo test --test harness` is the check.\n- A PR that changes what the language can do updates its row in `LANGUAGE-TRACKER.md` in the\n same PR, with evidence, and adds a `CHANGELOG.md` entry.\n- CI runs `cargo fmt -- --check`, `cargo clippy --all-targets -- -D warnings`, `cargo test`,\n and `mz check` and `mz contract` over every file in `examples/` and `primitives/`, one file\n per call. Its `lowering` job runs `mz build` on `examples/registry.mz`, then `cargo test` and\n one request over a socket against the package, `mzprobe verify` of task B1, and `mz run` of\n every example program with its output diffed against `examples/<name>.expected`. `AGENTS.md`\n in the repo has the exact script.\n- Pull requests are squash-merged through the merge queue, and the squash commit keeps every\n commit message. State what you measured in the commit message.\n- The repo consumes nothing else in the org: no build step, test or script may reach into\n `mzizi-registry` or `agent-tools`.\n\n## See also\n\n- `LANGUAGE-TRACKER.md` (what Mzizi can and cannot do), `CHARTER.md` (v0.4),\n `design/RFC-0001` (syntax and the agent protocol), `RFC-0013` (the core language: programs),\n `RFC-0014` (Tier 2, draft), `RFC-0006` (contracts), `RFC-0008`\n (types, collections, records), `RFC-0009` (the comparison benchmark and the kill\n criterion), `RFC-0010` (contracts everywhere, draft), `RFC-0011` (handlers, the backend\n slice), `RFC-0012` (the language harness, draft), `benchmarks/prompts/mzizi-be-guide.md` (the\n one-file service guide) and `benchmarks/READINESS.md`.\n- The `mzizi-backend` skill: where a Mzizi service stands against the live TypeScript Workers.\n- The `mzizi-roots` skill: the Rust components built to support the language, its component\n model and where a component lowering would target.",
|
|
17
17
|
"source": "mzizi-dev/agent-tools/mzizi-skills/skills/mzizi-language",
|
|
18
18
|
"files": []
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
21
|
"name": "mzizi-roots",
|
|
22
22
|
"category": "build",
|
|
23
|
-
"description": "
|
|
24
|
-
"body_mdx": "# Mzizi Roots — the Rust build of Mzizi's components\n\n**Mzizi Roots** is Mzizi's own components in Rust: UI components, and server components as\nhost-agnostic Rust. The UI crates are built on Dioxus 0.7 today and are moving to Mzizi's own\nRust renderer (a pure `render(props) -> String`, with behaviour attached in the browser through\n`web-sys`). Do not write new Dioxus code for a Roots component. They are built to support the Mzizi language (the\n`mzizi-language` skill) as its component model, the way React is JavaScript's; they are not\nthe language. They live in\n[`mzizi-dev/mzizi-registry`](https://github.com/mzizi-dev/mzizi-registry) and are compiled by\nthe crates in its `mzizi-rs/` workspace. The plan is `docs/roots/RFC-roots.md` there.\n\n**Rust first.** Where a Rust implementation exists, present it and use it first. The React/TSX\ncomponents are **the React build**: they keep working for existing React apps, and they are\ndeprioritised, so no new work goes into them.\n\nThis skill describes registry `main` at v4.18.0 (`0d17818`, 2026-11-01) and its `contracts/`\n(77 component contracts in four families, and the `lib/` family), the crates at **0.7.0**\n(registry v4.18.0, on crates.io since 2026-11-01; 0.7.0 adds two crates, `mzizi-resilience`,\nthe N5 core, and `mzizi-chaos-engine`, the N8 seeded chaos engine, from registry PRs #584\nand #586; 0.6.0 before it carried the `--assurance-*` and `--attestation-*` roles in the generated\n`mzizi-tokens` pack, from mzizi-registry #578, and 0.5.0 the `mzizi-mongodb` security fixes from mzizi-registry #567; the UI crates\notherwise match 0.4.0, on crates.io since 2026-10-20 with `markdown-renderer` 1.2.0 and 1.2.1\nfrom mzizi-registry #483 and #517), and `mzizi-mcp` 0.13.0. It replaces three 0.7.0 skills:\n`scaffold-component` (now \"Contributing a component\"), `simplify` (now \"Reuse before build\")\nand the frontend half of `ecosystem-app-setup` (now \"Starting an app\"). Tokens, brand and the\nUbuntu constraints are in the `mzizi-design` skill.\n\n## Install\n\nTwo umbrella crates with no code of their own re-export the node crates behind features:\n\n```sh\ncargo add mzizi-roots # UI: tokens + ui + brand + shell (all default features)\ncargo add mzizi-roots-server # server: assurance + fundi + docs + discovery\n```\n\nTake only what you use:\n\n```toml\n[dependencies]\nmzizi-roots = { version = \"0.7\", default-features = false, features = [\"brand\"] }\n```\n\nTo follow `main`, or pin a commit, install from git. Cargo finds a crate by name anywhere in the\nrepository's workspace:\n\n```sh\ncargo add mzizi-roots --git https://github.com/mzizi-dev/mzizi-registry\n```\n\n```toml\nmzizi-brand = { git = \"https://github.com/mzizi-dev/mzizi-registry\", rev = \"<commit>\" }\n```\n\nA single node crate works too (`cargo add mzizi-ui`). The crates depend on `dioxus` with\n`macro`, `signals`, `hooks` and `html` only, so the app that mounts them chooses web, desktop,\nmobile or server rendering.\n\n`mzizi add <name> --target rust` (`@nyuchi/mzizi-cli`) records the crate and prints the\n`cargo add` rather than copying a `.rs` file into your project: a copied component is a fork\nof a crate module.\n\n## What the MCP returns\n\n`mzizi_get_component` on `https://mcp.mzizi.dev/mcp` (free, no account; `mzizi-mcp` 0.11.0) is\nRust first. It returns `{name, renamedFrom?, lead, note, rust, react, docs?, versions?}`:\n\n- `lead` is `\"rust\"` when the component has a Roots implementation, else `\"react\"`; `note`\n says which build to use.\n- `rust` is the registry's `/v1/rs/<name>` answer with the install first: `crate`\n (`{name, registry, git}`), `install.cargo` (`cargo add <crate>`), `install.pinned`\n (`cargo add <crate> --git https://github.com/mzizi-dev/mzizi-registry --rev <pin>`, which\n builds exactly the source shown), `module` (e.g. `mzizi_brand`), `contract` (the\n `contract … end` text, or `null`), and `files[]` with the `.rs` source inline. With no Rust\n it is `null`.\n- `react` is the React build: `build: \"react\"`, `status: \"deprioritised\"`, `install` (the\n `npx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>` line), then the `/v1/ui/<name>`\n body. It is no longer under a `component` key (that was 0.10.x).\n\n`mzizi_list_components` rows carry `rustCrate` when a component has Rust, the response carries\n`withRust`, and `rust: true` lists only those. `mzizi_search` hits carry `rustCrate` too. For\n`mzizi-hero-stat` the answer is `lead: \"rust\"`, crate `mzizi-brand`, and the four-clause\ncontract shown under \"Contracts\" below.\n\n## The crates\n\nOne crate per registry node that has Rust. The map is `mzizi-rs/crate-for-node.json`, and\n`pnpm rust:generate` refuses a node with Rust in it that no crate claims.\n\n| Node directory | Crate | Umbrella | What it holds |\n| ------------------- | ----------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `n1-tokens` | `mzizi-tokens` | `mzizi-roots` (always on) | the 21-family palette and scales as Rust consts, generated from `lib/tokens/palette.source.ts` |\n| `n2-primitives` | `mzizi-ui` | `mzizi-roots` (`ui`) | alert, avatar, badge, button, card, chart, input, label, markdown renderer, progress, safe-area frame, separator, skeleton, status badge |\n| `n3-brand` | `mzizi-brand` | `mzizi-roots` (`brand`) | alert banner, avatar stack, cover header, empty state, escalation card, gauge card, hero stat, meta tile, stats row, success screen, suitability card, user card (all `mzizi-*`) |\n| `n7-shell` | `mzizi-shell` | `mzizi-roots` (`shell`) | bottom nav, command palette, connectivity bar, deep-link handler, footer, mini-app runtime, notification center, persistent player, route guard, theme provider, toast provider, update prompt |\n| `n8-assurance` | `mzizi-assurance` | `mzizi-roots-server` (`assurance`) | probes, telemetry, the OTLP exporter, a11y and RTL conformity checks, alert and incident engines |\n| `n9-fundi` | `mzizi-fundi` | `mzizi-roots-server` (`fundi`) | the Fundi reporter and learning loop (client side) |\n| `n10-documentation` | `mzizi-docs` | `mzizi-roots-server` (`docs`) | the docs API and routing, AI context, the changelog and docs renderers (brings in `dioxus`) |\n| `n11-discovery` | `mzizi-discovery` | `mzizi-roots-server` (`discovery`) | page metadata and Schema.org JSON-LD |\n\nN5 resilience has a crate since 0.7.0: `mzizi-resilience` (circuit breaker, retry backoff,\nbulkhead, rate limiter, fallback chain and deadline, each a sans-IO state machine with a\n`contracts/lib/` contract). Depend on it directly; no umbrella re-exports it yet, and\n`crate-for-node.json` still maps `n5-resilience` to `mzizi-data`. `mzizi-chaos-engine` (N8, a\nseeded deterministic fault schedule for chaos tests that refuses to run in production) is the\nsame. N4 safety and N6 pages have no crate (N6 never will: a page implements nothing). A Workers adapter\n(`mzizi-worker`) is planned for `mzizi-roots-server` and has not landed.\n\nThe registry has 577 items and 55 `.rs` files. Cite `GET https://api.mzizi.dev/v1/ui` or\n`/v1/stats` for a current count rather than repeating these. To find a component's crate, read\n`rust.crate` from `mzizi_get_component`, or take its node directory and read the table above.\n`https://api.mzizi.dev/v1/rs/<name>` also serves a `crate` field, derived from the same map on\nregistry `main`; the API serves the registry at a pinned commit, and until the gateway's pin\nmoves past the map it names `mzizi-ui` for every component. Trust the map, or the MCP.\n\n## The renderer\n\nMzizi components are built in-house in Rust, not borrowed from another framework.\n\n- **Built so far.** Seven components render with Mzizi's own `render(props) -> String`: the\n skeleton and six static primitives, merged in `mzizi-dev/mzizi-registry`\n ([#506](https://github.com/mzizi-dev/mzizi-registry/pull/506) and\n [#510](https://github.com/mzizi-dev/mzizi-registry/pull/510)).\n- **Not yet.** Dioxus is still a dependency of `mzizi-ui`, `mzizi-brand`, `mzizi-shell`,\n `mzizi-docs` and `mzizi-roots` until the migration in the registry's `RFC-roots-renderer`\n finishes. The examples in this skill use Dioxus because the published crates need it today.\n- **Direction.** No Dioxus, Leptos or Yew in new work. Astro renders HTML on the server, static\n components ship zero WASM, there is at most one WASM module per page, and Tokio runs\n server-side only.\n\n## Using a UI component\n\nThis compiles against `mzizi-roots` 0.7.0 from crates.io and renders the component to a static\nHTML fragment with `dioxus-ssr`:\n\n```toml\n[dependencies]\nmzizi-roots = { version = \"0.7\", default-features = false, features = [\"brand\"] }\ndioxus = { version = \"0.7\", default-features = false, features = [\"macro\", \"signals\", \"hooks\", \"html\"] }\ndioxus-ssr = \"0.7\"\n```\n\n```rust\nuse dioxus::prelude::*;\nuse mzizi_roots::brand::{AlertSeverity, MziziAlertBanner};\n\nfn banner() -> Element {\n rsx! {\n MziziAlertBanner {\n r#type: \"Thunderstorm\",\n severity: AlertSeverity::Severe,\n headline: \"Severe storms this afternoon\",\n }\n }\n}\n\nfn main() {\n let mut dom = VirtualDom::new(banner);\n dom.rebuild_in_place();\n let html = dioxus_ssr::render(&dom); // a static HTML fragment for Astro to place\n println!(\"{html}\");\n}\n```\n\nThe output is a `<div data-slot=\"mzizi-alert-banner\" data-portal=\"https://mzizi.dev/components/mzizi-alert-banner\" role=\"alert\" …>`\nwith the same Tailwind classes and CSS custom properties as the React build, so one stylesheet\nstyles both.\n\nWhat a Roots UI component looks like:\n\n- **Same contract as the React build:** the same registry name, `data-slot`, `data-portal`,\n variant names and Tailwind classes over the same `var(--…)` tokens. No colour literals.\n- **Variants are enums**, and a `match` replaces `class-variance-authority`\n (`n2-primitives/badge.rs`, `button.rs`).\n- **Browser facilities are props.** Where the `.tsx` reads `useMziziHarness`, the Rust takes the\n one bit it branches on, usually `prefers_reduced_motion: bool`. Logging is the host's.\n- **Icons are slots:** an `Element` the host renders, where the `.tsx` takes a Lucide component.\n- **Each file is self-contained.** No helpers shared between components.\n\n## The two frontend paths\n\nThe frontend is either **Astro** (pure `.astro` components, or Roots rendered underneath) or\n**pure Rust end to end**.\n\n- **Pure Rust.** A Dioxus app depends on the crates and mounts the components on web (wasm),\n desktop or mobile. Every component supports this today.\n- **Astro, static.** Render with `dioxus-ssr` at build time or in a Worker, and let Astro place\n the fragment, as in the example above. It ships no JavaScript and suits display components.\n The brand crate's contract suite does this for every component on every run.\n- **Astro, interactive islands.** Compile the behaviour to one wasm module per page and wrap it\n as a custom element. **Not packaged yet**: the wrapper crate (`mzizi-islands`) is proposed for\n batch 2 of the RFC.\n\n- **Astro, pure `.astro` components** (mzizi-registry #397, owner decision 2026-10-04). The\n registry is the single source of every component in every format: `.tsx`, `.rs`, `.astro`\n (and `.mz`, the Mzizi language, as it lands). A component's `.astro` sits beside its `.tsx`\n and `.rs` (`components/registry/n<N>-*/<name>.astro`), pure Astro with no React or\n Vue under it and no client JavaScript unless its contract allows one enhancement script.\n Install with `mzizi add <name> --target astro` (inferred in an Astro project, even one with\n React islands): it reads `api.mzizi.dev/v1/astro/<name>` and writes the component,\n its flat-imported siblings (`./button.astro`, `./ui-utils`, `./server-table`) and any brand\n asset into `src/components/mzizi/`. A component with no `.astro` is refused by name, never\n replaced by its `.tsx`. `mzizi_get_component` returns the Astro block too.\n\nThe Mzizi Dashboard Standard's app components (`app-*`, plus the primitives `button`, `badge`,\n`card`, `alert`, `input`, `label`, `skeleton`), the Mzizi Discover Standard's components\n(`discover-*`, including the detail pattern) and the marketing-site components (`site-*`) all\nhave an `.astro` AND a `.tsx` in the registry (the `ui/` family's Astro ports keep the\nregistry's own `.tsx`, which share only what their contract's `identity` says), each held to\nits contract (\"The Dashboard and Discover Standards' contracts\" below). `@bundu/ui`\n(`@bundu/ui/app/*`, `@bundu/ui/discover/*`) is BUILT from these files at a pinned registry\ncommit, and its CI fails on drift; a Next.js app adopts the same design through the registry's\n`.tsx` (`mzizi add app-data-table --target tsx` or\n`npx shadcn add` of `api.mzizi.dev/v1/ui/<name>`). Use them as they are: do not\nfork, restyle or wrap the shell or a component; fix it upstream in the registry (contract,\n`.astro` and `.tsx` together), in the same change.\n\n## Starting an app\n\n**Install the shell first.** Before the first page, mount the app's one shell: `AppShell` for a\nsigned-in app or console, `DiscoverShell` for a public discover site, both from `@bundu/ui`.\nThen check that every page renders inside it. The `mzizi-architecture` skill has the choice, the\ninstall and the check. Apps built page by page without a shell are the failure it exists to\nstop. Neither shell has a Rust build yet (`rs: null`), so a Rust or Astro-with-Roots app still\ntakes the shell from `@bundu/ui`.\n\nFor a new Mzizi-ecosystem app, or any app that consumes the design system. Pick the path\nfirst (above); these steps are the Astro one, with the pure-Rust differences noted.\n\n### 1. Create the project (Astro)\n\n```bash\npnpm create astro@latest my-app -- --template minimal --typescript strict\ncd my-app\npnpm astro add tailwind cloudflare\n```\n\nAstro renders HTML by default, which is what makes a new app indexable by N11 and paintable on\na low-end device without opting in to either. For a pure-Rust app, start from\n`cargo new my-app` and skip to step 3.\n\n### 2. Wire the tokens\n\nCopy the generated stylesheet rather than retyping hexes: `skills/mzizi-design/tokens/palette.css`\nin `@nyuchi/mzizi-skills` (all 21 families, light and dark, generated from\n`palette.canonical.json` at the package root). Import it from your global stylesheet (in Astro,\n`src/styles/global.css`) and register the properties in the Tailwind 4 `@theme` block so\nutilities generate. In Rust the same palette is the `mzizi-tokens` crate, always on in\n`mzizi-roots`. How the 21 families are meant to be used, the radius scale and the 48px floor\nare in the `mzizi-design` skill.\n\nPick the app's accent through `--brand-accent` rather than hardcoding a mineral hex: nyuchi =\ngold, mukoko = tanzanite, and a Mzizi surface = hematite (a Heritage tone, not a mineral; see the\nconstellation in `mzizi-design`).\n\n### 3. Add the Roots components\n\n```bash\ncargo add mzizi-roots # tokens, primitives, brand components, shell parts (no AppShell yet)\n```\n\nFor Astro, a small Rust crate (a build step, or a `workers-rs` Worker for request-time\nrendering) renders each component to HTML with `dioxus-ssr`, and Astro places the fragment.\nThe fragment carries the same Tailwind classes as the React build, so the stylesheet from step 2\nstyles it, and it ships no JavaScript. Interactive islands (Roots compiled to wasm and wrapped\nas custom elements) are proposed and not packaged yet. \"Using a UI component\" above has a\ncompiling example.\n\nNever copy component source between apps. Depend on the crate, so every app inherits fixes.\n\n### 4. Give the app's AI assistants the doctrine\n\nInstall the published skills bundle so any assistant working in the repo has the design system,\nthe language and the components on hand:\n\n```bash\nnpm install -D @nyuchi/mzizi-skills\nnpx skills experimental_sync # links the bundled skills into .agents/skills/\n```\n\n`npx skills add @nyuchi/mzizi-skills` does **not** work: the `skills` CLI reads that argument\nas a GitHub repository and fails to clone it. `experimental_sync` is the CLI's route for skills\nthat ship in `node_modules`, and it is marked experimental. The same skills are served with no\ninstall at all by the MCP's `mzizi_get_skills`.\n\nThe public Claude Code plugin installs these skills and the MCP in one step:\n\n```\n/plugin marketplace add mzizi-dev/mzizi-registry\n/plugin install mzizi@mzizi\n```\n\nTo wire the MCP directly instead, add it to `.mcp.json` or your client config. It is free with\nno account; only the Fundi tools need sign-in:\n\n```json\n{\n \"mcpServers\": {\n \"mzizi\": { \"type\": \"http\", \"url\": \"https://mcp.mzizi.dev/mcp\" }\n }\n}\n```\n\n### 5. Let fundi read the project\n\n`@nyuchi/mzizi-cli` ships the **fundi** agent, which reads the project off disk and plans the\nwiring against the registry:\n\n```bash\npnpm add -D @nyuchi/mzizi-cli\npnpm exec fundi explore # offline — snapshot the project\npnpm exec fundi plan \"wire up Mzizi tokens\"\n```\n\nRun the bins from the installed package (`pnpm exec`, or `npx` inside the project). A bare\n`npx fundi` outside it fetches an unrelated npm package called `fundi`. `explore` runs without\ncredentials; `plan` and `chat` need `ANTHROPIC_API_KEY`. A plan is\ninert until you apply it, so read it before letting it write.\n\n### 6. Verify\n\n```bash\npnpm dev\n```\n\nConfirm the tokens resolve in light and dark, a rendered Roots fragment picks up the styles,\nand the page is readable with JavaScript off.\n\n### 7. An existing React app\n\nIf the app is already React, the React build installs with the CLI or shadcn, by absolute URL:\n\n```bash\npnpm add -D @nyuchi/mzizi-cli && pnpm exec mzizi add button # infers the tsx target\nnpx shadcn@latest add https://api.mzizi.dev/v1/ui/mzizi-theme-provider\n```\n\nPoint `components.json` `registries.mzizi.url` at `https://api.mzizi.dev/v1/ui`. A bare name\nresolves against shadcn's own registry and installs a different component. Do not start a new\napp this way.\n\n## Server components\n\nA Roots server component is a **sans-IO core**: typed request in, typed response out, every\nexternal facility (a store, a clock, an outbound fetch, a secret) behind a trait. The host owns\nI/O and credentials. `n10-documentation/mzizi-docs-api.rs` is the model: it parses a path into a\ntyped `Route`, asks a `DocsStore` trait for data and returns an `ApiResponse`. `mzizi-assurance`,\n`mzizi-fundi` and `mzizi-discovery` have no dependencies at all. The RFC records a passing\n`wasm32-unknown-unknown` `cargo check` for those three and `mzizi-docs`; CI does not run a\nwasm32 job yet.\n\nA host adapter mounts the core: a `workers-rs` adapter for Cloudflare Workers, an axum adapter\nfor containers. Neither adapter crate exists yet. Until then the host writes the few lines that\nturn its request into the core's request (see the `mzizi-backend` skill). Dioxus\n`#[server]` functions are one more possible adapter, not the building block.\n\n## Contracts\n\nEvery Roots component carries a contract, in two parts:\n\n1. **The registry contract:** one registry name, description, dependencies and variants shared\n by both builds, and the same `data-slot`, `data-portal` and tokens. Each crate's\n `tests/contract.rs` reads the `.tsx` sibling and asserts that the slot, the portal and every\n variant value the Rust emits appear there too. A deliberate divergence, such as a touch\n target raised to the 48px floor, is asserted from both sides, so the test fails when the\n React build is fixed.\n2. **Checkable clauses** in the Mzizi language's `contract … end` grammar (RFC-0006; the\n `mzizi-language` skill), exported as `pub const CONTRACT: &str`. From\n `n3-brand/mzizi-hero-stat.rs`:\n\n ```rust\n pub const CONTRACT: &str = r#\"contract\n slot is \"mzizi-hero-stat\"\n role is \"region\"\n label not_empty\n button \"Share\" min_height 48\n end\"#;\n ```\n\n `mzizi-brand`'s contract suite renders each component with `dioxus-ssr` in named states and\n evaluates every clause against the markup it emits. **A clause it cannot evaluate fails.**\n `mzizi_roots::brand::CONTRACTS` lists every brand component with its contract.\n\nToday the twelve `mzizi-brand` components export `CONTRACT`. The other crates check the\nregistry contract against the `.tsx`, and their `CONTRACT` clauses arrive batch by batch.\nServer-component clauses (status per route, CORS and cache headers, no credential in the core)\nwait on the language's contracts-everywhere RFC (RFC-0010). Until then their tests assert those\nproperties against an in-memory implementation of the traits. The language's own `service`\ncontracts (`example` and `ensure`, RFC-0011) apply to a Mzizi `service`, not to these Rust\ncrates. No component lowers to Rust yet (only a Mzizi `service` does, to a local axum\npackage), so `mz contract` does not read a Roots `CONTRACT`.\n\n### The Dashboard and Discover Standards' contracts\n\nThe registry's\n[`contracts/`](https://github.com/mzizi-dev/mzizi-registry/tree/main/contracts) holds a\nmachine-readable, versioned contract for every component of `@bundu/ui` (built in\n`mzizi-dev/packages-npm`), in two families of one format:\n\n- **`app/`, the Mzizi Dashboard Standard** (since registry `a298a4e`, 2026-10-04,\n mzizi-registry #404): the one dashboard design for every product in the Bundu ecosystem,\n with each brand's mineral as an overlay (docs.mzizi.dev/patterns/dashboard-standard). Its 35\n contracts are the server-rendered app components (`@bundu/ui/app/*.astro`): the shell, the\n page and data patterns, and seven primitives.\n- **`discover/`, the Mzizi Discover Standard** (since registry `0bea10c`, 2026-10-04,\n mzizi-registry #415, tracking issue #413): one design for every public discover and browse\n page in the Mukoko family (docs.mzizi.dev/patterns/discover-standard). Its 16 contracts are\n the Discover components (`@bundu/ui/discover/*.astro`). \"The Discover Standard\" below says\n what is different.\n\n| File | What it is |\n| ------------------------------------------------- | ---------------------------------------------------------------------------------- |\n| `contracts/schema/component-contract.schema.json` | the format, one for both families |\n| `contracts/app/<name>.contract.json` | one Dashboard Standard component's contract |\n| `contracts/discover/<name>.contract.json` | one Discover Standard component's contract |\n| `contracts/site/<name>.contract.json` | one marketing-site component's contract |\n| `contracts/ui/<name>.contract.json` | one Astro port of a registry primitive (native select, segmented control, …) |\n| `contracts/index.json` | all 77 component contracts with their version and `.astro`, `.tsx`, `.rs` items |\n| `contracts/README.md` | the coverage tables, the recorded gaps, the shared rules, how to change a contract |\n\n**Read the contract before you build or change a `@bundu/ui` component, in any language.** It is\nthe specification: `props` and `slots` (types, required, defaults, closed `values`; the\n`.astro` file's props and slots must be exactly these), `behaviour`, `states` (named fixtures,\n`default` required; every runner renders every state), `accessibility` (roles, keyboard,\nfocus, ARIA, WCAG 2.2 criteria), `density`, `theming`, `noJs`, `responsive`, the `contract`\nblock and `checks` (selector assertions on the rendered markup). `implementations` names the\nAstro export and the `.tsx` and `.rs` siblings, with what they share (`identity`: `slot`,\n`slot+role` or `slot+variants`) and their `divergences`; items under `related` look similar\nand have a different contract, so they are not implementations. `implementations.astro`\ngives the package, the export and `since`: the `@bundu/ui` release that first shipped it, 0.3.0\nfor the original two families (mzizi-registry #419) and 0.6.0 for the components added since\n(money, plan summary, tier status, confirm action, the markdown renderer, and the trust and\naddress primitives).\n`@bundu/ui` is 0.7.0 on npm. `gaps` records where the current build falls short.\n\n**Every new build starts from its contract, library or component** (owner, 2026-10-31). Write or\nchange the contract first, then the builds. A fifth family, `lib/`, holds\ncontracts for non-visual libraries: the N5 resilience primitives (circuit breaker, retry,\nbulkhead, rate limiter, fallback chain, deadline) and the N8 chaos engine, in their own format\n(`schema/lib-contract.schema.json`), each invariant held by a test in the implementing crate\n(mzizi-registry #585, released in v4.18.0).\n\nFour fields are requirements. packages-npm's contract test evaluates the first three on the\nrendered Astro build; the responsive rules are prose. The values below are the Dashboard\nStandard's; the Discover Standard's differ (below):\n\n- **No-JS fallback.** `noJs.script` is `none` (no script) or `enhancement` (one script, and\n everything in `noJs.without` works when it does not run). Only AppShell and CommandPalette\n are `enhancement`; every other app component ships no script.\n- **Density.** Each row gives a part's height in px for a fine pointer and a coarse one\n (touch), read from the rendered classes (`h-N`, `min-h-N`, `size-N` = N×4;\n `pointer-coarse:` for touch). Dashboards are dense on a mouse and 44–48px on touch: the app\n Button is 36px fine (32 `sm`, 40 `lg`) and 48px coarse (44 `sm`), with the small radius,\n and nav rows are 32 / 44. That is the contracts' rule for `@bundu/ui/app`, not the registry\n primitives' 48px floor. A control under 44px on touch is listed in `gaps`, not hidden.\n- **Theming.** A brand overlay changes colour only: `--primary`, `--ring`, AppShell's\n `--app-accent` and the brand mark. Layout never varies by brand. A mineral may be named only\n as one of the component's declared `statusColours`; any other mineral class, or any colour\n value, fails (the `mzizi-design` skill).\n- **Responsive.** Each contract's rules from 320px up.\n\nIn both families the tests also fail **any inline `style` attribute** on a rendered element, so\na page's Content-Security-Policy can keep `style-src 'self'` with no\n`style-src-attr 'unsafe-inline'`: custom properties go through classes or data attributes\n(AppShell's `data-accent`), sizes through classes or SVG geometry (BarChart). JSON-LD\n(`type=\"application/ld+json\"`) is data and does not count as a script.\n\n**How they relate to `CONTRACT` and the language.** The `contract` field is a\n`contract … end` block in the same clause grammar as a Roots `pub const CONTRACT` (RFC-0006\nand RFC-0010), restricted to what applies to rendered markup: subjects `slot`, `role`,\n`label`, `class`, `portal`, `<element> \"<text>\"`, `uses <data-slot>` and `when <state> …`;\npredicates `is`, `contains`, `not_empty`, `in`, `uses \"--token\"`, `min_height` and `shows`. As\nin the language, **a clause, check or density row a runner cannot evaluate fails** (FM-12).\nThe registry's own suites evaluate them: `__tests__/astro/contracts.test.ts` renders every\n`.astro` in every state (Astro's container, no renderer), `__tests__/contracts/\ntsx-contracts.test.tsx` renders every `contract`-identity `.tsx` with the same runner\n(`contracts/runner.ts`), and `__tests__/contracts/contracts.test.tsx` validates every contract\nagainst the schema and the grammar and checks each identity-only sibling. packages-npm's\n`src/app/contracts.test.ts` runs the same contracts again on the built package. A crate's own `CONTRACT`\nand `tests/contract.rs` still run as before.\n\nEvery contract has an `.astro` (`implementations.astro.registry`). The seven app primitives'\n`.tsx` are the registry's own `button`, `badge`, `card`, `alert`, `input`, `label` and\n`skeleton`, which share only their identity (the contract lists the divergences), as do the\n`ui/` family's; every other `.tsx` (`app-*`, `discover-*`, `site-*`) has\n`identity: \"contract\"`, held to the whole contract exactly as the Astro build is. A `.rs`\nexists in `mzizi-ui` for `button`, `badge`, `card`, `input`, `label` and `safe-area-frame`;\nmzizi-registry #401 tracks Rust ports of the rest, done when each passes the contract.\n\n### The Discover Standard\n\nThe 11 Discover Standard list-page contracts in `discover/` and the node each declares (the detail pattern below adds five more). DiscoverShell's `kind` is `shell`;\nthe other ten are `pattern`s.\n\n| Contract | Component | Node | What it is |\n| --------------------------- | --------------- | ---- | --------------------------------------------------------------------------------------------- |\n| `discover/discover-shell` | DiscoverShell | N7 | the frame: skip link, mineral strip, header (brand, \"Main\" nav, one action), `<main>`, footer |\n| `discover/discover-meta` | DiscoverMeta | N6 | the `<head>` tags: title, description, robots, canonical, Open Graph, the X card, JSON-LD |\n| `discover/discover-hero` | DiscoverHero | N6 | breadcrumbs, eyebrow, the page's one `<h1>`, lead, the search box and actions |\n| `discover/discover-search` | DiscoverSearch | N6 | the one search box: a `role=\"search\"` GET form, so every search is a URL |\n| `discover/category-chips` | CategoryChips | N6 | the category links as a named navigation landmark; chips wrap |\n| `discover/category-chip` | CategoryChip | N6 | one category link, with an optional count; a link, never a button |\n| `discover/discover-section` | DiscoverSection | N6 | one band: eyebrow, heading, description and \"See all\" over its content |\n| `discover/result-grid` | ResultGrid | N6 | a polite status line, the cards, the empty state and \"load more\"; `grid`, `list` or `rail` |\n| `discover/discover-card` | DiscoverCard | N6 | one card for every page, variants `article`, `event`, `circle`, `place` |\n| `discover/load-more` | LoadMore | N6 | cursor pagination: one `rel=\"next\"` link to `?cursor=…` |\n| `discover/open-in-app` | OpenInApp | N6 | \"Open in Mukoko\": an https link into the super-app, with one line saying what happens |\n\nSame format, schema, clause grammar and runners as the Dashboard Standard. What the Discover\ncontracts state differently:\n\n- **What it is for.** Public discover and browse pages in the Mukoko family (circles, news,\n events, weather, and the super-app on the web), not admin or operator dashboards. It reuses\n the Dashboard Standard where they overlap: ResultGrid's `empty` slot takes `app/empty-state`\n or `app/state-message`, and `related` names `app/filter-bar` and `app/pagination` as similar\n components with a different contract.\n- **No script at all.** Every Discover contract's `noJs.script` is `none`; there is no\n `enhancement` like AppShell's or CommandPalette's. Views are URLs, not client state: search\n is a GET form (`?q=`, with no `cursor`, so a new search starts at the first page), a category\n is a link, and paging is LoadMore's `rel=\"next\"` link to `?cursor=`.\n- **The same size on every pointer.** In every Discover density row `fine` equals `coarse`:\n 48px for the search field and its button, \"See all\", LoadMore, OpenInApp and DiscoverCard's\n app link; 44px for chips, the navigation links and the home link. Discover pages are not\n dense on a mouse, unlike the Dashboard Standard (most controls 32–40px on a fine pointer).\n- **Theming.** The brand-overlay rule is the same (colour only, no colour values, a mineral\n only as a declared status colour), but there is no AppShell `--app-accent`. A brand shows in\n DiscoverShell's `brand` slot (its mark) and in whatever primary action its `action` slot\n holds; the frame, header, navigation and footer are identical in every brand. `--primary` reaches only\n DiscoverHero's wash (`gradient-showcase`), DiscoverCard's monogram and `brand` badge tone,\n and OpenInApp's `primary` variant. Chips, sections and LoadMore are neutral in every brand,\n and DiscoverSearch follows the brand only in its focus ring (`--ring`). Only DiscoverCard\n declares status colours (`cobalt`, `malachite`, `gold`, `terracotta`), for its badge tones.\n- **Server-filled shells.** Every Discover component also works in an Astro page built once\n with `{{placeholders}}` and filled by a server, as circles.mukoko.com's Rust Worker does:\n text props are plain strings, text that fills to `\"\"` hides itself, and state switches are\n attributes styled by classes (ResultGrid, DiscoverSection and LoadMore `state`, DiscoverCard\n `badgeTone`, CategoryChip `current`). CategoryChip, DiscoverCard, DiscoverHero, LoadMore and\n ResultGrid have a `template` state with placeholders, rendered like every other state.\n DiscoverCard and CategoryChip render their own `<li>`, so they repeat as fragments.\n- **One card.** DiscoverCard's `variant` changes emphasis, never structure. Its title is the\n one primary link, stretched over the card; other actions stay separately focusable.\n- **OpenInApp's `href` is https** (a universal link or a server redirect), never a bare\n `mukoko://` link, which does nothing without the app.\n- **No siblings and no gaps yet.** No `.tsx` or `.rs` implements a Discover contract, and none\n records `gaps`. A React or Rust port is done when it passes the same contract.\n\n**Upstream first** (registry `CONTRIBUTING.md`, from #415, for both families): a component an\napp needs that Mzizi does not have, or a change to one it has, goes upstream immediately: its\ncontract in the registry, its build in `@bundu/ui` and its page on docs.mzizi.dev. An app keeps\na local copy only while that upstream pull request is open, marked at the top of the file with\n`TODO(mzizi): <upstream PR URL>`.\n\n### Changing a contract\n\n1. Edit `contracts/<family>/<name>.contract.json` in the registry and bump its `version`:\n major when a clause, prop, slot or state is removed or narrowed, minor when one is added,\n patch for prose. A new family also needs its directory in `FAMILIES` in\n `__tests__/contracts/contracts.test.tsx` and in packages-npm's `scripts/contract-paths.mjs`.\n2. Update `contracts/index.json` and the README's coverage table for that family. The test\n fails if either disagrees.\n3. Run `pnpm test` in the registry, then `pnpm contracts:fetch <branch>` and `pnpm test` in\n packages-npm. The Astro build must pass the new contract before either side merges. Never\n edit the copy in packages-npm.\n\n**The detail pattern** (mzizi-registry #429; docs.mzizi.dev/patterns/discover-detail): the\npage for one item reached from a discover page uses five more `discover/` contracts, each an\n`.astro` and a `.tsx`: `detail-hero` (the page's only `<h1>`, when/where, lead, image or media\nslot), `breadcrumb` (named `<nav>`, `aria-current=\"page\"`, BreadcrumbList JSON-LD), `meta-list`\n(a `<dl>` of facts), `detail-actions` (Open in Mukoko first, then secondary and share links; no\nscript) and `related-rail` (DiscoverCards; a scroll-snap row on phones, a grid from 48rem;\nhidden when empty).\n\n**Open in Mukoko** is one canonical https universal link, `mukoko.com/open/<service>/<id>`\n(`openInMukokoUrl` in `discover-open-link.ts`; `@bundu/ui/discover/open`). `open-in-app` 1.1.0\nand `detail-actions` take `service` (`news`, `events`, `weather`, `circles`) and `id`; `href` is\nonly an override. The apps claim `mukoko.com/open/*` as universal and app links; on the web a\nresolver (mukoko-dev/super-app-web `/open/*`) sends the reader to the service's page. Never a\nbare `mukoko://` link.\n\n## Reuse before build\n\n**The simplest change is the one you don't write.** Reuse before you build, compose before you\nimplement, and let one source own each decision. Use this before adding a component, a style or\na page, and during any refactor.\n\n1. **Reuse before build.** Search the registry first: `mzizi_search` or\n `mzizi_list_components` over the MCP (`https://mcp.mzizi.dev/mcp`, free), or\n `GET https://api.mzizi.dev/v1/ui`. Where a Rust implementation exists, depend on its crate\n (`cargo add mzizi-roots`). For the React build, install with\n `mzizi add <name>` or `npx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>`. Never\n reimplement a primitive that already exists.\n2. **Styles are tokens.** Never hardcode a colour, radius, spacing, or motion value —\n consume `var(--…)` (or the `mzizi-tokens` consts in Rust). N1 is the only layer allowed to\n define values, so a rebrand is one edit. A new colour goes in the registry's\n `lib/tokens/palette.source.ts`, and `pnpm tokens:sync` regenerates `styles/globals.css`, the Rust\n tokens and the other emitters from it.\n3. **Compose, don't implement.** A page (N6) is a composition of N2 primitives and N3 brand\n components — never inline a `<button>`, a card, or an SVG. Pull spinners, skeletons, and\n badges from N2.\n4. **Variants over copies.** Collapse near-duplicate components into one variant table: an\n enum with a `match` in Rust, an `enum` table in Mzizi source, one `cva()` in the React\n build. Express differences as `variant` / `size` props, not new files.\n5. **One composition path.** In the React build, compose class names through `cn()` and\n extract shared logic into `lib/` or a hook. In Rust, a pure function beside the component.\n No logic duplicated across files.\n6. **Right altitude.** Keep each component self-contained, importing only from the layer\n below on its strand. A primitive that reaches up into page code can't be installed on its\n own.\n7. **Delete first.** Prefer removing over adding — dead code, unused props, redundant\n wrappers, commented-out blocks. Less code is less to break, read, and keep true.\n8. **Live data, not copies.** Read counts, tokens and doctrine from their source\n (`https://api.mzizi.dev/v1/stats`, `palette.canonical.json`, `mzizi_get_tokens`,\n `mzizi_get_doctrine`). A baked-in number or duplicated token guarantees drift.\n\n### Checklist\n\n- [ ] Searched the registry before building, and used the Rust build where one exists?\n- [ ] All styles are tokens (`var(--…)`), zero hardcoded values outside N1?\n- [ ] Page is pure composition of N2/N3, no inlined primitives?\n- [ ] Variation expressed as one variant table, not near-duplicate files?\n- [ ] One composition path, shared logic extracted once?\n- [ ] Each file self-contained and installable, importing only downward?\n- [ ] Deleted more than you added where you could?\n- [ ] Counts, tokens, and doctrine read live, not hardcoded?\n\n## Contributing a component\n\n**A component is a real file.** `components/registry/n<N>-<label>/<name>.rs` (Rust, compiled by\nthe node's crate in `mzizi-rs/`) and, for the React build, `<name>.tsx` beside it, which\n`pnpm typecheck` (`tsc --noEmit`) and `pnpm lint` check. An error in either fails CI. The\nregistry has no app to build: CI's `Rust` job runs `cargo fmt`, `check`, `clippy` and the\ncontract tests, and its `Build` job runs `pnpm build`, which runs every generator in write mode\nand fails if that changes a committed file.\n\n| File | What it is |\n| --------------------------------------------- | ----------------------------------------------------------------- |\n| `components/registry/n<N>-<label>/<name>.rs` | the Roots component (Dioxus UI, or a sans-IO server core) |\n| `components/registry/n<N>-<label>/<name>.tsx` | the React build of the same contract |\n| `registry.json` | the manifest `/v1/ui/<name>` and `npx shadcn add` resolve against |\n\n`registry.json` carries the install contract (description, dependencies,\n`registryDependencies`, target paths) and **never the source**. The node is derived from the\ndirectory the component lives in, so it cannot disagree with where the file is.\n\n**There is no database.** The registry's files are its data layer (mzizi-registry PR #368), and\na test fails the build if a Supabase import or a `SUPABASE_*` read comes back. Nothing about a\ncomponent, including its source, status or history, is written to a table. If you find\ninstructions anywhere telling you to `INSERT` a component or read a `component_documents` row,\nthey are stale.\n\n### Step 1 — Decide the node\n\nThe architecture is the **Mzizi DNA double helix**: eight nodes (N1–N8) on the engineering\nbackbone, four rungs (N9–N12) that bridge both backbones, and six strands. The N-numbers are\n**labels, not a sequence**.\n\nThe node decides the **directory**, the directory decides the **crate** that compiles the Rust\n(`mzizi-rs/crate-for-node.json`), and the directory is what the build reads. Choosing the node\nand choosing where the file goes are the same decision.\n\n| Node | Directory | Crate | What belongs there |\n| ----------------- | -------------------- | ----------------- | --------------------------------------------------------------------------- |\n| N1 tokens | `n1-tokens/` | `mzizi-tokens` | A token value or emitter (colour, spacing, motion, radius), not a component |\n| N2 primitive | `n2-primitives/` | `mzizi-ui` | Generic UI: button, card, input, dialog, toolbar, bento-grid |\n| N3 brand | `n3-brand/` | `mzizi-brand` | A branded composition with the mineral palette |\n| N4 safety | `n4-safety/` | none yet | A conditional-rendering gate: permission, geo, rate limit |\n| N5 resilience | `n5-resilience/` | none yet | Error boundary, skeleton, offline banner, fallback chain |\n| N6 pages | `n6-pages/` | none yet | Full-screen layout composition |\n| N7 shell | `n7-shell/` | `mzizi-shell` | App container: navigation, routing, lifecycle |\n| N8 assurance | `n8-assurance/` | `mzizi-assurance` | Instrumentation, a11y audit, RTL conformity, probes |\n| N9 fundi | `n9-fundi/` | `mzizi-fundi` | Automated healing (a rung) |\n| N10 documentation | `n10-documentation/` | `mzizi-docs` | Docs, AI instructions, docs infrastructure (a rung) |\n| N11 discovery | `n11-discovery/` | `mzizi-discovery` | SEO / AIO surfaces: if the machine can't see it, it doesn't exist (a rung) |\n| N12 skills | none in the registry | none | Agent skills (a rung), authored in `mzizi-dev/agent-tools/mzizi-skills/` |\n\nA node with no crate yet (N4, N5, N6) takes a `.tsx` today. Adding its first `.rs` means adding\nits crate and its `crate-for-node.json` entry in the same pull request; `pnpm rust:generate`\nrefuses Rust that no crate claims.\n\nIf you are unsure where something belongs, ask the MCP (`https://mcp.mzizi.dev/mcp`, free, no\naccount): `mzizi_list_components` with `node: N` shows what already lives in a node, and\n`mzizi_get_architecture` returns the node, rung and strand model with live counts. Never\nhardcode counts.\n\n### Step 2 — Write the file\n\nCreate it in the directory the node names:\n\n```\ncomponents/registry/n3-brand/mzizi-your-component.rs # the Roots component\ncomponents/registry/n3-brand/mzizi-your-component.tsx # the React build, same contract\n```\n\nThe Rust file carries `pub const CONTRACT: &str` and follows the rules in \"Using a UI component\" above: variants as enums, tokens by reference, browser facilities as props, icons as slots,\nno helpers shared between components. `.ts` is for a non-rendering module.\n\nThat is the authoring step. There is no row to insert and no registration to perform before\nwriting code: the file _is_ the component, and `cargo test` (for the `.rs`), `pnpm typecheck`\nand `pnpm lint` (for the `.tsx`) and `pnpm build` (every derived artifact regenerated, with no\ndiff) are what tell you it is real.\n\nNaming: N3 brand components take a `mzizi-` prefix. (Components formerly named `nyuchi-*` are\n`mzizi-*`, and the old names redirect.) Everything else is the bare name, matching what a\nconsumer will type in `mzizi add` or `npx shadcn add`.\n\n### Wiring the Rust into its crate\n\n`pnpm rust:generate` writes the committed copy to `mzizi-rs/crates/<crate>/src/generated/`. Add\nthe `#[path = \"generated/<name>.rs\"] pub mod <name_snake>;` line and the `pub use` to the\ncrate's `src/lib.rs` by hand, and add the component to the crate's `tests/contract.rs`. Edit\nthe registry file, never the copy. The workspace lints forbid `unsafe_code` and warn on\n`missing_docs`; clippy turns the warning into a failure. Cap parallel builds on a small machine\n(`CARGO_BUILD_JOBS=2`).\n\nA release is a version bump in `mzizi-rs/Cargo.toml`. On merge to `main`,\n`.github/workflows/publish-crates.yml` re-runs the Rust gate and publishes each crate in\ndependency order, skipping any version already on crates.io. A published crate name is\npermanent. Say in the PR body that components changed, so the site, docs and skills are\nupdated to match.\n\n### Step 3 — Declare it in `registry.json`\n\nOne item, carrying the install contract. `meta` is the part you author; four other fields are\n**derived from it** and must not be hand-written.\n\n```jsonc\n{\n \"name\": \"your-component-name\",\n \"type\": \"registry:ui\",\n \"description\": \"Short description of what it does.\",\n \"dependencies\": [],\n \"registryDependencies\": [],\n \"files\": [\n {\n \"path\": \"components/registry/n2-primitives/your-component-name.tsx\",\n \"target\": \"components/ui/your-component-name.tsx\",\n \"type\": \"registry:ui\"\n }\n ],\n \"meta\": {\n \"collection\": \"primitives\",\n \"owner\": \"mzizi\",\n \"useCases\": [\"Use case 1\", \"Use case 2\"],\n \"variants\": [\"default\", \"sm\", \"lg\"],\n \"sizes\": [\"default\", \"sm\", \"lg\"],\n \"features\": [\"cn() for className composition\", \"data-slot attribute\"],\n \"a11y\": [\"ARIA attributes\", \"Keyboard accessible\"],\n \"hasDemo\": true\n }\n}\n```\n\n`files[]` lists the `.tsx`: it is the shadcn install contract for the React build. The `.rs`\nbeside it is not listed; the node's crate compiles it, and `/v1/rs/<name>` serves it with the\ncrate's name.\n\n**Do not write `title`, `categories`, `docs` or `author`.** `pnpm registry:metadata` derives all\nfour from `meta` and the repo. `docs` in particular is what the shadcn CLI prints _after_\ninstalling, so it is generated from your `useCases` / `variants` / `sizes` / `features` / `a11y`\nrather than duplicated by hand — and an item with no `meta` deliberately gets no `docs`, because\na stub reads as documentation and says nothing.\n\nThen run the generators and let the gates check you:\n\n```bash\npnpm registry:paths # fill file paths from what is on disk\npnpm registry:metadata # derive title, categories, docs, author\npnpm registry:normalize # canonical ordering\npnpm registry:validate # every item resolves on disk; every dependency addressable\npnpm registry:verify # the CI drift gate\npnpm typecheck && pnpm lint && pnpm build # build: every generator; commit what it writes\n```\n\nFollow the criteria for the chosen node.\n\n#### Every node: the Rust file\n\n- `pub const CONTRACT: &str` with the clauses the rendered markup must meet (slot, role, touch\n floors, variant tables), evaluated by the crate's `tests/contract.rs` on `dioxus-ssr` output\n- The same `data-slot`, `data-portal` and variant values as the `.tsx`, asserted by the same\n suite; a deliberate divergence is asserted from both sides\n- `prefers_reduced_motion: bool` as a prop where the `.tsx` reads the harness's motion\n- Icons as `Element` slots, not an icon crate\n- Doc comments on every public item (`missing_docs` is a warning, and clippy runs with\n `-D warnings`); no `unsafe`\n\n#### N2 primitive checklist\n\n- No `useMziziHarness` import in the `.tsx`\n- `data-slot` attribute on the root element\n- `data-portal` attribute pointing at `https://mzizi.dev/components/<name>`\n- `cn()` for className composition in the `.tsx`\n- No raw Tailwind colours: use semantic tokens (`bg-primary`, `text-foreground`, …)\n- Icons from `@/lib/icons` in the `.tsx`, never `lucide-react` directly\n- **Touch targets never go below 48px.** Buttons and inputs are `h-14` (56px) default and\n `h-12` (48px) small, as `button.tsx`, `button.rs` and `primitives/button.mz` all state, and\n `/v1/brand` `.componentSpecs` serves (`minTouchTarget: 48`). A smaller visual box, such as a\n badge, is not a touch target; if it becomes one, give it `min-h-[48px]`.\n- Pill-shaped categories (button, input, avatar, badge, toggle) use `rounded-full`\n\n#### N3 brand checklist\n\n- `mzizi-` prefix on the name\n- `useMziziHarness(\"<name>\")` in the `.tsx` for the motion preference (and `log` or\n `LiveRegion` where the component uses them); `prefers_reduced_motion` as a prop in the `.rs`\n- The entrance animation off when motion is reduced\n- An ARIA role or `aria-label`, and a region that is named in every state, the loading\n skeleton included\n- `data-slot` and `data-portal` attributes, in every state\n- `focus-visible` ring on interactive elements, and interactive elements at the 48px floor\n- I18N via `Intl` formatters for dates, numbers and currency\n- Semantic tokens for status-bearing colours\n\n#### N6 page checklist\n\n- Pure composition: no inline buttons, cards or SVGs\n- Accept children/slots for content\n- Semantic CSS vars only (`bg-card`, `text-foreground`, `bg-primary`)\n- A loading-state prop\n- `role=\"main\"` and an `aria-label`\n\n#### N8 assurance checklist\n\n- A sans-IO Rust core (typed input, typed result, every facility behind a trait) in\n `mzizi-assurance`, with the `.ts` module as the React build\n- A banner comment identifying the node and purpose\n- A configurable rules array\n- Optional `onViolation` / `onComplete` callbacks\n\n### Step 4 — Docs and demos\n\nThere is nothing extra to write. Docs come from the `meta` you already authored in Step 3\n(`useCases`, `variants`, `sizes`, `features`, `a11y`), and `pnpm registry:metadata` turns them\ninto the `docs` string the CLI prints after install. Set `meta.hasDemo` when a demo exists.\n\nIf a prop table is expected, `pnpm props:extract` derives it from the component's own types and\n`pnpm props:verify` is the gate. Props are read from the code, never restated beside it.\n\n### Step 5 — Versions and history\n\nThere is no status row and no database. A component's version history is the registry's\nchangelog (`content/changelog/`, generated, checked by `pnpm changelog:generate:check`) and the\nrename map. `mzizi_get_component` with `include: [\"versions\"]` returns it. Read what a component\nis through the MCP or `https://api.mzizi.dev/v1/ui/<name>` rather than assuming.\n\n### Step 6 — Validate accessibility\n\nIf your component introduces a new colour pair, validate it before shipping. The MCP tool is\ncomputed locally and needs no account:\n\n```\nmzizi_check_accessibility(foreground: \"#FFFFFF\", background: \"#0047AB\")\n```\n\nIt returns the WCAG 2.1 contrast ratio with AA/AAA verdicts and whether the pair clears the\nMzizi floor (APCA and AAA).\n\n### Step 7 — Before you open the PR\n\n`pnpm check` runs every registry gate CI runs, in order, including `registry:validate`,\n`registry:verify`, `tokens:verify`, `changelog:generate:check`, `rust:generate:check`, the\ntests and the build. Then the Rust workspace gate:\n\n```bash\npnpm check\ncargo fmt --manifest-path mzizi-rs/Cargo.toml --all -- --check\ncargo clippy --manifest-path mzizi-rs/Cargo.toml --workspace --all-targets -- -D warnings\ncargo test --manifest-path mzizi-rs/Cargo.toml --workspace\ncargo package --manifest-path mzizi-rs/Cargo.toml --workspace\n```\n\nIf you touched `files[]` or `meta`, run `pnpm registry:paths`, `pnpm registry:metadata` and\n`pnpm registry:normalize` first; their `--check` twins fail otherwise.\n\nA green run means the item resolves to real files, every dependency is addressable, the derived\nfields match `meta`, the Rust compiles, and its contract holds on rendered markup. Say in the PR\nbody that a component changed, so the site, the docs and the skills are updated to match.\n\n### What to avoid\n\n- **Never put component source in a database row, a JSON document, or any second place.** The\n file under `components/registry/` is the component; everything else is a representation of\n it, and a representation can drift from the code while still looking correct. That is not\n hypothetical here: a `source_code` column once served stale copies for 571 components.\n- Never edit `mzizi-rs/crates/*/src/generated/`. It is overwritten by `pnpm rust:generate`.\n- Never hand-write `title`, `categories`, `docs` or `author` in `registry.json`. They are\n derived, and a hand-written value is a second source that the next generator run silently\n overwrites.\n- Never hardcode hex colour values (except documented third-party brands like Ethereum,\n Google, EcoCash).\n- Never use `lucide-react` directly in a `.tsx`; go through `@/lib/icons`.\n- Never use `margin-left` / `padding-right` / `left`; use logical properties\n (`margin-inline-start`, …) for RTL support.\n- Never start new work in the React build alone. If a component has a Rust implementation,\n change the Rust and keep the `.tsx` in step.\n- Never bump a major version on your own initiative. That is a maintainers' call.\n\n## The React build\n\n`npx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>` installs the `.tsx` for an existing\nReact app, for all 577 items. Always pass the absolute URL: a bare name resolves against\nshadcn's own registry and installs a different component. `mzizi add <name>` installs the React\nbuild too. Neither is where new work goes.\n\n## See also\n\n- `docs/roots/RFC-roots.md` in the registry: the inventory, the conversion batches and what\n the API and site still need for Rust-first serving (`/v1/roots` is proposed, not live).\n- The `mzizi-language` skill for the clause grammar and `mz contract`.\n- The `mzizi-design` skill for the palette, radius, type and the Ubuntu constraints.\n- The `mzizi-backend` skill for mounting a server core on Workers.",
|
|
23
|
+
"description": "Use this skill for anything involving Mzizi's components: using them, starting an app on them, or contributing one. Mzizi Roots is Mzizi's own components in Rust (UI on Dioxus today, moving to Mzizi's own render(props); server components) from mzizi-dev/mzizi-registry. Covers `cargo add mzizi-roots` / `mzizi-roots-server`, what mzizi_get_component returns (Rust first), the node-to-crate map, `pub const CONTRACT`, the contracts in the registry's contracts/ for the Dashboard Standard (app/, 35 components) and the Discover Standard (discover/, 16 components; no script, server-filled shells): read one before building or changing a @bundu/ui component. Also no-JS fallback, density and floors (49px hit region, 17px body, 14px inset, 7px gaps) and brand-overlay rules, the frontend paths (pure .astro via `mzizi add --target astro`, Astro with Roots, pure Rust; Next.js adopts the .tsx), reuse before build, and adding a component (node, .rs, .tsx, registry.json, the gates). Files are the data layer: no database.",
|
|
24
|
+
"body_mdx": "# Mzizi Roots — the Rust build of Mzizi's components\n\n**Mzizi Roots** is Mzizi's own components in Rust: UI components, and server components as\nhost-agnostic Rust. The UI crates are built on Dioxus 0.7 today and are moving to Mzizi's own\nRust renderer (a pure `render(props) -> String`, with behaviour attached in the browser through\n`web-sys`). Do not write new Dioxus code for a Roots component. They are built to support the Mzizi language (the\n`mzizi-language` skill) as its component model, the way React is JavaScript's; they are not\nthe language. They live in\n[`mzizi-dev/mzizi-registry`](https://github.com/mzizi-dev/mzizi-registry) and are compiled by\nthe crates in its `mzizi-rs/` workspace. The plan is `docs/roots/RFC-roots.md` there.\n\n**Rust first.** Where a Rust implementation exists, present it and use it first. The React/TSX\ncomponents are **the React build**: they keep working for existing React apps, and they are\ndeprioritised, so no new work goes into them.\n\nThis skill describes registry `main` at v4.18.0 (`0d17818`, 2026-11-01) and its `contracts/`\n(77 component contracts in four families, and the `lib/` family), the crates at **0.7.0**\n(registry v4.18.0, on crates.io since 2026-11-01; 0.7.0 adds two crates, `mzizi-resilience`,\nthe N5 core, and `mzizi-chaos-engine`, the N8 seeded chaos engine, from registry PRs #584\nand #586; 0.6.0 before it carried the `--assurance-*` and `--attestation-*` roles in the generated\n`mzizi-tokens` pack, from mzizi-registry #578, and 0.5.0 the `mzizi-mongodb` security fixes from mzizi-registry #567; the UI crates\notherwise match 0.4.0, on crates.io since 2026-10-20 with `markdown-renderer` 1.2.0 and 1.2.1\nfrom mzizi-registry #483 and #517), and `mzizi-mcp` 0.13.0. It replaces three 0.7.0 skills:\n`scaffold-component` (now \"Contributing a component\"), `simplify` (now \"Reuse before build\")\nand the frontend half of `ecosystem-app-setup` (now \"Starting an app\"). Tokens, brand and the\nUbuntu constraints are in the `mzizi-design` skill.\n\n## Install\n\nTwo umbrella crates with no code of their own re-export the node crates behind features:\n\n```sh\ncargo add mzizi-roots # UI: tokens + ui + brand + shell (all default features)\ncargo add mzizi-roots-server # server: assurance + fundi + docs + discovery\n```\n\nTake only what you use:\n\n```toml\n[dependencies]\nmzizi-roots = { version = \"0.7\", default-features = false, features = [\"brand\"] }\n```\n\nTo follow `main`, or pin a commit, install from git. Cargo finds a crate by name anywhere in the\nrepository's workspace:\n\n```sh\ncargo add mzizi-roots --git https://github.com/mzizi-dev/mzizi-registry\n```\n\n```toml\nmzizi-brand = { git = \"https://github.com/mzizi-dev/mzizi-registry\", rev = \"<commit>\" }\n```\n\nA single node crate works too (`cargo add mzizi-ui`). The crates depend on `dioxus` with\n`macro`, `signals`, `hooks` and `html` only, so the app that mounts them chooses web, desktop,\nmobile or server rendering.\n\n`mzizi add <name> --target rust` (`@nyuchi/mzizi-cli`) records the crate and prints the\n`cargo add` rather than copying a `.rs` file into your project: a copied component is a fork\nof a crate module.\n\n## What the MCP returns\n\n`mzizi_get_component` on `https://mcp.mzizi.dev/mcp` (free, no account; `mzizi-mcp` 0.11.0) is\nRust first. It returns `{name, renamedFrom?, lead, note, rust, react, docs?, versions?}`:\n\n- `lead` is `\"rust\"` when the component has a Roots implementation, else `\"react\"`; `note`\n says which build to use.\n- `rust` is the registry's `/v1/rs/<name>` answer with the install first: `crate`\n (`{name, registry, git}`), `install.cargo` (`cargo add <crate>`), `install.pinned`\n (`cargo add <crate> --git https://github.com/mzizi-dev/mzizi-registry --rev <pin>`, which\n builds exactly the source shown), `module` (e.g. `mzizi_brand`), `contract` (the\n `contract … end` text, or `null`), and `files[]` with the `.rs` source inline. With no Rust\n it is `null`.\n- `react` is the React build: `build: \"react\"`, `status: \"deprioritised\"`, `install` (the\n `npx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>` line), then the `/v1/ui/<name>`\n body. It is no longer under a `component` key (that was 0.10.x).\n\n`mzizi_list_components` rows carry `rustCrate` when a component has Rust, the response carries\n`withRust`, and `rust: true` lists only those. `mzizi_search` hits carry `rustCrate` too. For\n`mzizi-hero-stat` the answer is `lead: \"rust\"`, crate `mzizi-brand`, and the four-clause\ncontract shown under \"Contracts\" below.\n\n## The crates\n\nOne crate per registry node that has Rust. The map is `mzizi-rs/crate-for-node.json`, and\n`pnpm rust:generate` refuses a node with Rust in it that no crate claims.\n\n| Node directory | Crate | Umbrella | What it holds |\n| ------------------- | ----------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `n1-tokens` | `mzizi-tokens` | `mzizi-roots` (always on) | the 21-family palette and scales as Rust consts, generated from `lib/tokens/palette.source.ts` |\n| `n2-primitives` | `mzizi-ui` | `mzizi-roots` (`ui`) | alert, avatar, badge, button, card, chart, input, label, markdown renderer, progress, safe-area frame, separator, skeleton, status badge |\n| `n3-brand` | `mzizi-brand` | `mzizi-roots` (`brand`) | alert banner, avatar stack, cover header, empty state, escalation card, gauge card, hero stat, meta tile, stats row, success screen, suitability card, user card (all `mzizi-*`) |\n| `n7-shell` | `mzizi-shell` | `mzizi-roots` (`shell`) | bottom nav, command palette, connectivity bar, deep-link handler, footer, mini-app runtime, notification center, persistent player, route guard, theme provider, toast provider, update prompt |\n| `n8-assurance` | `mzizi-assurance` | `mzizi-roots-server` (`assurance`) | probes, telemetry, the OTLP exporter, a11y and RTL conformity checks, alert and incident engines |\n| `n9-fundi` | `mzizi-fundi` | `mzizi-roots-server` (`fundi`) | the Fundi reporter and learning loop (client side) |\n| `n10-documentation` | `mzizi-docs` | `mzizi-roots-server` (`docs`) | the docs API and routing, AI context, the changelog and docs renderers (brings in `dioxus`) |\n| `n11-discovery` | `mzizi-discovery` | `mzizi-roots-server` (`discovery`) | page metadata and Schema.org JSON-LD |\n\nN5 resilience has a crate since 0.7.0: `mzizi-resilience` (circuit breaker, retry backoff,\nbulkhead, rate limiter, fallback chain and deadline, each a sans-IO state machine with a\n`contracts/lib/` contract). Depend on it directly; no umbrella re-exports it yet, and\n`crate-for-node.json` still maps `n5-resilience` to `mzizi-data`. `mzizi-chaos-engine` (N8, a\nseeded deterministic fault schedule for chaos tests that refuses to run in production) is the\nsame. N4 safety and N6 pages have no crate (N6 never will: a page implements nothing). A Workers adapter\n(`mzizi-worker`) is planned for `mzizi-roots-server` and has not landed.\n\nThe registry has 577 items and 55 `.rs` files. Cite `GET https://api.mzizi.dev/v1/ui` or\n`/v1/stats` for a current count rather than repeating these. To find a component's crate, read\n`rust.crate` from `mzizi_get_component`, or take its node directory and read the table above.\n`https://api.mzizi.dev/v1/rs/<name>` also serves a `crate` field, derived from the same map on\nregistry `main`; the API serves the registry at a pinned commit, and until the gateway's pin\nmoves past the map it names `mzizi-ui` for every component. Trust the map, or the MCP.\n\n## The renderer\n\nMzizi components are built in-house in Rust, not borrowed from another framework.\n\n- **Built so far.** Seven components render with Mzizi's own `render(props) -> String`: the\n skeleton and six static primitives, merged in `mzizi-dev/mzizi-registry`\n ([#506](https://github.com/mzizi-dev/mzizi-registry/pull/506) and\n [#510](https://github.com/mzizi-dev/mzizi-registry/pull/510)).\n- **Not yet.** Dioxus is still a dependency of `mzizi-ui`, `mzizi-brand`, `mzizi-shell`,\n `mzizi-docs` and `mzizi-roots` until the migration in the registry's `RFC-roots-renderer`\n finishes. The examples in this skill use Dioxus because the published crates need it today.\n- **Direction.** No Dioxus, Leptos or Yew in new work. Astro renders HTML on the server, static\n components ship zero WASM, there is at most one WASM module per page, and Tokio runs\n server-side only.\n\n## Using a UI component\n\nThis compiles against `mzizi-roots` 0.7.0 from crates.io and renders the component to a static\nHTML fragment with `dioxus-ssr`:\n\n```toml\n[dependencies]\nmzizi-roots = { version = \"0.7\", default-features = false, features = [\"brand\"] }\ndioxus = { version = \"0.7\", default-features = false, features = [\"macro\", \"signals\", \"hooks\", \"html\"] }\ndioxus-ssr = \"0.7\"\n```\n\n```rust\nuse dioxus::prelude::*;\nuse mzizi_roots::brand::{AlertSeverity, MziziAlertBanner};\n\nfn banner() -> Element {\n rsx! {\n MziziAlertBanner {\n r#type: \"Thunderstorm\",\n severity: AlertSeverity::Severe,\n headline: \"Severe storms this afternoon\",\n }\n }\n}\n\nfn main() {\n let mut dom = VirtualDom::new(banner);\n dom.rebuild_in_place();\n let html = dioxus_ssr::render(&dom); // a static HTML fragment for Astro to place\n println!(\"{html}\");\n}\n```\n\nThe output is a `<div data-slot=\"mzizi-alert-banner\" data-portal=\"https://mzizi.dev/components/mzizi-alert-banner\" role=\"alert\" …>`\nwith the same Tailwind classes and CSS custom properties as the React build, so one stylesheet\nstyles both.\n\nWhat a Roots UI component looks like:\n\n- **Same contract as the React build:** the same registry name, `data-slot`, `data-portal`,\n variant names and Tailwind classes over the same `var(--…)` tokens. No colour literals.\n- **Variants are enums**, and a `match` replaces `class-variance-authority`\n (`n2-primitives/badge.rs`, `button.rs`).\n- **Browser facilities are props.** Where the `.tsx` reads `useMziziHarness`, the Rust takes the\n one bit it branches on, usually `prefers_reduced_motion: bool`. Logging is the host's.\n- **Icons are slots:** an `Element` the host renders, where the `.tsx` takes a Lucide component.\n- **Each file is self-contained.** No helpers shared between components.\n\n## The two frontend paths\n\nThe frontend is either **Astro** (pure `.astro` components, or Roots rendered underneath) or\n**pure Rust end to end**.\n\n- **Pure Rust.** A Dioxus app depends on the crates and mounts the components on web (wasm),\n desktop or mobile. Every component supports this today.\n- **Astro, static.** Render with `dioxus-ssr` at build time or in a Worker, and let Astro place\n the fragment, as in the example above. It ships no JavaScript and suits display components.\n The brand crate's contract suite does this for every component on every run.\n- **Astro, interactive islands.** Compile the behaviour to one wasm module per page and wrap it\n as a custom element. **Not packaged yet**: the wrapper crate (`mzizi-islands`) is proposed for\n batch 2 of the RFC.\n\n- **Astro, pure `.astro` components** (mzizi-registry #397, owner decision 2026-10-04). The\n registry is the single source of every component in every format: `.tsx`, `.rs`, `.astro`\n (and `.mz`, the Mzizi language, as it lands). A component's `.astro` sits beside its `.tsx`\n and `.rs` (`components/registry/n<N>-*/<name>.astro`), pure Astro with no React or\n Vue under it and no client JavaScript unless its contract allows one enhancement script.\n Install with `mzizi add <name> --target astro` (inferred in an Astro project, even one with\n React islands): it reads `api.mzizi.dev/v1/astro/<name>` and writes the component,\n its flat-imported siblings (`./button.astro`, `./ui-utils`, `./server-table`) and any brand\n asset into `src/components/mzizi/`. A component with no `.astro` is refused by name, never\n replaced by its `.tsx`. `mzizi_get_component` returns the Astro block too.\n\nThe Mzizi Dashboard Standard's app components (`app-*`, plus the primitives `button`, `badge`,\n`card`, `alert`, `input`, `label`, `skeleton`), the Mzizi Discover Standard's components\n(`discover-*`, including the detail pattern) and the marketing-site components (`site-*`) all\nhave an `.astro` AND a `.tsx` in the registry (the `ui/` family's Astro ports keep the\nregistry's own `.tsx`, which share only what their contract's `identity` says), each held to\nits contract (\"The Dashboard and Discover Standards' contracts\" below). `@bundu/ui`\n(`@bundu/ui/app/*`, `@bundu/ui/discover/*`) is BUILT from these files at a pinned registry\ncommit, and its CI fails on drift; a Next.js app adopts the same design through the registry's\n`.tsx` (`mzizi add app-data-table --target tsx` or\n`npx shadcn add` of `api.mzizi.dev/v1/ui/<name>`). Use them as they are: do not\nfork, restyle or wrap the shell or a component; fix it upstream in the registry (contract,\n`.astro` and `.tsx` together), in the same change.\n\n## Starting an app\n\n**Install the shell first.** Before the first page, mount the app's one shell: `AppShell` for a\nsigned-in app or console, `DiscoverShell` for a public discover site, both from `@bundu/ui`.\nThen check that every page renders inside it. The `mzizi-architecture` skill has the choice, the\ninstall and the check. Apps built page by page without a shell are the failure it exists to\nstop. Neither shell has a Rust build yet (`rs: null`), so a Rust or Astro-with-Roots app still\ntakes the shell from `@bundu/ui`.\n\nFor a new Mzizi-ecosystem app, or any app that consumes the design system. Pick the path\nfirst (above); these steps are the Astro one, with the pure-Rust differences noted.\n\n### 1. Create the project (Astro)\n\n```bash\npnpm create astro@latest my-app -- --template minimal --typescript strict\ncd my-app\npnpm astro add tailwind cloudflare\n```\n\nAstro renders HTML by default, which is what makes a new app indexable by N11 and paintable on\na low-end device without opting in to either. For a pure-Rust app, start from\n`cargo new my-app` and skip to step 3.\n\n### 2. Wire the tokens\n\nCopy the generated stylesheet rather than retyping hexes: `skills/mzizi-design/tokens/palette.css`\nin `@nyuchi/mzizi-skills` (all 21 families, light and dark, generated from\n`palette.canonical.json` at the package root). Import it from your global stylesheet (in Astro,\n`src/styles/global.css`) and register the properties in the Tailwind 4 `@theme` block so\nutilities generate. In Rust the same palette is the `mzizi-tokens` crate, always on in\n`mzizi-roots`. How the 21 families are meant to be used, the radius scale and the Bundu floor (17px body, 14px captions, a 49px hit region on every control, a 14px inset and 7px gaps)\nare in the `mzizi-design` skill.\n\nPick the app's accent through `--brand-accent` rather than hardcoding a mineral hex: nyuchi =\ngold, mukoko = tanzanite, and a Mzizi surface = hematite (a Heritage tone, not a mineral; see the\nconstellation in `mzizi-design`).\n\n### 3. Add the Roots components\n\n```bash\ncargo add mzizi-roots # tokens, primitives, brand components, shell parts (no AppShell yet)\n```\n\nFor Astro, a small Rust crate (a build step, or a `workers-rs` Worker for request-time\nrendering) renders each component to HTML with `dioxus-ssr`, and Astro places the fragment.\nThe fragment carries the same Tailwind classes as the React build, so the stylesheet from step 2\nstyles it, and it ships no JavaScript. Interactive islands (Roots compiled to wasm and wrapped\nas custom elements) are proposed and not packaged yet. \"Using a UI component\" above has a\ncompiling example.\n\nNever copy component source between apps. Depend on the crate, so every app inherits fixes.\n\n### 4. Give the app's AI assistants the doctrine\n\nInstall the published skills bundle so any assistant working in the repo has the design system,\nthe language and the components on hand:\n\n```bash\nnpm install -D @nyuchi/mzizi-skills\nnpx skills experimental_sync # links the bundled skills into .agents/skills/\n```\n\n`npx skills add @nyuchi/mzizi-skills` does **not** work: the `skills` CLI reads that argument\nas a GitHub repository and fails to clone it. `experimental_sync` is the CLI's route for skills\nthat ship in `node_modules`, and it is marked experimental. The same skills are served with no\ninstall at all by the MCP's `mzizi_get_skills`.\n\nThe public Claude Code plugin installs these skills and the MCP in one step:\n\n```\n/plugin marketplace add mzizi-dev/mzizi-registry\n/plugin install mzizi@mzizi\n```\n\nTo wire the MCP directly instead, add it to `.mcp.json` or your client config. It is free with\nno account; only the Fundi tools need sign-in:\n\n```json\n{\n \"mcpServers\": {\n \"mzizi\": { \"type\": \"http\", \"url\": \"https://mcp.mzizi.dev/mcp\" }\n }\n}\n```\n\n### 5. Let fundi read the project\n\n`@nyuchi/mzizi-cli` ships the **fundi** agent, which reads the project off disk and plans the\nwiring against the registry:\n\n```bash\npnpm add -D @nyuchi/mzizi-cli\npnpm exec fundi explore # offline — snapshot the project\npnpm exec fundi plan \"wire up Mzizi tokens\"\n```\n\nRun the bins from the installed package (`pnpm exec`, or `npx` inside the project). A bare\n`npx fundi` outside it fetches an unrelated npm package called `fundi`. `explore` runs without\ncredentials; `plan` and `chat` need `ANTHROPIC_API_KEY`. A plan is\ninert until you apply it, so read it before letting it write.\n\n### 6. Verify\n\n```bash\npnpm dev\n```\n\nConfirm the tokens resolve in light and dark, a rendered Roots fragment picks up the styles,\nand the page is readable with JavaScript off.\n\n### 7. An existing React app\n\nIf the app is already React, the React build installs with the CLI or shadcn, by absolute URL:\n\n```bash\npnpm add -D @nyuchi/mzizi-cli && pnpm exec mzizi add button # infers the tsx target\nnpx shadcn@latest add https://api.mzizi.dev/v1/ui/mzizi-theme-provider\n```\n\nPoint `components.json` `registries.mzizi.url` at `https://api.mzizi.dev/v1/ui`. A bare name\nresolves against shadcn's own registry and installs a different component. Do not start a new\napp this way.\n\n## Server components\n\nA Roots server component is a **sans-IO core**: typed request in, typed response out, every\nexternal facility (a store, a clock, an outbound fetch, a secret) behind a trait. The host owns\nI/O and credentials. `n10-documentation/mzizi-docs-api.rs` is the model: it parses a path into a\ntyped `Route`, asks a `DocsStore` trait for data and returns an `ApiResponse`. `mzizi-assurance`,\n`mzizi-fundi` and `mzizi-discovery` have no dependencies at all. The RFC records a passing\n`wasm32-unknown-unknown` `cargo check` for those three and `mzizi-docs`; CI does not run a\nwasm32 job yet.\n\nA host adapter mounts the core: a `workers-rs` adapter for Cloudflare Workers, an axum adapter\nfor containers. Neither adapter crate exists yet. Until then the host writes the few lines that\nturn its request into the core's request (see the `mzizi-backend` skill). Dioxus\n`#[server]` functions are one more possible adapter, not the building block.\n\n## Contracts\n\nEvery Roots component carries a contract, in two parts:\n\n1. **The registry contract:** one registry name, description, dependencies and variants shared\n by both builds, and the same `data-slot`, `data-portal` and tokens. Each crate's\n `tests/contract.rs` reads the `.tsx` sibling and asserts that the slot, the portal and every\n variant value the Rust emits appear there too. A deliberate divergence, such as a touch\n target raised to the 49px hit region, is asserted from both sides, so the test fails when the\n React build is fixed.\n2. **Checkable clauses** in the Mzizi language's `contract … end` grammar (RFC-0006; the\n `mzizi-language` skill), exported as `pub const CONTRACT: &str`. From\n `n3-brand/mzizi-hero-stat.rs`:\n\n ```rust\n pub const CONTRACT: &str = r#\"contract\n slot is \"mzizi-hero-stat\"\n role is \"region\"\n label not_empty\n button \"Share\" min_height 48\n end\"#;\n ```\n\n `mzizi-brand`'s contract suite renders each component with `dioxus-ssr` in named states and\n evaluates every clause against the markup it emits. **A clause it cannot evaluate fails.**\n `mzizi_roots::brand::CONTRACTS` lists every brand component with its contract.\n\nToday the twelve `mzizi-brand` components export `CONTRACT`. The other crates check the\nregistry contract against the `.tsx`, and their `CONTRACT` clauses arrive batch by batch.\nServer-component clauses (status per route, CORS and cache headers, no credential in the core)\nwait on the language's contracts-everywhere RFC (RFC-0010). Until then their tests assert those\nproperties against an in-memory implementation of the traits. The language's own `service`\ncontracts (`example` and `ensure`, RFC-0011) apply to a Mzizi `service`, not to these Rust\ncrates. No component lowers to Rust yet (only a Mzizi `service` does, to a local axum\npackage), so `mz contract` does not read a Roots `CONTRACT`.\n\n### The Dashboard and Discover Standards' contracts\n\nThe registry's\n[`contracts/`](https://github.com/mzizi-dev/mzizi-registry/tree/main/contracts) holds a\nmachine-readable, versioned contract for every component of `@bundu/ui` (built in\n`mzizi-dev/packages-npm`), in two families of one format:\n\n- **`app/`, the Mzizi Dashboard Standard** (since registry `a298a4e`, 2026-10-04,\n mzizi-registry #404): the one dashboard design for every product in the Bundu ecosystem,\n with each brand's mineral as an overlay (docs.mzizi.dev/patterns/dashboard-standard). Its 35\n contracts are the server-rendered app components (`@bundu/ui/app/*.astro`): the shell, the\n page and data patterns, and seven primitives.\n- **`discover/`, the Mzizi Discover Standard** (since registry `0bea10c`, 2026-10-04,\n mzizi-registry #415, tracking issue #413): one design for every public discover and browse\n page in the Mukoko family (docs.mzizi.dev/patterns/discover-standard). Its 16 contracts are\n the Discover components (`@bundu/ui/discover/*.astro`). \"The Discover Standard\" below says\n what is different.\n\n| File | What it is |\n| ------------------------------------------------- | ---------------------------------------------------------------------------------- |\n| `contracts/schema/component-contract.schema.json` | the format, one for both families |\n| `contracts/app/<name>.contract.json` | one Dashboard Standard component's contract |\n| `contracts/discover/<name>.contract.json` | one Discover Standard component's contract |\n| `contracts/site/<name>.contract.json` | one marketing-site component's contract |\n| `contracts/ui/<name>.contract.json` | one Astro port of a registry primitive (native select, segmented control, …) |\n| `contracts/index.json` | all 77 component contracts with their version and `.astro`, `.tsx`, `.rs` items |\n| `contracts/README.md` | the coverage tables, the recorded gaps, the shared rules, how to change a contract |\n\n**Read the contract before you build or change a `@bundu/ui` component, in any language.** It is\nthe specification: `props` and `slots` (types, required, defaults, closed `values`; the\n`.astro` file's props and slots must be exactly these), `behaviour`, `states` (named fixtures,\n`default` required; every runner renders every state), `accessibility` (roles, keyboard,\nfocus, ARIA, WCAG 2.2 criteria), `density`, `theming`, `noJs`, `responsive`, the `contract`\nblock and `checks` (selector assertions on the rendered markup). `implementations` names the\nAstro export and the `.tsx` and `.rs` siblings, with what they share (`identity`: `slot`,\n`slot+role` or `slot+variants`) and their `divergences`; items under `related` look similar\nand have a different contract, so they are not implementations. `implementations.astro`\ngives the package, the export and `since`: the `@bundu/ui` release that first shipped it, 0.3.0\nfor the original two families (mzizi-registry #419) and 0.6.0 for the components added since\n(money, plan summary, tier status, confirm action, the markdown renderer, and the trust and\naddress primitives).\n`@bundu/ui` is 0.7.0 on npm. `gaps` records where the current build falls short.\n\n**Every new build starts from its contract, library or component** (owner, 2026-10-31). Write or\nchange the contract first, then the builds. A fifth family, `lib/`, holds\ncontracts for non-visual libraries: the N5 resilience primitives (circuit breaker, retry,\nbulkhead, rate limiter, fallback chain, deadline) and the N8 chaos engine, in their own format\n(`schema/lib-contract.schema.json`), each invariant held by a test in the implementing crate\n(mzizi-registry #585, released in v4.18.0).\n\nFour fields are requirements. packages-npm's contract test evaluates the first three on the\nrendered Astro build; the responsive rules are prose. The values below are the Dashboard\nStandard's; the Discover Standard's differ (below):\n\n- **No-JS fallback.** `noJs.script` is `none` (no script) or `enhancement` (one script, and\n everything in `noJs.without` works when it does not run). Only AppShell and CommandPalette\n are `enhancement`; every other app component ships no script.\n- **Density.** Each row gives a part's height in px for a fine pointer and a coarse one\n (touch), read from the rendered classes (`h-N`, `min-h-N`, `size-N` = N×4;\n `pointer-coarse:` for touch). Dashboards are drawn dense on a mouse and at the floor on touch:\n the app Button's `md` row reads 36px fine and 48 coarse with the small radius (a button is a\n `hit-area` element, so its hit region is 49px; its `sm` link row is a pinned gap), and nav rows\n are drawn 32 on a mouse. The registry primitives read the same density from the size roles\n (`h-control`: 49px coarse, 36px drawn at desktop density, hit region 49px either way), which the\n runner and `mzizi-rs/contract-eval` understand (`ROLE_HEIGHTS`). Since the Bundu floor (registry\n 4.23.0) both fail a density row whose interactive control has a hit region under 49px on either\n pointer, or a button-like control drawn under 28px; they count the 49px hit region only where the\n token layer's `hit-area` rule provably applies (`contracts/hit-region-cases.json`). Rows that\n predate the floor are pinned in `HIT_AREA_GAPS` and listed in `gaps`, not hidden.\n- **Theming.** A brand overlay changes colour only: `--primary`, `--ring`, AppShell's\n `--app-accent` and the brand mark. Layout never varies by brand. A mineral may be named only\n as one of the component's declared `statusColours`; any other mineral class, or any colour\n value, fails (the `mzizi-design` skill).\n- **Responsive.** Each contract's rules from 320px up.\n\nIn both families the tests also fail **any inline `style` attribute** on a rendered element, so\na page's Content-Security-Policy can keep `style-src 'self'` with no\n`style-src-attr 'unsafe-inline'`: custom properties go through classes or data attributes\n(AppShell's `data-accent`), sizes through classes or SVG geometry (BarChart). JSON-LD\n(`type=\"application/ld+json\"`) is data and does not count as a script.\n\n**How they relate to `CONTRACT` and the language.** The `contract` field is a\n`contract … end` block in the same clause grammar as a Roots `pub const CONTRACT` (RFC-0006\nand RFC-0010), restricted to what applies to rendered markup: subjects `slot`, `role`,\n`label`, `class`, `portal`, `<element> \"<text>\"`, `uses <data-slot>` and `when <state> …`;\npredicates `is`, `contains`, `not_empty`, `in`, `uses \"--token\"`, `min_height` and `shows`. As\nin the language, **a clause, check or density row a runner cannot evaluate fails** (FM-12).\nThe registry's own suites evaluate them: `__tests__/astro/contracts.test.ts` renders every\n`.astro` in every state (Astro's container, no renderer), `__tests__/contracts/\ntsx-contracts.test.tsx` renders every `contract`-identity `.tsx` with the same runner\n(`contracts/runner.ts`), and `__tests__/contracts/contracts.test.tsx` validates every contract\nagainst the schema and the grammar and checks each identity-only sibling. packages-npm's\n`src/app/contracts.test.ts` runs the same contracts again on the built package. A crate's own `CONTRACT`\nand `tests/contract.rs` still run as before.\n\nEvery contract has an `.astro` (`implementations.astro.registry`). The seven app primitives'\n`.tsx` are the registry's own `button`, `badge`, `card`, `alert`, `input`, `label` and\n`skeleton`, which share only their identity (the contract lists the divergences), as do the\n`ui/` family's; every other `.tsx` (`app-*`, `discover-*`, `site-*`) has\n`identity: \"contract\"`, held to the whole contract exactly as the Astro build is. A `.rs`\nexists in `mzizi-ui` for `button`, `badge`, `card`, `input`, `label` and `safe-area-frame`;\nmzizi-registry #401 tracks Rust ports of the rest, done when each passes the contract.\n\n### The Discover Standard\n\nThe 11 Discover Standard list-page contracts in `discover/` and the node each declares (the detail pattern below adds five more). DiscoverShell's `kind` is `shell`;\nthe other ten are `pattern`s.\n\n| Contract | Component | Node | What it is |\n| --------------------------- | --------------- | ---- | --------------------------------------------------------------------------------------------- |\n| `discover/discover-shell` | DiscoverShell | N7 | the frame: skip link, mineral strip, header (brand, \"Main\" nav, one action), `<main>`, footer |\n| `discover/discover-meta` | DiscoverMeta | N6 | the `<head>` tags: title, description, robots, canonical, Open Graph, the X card, JSON-LD |\n| `discover/discover-hero` | DiscoverHero | N6 | breadcrumbs, eyebrow, the page's one `<h1>`, lead, the search box and actions |\n| `discover/discover-search` | DiscoverSearch | N6 | the one search box: a `role=\"search\"` GET form, so every search is a URL |\n| `discover/category-chips` | CategoryChips | N6 | the category links as a named navigation landmark; chips wrap |\n| `discover/category-chip` | CategoryChip | N6 | one category link, with an optional count; a link, never a button |\n| `discover/discover-section` | DiscoverSection | N6 | one band: eyebrow, heading, description and \"See all\" over its content |\n| `discover/result-grid` | ResultGrid | N6 | a polite status line, the cards, the empty state and \"load more\"; `grid`, `list` or `rail` |\n| `discover/discover-card` | DiscoverCard | N6 | one card for every page, variants `article`, `event`, `circle`, `place` |\n| `discover/load-more` | LoadMore | N6 | cursor pagination: one `rel=\"next\"` link to `?cursor=…` |\n| `discover/open-in-app` | OpenInApp | N6 | \"Open in Mukoko\": an https link into the super-app, with one line saying what happens |\n\nSame format, schema, clause grammar and runners as the Dashboard Standard. What the Discover\ncontracts state differently:\n\n- **What it is for.** Public discover and browse pages in the Mukoko family (circles, news,\n events, weather, and the super-app on the web), not admin or operator dashboards. It reuses\n the Dashboard Standard where they overlap: ResultGrid's `empty` slot takes `app/empty-state`\n or `app/state-message`, and `related` names `app/filter-bar` and `app/pagination` as similar\n components with a different contract.\n- **No script at all.** Every Discover contract's `noJs.script` is `none`; there is no\n `enhancement` like AppShell's or CommandPalette's. Views are URLs, not client state: search\n is a GET form (`?q=`, with no `cursor`, so a new search starts at the first page), a category\n is a link, and paging is LoadMore's `rel=\"next\"` link to `?cursor=`.\n- **Sizes come from the size roles, with gaps still pinned.** Discover's search field is 49px on\n both pointers and its submit button 36px drawn on a mouse and 49px on touch (the size roles,\n mzizi-registry#597). \"See all\", LoadMore, OpenInApp, DiscoverCard's app link, the category\n chips, the navigation links and the home link are pinned in the registry's `HIT_AREA_GAPS`\n (links, or literal `h-11`/`h-12` classes) until they move onto a size role or take `hit-area`;\n do not copy their heights. Until 2026-11-01 Discover was the same size on every pointer; the\n Bundu floor replaced that.\n- **Theming.** The brand-overlay rule is the same (colour only, no colour values, a mineral\n only as a declared status colour), but there is no AppShell `--app-accent`. A brand shows in\n DiscoverShell's `brand` slot (its mark) and in whatever primary action its `action` slot\n holds; the frame, header, navigation and footer are identical in every brand. `--primary` reaches only\n DiscoverHero's wash (`gradient-showcase`), DiscoverCard's monogram and `brand` badge tone,\n and OpenInApp's `primary` variant. Chips, sections and LoadMore are neutral in every brand,\n and DiscoverSearch follows the brand only in its focus ring (`--ring`). Only DiscoverCard\n declares status colours (`cobalt`, `malachite`, `gold`, `terracotta`), for its badge tones.\n- **Server-filled shells.** Every Discover component also works in an Astro page built once\n with `{{placeholders}}` and filled by a server, as circles.mukoko.com's Rust Worker does:\n text props are plain strings, text that fills to `\"\"` hides itself, and state switches are\n attributes styled by classes (ResultGrid, DiscoverSection and LoadMore `state`, DiscoverCard\n `badgeTone`, CategoryChip `current`). CategoryChip, DiscoverCard, DiscoverHero, LoadMore and\n ResultGrid have a `template` state with placeholders, rendered like every other state.\n DiscoverCard and CategoryChip render their own `<li>`, so they repeat as fragments.\n- **One card.** DiscoverCard's `variant` changes emphasis, never structure. Its title is the\n one primary link, stretched over the card; other actions stay separately focusable.\n- **OpenInApp's `href` is https** (a universal link or a server redirect), never a bare\n `mukoko://` link, which does nothing without the app.\n- **No siblings and no gaps yet.** No `.tsx` or `.rs` implements a Discover contract, and none\n records `gaps`. A React or Rust port is done when it passes the same contract.\n\n**Upstream first** (registry `CONTRIBUTING.md`, from #415, for both families): a component an\napp needs that Mzizi does not have, or a change to one it has, goes upstream immediately: its\ncontract in the registry, its build in `@bundu/ui` and its page on docs.mzizi.dev. An app keeps\na local copy only while that upstream pull request is open, marked at the top of the file with\n`TODO(mzizi): <upstream PR URL>`.\n\n### Changing a contract\n\n1. Edit `contracts/<family>/<name>.contract.json` in the registry and bump its `version`:\n major when a clause, prop, slot or state is removed or narrowed, minor when one is added,\n patch for prose. A new family also needs its directory in `FAMILIES` in\n `__tests__/contracts/contracts.test.tsx` and in packages-npm's `scripts/contract-paths.mjs`.\n2. Update `contracts/index.json` and the README's coverage table for that family. The test\n fails if either disagrees.\n3. Run `pnpm test` in the registry, then `pnpm contracts:fetch <branch>` and `pnpm test` in\n packages-npm. The Astro build must pass the new contract before either side merges. Never\n edit the copy in packages-npm.\n\n**The detail pattern** (mzizi-registry #429; docs.mzizi.dev/patterns/discover-detail): the\npage for one item reached from a discover page uses five more `discover/` contracts, each an\n`.astro` and a `.tsx`: `detail-hero` (the page's only `<h1>`, when/where, lead, image or media\nslot), `breadcrumb` (named `<nav>`, `aria-current=\"page\"`, BreadcrumbList JSON-LD), `meta-list`\n(a `<dl>` of facts), `detail-actions` (Open in Mukoko first, then secondary and share links; no\nscript) and `related-rail` (DiscoverCards; a scroll-snap row on phones, a grid from 48rem;\nhidden when empty).\n\n**Open in Mukoko** is one canonical https universal link, `mukoko.com/open/<service>/<id>`\n(`openInMukokoUrl` in `discover-open-link.ts`; `@bundu/ui/discover/open`). `open-in-app` 1.1.0\nand `detail-actions` take `service` (`news`, `events`, `weather`, `circles`) and `id`; `href` is\nonly an override. The apps claim `mukoko.com/open/*` as universal and app links; on the web a\nresolver (mukoko-dev/super-app-web `/open/*`) sends the reader to the service's page. Never a\nbare `mukoko://` link.\n\n## Reuse before build\n\n**The simplest change is the one you don't write.** Reuse before you build, compose before you\nimplement, and let one source own each decision. Use this before adding a component, a style or\na page, and during any refactor.\n\n1. **Reuse before build.** Search the registry first: `mzizi_search` or\n `mzizi_list_components` over the MCP (`https://mcp.mzizi.dev/mcp`, free), or\n `GET https://api.mzizi.dev/v1/ui`. Where a Rust implementation exists, depend on its crate\n (`cargo add mzizi-roots`). For the React build, install with\n `mzizi add <name>` or `npx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>`. Never\n reimplement a primitive that already exists.\n2. **Styles are tokens.** Never hardcode a colour, radius, spacing, or motion value —\n consume `var(--…)` (or the `mzizi-tokens` consts in Rust). N1 is the only layer allowed to\n define values, so a rebrand is one edit. A new colour goes in the registry's\n `lib/tokens/palette.source.ts`, and `pnpm tokens:sync` regenerates `styles/globals.css`, the Rust\n tokens and the other emitters from it.\n3. **Compose, don't implement.** A page (N6) is a composition of N2 primitives and N3 brand\n components — never inline a `<button>`, a card, or an SVG. Pull spinners, skeletons, and\n badges from N2.\n4. **Variants over copies.** Collapse near-duplicate components into one variant table: an\n enum with a `match` in Rust, an `enum` table in Mzizi source, one `cva()` in the React\n build. Express differences as `variant` / `size` props, not new files.\n5. **One composition path.** In the React build, compose class names through `cn()` and\n extract shared logic into `lib/` or a hook. In Rust, a pure function beside the component.\n No logic duplicated across files.\n6. **Right altitude.** Keep each component self-contained, importing only from the layer\n below on its strand. A primitive that reaches up into page code can't be installed on its\n own.\n7. **Delete first.** Prefer removing over adding — dead code, unused props, redundant\n wrappers, commented-out blocks. Less code is less to break, read, and keep true.\n8. **Live data, not copies.** Read counts, tokens and doctrine from their source\n (`https://api.mzizi.dev/v1/stats`, `palette.canonical.json`, `mzizi_get_tokens`,\n `mzizi_get_doctrine`). A baked-in number or duplicated token guarantees drift.\n\n### Checklist\n\n- [ ] Searched the registry before building, and used the Rust build where one exists?\n- [ ] All styles are tokens (`var(--…)`), zero hardcoded values outside N1?\n- [ ] Page is pure composition of N2/N3, no inlined primitives?\n- [ ] Variation expressed as one variant table, not near-duplicate files?\n- [ ] One composition path, shared logic extracted once?\n- [ ] Each file self-contained and installable, importing only downward?\n- [ ] Deleted more than you added where you could?\n- [ ] Counts, tokens, and doctrine read live, not hardcoded?\n\n## Contributing a component\n\n**A component is a real file.** `components/registry/n<N>-<label>/<name>.rs` (Rust, compiled by\nthe node's crate in `mzizi-rs/`) and, for the React build, `<name>.tsx` beside it, which\n`pnpm typecheck` (`tsc --noEmit`) and `pnpm lint` check. An error in either fails CI. The\nregistry has no app to build: CI's `Rust` job runs `cargo fmt`, `check`, `clippy` and the\ncontract tests, and its `Build` job runs `pnpm build`, which runs every generator in write mode\nand fails if that changes a committed file.\n\n| File | What it is |\n| --------------------------------------------- | ----------------------------------------------------------------- |\n| `components/registry/n<N>-<label>/<name>.rs` | the Roots component (Dioxus UI, or a sans-IO server core) |\n| `components/registry/n<N>-<label>/<name>.tsx` | the React build of the same contract |\n| `registry.json` | the manifest `/v1/ui/<name>` and `npx shadcn add` resolve against |\n\n`registry.json` carries the install contract (description, dependencies,\n`registryDependencies`, target paths) and **never the source**. The node is derived from the\ndirectory the component lives in, so it cannot disagree with where the file is.\n\n**There is no database.** The registry's files are its data layer (mzizi-registry PR #368), and\na test fails the build if a Supabase import or a `SUPABASE_*` read comes back. Nothing about a\ncomponent, including its source, status or history, is written to a table. If you find\ninstructions anywhere telling you to `INSERT` a component or read a `component_documents` row,\nthey are stale.\n\n### Step 1 — Decide the node\n\nThe architecture is the **Mzizi DNA double helix**: eight nodes (N1–N8) on the engineering\nbackbone, four rungs (N9–N12) that bridge both backbones, and six strands. The N-numbers are\n**labels, not a sequence**.\n\nThe node decides the **directory**, the directory decides the **crate** that compiles the Rust\n(`mzizi-rs/crate-for-node.json`), and the directory is what the build reads. Choosing the node\nand choosing where the file goes are the same decision.\n\n| Node | Directory | Crate | What belongs there |\n| ----------------- | -------------------- | ----------------- | --------------------------------------------------------------------------- |\n| N1 tokens | `n1-tokens/` | `mzizi-tokens` | A token value or emitter (colour, spacing, motion, radius), not a component |\n| N2 primitive | `n2-primitives/` | `mzizi-ui` | Generic UI: button, card, input, dialog, toolbar, bento-grid |\n| N3 brand | `n3-brand/` | `mzizi-brand` | A branded composition with the mineral palette |\n| N4 safety | `n4-safety/` | none yet | A conditional-rendering gate: permission, geo, rate limit |\n| N5 resilience | `n5-resilience/` | none yet | Error boundary, skeleton, offline banner, fallback chain |\n| N6 pages | `n6-pages/` | none yet | Full-screen layout composition |\n| N7 shell | `n7-shell/` | `mzizi-shell` | App container: navigation, routing, lifecycle |\n| N8 assurance | `n8-assurance/` | `mzizi-assurance` | Instrumentation, a11y audit, RTL conformity, probes |\n| N9 fundi | `n9-fundi/` | `mzizi-fundi` | Automated healing (a rung) |\n| N10 documentation | `n10-documentation/` | `mzizi-docs` | Docs, AI instructions, docs infrastructure (a rung) |\n| N11 discovery | `n11-discovery/` | `mzizi-discovery` | SEO / AIO surfaces: if the machine can't see it, it doesn't exist (a rung) |\n| N12 skills | none in the registry | none | Agent skills (a rung), authored in `mzizi-dev/agent-tools/mzizi-skills/` |\n\nA node with no crate yet (N4, N5, N6) takes a `.tsx` today. Adding its first `.rs` means adding\nits crate and its `crate-for-node.json` entry in the same pull request; `pnpm rust:generate`\nrefuses Rust that no crate claims.\n\nIf you are unsure where something belongs, ask the MCP (`https://mcp.mzizi.dev/mcp`, free, no\naccount): `mzizi_list_components` with `node: N` shows what already lives in a node, and\n`mzizi_get_architecture` returns the node, rung and strand model with live counts. Never\nhardcode counts.\n\n### Step 2 — Write the file\n\nCreate it in the directory the node names:\n\n```\ncomponents/registry/n3-brand/mzizi-your-component.rs # the Roots component\ncomponents/registry/n3-brand/mzizi-your-component.tsx # the React build, same contract\n```\n\nThe Rust file carries `pub const CONTRACT: &str` and follows the rules in \"Using a UI component\" above: variants as enums, tokens by reference, browser facilities as props, icons as slots,\nno helpers shared between components. `.ts` is for a non-rendering module.\n\nThat is the authoring step. There is no row to insert and no registration to perform before\nwriting code: the file _is_ the component, and `cargo test` (for the `.rs`), `pnpm typecheck`\nand `pnpm lint` (for the `.tsx`) and `pnpm build` (every derived artifact regenerated, with no\ndiff) are what tell you it is real.\n\nNaming: N3 brand components take a `mzizi-` prefix. (Components formerly named `nyuchi-*` are\n`mzizi-*`, and the old names redirect.) Everything else is the bare name, matching what a\nconsumer will type in `mzizi add` or `npx shadcn add`.\n\n### Wiring the Rust into its crate\n\n`pnpm rust:generate` writes the committed copy to `mzizi-rs/crates/<crate>/src/generated/`. Add\nthe `#[path = \"generated/<name>.rs\"] pub mod <name_snake>;` line and the `pub use` to the\ncrate's `src/lib.rs` by hand, and add the component to the crate's `tests/contract.rs`. Edit\nthe registry file, never the copy. The workspace lints forbid `unsafe_code` and warn on\n`missing_docs`; clippy turns the warning into a failure. Cap parallel builds on a small machine\n(`CARGO_BUILD_JOBS=2`).\n\nA release is a version bump in `mzizi-rs/Cargo.toml`. On merge to `main`,\n`.github/workflows/publish-crates.yml` re-runs the Rust gate and publishes each crate in\ndependency order, skipping any version already on crates.io. A published crate name is\npermanent. Say in the PR body that components changed, so the site, docs and skills are\nupdated to match.\n\n### Step 3 — Declare it in `registry.json`\n\nOne item, carrying the install contract. `meta` is the part you author; four other fields are\n**derived from it** and must not be hand-written.\n\n```jsonc\n{\n \"name\": \"your-component-name\",\n \"type\": \"registry:ui\",\n \"description\": \"Short description of what it does.\",\n \"dependencies\": [],\n \"registryDependencies\": [],\n \"files\": [\n {\n \"path\": \"components/registry/n2-primitives/your-component-name.tsx\",\n \"target\": \"components/ui/your-component-name.tsx\",\n \"type\": \"registry:ui\"\n }\n ],\n \"meta\": {\n \"collection\": \"primitives\",\n \"owner\": \"mzizi\",\n \"useCases\": [\"Use case 1\", \"Use case 2\"],\n \"variants\": [\"default\", \"sm\", \"lg\"],\n \"sizes\": [\"default\", \"sm\", \"lg\"],\n \"features\": [\"cn() for className composition\", \"data-slot attribute\"],\n \"a11y\": [\"ARIA attributes\", \"Keyboard accessible\"],\n \"hasDemo\": true\n }\n}\n```\n\n`files[]` lists the `.tsx`: it is the shadcn install contract for the React build. The `.rs`\nbeside it is not listed; the node's crate compiles it, and `/v1/rs/<name>` serves it with the\ncrate's name.\n\n**Do not write `title`, `categories`, `docs` or `author`.** `pnpm registry:metadata` derives all\nfour from `meta` and the repo. `docs` in particular is what the shadcn CLI prints _after_\ninstalling, so it is generated from your `useCases` / `variants` / `sizes` / `features` / `a11y`\nrather than duplicated by hand — and an item with no `meta` deliberately gets no `docs`, because\na stub reads as documentation and says nothing.\n\nThen run the generators and let the gates check you:\n\n```bash\npnpm registry:paths # fill file paths from what is on disk\npnpm registry:metadata # derive title, categories, docs, author\npnpm registry:normalize # canonical ordering\npnpm registry:validate # every item resolves on disk; every dependency addressable\npnpm registry:verify # the CI drift gate\npnpm typecheck && pnpm lint && pnpm build # build: every generator; commit what it writes\n```\n\nFollow the criteria for the chosen node.\n\n#### Every node: the Rust file\n\n- `pub const CONTRACT: &str` with the clauses the rendered markup must meet (slot, role, touch\n floors, variant tables), evaluated by the crate's `tests/contract.rs` on `dioxus-ssr` output\n- The same `data-slot`, `data-portal` and variant values as the `.tsx`, asserted by the same\n suite; a deliberate divergence is asserted from both sides\n- `prefers_reduced_motion: bool` as a prop where the `.tsx` reads the harness's motion\n- Icons as `Element` slots, not an icon crate\n- Doc comments on every public item (`missing_docs` is a warning, and clippy runs with\n `-D warnings`); no `unsafe`\n\n#### N2 primitive checklist\n\n- No `useMziziHarness` import in the `.tsx`\n- `data-slot` attribute on the root element\n- `data-portal` attribute pointing at `https://mzizi.dev/components/<name>`\n- `cn()` for className composition in the `.tsx`\n- No raw Tailwind colours: use semantic tokens (`bg-primary`, `text-foreground`, …)\n- Icons from `@/lib/icons` in the `.tsx`, never `lucide-react` directly\n- **The Bundu floor: every interactive control has a 49px hit region on every device** (owner,\n 2026-11-02: 7 x 7, above Apple's 44 pt; desktop included). Body text is 17px and captions 14px\n everywhere; nothing readable is under 14px, so use `text-sm` (14px), never `text-[10px]`.\n Buttons and inputs take their height from the size roles, never `h-12`, `h-14` or\n `min-h-[44px]`: `h-control` (49px touch, drawn 36px at desktop density), `h-control-sm`,\n `h-control-lg` (56px touch), `size-control-icon`, `h-field` (49px drawn everywhere, because a\n native field cannot carry a pseudo-element), as `button.tsx`, `button.astro` and `button.rs`\n state, and `/v1/brand` `.componentSpecs` serves (`minTouchTarget: 49`). At desktop density a\n button-like control may be drawn 28 to 40px: the token layer's `hit-area` rule gives it a\n transparent `::after` of `max(100%, var(--target-floor))`. The rule steps aside for an element\n with its own `after:` utility, for menu and listbox rows (they take `min-height: var(--target-floor)`),\n for a calendar day and for an element with `overflow: hidden`; a standalone link that is a target\n is drawn at 49px or takes `hit-area`. `primitives/button.mz` still says 56 until it is updated in\n its own repository. A smaller visual box, such as a badge, is not a touch target; if it becomes\n one, give it `min-h-touch-min`. `data-mzizi-sizes=\"legacy\"` on the root is the compatibility\n alias that restores the pre-2026-11-01 sizes (and steps out of the floor); it is planned for removal\n in the next major, so set it only to buy time. Contrast floors beside it: body 7:1, non-body\n 4.5:1, UI 3:1, with five button-label roles held at 4.5:1 (`primary-foreground`,\n `brand-accent-foreground`, `app-accent-foreground`, `sidebar-primary-foreground`,\n `destructive-foreground`). `text-base` can collide with `--color-base`: write `text-body`\n (details in `mzizi-design`).\n- **The spacing floor: padding, gaps and leading have minimums too** (owner, 2026-11-04, mzizi-registry#617; `BUNDU_FLOOR.spacing`). **Inset 14px** between text and its container edge (`--inset-floor`), **7px** between adjacent controls, between a label and its field and between sibling items (`--gap-floor`; visible edge to visible edge, hit regions may touch but never overlap), and **1.5** body line height (`--leading-floor`). The base layer gives every element the floor even with no padding or gap set; the token test fails a component spec below it; every component contract asserts its spacing minimums, and the TypeScript runner and `mzizi-rs/contract-eval` must agree; a rendered geometry guard checks neighbouring controls, text inset and WCAG 1.4.12 stress. A component you add or change keeps at or above these numbers in its spec and its contract, in the `.tsx`, the `.astro` and the `.rs` alike, and nothing may lower them (no brand pack, no density layer); `data-mzizi-sizes=\"legacy\"` is the only opt-out. Use the components and tokens, and never `p-0`, `px-0`, `gap-0`, a negative margin, `leading-none`, `leading-tight` or an arbitrary `p-[4px]` on a text container or a row of controls without an exception comment (`mzizi-spacing-exempt: <reason>`); `mzizi check` (`@nyuchi/mzizi-cli` 0.8.0 or later) fails the project otherwise. A registry or `@bundu/ui` older than the spacing floor has none of the custom properties. Details in `mzizi-design`.\n- Pill-shaped categories (button, input, avatar, badge, toggle) use `rounded-full`\n\n#### N3 brand checklist\n\n- `mzizi-` prefix on the name\n- `useMziziHarness(\"<name>\")` in the `.tsx` for the motion preference (and `log` or\n `LiveRegion` where the component uses them); `prefers_reduced_motion` as a prop in the `.rs`\n- The entrance animation off when motion is reduced\n- An ARIA role or `aria-label`, and a region that is named in every state, the loading\n skeleton included\n- `data-slot` and `data-portal` attributes, in every state\n- `focus-visible` ring on interactive elements, and interactive elements with a 49px hit region\n- I18N via `Intl` formatters for dates, numbers and currency\n- Semantic tokens for status-bearing colours\n\n#### N6 page checklist\n\n- Pure composition: no inline buttons, cards or SVGs\n- Accept children/slots for content\n- Semantic CSS vars only (`bg-card`, `text-foreground`, `bg-primary`)\n- A loading-state prop\n- `role=\"main\"` and an `aria-label`\n\n#### N8 assurance checklist\n\n- A sans-IO Rust core (typed input, typed result, every facility behind a trait) in\n `mzizi-assurance`, with the `.ts` module as the React build\n- A banner comment identifying the node and purpose\n- A configurable rules array\n- Optional `onViolation` / `onComplete` callbacks\n\n### Step 4 — Docs and demos\n\nThere is nothing extra to write. Docs come from the `meta` you already authored in Step 3\n(`useCases`, `variants`, `sizes`, `features`, `a11y`), and `pnpm registry:metadata` turns them\ninto the `docs` string the CLI prints after install. Set `meta.hasDemo` when a demo exists.\n\nIf a prop table is expected, `pnpm props:extract` derives it from the component's own types and\n`pnpm props:verify` is the gate. Props are read from the code, never restated beside it.\n\n### Step 5 — Versions and history\n\nThere is no status row and no database. A component's version history is the registry's\nchangelog (`content/changelog/`, generated, checked by `pnpm changelog:generate:check`) and the\nrename map. `mzizi_get_component` with `include: [\"versions\"]` returns it. Read what a component\nis through the MCP or `https://api.mzizi.dev/v1/ui/<name>` rather than assuming.\n\n### Step 6 — Validate accessibility\n\nIf your component introduces a new colour pair, validate it before shipping. The MCP tool is\ncomputed locally and needs no account:\n\n```\nmzizi_check_accessibility(foreground: \"#FFFFFF\", background: \"#0047AB\")\n```\n\nIt returns the WCAG contrast ratio with AA/AAA verdicts and whether the pair clears 7:1, the\nbody-text floor. Non-body text needs 4.5:1 and UI parts 3:1 (five button-label roles hold 4.5:1);\nthe tool does not compute APCA, so read the role values in `mzizi_get_tokens` for those.\n\n### Step 7 — Before you open the PR\n\n`pnpm check` runs every registry gate CI runs, in order, including `registry:validate`,\n`registry:verify`, `tokens:verify`, `changelog:generate:check`, `rust:generate:check`, the\ntests and the build. Then the Rust workspace gate:\n\n```bash\npnpm check\ncargo fmt --manifest-path mzizi-rs/Cargo.toml --all -- --check\ncargo clippy --manifest-path mzizi-rs/Cargo.toml --workspace --all-targets -- -D warnings\ncargo test --manifest-path mzizi-rs/Cargo.toml --workspace\ncargo package --manifest-path mzizi-rs/Cargo.toml --workspace\n```\n\nIf you touched `files[]` or `meta`, run `pnpm registry:paths`, `pnpm registry:metadata` and\n`pnpm registry:normalize` first; their `--check` twins fail otherwise.\n\nA green run means the item resolves to real files, every dependency is addressable, the derived\nfields match `meta`, the Rust compiles, and its contract holds on rendered markup. Say in the PR\nbody that a component changed, so the site, the docs and the skills are updated to match.\n\n### What to avoid\n\n- **Never put component source in a database row, a JSON document, or any second place.** The\n file under `components/registry/` is the component; everything else is a representation of\n it, and a representation can drift from the code while still looking correct. That is not\n hypothetical here: a `source_code` column once served stale copies for 571 components.\n- Never edit `mzizi-rs/crates/*/src/generated/`. It is overwritten by `pnpm rust:generate`.\n- Never hand-write `title`, `categories`, `docs` or `author` in `registry.json`. They are\n derived, and a hand-written value is a second source that the next generator run silently\n overwrites.\n- Never hardcode hex colour values (except documented third-party brands like Ethereum,\n Google, EcoCash).\n- Never use `lucide-react` directly in a `.tsx`; go through `@/lib/icons`.\n- Never use `margin-left` / `padding-right` / `left`; use logical properties\n (`margin-inline-start`, …) for RTL support.\n- Never start new work in the React build alone. If a component has a Rust implementation,\n change the Rust and keep the `.tsx` in step.\n- Never bump a major version on your own initiative. That is a maintainers' call.\n\n## The React build\n\n`npx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>` installs the `.tsx` for an existing\nReact app, for all 577 items. Always pass the absolute URL: a bare name resolves against\nshadcn's own registry and installs a different component. `mzizi add <name>` installs the React\nbuild too. Neither is where new work goes.\n\n## See also\n\n- `docs/roots/RFC-roots.md` in the registry: the inventory, the conversion batches and what\n the API and site still need for Rust-first serving (`/v1/roots` is proposed, not live).\n- The `mzizi-language` skill for the clause grammar and `mz contract`.\n- The `mzizi-design` skill for the palette, radius, type and the Ubuntu constraints.\n- The `mzizi-backend` skill for mounting a server core on Workers.",
|
|
25
25
|
"source": "mzizi-dev/agent-tools/mzizi-skills/skills/mzizi-roots",
|
|
26
26
|
"files": []
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
"name": "mzizi-design",
|
|
30
30
|
"category": "design",
|
|
31
|
-
"description": "
|
|
32
|
-
"body_mdx": "# Mzizi design — the system and the ecosystem\n\nThis skill replaces three 0.7.0 skills: `nyuchi-design` (UI and tokens, now \"The design\nsystem\" below), the Ubuntu and ownership half of `ecosystem-app-setup` (now \"What belongs in\nthe ecosystem\"), and the tokens of `mukoko-design`, which moved here. mukoko's brand files (the\nSwarm mark, the app icon, favicons, the lockup and backgrounds) are in the `mukoko-design` skill,\nrestored in 0.15.0.\n\n## Files this skill ships\n\nAll generated from `palette.canonical.json` at the package root by\n`scripts/generate-tokens.mjs`, and gated against it. Never hand-edit them; never retype a hex.\n\n| File | Contents |\n| ---------------------- | ---------------------------------------------------------------------------------------------------- |\n| `tokens/palette.css` | all 21 families, light + dark, as CSS custom properties |\n| `tokens/palette.json` | all 21 families, machine-readable |\n| `tokens/minerals.css` | the seven minerals only |\n| `tokens/minerals.json` | the seven minerals only, machine-readable |\n| `tokens-update.css` | all 21 families (light, dark, `.dark`, `prefers-color-scheme`) plus the radius scale, as one drop-in |\n\n## The ecosystem\n\n**Mzizi** is the parent identity at the ecosystem level — the Mzizi programming language, its\ntoolchain, the design language, the component registry and the open doctrine every brand builds\non. Everything revenue-generating (the\nMzizi console, fundi, paid plans, billing) is Nyuchi's. Three pillars: **mukoko** (consumer super app), **nyuchi** (infrastructure &\nenterprise), and **shamwari ai** (the intelligence pillar), with the **sister brands** as specialist\nverticals — one identity, one design system, one open data commons.\n\nDoctrine **v4.1.0**. **The repo is the source of truth, and there is no database.** Colour\ncomes from `palette.canonical.json` at the root of this package — 21 families, vendored from\n`mzizi-dev/mzizi-registry → lib/tokens/palette.source.ts` and gated against it. The ecosystem\nconstellation and the Ubuntu pillars and principles come from `mzizi-registry →\ncontent/doctrine/`, served at <https://api.mzizi.dev/v1/brand> (`.ecosystem`, `.philosophy`) and\nby the MCP as `mzizi_get_tokens` (`family: \"ecosystem\"`) and `mzizi_get_doctrine`. Two notes:\nprinciples were renamed in v4.x, so use the set below and not older Shona-named lists; and the\nregistry still names its ecosystem-layer entry `bundu` (URL **bundu.org**). That is registry\ndata, and it changes in mzizi-registry, not here.\n\nThe **five** pillars and **five** principles below are a count of Ubuntu doctrine, not of the\npalette, and they are correct — `/v1/brand` `.philosophy.ubuntuPillars` serves exactly these five.\nDo not \"fix\" them to seven to match the colour families.\n\n### The ecosystem-level identity\n\n- **Mineral:** `copper` — `#BF5A36` light / `#FF8A65` dark (`--color-copper`) · connection, foundation, stewardship\n- **Registry entry:** `bundu` in `/v1/brand` `.ecosystem` (meaning _wilderness_, Shona) · **URL:** <https://bundu.org>\n- **Voice:** visionary, grounded, inclusive\n- **Role:** the ecosystem — the layer that holds the whole, the commons, governance, philosophy\n- **Wordmark:** `Mzizi`, capitalised, Noto Serif 600. (The other wordmarks stay lowercase.)\n- **No standalone ecosystem-level logo mark is registered in the registry yet** — that's an open identity\n item. Until one exists, ecosystem-level materials use the wordmark in copper, optionally with\n Mukoko's Swarm mark (the `mukoko-design` skill) when the subject is the product family. Don't invent an ecosystem mark ad hoc.\n- **Copper is the ecosystem layer, not Mzizi's own surfaces.** mzizi.dev, docs.mzizi.dev, the\n registry and the language are the `mzizi` row of the constellation below: theme them in\n **hematite** (owner decision, 2026-09-30). Keep copper for material where the ecosystem as a\n whole speaks: governance, the commons, the manifesto register.\n\n### The brand constellation (verified, v4.1.0)\n\nEvery brand owns a mineral and a voice. Theme each brand's surfaces and copy in **its own** mineral\nand register — never default everything to tanzanite.\n\n| Brand | Role | Mineral | Meaning | Voice |\n| ------------ | ------------------------------------------- | ---------- | ---------- | -------------------------------------- |\n| **bundu** | The ecosystem | copper | Wilderness | visionary, grounded, inclusive |\n| **nyuchi** | Infrastructure & enterprise | gold | Bee | technical, reliable, industrious |\n| **mukoko** | Africa's super app | tanzanite | Beehive | welcoming, structured, protective |\n| **shamwari** | Sovereign AI companion | sodalite | Friend | helpful, warm, intelligent |\n| **Mzizi** | The root: language, registry, design system | hematite ¹ | Root | precise, honest, grounded |\n| events | Events & gatherings (Mukoko Events) | malachite | Gathering | celebratory, communal, vibrant |\n| bushtrade | Marketplace | gold | Bush trade | practical, trustworthy, local |\n| lingo | Language learning | cobalt | Language | encouraging, cultural, playful |\n| campfire | Platform messaging anchor | malachite | Campfire | direct, warm, always present |\n| bytes | Short-form creator video | tanzanite | Bytes | energetic, creative, youthful |\n| novels | Publishing platform | malachite | Novels | literary, thoughtful, immersive |\n| places | Geographic knowledge graph | gold | Places | authoritative, helpful, discoverable |\n| transport | Public transit & booking | gold | Transport | efficient, reliable, practical |\n| planner | Productivity hub | cobalt | Planner | organised, clear, supportive |\n| wallet | Payments & tokens | gold | Wallet | trustworthy, precise, secure |\n| pulse | Feed & Mukoko Home | tanzanite | Pulse | personal, adaptive, ambient |\n| health | Wellness & telemedicine | malachite | Health | caring, accurate, private |\n| circles | Community messaging | terracotta | Circles | inclusive, moderated, community-driven |\n| kweli | Trust and verification | malachite | Truth | honest, rigorous, reassuring |\n| learning | Education (Nyuchi Learning) | gold | Learning | patient, practical, encouraging |\n| news | News and journalism | cobalt | News | clear, credible, balanced |\n| weather | Weather and alerts | cobalt | Weather | calm, precise, timely |\n\nThe `bundu` row is the ecosystem layer as `/v1/brand` serves it today; the table mirrors the\nregistry, so it changes when mzizi-registry renames the entry. The last four rows joined canon\non 2026-10-04 (owner decisions, mzizi-registry #409): kweli is malachite (it used to borrow\nMukoko's tanzanite), news and weather are cobalt, and **learning is gold** because every Nyuchi\nbrand is gold. The education industry category follows it: education is gold, not cobalt.\n\nThe `events` row is **Mukoko Events**, at `events.mukoko.com` (owner decision,\n2026-10-04). The _nhimbe_ brand is retired. `nhimbe` survives only as a deprecated alias of\n`events`: canon still resolves it, and `brand-nhimbe.css` re-exports `brand-events.css`, so\nnothing that names it breaks. New work names `events` and writes \"Mukoko Events\" in copy. The\nmineral is unchanged: malachite.\n\n¹ **Mzizi's brand colour is hematite, and hematite is a Heritage tone, not one of the seven\nminerals** (owner decision, 2026-09-30). `/v1/brand` serves it in `.ecosystem` as\n`{\"name\": \"mzizi\", \"meaning\": \"Root\", \"language\": \"Swahili\", \"mineral\": \"hematite\"}` and lists\nhematite under `.heritage`, not `.minerals`: the field is called `mineral`, but it names a\npalette family, usually a mineral. Mzizi owns and operates the language, the registry, the design\nsystem, the docs and the API; `/v1/brand` gives its role as \"Language, registry and design\nsystem\", and its voice as \"Precise, honest, grounded\". A Mzizi surface takes its primary from\nhematite, as the shipped stylesheets do: `@bundu/ui`'s `brand-mzizi.css` (which mzizi.dev uses)\nsets `--primary` to `var(--color-hematite)`, and the registry's `mzizi-tokens-globals.css`\ndefaults `--primary` to `var(--heritage-hematite-aa)`. Not gold (nyuchi's), not tanzanite (mukoko's), not copper (the\necosystem layer's). Take the values from `palette.canonical.json` (`hematite`, Heritage).\n\nThe narrative spine: **nyuchi is the bee, mukoko is the hive, Mzizi is the root everything\ngrows from.** Use this when explaining the ecosystem — it is the one-sentence architecture.\n\n### Ubuntu as identity (live doctrine)\n\nMzizi carries the philosophy. _Ndiri nekuti tiri_ — I am because we are.\n\n**Five Pillars** (spheres of belonging): **family** (the household and kin, the primary web of\nbelonging) → **community** (neighbours, peers, chosen kin) → **society** (the broader human web —\nstrangers, institutions, the polity) → **environment** (the natural world that holds all\nrelationships; Ubuntu is ecological before it is social) → **spirituality** (the vertical\ndimension — meaning, purpose, ancestry, the sacred).\n\n**Five Principles** (operating rules): **survival** (collective endurance — no one survives alone) ·\n**solidarity** (firm, persevering commitment to the common good) · **compassion** (active care shown\nthrough helping, sharing, welcoming) · **respect** (unprejudiced consideration of another's\nprivileges, beliefs, norms — includes elder-respect) · **dignity** (inherent worth by virtue of\nexistence, irrespective of status or background).\n\nIn ecosystem materials, philosophy is **structural, not decorative**: name a pillar/principle only\nwhen the feature or message genuinely embodies it.\n\n### Rules for ecosystem-level work\n\n- **Wordmarks:** `Mzizi` is capitalised. Every other wordmark is lowercase: `nyuchi`, `mukoko`,\n `shamwari`, `bundu` and every sister brand.\n- **Copper accents** signal \"this is the ecosystem speaking\" (parent-level pages, governance,\n commons, manifesto-register material).\n- **Cross-brand layouts:** lead with the brand that owns the message; bring in the constellation\n table's minerals when showing the family together (each brand in its own mineral).\n- **Voice shifts with the speaker.** An ecosystem-level page is visionary/grounded/inclusive; the same content\n on mukoko.com becomes welcoming/structured/protective. Don't copy-paste tone across brands.\n- Cultural specificity is non-negotiable: Shona vocabulary and Zimbabwean grounding are structural.\n- All UI tokens (radius, type, spacing, surfaces, status colours) are under \"The design system\"\n below. Mukoko's logo and assets are in the `mukoko-design` skill.\n- **Every brand owns a mineral, but the palette is not only minerals.** A cross-brand piece has 21\n families to draw on — seven minerals, seven Heritage tones, the Experimental Seven. Use the\n minerals to say _who is speaking_ (and hematite when Mzizi is); use Heritage for atmosphere and surface, and the Experimental\n Seven for categorical sets that would otherwise stretch a mineral past its meaning.\n\n## The design system\n\n> **Doctrine v4.1.0. The canonical palette ships with this package.**\n> `palette.canonical.json` at the root of `@nyuchi/mzizi-skills` states all **21 colour\n> families** — seven African Minerals, seven Heritage tones, the Experimental Seven — and\n> every token file in this bundle is generated from it and gated against it. Read it, or the\n> stylesheets generated from it (`tokens-update.css` and `tokens/palette.css` here). Do not retype hex values out of this prose into a project.\n>\n> Canon upstream is `mzizi-dev/mzizi-registry → lib/tokens/palette.source.ts`, served publicly\n> at <https://api.mzizi.dev/v1/brand> and by the MCP as `mzizi_get_tokens`. There is no\n> database: the registry's files are the data layer.\n>\n> **Colour is derived and gated. Everything else on this page is hand-maintained** —\n> typography, spacing, radius, sizes, motion, shadow, z-index and the semantic set. The\n> radius scale and control sizes were checked against `mzizi-registry` `main` for 0.8.0;\n> confirm the rest with `mzizi_get_tokens` (`typography`, `spacing`, `radii`, `semantic`,\n> `componentSpecs`) if precision matters.\n\n**The published design system.** The brand book, the tokens as data, the marks and static\npreviews of the components (Badge, Button, Card, Checkbox, Input, StatusBadge, Switch, Tabs and\nmore) are published as the Design System artifact at\n<https://claude.ai/artifact/G8CCtAbZ8w717uQ3R5itCc>. Its source of truth is the `design-system/`\nfolder in `mzizi-dev/mzizi-registry` (arriving with mzizi-registry#418), which the artifact is\nbuilt from file for file. To change the design system, change that folder, never the artifact\npage: the artifact is republished from registry `main` after a merge that touches it.\n\n### Rules that are non-negotiable\n\n- **Wordmarks** — `Mzizi` is capitalised. `mukoko`, `nyuchi`, `bundu`, `shamwari`, `bushtrade` are lowercase, always. (Product names such as Mukoko Events are written as names, not wordmarks.)\n- **The palette is 21 families: seven Minerals + seven Heritage + the Experimental Seven.** Any interface that uses only the minerals is using a third of the system. Do not describe it as \"the seven-colour palette\".\n- **Seven African Minerals** (geological, the core palette). The custom properties are `--color-<mineral>`, `--color-<mineral>-container`, `--color-<mineral>-on-container` — those are the names the design system defines. (`--container-<mineral>` / `--on-container-<mineral>` appeared in earlier revisions of this skill and are defined nowhere upstream; `tokens-update.css` now ships them as aliases so existing work keeps rendering, but do not reach for them in new code.)\n - `cobalt` — primary blue, links, CTAs · digital future, trust\n - `tanzanite` — **mukoko's brand mineral**, social features · premium, connection\n - `malachite` — success states, positive actions · growth\n - `gold` — achievements, rewards, highlights · honey, warmth\n - `terracotta` — community features · earth, grounding\n - `sodalite` — **AI / Shamwari surfaces, deep-reasoning states** · intelligence, depth _(added v4.1.0)_\n - `copper` — **Bundu ecosystem identity, the commons** · connection, stewardship _(added v4.1.0)_\n- **Seven Heritage tones** (atmospheric anchors — mini-app surfaces, backgrounds, moods; no family or role, with one exception: `hematite` is Mzizi's brand colour): `indigo` (dusk, the dyer's craft) · `savanna` (sun-dried grass, the dry season) · `baobab` (the tree of life, bark, shelter) · `sunset` (day's end, the gathering hour) · `river` (flow, the journey) · `hematite` (the neutral anchor, the substrate) · `kalahari` (the light anchor, openness). Reach for them via `--color-<name>` (aliasing `--heritage-<name>`); values are in `palette.canonical.json`. _(Two earlier framings are withdrawn: the ten-colour one from before the palette settled, and the two-anchor one that survived into v0.5.1 of this bundle. **There are seven.** A skill that named two produced two-tone output in projects that had never seen each other.)_\n- **The Experimental Seven** — a computed heptagon (hues offset 17°, prime saturations, foregrounds solved to P7), each tone carrying a `heptagonIndex` 0–6 that fixes its place on the wheel: `ember` · `acacia` · `fern` · `lagoon` · `storm` · `dusk` · `protea`. Four tiers each: `--exp-<name>`, `--exp-<name>-container`, `--exp-<name>-on`, `--exp-<name>-ui`. Use them for generated/wheel-derived surfaces and categorical sets where the minerals would have to be stretched past their meanings.\n- **Each brand owns a mineral** (the ecosystem map, verified v4.1.0 and served at `/v1/brand` as `.ecosystem`): bundu→**copper**, nyuchi→**gold**, mukoko→**tanzanite**, shamwari→**sodalite**, Mzizi→**hematite** (a Heritage tone, not a mineral: the one brand whose colour comes from the Heritage set), events (Mukoko Events)/campfire/novels/health/kweli→malachite, lingo/planner/news/weather→cobalt, bushtrade/places/transport/wallet/learning→gold (learning, and with it the education category, is gold because every Nyuchi brand is), bytes/pulse→tanzanite, circles→terracotta. Theme a surface in its brand's colour — never default everything to tanzanite, and never give a Mzizi surface gold, tanzanite or copper as its primary.\n- **Buttons are ALWAYS pill** (`rounded-full` → `--radius-full`, 9999px). Inputs match buttons visually. The exception is the Mzizi Dashboard Standard (below): its app Button and controls use the small radius, as their contracts state.\n- **Nothing tappable goes below 48px.** Buttons are `h-14` (56px) default and large, `h-12` (48px) small; icon buttons `size-14` / `size-12`. Inputs are 56px default, 48px small. That is what `button.tsx`, the Roots `button.rs` and the language's `primitives/button.mz` all ship, and what `/v1/brand` `.componentSpecs` serves (`minTouchTarget: 48`). (0.7.0 of this skill said controls were dense, 32/36/40px, with 56px only for hero CTAs. The shipped primitives do not do that, so that rule described a system that does not exist.) Badges are 20px tall and are not touch targets; a small element that becomes one gets `min-h-[48px]`. Dashboards are the exception: the Dashboard Standard's app components are dense on a fine pointer (most controls 32–40px) and 44–48px on touch, each as its contract's `density` states. The Discover Standard's components are the same size on every pointer: 48px for the search field and button, \"See all\", \"Load more\" and \"Open in Mukoko\", 44px for category chips, navigation links and the home link.\n- **Radius scale** (px), every step from a 7px unit: `xs` 4 (unit − 3) · `sm` 7 · `md` 12 · `lg` 14 (**DEFAULT** cards/panels) · `xl` 17 · `2xl` 17 · `full` 9999 (pills). That is `mzizi-registry/styles/globals.css`, the Rust `mzizi-tokens` `Radius`, and `/v1/brand` `.radii` (\"ecosystem numbers: 4, 7, 12, 14, 17\"; `xs` joined in mzizi-registry#544). Legend swatches, kbd keys and inline tags use `xs`, checkboxes `sm`, inputs and small cards `md`, cards and panels `lg`, modals, sheets, dialogs and tabs `xl`. Reference via `--radius-<name>`. `2xl` is 17px, never 24. (Until 0.8.0 this skill listed `none` 0 and `2xl` 24, which were never upstream values.)\n- **H1–H3 are Noto Serif. H4+ and body are Noto Sans. Code is JetBrains Mono.**\n- **Rings over shadows.** Cards get `ring-1` of `--border`, not a drop shadow.\n- **No gradients, no glassmorphism** except sticky chrome (`backdrop-blur`) and the Discover Standard's hero wash (DiscoverHero's `gradient-showcase`, which mixes `--primary` into the background, as its contract declares).\n- **Emoji only as mini-app identifiers**, never as chrome or body decoration.\n- **Status has its own names: `--status-success` / `--status-warning` / `--status-error` / `--status-info` / `--status-neutral`.** Use those names and do not hardcode a hex. Upstream they alias `--success` / `--warning` / `--destructive` / `--info` / `--neutral`, so there is one decision per status — which means the resolved values are mineral-derived in the shipped theme (success reads as malachite, info as cobalt), not the standalone #22C55E/#F59E0B/#EF4444/#3B82F6 set an earlier revision of this skill listed. Those hexes are the per-component `var(--token, #hex)` fallback for an app that has not defined the properties; they are not the theme. Severity scale (`--severity-low/moderate/high/severe/extreme/cold`) and connection (`--connection-online/syncing/cached/offline`) follow the same logic. Verification is the only place minerals carry status meaning; it has its own rule, next.\n- **Verification is two things, not one ladder: assurance levels and attestations** (mzizi-registry#577; the source is `gateway/lib/assurance.py` in `nyuchi/nyuchi-api`, the one access decision every surface derives from — read that, never re-derive it). Read the roles, never a hex; each has `-container`, `-on-container` and `-text`, and passes APCA in both themes.\n - **Assurance levels** are ordered (`>=` is meaningful): how strongly an identity is proven. 0 `none` \"Unverified\" → `--assurance-none` (the neutral grey, `--status-neutral`) · 1 `otp` \"Contact Verified\" → `--assurance-otp` (cobalt) · 2 `documented` \"Documents Checked\" → `--assurance-documented` (sodalite) · 3 `government` \"Government Verified\" → `--assurance-government` (gold) · 4 `licensed` \"Licensed Professional\" → `--assurance-licensed` (tanzanite).\n - **A level can expire.** A `government` check holds for five years and a `licensed` one for a year (`LEVEL_VALIDITY_YEARS` in assurance.py, nyuchi-api#375). When the term runs out the level is withdrawn, so show the level a person holds now and never cache it as permanent.\n - **Attestations** are unordered and held beside the level: who vouches for the subject. `community` \"Community Vouched\" → `--attestation-community` (terracotta) · `business` \"Registered Business\" → `--attestation-business` (malachite) · `domain` \"Domain Verified\" → `--attestation-domain` (copper). Show each as its own chip next to the level (`app/tier-status` and `ui/mzizi-verified-badge` do this, from registry v4.17.0; `ui/mzizi-trust-meter` shows the level and renders no attestation chips).\n - **Provenance: where an attestation came from, and when it was checked** (mzizi-registry#593, on registry `staging`, released with the next registry version: `ui/mzizi-verified-badge` 1.2.0 and `app/tier-status` 1.2.0). A stored attestation record may carry `source` (a short id such as `\"osm\"`), `sourceRef` (the source's own reference, kept and never shown) and `checkedAt` (an ISO date or date-time). The chip label stays the canonical one (\"Community Vouched\"), and an optional second line reads \"via <Source> · checked <date>\", for example \"via OpenStreetMap · checked 14 Mar 2026\". Source names come from `ATTESTATION_SOURCES` in `mzizi-trust-tier-data` (`osm` → \"OpenStreetMap\"), and the line is built by `attestationProvenance`. Two rules: **never invent a check date.** With no `checkedAt`, or one that does not parse or names a day that does not exist, the line is \"via OpenStreetMap\" with no date. And **never a per-source label**: a source changes the second line only, never the chip's name, its colour or the closed set of three attestations (an OpenStreetMap verification is a `community` attestation, never a level). The date is formatted in UTC from the locale (`en-GB` by default) so the day never shifts. The chip's `aria-label` stays the canonical name and the line is its `aria-description` (badge) or is read with the name (tier status, the date as `<time datetime>`).\n - **Community is not a rung.** The old ladder put `community` at 1, below `otp`, which ranked a trader vouched for by their whole street below anyone who clicked a link in an email. Mukoko serves the informal economy: a tuck shop is level `otp` plus community, a registered firm is `government` plus business, and neither outranks the other. So never draw community on the level scale and never below otp; `tier=\"community\"` now renders as level Unverified plus a \"Community Vouched\" chip.\n - **`--tier-*` are legacy aliases**, kept while `bundu.verificationTier` (the `legacy_tier()` projection in assurance.py) still has readers: `--tier-unverified` → `--assurance-none`, `--tier-community` → `--attestation-community`, `--tier-otp` → `--assurance-otp`, `--tier-government` → `--assurance-government`, `--tier-licensed` → `--assurance-licensed`. They paint what they always did. New code reads `--assurance-*` and `--attestation-*`. (Earlier revisions of this skill listed a five-rung tier ladder, first as community→malachite, government→tanzanite, licensed→gold, then, after mzizi-registry#543, with community as terracotta; both treated community as a rung.)\n - Colour is never the only cue: every level and attestation carries its label and glyph. Heritage tones never carry a level or an attestation.\n- **Surfaces (April 2026 AAA refresh):** light mode — `--background` warm paper, `--card` pure white, `--muted` cream, `--overlay` white. Dark mode — `--background` warm stone L10%, `--card` L6% (darker than bg), `--muted` L2% (deepest), `--overlay` L14% (lighter than bg, scrim creates pop).\n- **Icons:** in the React build, Lucide through `@/lib/icons`, never `lucide-react` directly; in Mzizi Roots, an icon is an `Element` slot the host renders. For an HTML prototype, the Lucide CDN build is fine.\n- **APCA 3.0 AAA contrast** on both primary and secondary text on every surface.\n\n### When creating visual artifacts\n\nCreate static HTML files for the user to view. `@import` `tokens-update.css` at the top of every HTML file (or inline it). For slide decks use a mineral accent strip and serif display type.\n\n### When working on production code\n\nUse the registry's components, never hand-rolled copies. **Rust first:** Mzizi Roots is the lead build (`cargo add mzizi-roots`; see the `mzizi-roots` skill). The React build installs by absolute URL, for existing React apps:\n\n```bash\nnpx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>\n```\n\nTake CSS variables from `tokens-update.css` or `tokens/palette.css` in this skill — it is generated from `palette.canonical.json` and carries all 21 families. The upstream equivalent is `mzizi-registry/styles/globals.css`, which is generated from the same canon. There is no database to refresh from.\n\n### Apple HIG: the baseline, and where Mzizi overrides it\n\nMzizi designs on Apple's Human Interface Guidelines\n(<https://developer.apple.com/design/human-interface-guidelines>). It applies them to **every**\nMzizi surface (web, native and console) and not only to Apple platforms. The HIG is the\nbaseline wherever Mzizi says nothing. Where Mzizi has a token, a registry component or a\ncontract, **Mzizi is the authority, across all apps**, and the HIG value does not apply. Read\nthe HIG pages live, because Apple revises them: the design principles were reintroduced on\n8 June 2026, Layout was updated on 9 September 2026, and Liquid Glass guidance arrived on\n9 June 2025.\n\n**The eight HIG design principles** (Design principles, June 2026) are the questions to ask of\nevery screen:\n\n| Principle | In Apple's words | In Mzizi terms |\n| -------------- | ------------------------------------ | ------------------------------------------------------------------------------------- |\n| Purpose | Make something meaningful. | Build what the person came for, and drop features nobody asked for. |\n| Agency | Let people do things their own way. | No locked flows, an escape from every guided step, undo before confirm. |\n| Responsibility | Act in people's best interest. | Say what is collected and why. Collect only what the product needs (Ubuntu: dignity). |\n| Familiarity | Build on what people know. | One shell and one set of patterns across every app (`mzizi-architecture`). |\n| Flexibility | Adapt to diverse contexts and needs. | Accessibility from the start, and every input (touch, keyboard, voice, pointer). |\n| Simplicity | Be clear and direct. | Every element earns its place. Hierarchy first, concise labels. |\n| Craft | Care about every detail. | The contract's checks pass, every state renders, motion is purposeful. |\n| Delight | Make it human. | Delight is never decoration. The minerals carry meaning, not ornament. |\n\n**Where Mzizi is the authority** (the HIG value is replaced, not blended):\n\n| Concern | HIG says | Mzizi rule (wins) |\n| ----------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Touch targets | 44×44 pt default, 28×28 pt minimum on iOS/iPadOS | **48px floor** for anything tappable (56px default buttons and inputs). Dashboards: each contract's `density`, 44–48px on touch |\n| Spacing and layout grid | system margins, layout guides | the Mzizi spacing tokens and each component contract's `responsive` rules, from 320px up |\n| Type scale and families | San Francisco, Dynamic Type sizes (17 pt default body on iOS) | Noto Serif H1–H3, Noto Sans H4 and body, JetBrains Mono for code, at the Mzizi type scale. **Text still scales with the user's setting** (rem units, no fixed px caps) |\n| Colour | system colours, dynamic colours | the 21 families and the semantic tokens only, through `var(--…)`. A brand overlay changes colour and mark only |\n| Contrast | 4.5:1 up to 17 pt, 3:1 at 18 pt or bold (WCAG) | APCA in both themes (the palette is solved for it), never weaker than the HIG/WCAG floor |\n| Shape | system control shapes, capsules in Liquid Glass | **pill buttons and inputs** (`--radius-full`), the radius scale above, and the Dashboard Standard's small radius where its contracts say so |\n| Materials | Liquid Glass for controls over content | solid surfaces and rings; blur only on sticky chrome. No glass panels on web surfaces |\n| Density | per-platform defaults | comfortable by default, dense only where a Standard's contract says so |\n| App chrome | tab bars, sidebars, toolbars per platform | **the shell**: AppShell or DiscoverShell from `@bundu/ui`, mounted once (`mzizi-architecture`) |\n\n**Where the HIG applies as written** (Mzizi is silent, so follow Apple):\n\n- **Hierarchy and layout.** Order content by importance (top and leading side first), align to\n make it scannable, group related items with space, containers or separators, and use\n progressive disclosure instead of showing every choice at once (Layout).\n- **Adaptivity.** Lay out by the space available, never by device type. Keep functionality\n the same as the size changes, and respect safe areas (the registry's `safe-area-frame`\n primitive does this on the web).\n- **Accessibility.** VoiceOver and screen-reader labels, Reduce Motion (stop auto-playing,\n zooming and parallax motion; Mzizi's harness exposes `prefers-reduced-motion`), Increase\n Contrast, keyboard and switch access, and never colour alone.\n- **Feedback and recovery.** Show state changes, confirm destructive actions, and make\n reversal cheap.\n- **Writing.** Be concise, use the exact words a control needs, and say what will happen.\n- **Native apps** (SwiftUI and other native platforms) use the system components and\n materials, Liquid Glass included, themed through the generated Swift and Kotlin tokens, while\n the Mzizi floor, type and colour rules still hold.\n\nWhen a HIG update conflicts with a Mzizi rule, the Mzizi rule stands until the registry changes\nit. Raise the conflict upstream in `mzizi-dev/mzizi-registry` and do not patch around it in an\napp.\n\n### Dashboards: brand overlays only\n\nAn admin or operator dashboard uses the **Mzizi Dashboard Standard**: the app components of\n`@bundu/ui` (`@bundu/ui/app/*`), one design for every product in the Bundu ecosystem, each\nwith a versioned contract in `mzizi-registry/contracts/app/` (see `mzizi-roots`). A brand\nchanges colour and mark, and nothing else:\n\n- **A brand overlay changes `--primary`, `--ring`, AppShell's `--app-accent` and the brand\n mark.** Layout, spacing, type and size never vary by brand. The overlay is one stylesheet\n imported after the tokens (`@bundu/ui/styles/brand-<brand>.css`). AppShell's `accent`\n takes one of the seven minerals and fills primary actions and the current nav indicator\n with its brand fill (`--color-<mineral>-brand`, text `--color-<mineral>-on-brand`); text\n links and the focus ring keep `--primary` and `--ring`.\n- **Status colours do not follow the brand.** Success is malachite, warning gold, error\n terracotta, info cobalt, in every brand. A component names a mineral only as one of its\n contract's `theming.statusColours`; any other mineral class, or a literal colour value,\n fails its contract test.\n- **Mzizi's hematite** is a Heritage tone, so AppShell's `accent` does not apply to it;\n `brand-mzizi.css` sets `--primary`.\n\n### Discover pages: the same overlay, touch-first\n\nA public discover or browse page in the Mukoko family (circles, news, events, weather, and the\nsuper-app on the web) uses the **Mzizi Discover Standard**: the Discover components of\n`@bundu/ui` (`@bundu/ui/discover/*`), each with a versioned contract in\n`mzizi-registry/contracts/discover/` (see `mzizi-roots`). The brand-overlay rule is the\nDashboard Standard's, with two differences the contracts state:\n\n- **No `--app-accent`.** A brand shows in DiscoverShell's `brand` slot (its mark) and in the\n primary action its `action` slot holds; the frame, header, navigation and footer are\n identical in every brand. `--primary` reaches only DiscoverHero's wash, DiscoverCard's\n monogram and `brand` badge tone, and OpenInApp's `primary` variant. Category chips, sections,\n search and \"Load more\" are neutral (search follows the brand only in its `--ring` focus\n ring). DiscoverCard is the one Discover component with status colours (`cobalt`,\n `malachite`, `gold`, `terracotta`), for its badge tones, and they do not follow the brand.\n- **The same size on every pointer.** Discover pages are not dense on a mouse: every density\n row in `contracts/discover/` gives the same height for a fine and a coarse pointer (48px or\n 44px, as above).\n\n## What belongs in the ecosystem\n\n### Ubuntu alignment\n\nThe ecosystem is built on Ubuntu — \"I am because we are.\" When you architect a new app:\n\n- Prefer open source over proprietary\n- Build for community, not extraction\n- Respect data sovereignty — African data stays on African infrastructure where possible\n- Accessibility is not an afterthought; it is the starting point\n- Every feature should contribute to collective wellbeing, not just individual efficiency\n\nThese aren't marketing claims, they're architectural constraints. If you find yourself\ndesigning something that only benefits a small subset of users at others' expense, question\nwhether it belongs in the ecosystem.\n\n### What is and isn't an ecosystem app\n\nThe ecosystem has two layers. **Mzizi** owns and operates the language, its toolchain, the\nregistry, the design system, the docs and the API, and sits under every app. **Nyuchi Africa**\noperates the Mzizi console and everything revenue-generating. Inside Nyuchi Africa\nsit three pillars and the platform:\n\n- **mukoko** — the consumer pillar: the super-app (social, commerce, payments, identity)\n- **nyuchi** — the commercial/infrastructure pillar and the platform (web services, developer\n tools, storage)\n- **shamwari ai** — the intelligence pillar, sovereign by nature (on-device inference,\n pod-resident context, no third-party AI vendor)\n- **Mukoko Events** (formerly nhimbe) — community events and gatherings\n\nMzizi is not an app and not a layer in a stack; it is the open, non-revenue foundation the\necosystem builds on and the doctrine that governs it. mukoko and shamwari ai are pillars inside Nyuchi Africa, not separate companies.\n\nNot ecosystem apps:\n\n- Any app that extracts value without giving back\n- Any app that requires surveillance for its business model\n- Any app that centralises what should be federated\n\nIf the app you're building doesn't fit, that's fine — consume the Mzizi design system, but\ndon't brand it as ecosystem.\n\n## If invoked with no other guidance\n\nAsk what the work is: (a) an interface or prototype (which surface: mobile, desktop, print,\nslide; which brand voice: Mzizi, nyuchi, mukoko, shamwari, Mukoko Events), (b) parent-brand material\n(ecosystem page, manifesto, deck), (c) a cross-brand piece, or (d) a voice or mineral decision\nfor one brand. Then apply the constellation table, the voice rules and the design system, and\noutput HTML artifacts or production code as the need requires.\n\n_Mzizi — the root everything grows from._",
|
|
31
|
+
"description": "Use this skill for the Mzizi design system and brand: generating well-branded interfaces (production or prototype), design tokens, and ecosystem-level work. Contains the canonical 21-family palette (seven African Minerals, seven Heritage tones, the Experimental Seven) with generated stylesheets (tokens/palette.css, tokens-update.css), the radius scale, Noto Sans/Serif and JetBrains Mono type, pill buttons, the Bundu floor (17px body, 14px captions, 49px hit region, 14px inset, 7px gaps, 1.5 leading) and the density layer, 7:1/4.5:1/3:1 contrast, status and surface tokens, the Dashboard and Discover Standards' brand overlays (colour and mark only), the verified brand constellation (each brand's mineral, voice, meaning, URL), the five Ubuntu pillars and principles, what is and isn't an ecosystem app, and the wordmarks. Also covers Apple's Human Interface Guidelines as the baseline and where Mzizi is the authority: touch targets, spacing, type scale, colour, pill geometry, materials, density and the shell.",
|
|
32
|
+
"body_mdx": "# Mzizi design — the system and the ecosystem\n\nThis skill replaces three 0.7.0 skills: `nyuchi-design` (UI and tokens, now \"The design\nsystem\" below), the Ubuntu and ownership half of `ecosystem-app-setup` (now \"What belongs in\nthe ecosystem\"), and the tokens of `mukoko-design`, which moved here. mukoko's brand files (the\nSwarm mark, the app icon, favicons, the lockup and backgrounds) are in the `mukoko-design` skill,\nrestored in 0.15.0.\n\n## Files this skill ships\n\nAll generated from `palette.canonical.json` at the package root by\n`scripts/generate-tokens.mjs`, and gated against it. Never hand-edit them; never retype a hex.\n\n| File | Contents |\n| ---------------------- | ---------------------------------------------------------------------------------------------------- |\n| `tokens/palette.css` | all 21 families, light + dark, as CSS custom properties |\n| `tokens/palette.json` | all 21 families, machine-readable |\n| `tokens/minerals.css` | the seven minerals only |\n| `tokens/minerals.json` | the seven minerals only, machine-readable |\n| `tokens-update.css` | all 21 families (light, dark, `.dark`, `prefers-color-scheme`) plus the radius scale, as one drop-in |\n\n## The ecosystem\n\n**Mzizi** is the parent identity at the ecosystem level — the Mzizi programming language, its\ntoolchain, the design language, the component registry and the open doctrine every brand builds\non. Everything revenue-generating (the\nMzizi console, fundi, paid plans, billing) is Nyuchi's. Three pillars: **mukoko** (consumer super app), **nyuchi** (infrastructure &\nenterprise), and **shamwari ai** (the intelligence pillar), with the **sister brands** as specialist\nverticals — one identity, one design system, one open data commons.\n\nDoctrine **v4.1.0**. **The repo is the source of truth, and there is no database.** Colour\ncomes from `palette.canonical.json` at the root of this package — 21 families, vendored from\n`mzizi-dev/mzizi-registry → lib/tokens/palette.source.ts` and gated against it. The ecosystem\nconstellation and the Ubuntu pillars and principles come from `mzizi-registry →\ncontent/doctrine/`, served at <https://api.mzizi.dev/v1/brand> (`.ecosystem`, `.philosophy`) and\nby the MCP as `mzizi_get_tokens` (`family: \"ecosystem\"`) and `mzizi_get_doctrine`. Two notes:\nprinciples were renamed in v4.x, so use the set below and not older Shona-named lists; and the\nregistry still names its ecosystem-layer entry `bundu` (URL **bundu.org**). That is registry\ndata, and it changes in mzizi-registry, not here.\n\nThe **five** pillars and **five** principles below are a count of Ubuntu doctrine, not of the\npalette, and they are correct — `/v1/brand` `.philosophy.ubuntuPillars` serves exactly these five.\nDo not \"fix\" them to seven to match the colour families.\n\n### The ecosystem-level identity\n\n- **Mineral:** `copper` — `#BF5A36` light / `#FF8A65` dark (`--color-copper`) · connection, foundation, stewardship\n- **Registry entry:** `bundu` in `/v1/brand` `.ecosystem` (meaning _wilderness_, Shona) · **URL:** <https://bundu.org>\n- **Voice:** visionary, grounded, inclusive\n- **Role:** the ecosystem — the layer that holds the whole, the commons, governance, philosophy\n- **Wordmark:** `Mzizi`, capitalised, Noto Serif 600. (The other wordmarks stay lowercase.)\n- **No standalone ecosystem-level logo mark is registered in the registry yet** — that's an open identity\n item. Until one exists, ecosystem-level materials use the wordmark in copper, optionally with\n Mukoko's Swarm mark (the `mukoko-design` skill) when the subject is the product family. Don't invent an ecosystem mark ad hoc.\n- **Copper is the ecosystem layer, not Mzizi's own surfaces.** mzizi.dev, docs.mzizi.dev, the\n registry and the language are the `mzizi` row of the constellation below: theme them in\n **hematite** (owner decision, 2026-09-30). Keep copper for material where the ecosystem as a\n whole speaks: governance, the commons, the manifesto register.\n\n### The brand constellation (verified, v4.1.0)\n\nEvery brand owns a mineral and a voice. Theme each brand's surfaces and copy in **its own** mineral\nand register — never default everything to tanzanite.\n\n| Brand | Role | Mineral | Meaning | Voice |\n| ------------ | ------------------------------------------- | ---------- | ---------- | -------------------------------------- |\n| **bundu** | The ecosystem | copper | Wilderness | visionary, grounded, inclusive |\n| **nyuchi** | Infrastructure & enterprise | gold | Bee | technical, reliable, industrious |\n| **mukoko** | Africa's super app | tanzanite | Beehive | welcoming, structured, protective |\n| **shamwari** | Sovereign AI companion | sodalite | Friend | helpful, warm, intelligent |\n| **Mzizi** | The root: language, registry, design system | hematite ¹ | Root | precise, honest, grounded |\n| events | Events & gatherings (Mukoko Events) | malachite | Gathering | celebratory, communal, vibrant |\n| bushtrade | Marketplace | gold | Bush trade | practical, trustworthy, local |\n| lingo | Language learning | cobalt | Language | encouraging, cultural, playful |\n| campfire | Platform messaging anchor | malachite | Campfire | direct, warm, always present |\n| bytes | Short-form creator video | tanzanite | Bytes | energetic, creative, youthful |\n| novels | Publishing platform | malachite | Novels | literary, thoughtful, immersive |\n| places | Geographic knowledge graph | gold | Places | authoritative, helpful, discoverable |\n| transport | Public transit & booking | gold | Transport | efficient, reliable, practical |\n| planner | Productivity hub | cobalt | Planner | organised, clear, supportive |\n| wallet | Payments & tokens | gold | Wallet | trustworthy, precise, secure |\n| pulse | Feed & Mukoko Home | tanzanite | Pulse | personal, adaptive, ambient |\n| health | Wellness & telemedicine | malachite | Health | caring, accurate, private |\n| circles | Community messaging | terracotta | Circles | inclusive, moderated, community-driven |\n| kweli | Trust and verification | malachite | Truth | honest, rigorous, reassuring |\n| learning | Education (Nyuchi Learning) | gold | Learning | patient, practical, encouraging |\n| news | News and journalism | cobalt | News | clear, credible, balanced |\n| weather | Weather and alerts | cobalt | Weather | calm, precise, timely |\n\nThe `bundu` row is the ecosystem layer as `/v1/brand` serves it today; the table mirrors the\nregistry, so it changes when mzizi-registry renames the entry. The last four rows joined canon\non 2026-10-04 (owner decisions, mzizi-registry #409): kweli is malachite (it used to borrow\nMukoko's tanzanite), news and weather are cobalt, and **learning is gold** because every Nyuchi\nbrand is gold. The education industry category follows it: education is gold, not cobalt.\n\nThe `events` row is **Mukoko Events**, at `events.mukoko.com` (owner decision,\n2026-10-04). The _nhimbe_ brand is retired. `nhimbe` survives only as a deprecated alias of\n`events`: canon still resolves it, and `brand-nhimbe.css` re-exports `brand-events.css`, so\nnothing that names it breaks. New work names `events` and writes \"Mukoko Events\" in copy. The\nmineral is unchanged: malachite.\n\n¹ **Mzizi's brand colour is hematite, and hematite is a Heritage tone, not one of the seven\nminerals** (owner decision, 2026-09-30). `/v1/brand` serves it in `.ecosystem` as\n`{\"name\": \"mzizi\", \"meaning\": \"Root\", \"language\": \"Swahili\", \"mineral\": \"hematite\"}` and lists\nhematite under `.heritage`, not `.minerals`: the field is called `mineral`, but it names a\npalette family, usually a mineral. Mzizi owns and operates the language, the registry, the design\nsystem, the docs and the API; `/v1/brand` gives its role as \"Language, registry and design\nsystem\", and its voice as \"Precise, honest, grounded\". A Mzizi surface takes its primary from\nhematite, as the shipped stylesheets do: `@bundu/ui`'s `brand-mzizi.css` (which mzizi.dev uses)\nsets `--primary` to `var(--color-hematite)`, and the registry's `mzizi-tokens-globals.css`\ndefaults `--primary` to `var(--heritage-hematite-aa)`. Not gold (nyuchi's), not tanzanite (mukoko's), not copper (the\necosystem layer's). Take the values from `palette.canonical.json` (`hematite`, Heritage).\n\nThe narrative spine: **nyuchi is the bee, mukoko is the hive, Mzizi is the root everything\ngrows from.** Use this when explaining the ecosystem — it is the one-sentence architecture.\n\n### Ubuntu as identity (live doctrine)\n\nMzizi carries the philosophy. _Ndiri nekuti tiri_ — I am because we are.\n\n**Five Pillars** (spheres of belonging): **family** (the household and kin, the primary web of\nbelonging) → **community** (neighbours, peers, chosen kin) → **society** (the broader human web —\nstrangers, institutions, the polity) → **environment** (the natural world that holds all\nrelationships; Ubuntu is ecological before it is social) → **spirituality** (the vertical\ndimension — meaning, purpose, ancestry, the sacred).\n\n**Five Principles** (operating rules): **survival** (collective endurance — no one survives alone) ·\n**solidarity** (firm, persevering commitment to the common good) · **compassion** (active care shown\nthrough helping, sharing, welcoming) · **respect** (unprejudiced consideration of another's\nprivileges, beliefs, norms — includes elder-respect) · **dignity** (inherent worth by virtue of\nexistence, irrespective of status or background).\n\nIn ecosystem materials, philosophy is **structural, not decorative**: name a pillar/principle only\nwhen the feature or message genuinely embodies it.\n\n### Rules for ecosystem-level work\n\n- **Wordmarks:** `Mzizi` is capitalised. Every other wordmark is lowercase: `nyuchi`, `mukoko`,\n `shamwari`, `bundu` and every sister brand.\n- **Copper accents** signal \"this is the ecosystem speaking\" (parent-level pages, governance,\n commons, manifesto-register material).\n- **Cross-brand layouts:** lead with the brand that owns the message; bring in the constellation\n table's minerals when showing the family together (each brand in its own mineral).\n- **Voice shifts with the speaker.** An ecosystem-level page is visionary/grounded/inclusive; the same content\n on mukoko.com becomes welcoming/structured/protective. Don't copy-paste tone across brands.\n- Cultural specificity is non-negotiable: Shona vocabulary and Zimbabwean grounding are structural.\n- All UI tokens (radius, type, spacing, surfaces, status colours) are under \"The design system\"\n below. Mukoko's logo and assets are in the `mukoko-design` skill.\n- **Every brand owns a mineral, but the palette is not only minerals.** A cross-brand piece has 21\n families to draw on — seven minerals, seven Heritage tones, the Experimental Seven. Use the\n minerals to say _who is speaking_ (and hematite when Mzizi is); use Heritage for atmosphere and surface, and the Experimental\n Seven for categorical sets that would otherwise stretch a mineral past its meaning.\n\n## The design system\n\n> **Doctrine v4.1.0. The canonical palette ships with this package.**\n> `palette.canonical.json` at the root of `@nyuchi/mzizi-skills` states all **21 colour\n> families** — seven African Minerals, seven Heritage tones, the Experimental Seven — and\n> every token file in this bundle is generated from it and gated against it. Read it, or the\n> stylesheets generated from it (`tokens-update.css` and `tokens/palette.css` here). Do not retype hex values out of this prose into a project.\n>\n> Canon upstream is `mzizi-dev/mzizi-registry → lib/tokens/palette.source.ts`, served publicly\n> at <https://api.mzizi.dev/v1/brand> and by the MCP as `mzizi_get_tokens`. There is no\n> database: the registry's files are the data layer.\n>\n> **Colour is derived and gated. Everything else on this page is hand-maintained** —\n> typography, spacing, radius, sizes, motion, shadow, z-index and the semantic set. The\n> radius scale and control sizes were checked against `mzizi-registry` `main` for 0.8.0;\n> confirm the rest with `mzizi_get_tokens` (`typography`, `spacing`, `radii`, `semantic`,\n> `componentSpecs`) if precision matters.\n\n**The published design system.** The brand book, the tokens as data, the marks and static\npreviews of the components (Badge, Button, Card, Checkbox, Input, StatusBadge, Switch, Tabs and\nmore) are published as the Design System artifact at\n<https://claude.ai/artifact/G8CCtAbZ8w717uQ3R5itCc>. Its source of truth is the `design-system/`\nfolder in `mzizi-dev/mzizi-registry` (arriving with mzizi-registry#418), which the artifact is\nbuilt from file for file. To change the design system, change that folder, never the artifact\npage: the artifact is republished from registry `main` after a merge that touches it.\n\n### Rules that are non-negotiable\n\n- **Wordmarks** — `Mzizi` is capitalised. `mukoko`, `nyuchi`, `bundu`, `shamwari`, `bushtrade` are lowercase, always. (Product names such as Mukoko Events are written as names, not wordmarks.)\n- **The palette is 21 families: seven Minerals + seven Heritage + the Experimental Seven.** Any interface that uses only the minerals is using a third of the system. Do not describe it as \"the seven-colour palette\".\n- **Seven African Minerals** (geological, the core palette). The custom properties are `--color-<mineral>`, `--color-<mineral>-container`, `--color-<mineral>-on-container` — those are the names the design system defines. (`--container-<mineral>` / `--on-container-<mineral>` appeared in earlier revisions of this skill and are defined nowhere upstream; `tokens-update.css` now ships them as aliases so existing work keeps rendering, but do not reach for them in new code.)\n - `cobalt` — primary blue, links, CTAs · digital future, trust\n - `tanzanite` — **mukoko's brand mineral**, social features · premium, connection\n - `malachite` — success states, positive actions · growth\n - `gold` — achievements, rewards, highlights · honey, warmth\n - `terracotta` — community features · earth, grounding\n - `sodalite` — **AI / Shamwari surfaces, deep-reasoning states** · intelligence, depth _(added v4.1.0)_\n - `copper` — **Bundu ecosystem identity, the commons** · connection, stewardship _(added v4.1.0)_\n- **Seven Heritage tones** (atmospheric anchors — mini-app surfaces, backgrounds, moods; no family or role, with one exception: `hematite` is Mzizi's brand colour): `indigo` (dusk, the dyer's craft) · `savanna` (sun-dried grass, the dry season) · `baobab` (the tree of life, bark, shelter) · `sunset` (day's end, the gathering hour) · `river` (flow, the journey) · `hematite` (the neutral anchor, the substrate) · `kalahari` (the light anchor, openness). Reach for them via `--color-<name>` (aliasing `--heritage-<name>`); values are in `palette.canonical.json`. _(Two earlier framings are withdrawn: the ten-colour one from before the palette settled, and the two-anchor one that survived into v0.5.1 of this bundle. **There are seven.** A skill that named two produced two-tone output in projects that had never seen each other.)_\n- **The Experimental Seven** — a computed heptagon (hues offset 17°, prime saturations, foregrounds solved to P7), each tone carrying a `heptagonIndex` 0–6 that fixes its place on the wheel: `ember` · `acacia` · `fern` · `lagoon` · `storm` · `dusk` · `protea`. Four tiers each: `--exp-<name>`, `--exp-<name>-container`, `--exp-<name>-on`, `--exp-<name>-ui`. Use them for generated/wheel-derived surfaces and categorical sets where the minerals would have to be stretched past their meanings.\n- **Each brand owns a mineral** (the ecosystem map, verified v4.1.0 and served at `/v1/brand` as `.ecosystem`): bundu→**copper**, nyuchi→**gold**, mukoko→**tanzanite**, shamwari→**sodalite**, Mzizi→**hematite** (a Heritage tone, not a mineral: the one brand whose colour comes from the Heritage set), events (Mukoko Events)/campfire/novels/health/kweli→malachite, lingo/planner/news/weather→cobalt, bushtrade/places/transport/wallet/learning→gold (learning, and with it the education category, is gold because every Nyuchi brand is), bytes/pulse→tanzanite, circles→terracotta. Theme a surface in its brand's colour — never default everything to tanzanite, and never give a Mzizi surface gold, tanzanite or copper as its primary.\n- **Buttons are ALWAYS pill** (`rounded-full` → `--radius-full`, 9999px). Inputs match buttons visually. The exception is the Mzizi Dashboard Standard (below): its app Button and controls use the small radius, as their contracts state.\n- **The Bundu floor: hard minimums on every device** (owner rulings 2026-11-01 and 2026-11-02, mzizi-registry#597; registry 4.23.0, `@bundu/ui` 0.8.0). Bundu sets the standard for outdoor reading and for people who find technology hard, so the minimums are as high as they can be. **Body text is 17px on every device**, desktop included (Apple's iOS default). **Captions, metadata and secondary text are 14px** (2 x 7), and nothing readable is smaller: `text-xs` and `text-sm` both paint 14px, badges and chart labels are 14px, and an arbitrary `text-[10px]` is wrong. **Every interactive control keeps a 49px hit region (7 x 7) on every pointer**, above Apple's 44 pt (Buttons: \"a hit region of at least 44x44 pt\"). On touch, controls are drawn at 49px: `h-control`, `h-control-sm`, `size-control-icon` and `h-field` are 49px, `h-control-lg` 56px (`--target-floor`, `min-h-touch-min`). At desktop density (fine pointer, hover, 64rem and up) a control may be DRAWN 28 to 40px (`--control-floor` is 28px) but keeps its 49px hit region through a transparent `::after` the token layer's `hit-area` rule gives every `button`, `summary`, button-like role and `.hit-area` element; `--target-floor` is never lowered. A native input, select or textarea cannot carry a pseudo-element, so `h-field` stays 49px drawn on desktop, and a standalone link that is a target is drawn at 49px or takes `hit-area`. Use the **size roles**, never `h-12`, `h-14`, `size-12`, `size-14` or `min-h-[44px]`. Only display and heading sizes step down as screens grow; body and secondary text never do (h1 is 34px on a phone, Apple Large Title). Density is visual size only: **never use screen size to guess network quality**. Spacing between targets has its own floor (next bullet): a 7px gap, and the spacing tokens do not change with density. The radii are unchanged: 4, 7, 12, 14, 17 and a pill. `<html data-mzizi-sizes=\"legacy\">` is the compatibility alias: it restores the sizes from before 2026-11-01 (48px floor, 56px default, 16px body, 12px captions), steps out of the Bundu floor, and is planned for removal in the next major; set it only for an app that has not migrated, and remove it once it has. The numbers live in one place, `BUNDU_FLOOR` in the registry's `lib/tokens/roles.source.ts`, and are what `button.tsx`, `button.astro`, the Roots `button.rs` and `/v1/brand` `.componentSpecs` (`minTouchTarget: 49`) state; the language's `primitives/button.mz` still says 56 until that file is updated in its own repository. Dashboards and Discover: the Dashboard and Discover Standards' components read the same size roles; each contract's `density` states the rows, and about 40 rows that predate the floor (links and controls on literal `h-11`/`h-12` classes) are pinned in the registry's `HIT_AREA_GAPS` until they move, so do not copy their heights. **The Bundu Order** (registry `brand-guidelines`, \"The Bundu Order\") is where these numbers come from: the unit is 7px, the floors are 17 (a prime), 14 (2 x 7) and 49 (7 x 7), P7 and P17 are prime steps of the surface ladder, and the owner says it draws on biblical mathematical patterns and prime numbers. Cite it as the owner's provenance, not as an engineering reason.\n- **The spacing floor: minimum padding, gaps and leading that nothing can go under** (owner, 2026-11-04, mzizi-registry#617). The Bundu floor sets spacing as well as sizes, on the same 7px unit. The five minimums: **inset 14px** (`--inset-floor`, 2 x 7: padding between text and its container edge); **control gap 7px** (`--gap-floor`: between adjacent interactive controls, measured visible edge to visible edge; hit regions may touch, never overlap, and the `hit-area` overlay of a control drawn small at desktop density may reach into the neighbouring space as before); **label gap 7px** (a label and its field); **item gap 7px** (sibling items in a list, grid or toolbar); and **body leading 1.5** (`--leading-floor`: line height, WCAG 1.4.12). They sit beside 17px body, 14px captions, the 49px hit region (`--target-floor`), 7:1 body contrast and the 28px visible control. The numbers live in one place, `BUNDU_FLOOR.spacing` in the registry's `lib/tokens/roles.source.ts` (`insetPx` 14, `controlGapPx` 7, `labelGapPx` 7, `itemGapPx` 7, `leadingBody` 1.5), and `/v1/brand`, `mzizi_get_tokens` and `mzizi_get_doctrine` serve them. **How it is enforced:** a base-layer rule in the token layer gives every element the floor even when you add no padding or gap, so an unstyled container is already at the floor; the token test fails any component spec that sets padding or a gap below it; each component contract asserts its spacing minimums (TypeScript and Rust agree); a rendered geometry guard checks neighbouring controls, text inset and the WCAG 1.4.12 stress (line height 1.5, paragraph spacing 2x, letter spacing 0.12em, word spacing 0.16em) for clipping; and `mzizi check` (`@nyuchi/mzizi-cli`) fails a project's CSS or TSX that goes under it. **Nothing may go under a floor:** no brand pack, no density layer, no app, no one-off. Density changes visual size only; the spacing floor holds at every density. `<html data-mzizi-sizes=\"legacy\">` is the one opt-out, the same alias that restores the pre-floor sizes (it steps out of the spacing floor too, and is planned for removal in the next major). **What to do:** build with the components and the tokens (`--inset-floor`, `--gap-floor`, `--leading-floor`), which already sit at or above the floor, and run `mzizi check` before you push. Never write `p-0`, `px-0`, `gap-0`, a negative margin, `leading-none`, `leading-tight` or an arbitrary `p-[4px]` on a text container or a row of controls. The floor is rolling out across the registry, `@bundu/ui`, `@nyuchi/mzizi-cli` and the MCP (mzizi-registry#617): `mzizi check` needs `@nyuchi/mzizi-cli` 0.8.0 or later, and a registry or `@bundu/ui` older than the spacing floor has none of the custom properties, so check what you installed before relying on them. Where a value below the floor is deliberate (a decorative, non-text box; a hit region that overlaps by design is still never allowed), put an exception comment on the same or the previous line: `mzizi-spacing-exempt: <reason>`, with a reason. An exception without a reason fails.\n- **`text-base` can collide with `--color-base`.** The token layer registers `--text-base` (17px, the font-size step) and the palette also has a surface colour named `base` (`--color-base`), and Tailwind 4 reads both namespaces for a `text-base` class, so an app that has both may not get the 17px step. This is a known open point (registry decision pending, a follow-up to mzizi-registry#597), not something to work around by renaming a token. Until it is decided: write body copy as `text-body` (17px) or leave it unclassed, use `text-xs` and `text-sm` (both 14px) for secondary text, which work, and never use `text-[Npx]` under 14.\n- **Radius scale** (px), every step from a 7px unit: `xs` 4 (unit − 3) · `sm` 7 · `md` 12 · `lg` 14 (**DEFAULT** cards/panels) · `xl` 17 · `2xl` 17 · `full` 9999 (pills). That is `mzizi-registry/styles/globals.css`, the Rust `mzizi-tokens` `Radius`, and `/v1/brand` `.radii` (\"ecosystem numbers: 4, 7, 12, 14, 17\"; `xs` joined in mzizi-registry#544). Legend swatches, kbd keys and inline tags use `xs`, checkboxes `sm`, inputs and small cards `md`, cards and panels `lg`, modals, sheets, dialogs and tabs `xl`. Reference via `--radius-<name>`. `2xl` is 17px, never 24. (Until 0.8.0 this skill listed `none` 0 and `2xl` 24, which were never upstream values.)\n- **H1–H3 are Noto Serif. H4+ and body are Noto Sans. Code is JetBrains Mono.**\n- **Rings over shadows.** Cards get `ring-1` of `--border`, not a drop shadow.\n- **No gradients, no glassmorphism** except sticky chrome (`backdrop-blur`) and the Discover Standard's hero wash (DiscoverHero's `gradient-showcase`, which mixes `--primary` into the background, as its contract declares).\n- **Emoji only as mini-app identifiers**, never as chrome or body decoration.\n- **Status has its own names: `--status-success` / `--status-warning` / `--status-error` / `--status-info` / `--status-neutral`.** Use those names and do not hardcode a hex. Upstream they alias `--success` / `--warning` / `--destructive` / `--info` / `--neutral`, so there is one decision per status — which means the resolved values are mineral-derived in the shipped theme (success reads as malachite, info as cobalt), not the standalone #22C55E/#F59E0B/#EF4444/#3B82F6 set an earlier revision of this skill listed. Those hexes are the per-component `var(--token, #hex)` fallback for an app that has not defined the properties; they are not the theme. Severity scale (`--severity-low/moderate/high/severe/extreme/cold`) and connection (`--connection-online/syncing/cached/offline`) follow the same logic. Verification is the only place minerals carry status meaning; it has its own rule, next.\n- **Verification is two things, not one ladder: assurance levels and attestations** (mzizi-registry#577; the source is `gateway/lib/assurance.py` in `nyuchi/nyuchi-api`, the one access decision every surface derives from — read that, never re-derive it). Read the roles, never a hex; each has `-container`, `-on-container` and `-text`, and passes APCA in both themes.\n - **Assurance levels** are ordered (`>=` is meaningful): how strongly an identity is proven. 0 `none` \"Unverified\" → `--assurance-none` (the neutral grey, `--status-neutral`) · 1 `otp` \"Contact Verified\" → `--assurance-otp` (cobalt) · 2 `documented` \"Documents Checked\" → `--assurance-documented` (sodalite) · 3 `government` \"Government Verified\" → `--assurance-government` (gold) · 4 `licensed` \"Licensed Professional\" → `--assurance-licensed` (tanzanite).\n - **A level can expire.** A `government` check holds for five years and a `licensed` one for a year (`LEVEL_VALIDITY_YEARS` in assurance.py, nyuchi-api#375). When the term runs out the level is withdrawn, so show the level a person holds now and never cache it as permanent.\n - **Attestations** are unordered and held beside the level: who vouches for the subject. `community` \"Community Vouched\" → `--attestation-community` (terracotta) · `business` \"Registered Business\" → `--attestation-business` (malachite) · `domain` \"Domain Verified\" → `--attestation-domain` (copper). Show each as its own chip next to the level (`app/tier-status` and `ui/mzizi-verified-badge` do this, from registry v4.17.0; `ui/mzizi-trust-meter` shows the level and renders no attestation chips).\n - **Provenance: where an attestation came from, and when it was checked** (mzizi-registry#593, on registry `staging`, released with the next registry version: `ui/mzizi-verified-badge` 1.2.0 and `app/tier-status` 1.2.0). A stored attestation record may carry `source` (a short id such as `\"osm\"`), `sourceRef` (the source's own reference, kept and never shown) and `checkedAt` (an ISO date or date-time). The chip label stays the canonical one (\"Community Vouched\"), and an optional second line reads \"via <Source> · checked <date>\", for example \"via OpenStreetMap · checked 14 Mar 2026\". Source names come from `ATTESTATION_SOURCES` in `mzizi-trust-tier-data` (`osm` → \"OpenStreetMap\"), and the line is built by `attestationProvenance`. Two rules: **never invent a check date.** With no `checkedAt`, or one that does not parse or names a day that does not exist, the line is \"via OpenStreetMap\" with no date. And **never a per-source label**: a source changes the second line only, never the chip's name, its colour or the closed set of three attestations (an OpenStreetMap verification is a `community` attestation, never a level). The date is formatted in UTC from the locale (`en-GB` by default) so the day never shifts. The chip's `aria-label` stays the canonical name and the line is its `aria-description` (badge) or is read with the name (tier status, the date as `<time datetime>`).\n - **Community is not a rung.** The old ladder put `community` at 1, below `otp`, which ranked a trader vouched for by their whole street below anyone who clicked a link in an email. Mukoko serves the informal economy: a tuck shop is level `otp` plus community, a registered firm is `government` plus business, and neither outranks the other. So never draw community on the level scale and never below otp; `tier=\"community\"` now renders as level Unverified plus a \"Community Vouched\" chip.\n - **`--tier-*` are legacy aliases**, kept while `bundu.verificationTier` (the `legacy_tier()` projection in assurance.py) still has readers: `--tier-unverified` → `--assurance-none`, `--tier-community` → `--attestation-community`, `--tier-otp` → `--assurance-otp`, `--tier-government` → `--assurance-government`, `--tier-licensed` → `--assurance-licensed`. They paint what they always did. New code reads `--assurance-*` and `--attestation-*`. (Earlier revisions of this skill listed a five-rung tier ladder, first as community→malachite, government→tanzanite, licensed→gold, then, after mzizi-registry#543, with community as terracotta; both treated community as a rung.)\n - Colour is never the only cue: every level and attestation carries its label and glyph. Heritage tones never carry a level or an attestation.\n- **Surfaces (April 2026 AAA refresh):** light mode — `--background` warm paper, `--card` pure white, `--muted` cream, `--overlay` white. Dark mode — `--background` warm stone L10%, `--card` L6% (darker than bg), `--muted` L2% (deepest), `--overlay` L14% (lighter than bg, scrim creates pop).\n- **Icons:** in the React build, Lucide through `@/lib/icons`, never `lucide-react` directly; in Mzizi Roots, an icon is an `Element` slot the host renders. For an HTML prototype, the Lucide CDN build is fine.\n- **Contrast has two bars, both enforced** (registry #399, #610; owner 2026-11-02): WCAG 2 minimums of **7:1 for body text** (on `base` and `surface`, both themes), **4.5:1 for non-body text** (captions, timestamps, metadata) and **3:1 for UI parts** (focus rings, control edges, WCAG 1.4.11), and the APCA bars (Lc 75 and up) beside them. `pnpm tokens:verify` in the registry fails a default role under its minimum. **One named exception, owner-confirmed:** five button-label roles hold 4.5:1, not 7:1, on their fill: `primary-foreground`, `brand-accent-foreground`, `app-accent-foreground`, `sidebar-primary-foreground` and `destructive-foreground`. The fills are each brand's `-aa` tier, which is 4.5:1 under white by definition, so 7:1 would darken every brand's action colour. Never apply the exception to anything that is not one of those five roles: a label on any other fill, and all running text, needs 7:1. Read the roles (`--text-secondary`, `--muted-foreground`, the `-text` tier) instead of picking a grey; `mzizi_check_accessibility` returns the WCAG ratio and checks 7:1 (it does not compute APCA or the 4.5:1 and 3:1 tiers).\n\n### When creating visual artifacts\n\nCreate static HTML files for the user to view. `@import` `tokens-update.css` at the top of every HTML file (or inline it). For slide decks use a mineral accent strip and serif display type.\n\n### When working on production code\n\nUse the registry's components, never hand-rolled copies. **Rust first:** Mzizi Roots is the lead build (`cargo add mzizi-roots`; see the `mzizi-roots` skill). The React build installs by absolute URL, for existing React apps:\n\n```bash\nnpx shadcn@latest add https://api.mzizi.dev/v1/ui/<name>\n```\n\nTake CSS variables from `tokens-update.css` or `tokens/palette.css` in this skill — it is generated from `palette.canonical.json` and carries all 21 families. The upstream equivalent is `mzizi-registry/styles/globals.css`, which is generated from the same canon. There is no database to refresh from.\n\n### Apple HIG: the baseline, and where Mzizi overrides it\n\nMzizi designs on Apple's Human Interface Guidelines\n(<https://developer.apple.com/design/human-interface-guidelines>). It applies them to **every**\nMzizi surface (web, native and console) and not only to Apple platforms. The HIG is the\nbaseline wherever Mzizi says nothing. Where Mzizi has a token, a registry component or a\ncontract, **Mzizi is the authority, across all apps**, and the HIG value does not apply. Read\nthe HIG pages live, because Apple revises them: the design principles were reintroduced on\n8 June 2026, Layout was updated on 9 September 2026, and Liquid Glass guidance arrived on\n9 June 2025.\n\n**The eight HIG design principles** (Design principles, June 2026) are the questions to ask of\nevery screen:\n\n| Principle | In Apple's words | In Mzizi terms |\n| -------------- | ------------------------------------ | ------------------------------------------------------------------------------------- |\n| Purpose | Make something meaningful. | Build what the person came for, and drop features nobody asked for. |\n| Agency | Let people do things their own way. | No locked flows, an escape from every guided step, undo before confirm. |\n| Responsibility | Act in people's best interest. | Say what is collected and why. Collect only what the product needs (Ubuntu: dignity). |\n| Familiarity | Build on what people know. | One shell and one set of patterns across every app (`mzizi-architecture`). |\n| Flexibility | Adapt to diverse contexts and needs. | Accessibility from the start, and every input (touch, keyboard, voice, pointer). |\n| Simplicity | Be clear and direct. | Every element earns its place. Hierarchy first, concise labels. |\n| Craft | Care about every detail. | The contract's checks pass, every state renders, motion is purposeful. |\n| Delight | Make it human. | Delight is never decoration. The minerals carry meaning, not ornament. |\n\n**Where Mzizi is the authority** (the HIG value is replaced, not blended):\n\n| Concern | HIG says | Mzizi rule (wins) |\n| ----------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Touch targets | 44×44 pt default, 28×28 pt minimum on iOS/iPadOS | **49px hit region on every pointer** (`--target-floor`; `h-control` and `h-field` 49px on touch), drawn 28 to 40px at desktop density with the 49px region kept by `hit-area`. Dashboards: each contract's `density` |\n| Spacing and layout grid | system margins, layout guides | **the spacing floor**: 14px inset, 7px control, label and item gaps, 1.5 leading (`--inset-floor`, `--gap-floor`, `--leading-floor`), then the Mzizi spacing tokens and each component contract's `responsive` rules, from 320px up |\n| Type scale and families | San Francisco, Dynamic Type sizes (17 pt default body on iOS) | Noto Serif H1–H3, Noto Sans H4 and body, JetBrains Mono for code, at Apple's Large (default) sizes on a phone (body 17px, h1 34px), and body stays 17px on every device with captions at 14px (the Bundu floor). **Text still scales with the user's setting** (rem units, no fixed px caps) |\n| Colour | system colours, dynamic colours | the 21 families and the semantic tokens only, through `var(--…)`. A brand overlay changes colour and mark only |\n| Contrast | 4.5:1 up to 17 pt, 3:1 at 18 pt or bold (WCAG) | WCAG 7:1 body, 4.5:1 non-body, 3:1 UI, plus APCA, in both themes (the palette is solved for it), with five button-label roles at 4.5:1 |\n| Shape | system control shapes, capsules in Liquid Glass | **pill buttons and inputs** (`--radius-full`), the radius scale above, and the Dashboard Standard's small radius where its contracts say so |\n| Materials | Liquid Glass for controls over content | solid surfaces and rings; blur only on sticky chrome. No glass panels on web surfaces |\n| Density | per-platform defaults | comfortable by default, dense only where a Standard's contract says so |\n| App chrome | tab bars, sidebars, toolbars per platform | **the shell**: AppShell or DiscoverShell from `@bundu/ui`, mounted once (`mzizi-architecture`) |\n\n**Where the HIG applies as written** (Mzizi is silent, so follow Apple):\n\n- **Hierarchy and layout.** Order content by importance (top and leading side first), align to\n make it scannable, group related items with space, containers or separators, and use\n progressive disclosure instead of showing every choice at once (Layout).\n- **Adaptivity.** Lay out by the space available, never by device type. Keep functionality\n the same as the size changes, and respect safe areas (the registry's `safe-area-frame`\n primitive does this on the web).\n- **Accessibility.** VoiceOver and screen-reader labels, Reduce Motion (stop auto-playing,\n zooming and parallax motion; Mzizi's harness exposes `prefers-reduced-motion`), Increase\n Contrast, keyboard and switch access, and never colour alone.\n- **Feedback and recovery.** Show state changes, confirm destructive actions, and make\n reversal cheap.\n- **Writing.** Be concise, use the exact words a control needs, and say what will happen.\n- **Native apps** (SwiftUI and other native platforms) use the system components and\n materials, Liquid Glass included, themed through the generated Swift and Kotlin tokens, while\n the Mzizi floor, type and colour rules still hold.\n\nWhen a HIG update conflicts with a Mzizi rule, the Mzizi rule stands until the registry changes\nit. Raise the conflict upstream in `mzizi-dev/mzizi-registry` and do not patch around it in an\napp.\n\n### Dashboards: brand overlays only\n\nAn admin or operator dashboard uses the **Mzizi Dashboard Standard**: the app components of\n`@bundu/ui` (`@bundu/ui/app/*`), one design for every product in the Bundu ecosystem, each\nwith a versioned contract in `mzizi-registry/contracts/app/` (see `mzizi-roots`). A brand\nchanges colour and mark, and nothing else:\n\n- **A brand overlay changes `--primary`, `--ring`, AppShell's `--app-accent` and the brand\n mark.** Layout, spacing, type and size never vary by brand. The overlay is one stylesheet\n imported after the tokens (`@bundu/ui/styles/brand-<brand>.css`). AppShell's `accent`\n takes one of the seven minerals and fills primary actions and the current nav indicator\n with its brand fill (`--color-<mineral>-brand`, text `--color-<mineral>-on-brand`); text\n links and the focus ring keep `--primary` and `--ring`.\n- **Status colours do not follow the brand.** Success is malachite, warning gold, error\n terracotta, info cobalt, in every brand. A component names a mineral only as one of its\n contract's `theming.statusColours`; any other mineral class, or a literal colour value,\n fails its contract test.\n- **Mzizi's hematite** is a Heritage tone, so AppShell's `accent` does not apply to it;\n `brand-mzizi.css` sets `--primary`.\n\n### Discover pages: the same overlay, touch-first\n\nA public discover or browse page in the Mukoko family (circles, news, events, weather, and the\nsuper-app on the web) uses the **Mzizi Discover Standard**: the Discover components of\n`@bundu/ui` (`@bundu/ui/discover/*`), each with a versioned contract in\n`mzizi-registry/contracts/discover/` (see `mzizi-roots`). The brand-overlay rule is the\nDashboard Standard's, with two differences the contracts state:\n\n- **No `--app-accent`.** A brand shows in DiscoverShell's `brand` slot (its mark) and in the\n primary action its `action` slot holds; the frame, header, navigation and footer are\n identical in every brand. `--primary` reaches only DiscoverHero's wash, DiscoverCard's\n monogram and `brand` badge tone, and OpenInApp's `primary` variant. Category chips, sections,\n search and \"Load more\" are neutral (search follows the brand only in its `--ring` focus\n ring). DiscoverCard is the one Discover component with status colours (`cobalt`,\n `malachite`, `gold`, `terracotta`), for its badge tones, and they do not follow the brand.\n- **Sizes come from the size roles.** Discover pages take their heights from `h-control` and\n friends (search field 49px, submit button 36px drawn at desktop density and 49px on touch,\n as the density rows in `contracts/discover/` state); its links and chips are still pinned\n gaps in the registry. (Until 2026-11-01 Discover was the same size on every pointer, 48px or\n 44px; the Bundu floor replaced that.)\n\n## What belongs in the ecosystem\n\n### Ubuntu alignment\n\nThe ecosystem is built on Ubuntu — \"I am because we are.\" When you architect a new app:\n\n- Prefer open source over proprietary\n- Build for community, not extraction\n- Respect data sovereignty — African data stays on African infrastructure where possible\n- Accessibility is not an afterthought; it is the starting point\n- Every feature should contribute to collective wellbeing, not just individual efficiency\n\nThese aren't marketing claims, they're architectural constraints. If you find yourself\ndesigning something that only benefits a small subset of users at others' expense, question\nwhether it belongs in the ecosystem.\n\n### What is and isn't an ecosystem app\n\nThe ecosystem has two layers. **Mzizi** owns and operates the language, its toolchain, the\nregistry, the design system, the docs and the API, and sits under every app. **Nyuchi Africa**\noperates the Mzizi console and everything revenue-generating. Inside Nyuchi Africa\nsit three pillars and the platform:\n\n- **mukoko** — the consumer pillar: the super-app (social, commerce, payments, identity)\n- **nyuchi** — the commercial/infrastructure pillar and the platform (web services, developer\n tools, storage)\n- **shamwari ai** — the intelligence pillar, sovereign by nature (on-device inference,\n pod-resident context, no third-party AI vendor)\n- **Mukoko Events** (formerly nhimbe) — community events and gatherings\n\nMzizi is not an app and not a layer in a stack; it is the open, non-revenue foundation the\necosystem builds on and the doctrine that governs it. mukoko and shamwari ai are pillars inside Nyuchi Africa, not separate companies.\n\nNot ecosystem apps:\n\n- Any app that extracts value without giving back\n- Any app that requires surveillance for its business model\n- Any app that centralises what should be federated\n\nIf the app you're building doesn't fit, that's fine — consume the Mzizi design system, but\ndon't brand it as ecosystem.\n\n## If invoked with no other guidance\n\nAsk what the work is: (a) an interface or prototype (which surface: mobile, desktop, print,\nslide; which brand voice: Mzizi, nyuchi, mukoko, shamwari, Mukoko Events), (b) parent-brand material\n(ecosystem page, manifesto, deck), (c) a cross-brand piece, or (d) a voice or mineral decision\nfor one brand. Then apply the constellation table, the voice rules and the design system, and\noutput HTML artifacts or production code as the need requires.\n\n_Mzizi — the root everything grows from._",
|
|
33
33
|
"source": "mzizi-dev/agent-tools/mzizi-skills/skills/mzizi-design",
|
|
34
34
|
"files": [
|
|
35
35
|
{
|
|
@@ -208,23 +208,23 @@ export const BUNDLED_SKILLS = [
|
|
|
208
208
|
{
|
|
209
209
|
"name": "mzizi-architecture",
|
|
210
210
|
"category": "build",
|
|
211
|
-
"description": "
|
|
212
|
-
"body_mdx": "# Mzizi architecture: design, app and system\n\nThree questions, in this order, before anything is built:\n\n1. **Design architecture.** Where on the helix does this belong, and what does that placement\n oblige it to do?\n2. **App architecture.** Which shell frames it? The shell is installed **first**, before the\n first page.\n3. **System architecture.** Which service owns the data, which gateway serves it, and what runs\n where?\n\nThe rule behind all three is **upstream first**. If the registry lacks a component, a shell or a\npattern that you need, or has one that is wrong, change it in `mzizi-dev/mzizi-registry`, its\ncontract in `contracts/` and its build in `@bundu/ui`. Keep a local copy only while that pull\nrequest is open, marked `TODO(mzizi): <PR URL>`. Never fork the shell or restyle it locally.\n\nThis skill describes registry `main` at v4.18.0 (`0d17818`, 2026-11-01), the Roots crates at\n0.7.0 and `@bundu/ui` 0.7.0. Counts move, so read the live sources below rather than this text.\n\n## Design architecture: the DNA helix\n\nThe design system is a double helix. Read it live, not from memory:\n\n```bash\ncurl -s https://api.mzizi.dev/v1/architecture # nodes, rungs, strands\ncurl -s https://api.mzizi.dev/v1/architecture/nodes/7 # one node, with its rules\n```\n\nOn the MCP, call `mzizi_get_architecture`, or `mzizi_get_architecture node=7` for one node. The\nnode number is uncapped, so never assume a range.\n\n**Eight nodes.** These are the backbone, and each owns one kind of thing:\n\n| Node | Name | Owns | Strand |\n| ---- | ---------------- | --------------------------------------------------------------- | -------------- |\n| N1 | Design tokens | the only node allowed to define CSS values | swappable |\n| N2 | Primitives | the smallest composable unit of UI | core-guarantee |\n| N3 | Brand components | primitives wrapped in Ubuntu, the only consumers of the harness | shipped |\n| N4 | Safety rails | input validation, output sanitisation, AI guardrails | core-guarantee |\n| N5 | Resilience | circuit breakers, retries, timeouts, fallback chains | core-guarantee |\n| N6 | Pages | composition of N2 and N3, implementing nothing | shipped |\n| N7 | Shell | the app chrome: header, footer, navigation, theme, mounted once | shipped |\n| N8 | Assurance | observability, health monitoring, SLO tracking | core-guarantee |\n\n**Four rungs.** These bridge both backbones: N9 Fundi (self-healing: it consumes N8 signals and\nfiles structured issues for people, and never auto-merges), N10 Documentation, N11 Discovery\n(SEO and AIO, preview images, structured data, agent-readiness) and N12 Skills (this bundle).\n\n**Six strands.** Four belong to the engineering backbone: `core-guarantee` (travels unchanged\nand cannot be turned off), `shipped` (in the box, but free to evolve), `swappable` (the only fork\nseams: tokens, icons and the framework) and `spine`. Two belong to the meaning backbone:\n`genetic-code` and `transcription` (\"Doctrine is data, not tribal knowledge\").\n\nWhat a placement obliges, from the nodes' own implementation rules:\n\n- **N1 only defines values.** No hex, `rgba()` or inline style outside N1. Everything else reads\n `var(--…)` or its platform equivalent.\n- **N6 composes and implements nothing.** A page imports from N2 and N3, never defines a\n primitive, and pulls a spinner or skeleton from N2 rather than inlining one. Pages render on\n the server by default (Astro, no client JavaScript).\n- **N7 is singular.** There is one shell per app. It is the only place that holds sticky\n positioning, backdrop blur and theme-provider wiring. It surfaces auth state and never\n implements auth logic. It is responsive from 320px up.\n- **N4, N5 and N8 are core guarantees.** You cannot configure accessibility, safety,\n resilience or observability away.\n\nDo not confuse the helix with the five application layers below. They are two models with two\nnumberings, so never quote a count from one while describing the other.\n\n## App architecture: the shell comes first\n\nThe failure this section exists to stop is apps built page by page with no shell installed. Each\none then hand-rolls its own header, nav and footer, drifts from every other app, and loses the\nno-JS, density, accessibility and brand-overlay guarantees the shell's contract carries.\n**Step one of any new app is the shell.** Step one of any new page in an existing app is to\nconfirm the page renders inside it.\n\n### 1. Pick the shell from the kind of surface\n\n| Surface | Shell | Standard | Contract |\n| ---------------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------- |\n| A signed-in app, console or admin surface | `@bundu/ui/app/AppShell.astro` | Mzizi Dashboard Standard (docs.mzizi.dev/patterns/dashboard-standard) | `contracts/app/app-shell` |\n| A public discover or browse site (the Mukoko family) | `@bundu/ui/discover/DiscoverShell.astro` | Mzizi Discover Standard (docs.mzizi.dev/patterns/discover-standard) | `contracts/discover/discover-shell` |\n| A marketing site | none yet: see \"Gaps\" below | the `site/` components (`Hero`, `Section`, `Container`, `MineralStrip`, …) | no shell contract |\n\nRead the shell's contract before you wire it. The props, slots, `noJs`, `density` and `theming`\nthere are the specification. On the MCP that is `mzizi_get_component app-shell`, and in the\npackage, `@bundu/ui/contracts/app/*`.\n\n### 2. Install it (Astro, the default)\n\n```bash\nnpm install @bundu/ui # 0.7.0 or later\n```\n\nImport the tokens and your brand's overlay (colour and mark only) in your global stylesheet, as\nthe `mzizi-design` skill says. Then describe the navigation as data and wrap **every**\nsigned-in page in one layout that mounts `AppShell`:\n\n```astro\n---\n// src/layouts/App.astro: the one layout every signed-in page uses\nimport AppShell from \"@bundu/ui/app/AppShell.astro\";\nimport { nav } from \"../nav\"; // navigation as data, not markup\nconst { title } = Astro.props;\n---\n<AppShell nav={nav} accent=\"gold\" footerLinks={[{ label: \"Status\", href: \"/status\" }]}>\n <slot />\n</AppShell>\n```\n\nA public discover page puts `DiscoverMeta` in `<head>` and `DiscoverShell` in `<body>`, then\n`DiscoverHero`. The Discover Standard page has the full order.\n\nThen build pages from the patterns, never from scratch. For a dashboard, that means\n`PageHeader` first, then `Toolbar` or `FilterBar`, then `StatTiles`, then content in `Card`,\n`DataTable`, `FormLayout`, `EmptyState` or `DetailPanel`. For discover pages, that means\n`DiscoverSection`, `ResultGrid`, `DetailHero` and `RelatedRail`.\n\n### 3. Check that the shell is there\n\nRun this before you add a page, and in review. An app passes when each line holds:\n\n```bash\n# The shell is a dependency.\ngrep -E '\"@bundu/ui\"' package.json\n# Exactly one layout mounts it.\ngrep -rlE 'AppShell|DiscoverShell' src/layouts\n# Every page uses that layout. List the pages that do not:\ngrep -rL -E 'layouts/(App|Discover)' src/pages --include='*.astro'\n# No page hand-rolls chrome the shell owns.\ngrep -rnE '<(header|nav|footer)[ >]' src/pages\n```\n\nAny page the third command prints, and any match from the fourth, is a page outside the shell.\nMove it into the layout. A shell that is missing entirely means step 2 was skipped, so install\nthe shell before anything else.\n\n### 4. Structure the app's own code: five layers, imports flow down\n\nThese layers are local to one application (docs.mzizi.dev/patterns/architecture). They are not\nthe helix:\n\n| Layer | What it holds |\n| ----- | ------------------------------------------------------------------------------------------------- |\n| L1 | Shared primitives, installed from the registry and never edited in place |\n| L2 | Domain composites built only from L1, the first layer that knows what the app is about |\n| L3 | Page sections that arrange L2. They arrange and do not implement |\n| L4 | Error boundaries and loading states around every L3 section, so one failure never blanks the page |\n| L5 | Routes: data fetching and composition, inside the shell. This is where the network lives |\n\nA component imports from the layer below, never sideways or upward.\n\n### 5. A React or Rust app\n\n- **React (Next.js) adopts through the registry's `.tsx` builds.** AppShell and DiscoverShell\n have `.tsx` siblings that implement the same contract:\n `npx shadcn@latest add https://api.mzizi.dev/v1/ui/app-shell`. Always give the full URL. A bare\n name installs shadcn's own component and reports success.\n- **Rust.** `mzizi-shell` (in `mzizi-roots`) carries N7 parts such as the bottom nav, footer,\n command palette, connectivity bar, theme provider, route guard and deep links. **It has no\n AppShell or DiscoverShell.** Both contracts have `rs: null`. A Rust or Astro-with-Roots app\n takes the shell from `@bundu/ui` and renders Roots components inside it. Do not write a Rust\n shell in an app. See the gaps below.\n\n### Gaps: report them upstream, do not paper over them\n\nThese were found in the registry at v4.18.0 and are tracked in mzizi-registry#589:\n\n- AppShell and DiscoverShell have no Rust build (`rs: null`), and `mzizi-shell` documents that\n `mzizi-header` and `mzizi-sidebar` wait on N2 ports.\n- No shell contract exists for marketing sites (`site/` has no `site-shell`). The N6 doctrine\n names a \"Site Standard\", but docs.mzizi.dev has no page for it.\n\nWhen you hit one of these, open or comment on the registry issue instead of building a local\nshell.\n\n## System architecture\n\nWhat runs where, and who owns what. **Decided** and **built** are different words. Say which one\napplies.\n\n- **Rust first, Astro for the UI.** Astro is the default web UI. Underneath, the code is Rust\n first and TypeScript second, and Mzizi's own surfaces have no third UI framework. Components\n are Mzizi Roots (Rust) where a Rust build exists and the React build otherwise, with React\n deprioritised. No Dioxus, Leptos or Yew in new work. The Roots crates still depend on Dioxus\n until the move to Mzizi's own `render(props)` finishes.\n- **Cloudflare Workers** run the services: `api.mzizi.dev` (`mzizi-dev/mzizi-api`, a Hono\n Worker), `mcp.mzizi.dev` (`mzizi-mcp`) and fundi. Containers and bare metal are decided as\n further targets and are not built. The `mzizi-backend` skill has the patterns.\n- **Four API gateways.** The Mzizi API (`api.mzizi.dev`, Hono, built), the Nyuchi API (B2B), the\n Mukoko API (consumer) and the Nyuchi Servers API (internal). It is decided that the last three\n are Rust. An app reads through its gateway and never opens a database connection of its own\n for a read the gateway serves. Add the endpoint to the gateway instead.\n- **The registry's files are its data layer.** `registry.json`, `components/`, `contracts/` and\n `content/doctrine/` are bundled into the API and the MCP at a pinned registry commit (the\n `x-mzizi-source` header and `catalogue.json` name it). Nothing is read from a database at\n request time. Only the console keeps state, and its move to Cloudflare D1 is decided, not\n built.\n- **One owner per domain.** Platform data lives in domain-separated databases (news,\n engagement, identity, entity, places, …). Each is written only by the service that owns it,\n and other services reference its records by id rather than copying them. Doctrine is data:\n read `mzizi_get_doctrine`, never a copy in a prompt.\n- **Trust is two things.** Assurance levels and attestations come from the one access decision\n in nyuchi-api `gateway/lib/assurance.py`. Read that decision rather than deriving your own,\n and draw them with the `--assurance-*` and `--attestation-*` roles in `mzizi-design`.\n- **Every surface is free by default.** Gate only what acts as a person: WorkOS sign-in, with\n 401 for a missing token and 403 for a token that lacks the scope.\n- **Failures become issues.** N8 signals feed fundi (N9), which files deduplicated GitHub\n issues for people and never merges a fix itself.\n\n## Checklist for a new app or page\n\n- [ ] Helix placement decided, and its node's rules read live.\n- [ ] Shell chosen from the surface table, its contract read, and `@bundu/ui` installed.\n- [ ] One layout mounts the shell, and every page uses it (the check in step 3 prints nothing).\n- [ ] Pages built from the Standard's patterns, with no hand-rolled header, nav or footer.\n- [ ] Tokens and brand overlay from `mzizi-design`. No local colour, radius, spacing or type.\n- [ ] Data read through the owning gateway. No new direct database read.\n- [ ] Anything missing or wrong in the registry raised upstream, with any local stopgap marked\n `TODO(mzizi): <PR URL>`.\n\n## See also\n\n- `mzizi-design`: tokens, brand overlays, Apple HIG and where Mzizi overrides it, and the 48px\n floor.\n- `mzizi-roots`: the components, Rust first. \"Starting an app\" covers project setup, and\n \"Contributing a component\" covers adding one upstream.\n- `mzizi-backend`: Workers and MCP servers.\n- `discoverability`: N11 in practice, covering OG images, structured data and agent-readiness.",
|
|
211
|
+
"description": "Use this skill before you start a new Mzizi app, site, console or page, before you add a page to one, and whenever you decide where code, data or a component belongs. Design architecture is the DNA helix (8 nodes, 4 rungs, 6 strands), read live from api.mzizi.dev/v1/architecture or mzizi_get_architecture, with what each placement obliges. App architecture puts the shell first: every app or page mounts exactly one shell before anything else (AppShell for a signed-in app, the Dashboard Standard; DiscoverShell for a public browse site, the Discover Standard), then the page patterns, the five application layers, and the rule that a missing component goes upstream. System architecture covers Rust first, Astro as the default UI, Cloudflare Workers, the four API gateways, files as the registry's data layer at a pinned commit, one service per domain database, and what is decided versus built. Use it when an app lacks a shell, a page invents its own chrome, or someone asks how the pieces fit.",
|
|
212
|
+
"body_mdx": "# Mzizi architecture: design, app and system\n\nThree questions, in this order, before anything is built:\n\n1. **Design architecture.** Where on the helix does this belong, and what does that placement\n oblige it to do?\n2. **App architecture.** Which shell frames it? The shell is installed **first**, before the\n first page.\n3. **System architecture.** Which service owns the data, which gateway serves it, and what runs\n where?\n\nThe rule behind all three is **upstream first**. If the registry lacks a component, a shell or a\npattern that you need, or has one that is wrong, change it in `mzizi-dev/mzizi-registry`, its\ncontract in `contracts/` and its build in `@bundu/ui`. Keep a local copy only while that pull\nrequest is open, marked `TODO(mzizi): <PR URL>`. Never fork the shell or restyle it locally.\n\nThis skill describes registry `main` at v4.18.0 (`0d17818`, 2026-11-01), the Roots crates at\n0.7.0 and `@bundu/ui` 0.7.0. Counts move, so read the live sources below rather than this text.\n\n## Design architecture: the DNA helix\n\nThe design system is a double helix. Read it live, not from memory:\n\n```bash\ncurl -s https://api.mzizi.dev/v1/architecture # nodes, rungs, strands\ncurl -s https://api.mzizi.dev/v1/architecture/nodes/7 # one node, with its rules\n```\n\nOn the MCP, call `mzizi_get_architecture`, or `mzizi_get_architecture node=7` for one node. The\nnode number is uncapped, so never assume a range.\n\n**Eight nodes.** These are the backbone, and each owns one kind of thing:\n\n| Node | Name | Owns | Strand |\n| ---- | ---------------- | --------------------------------------------------------------- | -------------- |\n| N1 | Design tokens | the only node allowed to define CSS values | swappable |\n| N2 | Primitives | the smallest composable unit of UI | core-guarantee |\n| N3 | Brand components | primitives wrapped in Ubuntu, the only consumers of the harness | shipped |\n| N4 | Safety rails | input validation, output sanitisation, AI guardrails | core-guarantee |\n| N5 | Resilience | circuit breakers, retries, timeouts, fallback chains | core-guarantee |\n| N6 | Pages | composition of N2 and N3, implementing nothing | shipped |\n| N7 | Shell | the app chrome: header, footer, navigation, theme, mounted once | shipped |\n| N8 | Assurance | observability, health monitoring, SLO tracking | core-guarantee |\n\n**Four rungs.** These bridge both backbones: N9 Fundi (self-healing: it consumes N8 signals and\nfiles structured issues for people, and never auto-merges), N10 Documentation, N11 Discovery\n(SEO and AIO, preview images, structured data, agent-readiness) and N12 Skills (this bundle).\n\n**Six strands.** Four belong to the engineering backbone: `core-guarantee` (travels unchanged\nand cannot be turned off), `shipped` (in the box, but free to evolve), `swappable` (the only fork\nseams: tokens, icons and the framework) and `spine`. Two belong to the meaning backbone:\n`genetic-code` and `transcription` (\"Doctrine is data, not tribal knowledge\").\n\nWhat a placement obliges, from the nodes' own implementation rules:\n\n- **N1 only defines values.** No hex, `rgba()` or inline style outside N1. Everything else reads\n `var(--…)` or its platform equivalent.\n- **N6 composes and implements nothing.** A page imports from N2 and N3, never defines a\n primitive, and pulls a spinner or skeleton from N2 rather than inlining one. Pages render on\n the server by default (Astro, no client JavaScript).\n- **N7 is singular.** There is one shell per app. It is the only place that holds sticky\n positioning, backdrop blur and theme-provider wiring. It surfaces auth state and never\n implements auth logic. It is responsive from 320px up.\n- **N4, N5 and N8 are core guarantees.** You cannot configure accessibility, safety,\n resilience or observability away.\n\nDo not confuse the helix with the five application layers below. They are two models with two\nnumberings, so never quote a count from one while describing the other.\n\n## App architecture: the shell comes first\n\nThe failure this section exists to stop is apps built page by page with no shell installed. Each\none then hand-rolls its own header, nav and footer, drifts from every other app, and loses the\nno-JS, density, accessibility and brand-overlay guarantees the shell's contract carries.\n**Step one of any new app is the shell.** Step one of any new page in an existing app is to\nconfirm the page renders inside it.\n\n### 1. Pick the shell from the kind of surface\n\n| Surface | Shell | Standard | Contract |\n| ---------------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------- |\n| A signed-in app, console or admin surface | `@bundu/ui/app/AppShell.astro` | Mzizi Dashboard Standard (docs.mzizi.dev/patterns/dashboard-standard) | `contracts/app/app-shell` |\n| A public discover or browse site (the Mukoko family) | `@bundu/ui/discover/DiscoverShell.astro` | Mzizi Discover Standard (docs.mzizi.dev/patterns/discover-standard) | `contracts/discover/discover-shell` |\n| A marketing site | none yet: see \"Gaps\" below | the `site/` components (`Hero`, `Section`, `Container`, `MineralStrip`, …) | no shell contract |\n\nRead the shell's contract before you wire it. The props, slots, `noJs`, `density` and `theming`\nthere are the specification. On the MCP that is `mzizi_get_component app-shell`, and in the\npackage, `@bundu/ui/contracts/app/*`.\n\n### 2. Install it (Astro, the default)\n\n```bash\nnpm install @bundu/ui # 0.7.0 or later\n```\n\nImport the tokens and your brand's overlay (colour and mark only) in your global stylesheet, as\nthe `mzizi-design` skill says. Then describe the navigation as data and wrap **every**\nsigned-in page in one layout that mounts `AppShell`:\n\n```astro\n---\n// src/layouts/App.astro: the one layout every signed-in page uses\nimport AppShell from \"@bundu/ui/app/AppShell.astro\";\nimport { nav } from \"../nav\"; // navigation as data, not markup\nconst { title } = Astro.props;\n---\n<AppShell nav={nav} accent=\"gold\" footerLinks={[{ label: \"Status\", href: \"/status\" }]}>\n <slot />\n</AppShell>\n```\n\nA public discover page puts `DiscoverMeta` in `<head>` and `DiscoverShell` in `<body>`, then\n`DiscoverHero`. The Discover Standard page has the full order.\n\nThen build pages from the patterns, never from scratch. For a dashboard, that means\n`PageHeader` first, then `Toolbar` or `FilterBar`, then `StatTiles`, then content in `Card`,\n`DataTable`, `FormLayout`, `EmptyState` or `DetailPanel`. For discover pages, that means\n`DiscoverSection`, `ResultGrid`, `DetailHero` and `RelatedRail`.\n\n### 3. Check that the shell is there\n\nRun this before you add a page, and in review. An app passes when each line holds:\n\n```bash\n# The shell is a dependency.\ngrep -E '\"@bundu/ui\"' package.json\n# Exactly one layout mounts it.\ngrep -rlE 'AppShell|DiscoverShell' src/layouts\n# Every page uses that layout. List the pages that do not:\ngrep -rL -E 'layouts/(App|Discover)' src/pages --include='*.astro'\n# No page hand-rolls chrome the shell owns.\ngrep -rnE '<(header|nav|footer)[ >]' src/pages\n```\n\nAny page the third command prints, and any match from the fourth, is a page outside the shell.\nMove it into the layout. A shell that is missing entirely means step 2 was skipped, so install\nthe shell before anything else.\n\n### 4. Structure the app's own code: five layers, imports flow down\n\nThese layers are local to one application (docs.mzizi.dev/patterns/architecture). They are not\nthe helix:\n\n| Layer | What it holds |\n| ----- | ------------------------------------------------------------------------------------------------- |\n| L1 | Shared primitives, installed from the registry and never edited in place |\n| L2 | Domain composites built only from L1, the first layer that knows what the app is about |\n| L3 | Page sections that arrange L2. They arrange and do not implement |\n| L4 | Error boundaries and loading states around every L3 section, so one failure never blanks the page |\n| L5 | Routes: data fetching and composition, inside the shell. This is where the network lives |\n\nA component imports from the layer below, never sideways or upward.\n\n### 5. A React or Rust app\n\n- **React (Next.js) adopts through the registry's `.tsx` builds.** AppShell and DiscoverShell\n have `.tsx` siblings that implement the same contract:\n `npx shadcn@latest add https://api.mzizi.dev/v1/ui/app-shell`. Always give the full URL. A bare\n name installs shadcn's own component and reports success.\n- **Rust.** `mzizi-shell` (in `mzizi-roots`) carries N7 parts such as the bottom nav, footer,\n command palette, connectivity bar, theme provider, route guard and deep links. **It has no\n AppShell or DiscoverShell.** Both contracts have `rs: null`. A Rust or Astro-with-Roots app\n takes the shell from `@bundu/ui` and renders Roots components inside it. Do not write a Rust\n shell in an app. See the gaps below.\n\n### Gaps: report them upstream, do not paper over them\n\nThese were found in the registry at v4.18.0 and are tracked in mzizi-registry#589:\n\n- AppShell and DiscoverShell have no Rust build (`rs: null`), and `mzizi-shell` documents that\n `mzizi-header` and `mzizi-sidebar` wait on N2 ports.\n- No shell contract exists for marketing sites (`site/` has no `site-shell`). The N6 doctrine\n names a \"Site Standard\", but docs.mzizi.dev has no page for it.\n\nWhen you hit one of these, open or comment on the registry issue instead of building a local\nshell.\n\n## System architecture\n\nWhat runs where, and who owns what. **Decided** and **built** are different words. Say which one\napplies.\n\n- **Rust first, Astro for the UI.** Astro is the default web UI. Underneath, the code is Rust\n first and TypeScript second, and Mzizi's own surfaces have no third UI framework. Components\n are Mzizi Roots (Rust) where a Rust build exists and the React build otherwise, with React\n deprioritised. No Dioxus, Leptos or Yew in new work. The Roots crates still depend on Dioxus\n until the move to Mzizi's own `render(props)` finishes.\n- **Cloudflare Workers** run the services: `api.mzizi.dev` (`mzizi-dev/mzizi-api`, a Hono\n Worker), `mcp.mzizi.dev` (`mzizi-mcp`) and fundi. Containers and bare metal are decided as\n further targets and are not built. The `mzizi-backend` skill has the patterns.\n- **Four API gateways.** The Mzizi API (`api.mzizi.dev`, Hono, built), the Nyuchi API (B2B), the\n Mukoko API (consumer) and the Nyuchi Servers API (internal). It is decided that the last three\n are Rust. An app reads through its gateway and never opens a database connection of its own\n for a read the gateway serves. Add the endpoint to the gateway instead.\n- **The registry's files are its data layer.** `registry.json`, `components/`, `contracts/` and\n `content/doctrine/` are bundled into the API and the MCP at a pinned registry commit (the\n `x-mzizi-source` header and `catalogue.json` name it). Nothing is read from a database at\n request time. Only the console keeps state, and its move to Cloudflare D1 is decided, not\n built.\n- **One owner per domain.** Platform data lives in domain-separated databases (news,\n engagement, identity, entity, places, …). Each is written only by the service that owns it,\n and other services reference its records by id rather than copying them. Doctrine is data:\n read `mzizi_get_doctrine`, never a copy in a prompt.\n- **Trust is two things.** Assurance levels and attestations come from the one access decision\n in nyuchi-api `gateway/lib/assurance.py`. Read that decision rather than deriving your own,\n and draw them with the `--assurance-*` and `--attestation-*` roles in `mzizi-design`.\n- **Every surface is free by default.** Gate only what acts as a person: WorkOS sign-in, with\n 401 for a missing token and 403 for a token that lacks the scope.\n- **Failures become issues.** N8 signals feed fundi (N9), which files deduplicated GitHub\n issues for people and never merges a fix itself.\n\n## Checklist for a new app or page\n\n- [ ] Helix placement decided, and its node's rules read live.\n- [ ] Shell chosen from the surface table, its contract read, and `@bundu/ui` installed.\n- [ ] One layout mounts the shell, and every page uses it (the check in step 3 prints nothing).\n- [ ] Pages built from the Standard's patterns, with no hand-rolled header, nav or footer.\n- [ ] Tokens and brand overlay from `mzizi-design`. No local colour, radius, spacing or type. Nothing under the spacing floor (14px inset, 7px gaps between controls, labels and items, 1.5 leading); `mzizi check` passes.\n- [ ] Data read through the owning gateway. No new direct database read.\n- [ ] Anything missing or wrong in the registry raised upstream, with any local stopgap marked\n `TODO(mzizi): <PR URL>`.\n\n## See also\n\n- `mzizi-design`: tokens, brand overlays, Apple HIG and where Mzizi overrides it, and the Bundu\n floor (17px body, 14px captions, a 49px hit region, and the spacing floor: 14px inset, 7px gaps, 1.5 leading) with the desktop density layer.\n- `mzizi-roots`: the components, Rust first. \"Starting an app\" covers project setup, and\n \"Contributing a component\" covers adding one upstream.\n- `mzizi-backend`: Workers and MCP servers.\n- `discoverability`: N11 in practice, covering OG images, structured data and agent-readiness.",
|
|
213
213
|
"source": "mzizi-dev/agent-tools/mzizi-skills/skills/mzizi-architecture",
|
|
214
214
|
"files": []
|
|
215
215
|
},
|
|
216
216
|
{
|
|
217
217
|
"name": "discoverability",
|
|
218
218
|
"category": "build",
|
|
219
|
-
"description": "SEO + AIO for the Mzizi ecosystem: how to guarantee machines can find and render a site — social/messaging link previews (OpenGraph), search engines, and AI agents. Covers the 'if the machine can't see it, it doesn't exist' convention, the brand-agnostic Mzizi hive OG-image template (dynamic per page: title + description + site icon), build-time OG images on static Astro (the standard), the rules for a surface already on Next.js (next
|
|
220
|
-
"body_mdx": "# Discoverability — SEO + AIO for the Mzizi ecosystem\n\n> **Covenant:** _If the machine can't see it, it doesn't exist._\n\nDiscoverability is the machine-facing surface of every Mzizi-ecosystem site — the\npart crawlers, social/messaging scrapers, and AI agents read. It is **cross-cutting**,\nnot a component you install: it is a genetic-code **convention** (\"every surface ships\na title, a description, a dynamic preview image, structured data, and an\nagent-readable index\") base-paired to the components that satisfy it (the OG template,\n`llms.txt`, JSON-LD helpers). A surface a machine can't parse may as well not exist —\nand a **missing social preview is the single biggest discoverability no-go**, because\nit's the first thing a human sees when a link is shared.\n\nAll values here are the current doctrine. The source of truth is the repo:\n`mzizi-dev/mzizi-registry` for doctrine and tokens, this package for the skills, served\nread-only at <https://api.mzizi.dev/v1> and by the MCP at `https://mcp.mzizi.dev/mcp`. There is\nno database behind any of it. Brand colour specifically is `palette.canonical.json` in this\npackage: 21 families, generated and gated, never retyped.\n\n## The number-one failure: a missing OG image\n\nThe most common — and most damaging — bug is a link that unfurls with **no preview\nimage at all**. The canonical cause is metadata that points at an image file that\ndoesn't exist:\n\n```ts\n// ❌ WRONG — points at a static file that isn't in /public, so every scraper 404s\nopenGraph: { images: [\"/og-image.png\"] }\ntwitter: { images: [\"/og-image.png\"] }\n```\n\nWorse, an explicit `images` array **overrides** a framework's dynamic OG route, so\nthe good image never gets a chance. **Rule: never hardcode `openGraph.images`.** Let\nthe framework's file-convention / endpoint serve a _generated_ image, and set an\nabsolute base URL so scrapers get an absolute URL.\n\n## The Mzizi \"hive\" OG card\n\nOne template, every route, every brand. The card is:\n\n- **Deep-night ground** (`#1B1A17`) with a faint flat-top **honeycomb hex lattice**\n (white at ~5% — texture, not noise) and a few solid **mineral accent cells**\n (cobalt / gold lead, plus tanzanite / malachite / copper for warmth).\n- The **site icon on top, above the title** (brand recognition first), then the\n title, an optional eyebrow, the SEO description, and the domain.\n- **Left-aligned** (reads better than centred at thumbnail size).\n- **Brand-agnostic:** the icon is a parameter, so the same template serves nyuchi\n (the bee), mukoko (the swarm), mzizi, shamwari — each with its own mark + domain.\n It picks up the _site's own icon_, never a hardcoded one.\n\n### Colours are APCA
|
|
219
|
+
"description": "SEO + AIO for the Mzizi ecosystem: how to guarantee machines can find and render a site — social/messaging link previews (OpenGraph), search engines, and AI agents. Covers the 'if the machine can't see it, it doesn't exist' convention, the brand-agnostic Mzizi hive OG-image template (dynamic per page: title + description + site icon), build-time OG images on static Astro (the standard), the rules for a surface already on Next.js (next 16.3.6 or later, no request input in an ImageResponse), APCA-checked text colours, and a pre-ship checklist. Use when a shared link shows no preview or when adding a new route/site.",
|
|
220
|
+
"body_mdx": "# Discoverability — SEO + AIO for the Mzizi ecosystem\n\n> **Covenant:** _If the machine can't see it, it doesn't exist._\n\nDiscoverability is the machine-facing surface of every Mzizi-ecosystem site — the\npart crawlers, social/messaging scrapers, and AI agents read. It is **cross-cutting**,\nnot a component you install: it is a genetic-code **convention** (\"every surface ships\na title, a description, a dynamic preview image, structured data, and an\nagent-readable index\") base-paired to the components that satisfy it (the OG template,\n`llms.txt`, JSON-LD helpers). A surface a machine can't parse may as well not exist —\nand a **missing social preview is the single biggest discoverability no-go**, because\nit's the first thing a human sees when a link is shared.\n\nAll values here are the current doctrine. The source of truth is the repo:\n`mzizi-dev/mzizi-registry` for doctrine and tokens, this package for the skills, served\nread-only at <https://api.mzizi.dev/v1> and by the MCP at `https://mcp.mzizi.dev/mcp`. There is\nno database behind any of it. Brand colour specifically is `palette.canonical.json` in this\npackage: 21 families, generated and gated, never retyped.\n\n## The number-one failure: a missing OG image\n\nThe most common — and most damaging — bug is a link that unfurls with **no preview\nimage at all**. The canonical cause is metadata that points at an image file that\ndoesn't exist:\n\n```ts\n// ❌ WRONG — points at a static file that isn't in /public, so every scraper 404s\nopenGraph: { images: [\"/og-image.png\"] }\ntwitter: { images: [\"/og-image.png\"] }\n```\n\nWorse, an explicit `images` array **overrides** a framework's dynamic OG route, so\nthe good image never gets a chance. **Rule: never hardcode `openGraph.images`.** Let\nthe framework's file-convention / endpoint serve a _generated_ image, and set an\nabsolute base URL so scrapers get an absolute URL.\n\n## The Mzizi \"hive\" OG card\n\nOne template, every route, every brand. The card is:\n\n- **Deep-night ground** (`#1B1A17`) with a faint flat-top **honeycomb hex lattice**\n (white at ~5% — texture, not noise) and a few solid **mineral accent cells**\n (cobalt / gold lead, plus tanzanite / malachite / copper for warmth).\n- The **site icon on top, above the title** (brand recognition first), then the\n title, an optional eyebrow, the SEO description, and the domain.\n- **Left-aligned** (reads better than centred at thumbnail size).\n- **Brand-agnostic:** the icon is a parameter, so the same template serves nyuchi\n (the bee), mukoko (the swarm), mzizi, shamwari — each with its own mark + domain.\n It picks up the _site's own icon_, never a hardcoded one.\n\n### Colours are checked twice: WCAG ratio and APCA\n\nOG text is read at thumbnail size, so contrast matters. The design system's floors are WCAG 2\n**7:1 for body text and 4.5:1 for non-body text** (owner, 2026-11-02; the Bundu floor), and\nthe APCA bars beside them (**Lc 75 large / Lc 90 body**). Verified against the `#1B1A17`\nground (APCA reverse polarity):\n\n| Role | Hex | WCAG ratio | APCA Lc | Verdict |\n| -------------------------------- | --------- | ---------- | ------- | ------- |\n| Title | `#F5F5F4` | 15.95:1 | 92 | ✓ body |\n| Secondary (eyebrow, description) | `#E5E3DE` | 13.57:1 | 81 | ✓ large |\n| Domain (gold) | `#FFD740` | 12.47:1 | 76 | ✓ large |\n\nDo **not** use dim greys like `#B2AFA8` (only Lc 50, though it measures 7.95:1 and passes WCAG 7:1, so\nthe ratio alone is not enough) for OG text: it looks fine on a monitor and vanishes in a\nmessaging thumbnail. Re-run both checks when changing any pair\n(`mzizi_check_accessibility` gives the WCAG ratio; APCA needs its own tool).\n\nThe Bundu spacing floor (inset, gaps and leading minimums; the `mzizi-design` skill has the\nnumbers) applies to the pages you build around these tags, not to the image.\n\n## Implementation — Astro, static (the standard)\n\nNew surfaces are static Astro: mzizi.dev (`mzizi-dev/mzizi-site`) is one. A statically built\nsite generates its OG images **at build time**:\n\n1. A build-time image endpoint, `src/pages/og/[...slug].png.ts`, with `getStaticPaths()` over\n the site's pages or content collection. Render each page's title and description with\n **satori** to SVG, then rasterise with **sharp** to PNG.\n2. In the shared layout's `<head>`, set\n `<meta property=\"og:image\" content={new URL(`/og/${slug}.png`, Astro.site)} />` per page,\n plus `twitter:card` = `summary_large_image`. Set `site` in `astro.config.mjs` so every URL is\n absolute.\n3. Use the hive design: honeycomb, the site's icon, and the brand's colour (for mzizi.dev,\n hematite, a Heritage tone; see the constellation in `mzizi-design`).\n\nKeep the site's runtime dependency-light; satori and sharp are build-only. If the site renders\nMzizi Roots components, they arrive as static HTML from `dioxus-ssr` and change nothing here.\n\n**Docs on Mintlify** (docs.mzizi.dev) get their preview images from Mintlify. Check them with a\nscraper like any other surface; do not hand-wire `og:image` there.\n\n## A surface already on Next.js (App Router)\n\nDo not start a new surface on Next.js; the standard is the static Astro build above. There is\nno Next.js reference template to copy any more: `mzizi-registry` is not a Next.js app, and its\n`next/og` template (`lib/og/`) and OG routes were removed on 2026-10-02 over a critical RCE\nexposure (GHSA-vcvr-r3jv-pc5j, `next` >= 16.2.0 < 16.3.6): a route put the URL slug into a\nNode `ImageResponse`. Build the hive card from the design above. For a surface that is already\non Next.js:\n\n- **Require `next` >= 16.3.6** (the patched line). Check the lockfile, not only the range.\n- **Never put request input into an `ImageResponse`.** No route param, slug, search param,\n header or cookie goes into the image, as text or as a path. Look the slug up in the site's\n own content and render only that entry's own title and description; an unknown slug is a\n 404 (`notFound()`), never a fallback that echoes it.\n- **Generate the images at build time.** Give each dynamic segment's `opengraph-image.tsx`\n `generateStaticParams()` over the site's own content and `export const dynamicParams = false`,\n so no OG image renders per request and an unknown path is a 404.\n- **Set `metadataBase` and never `openGraph.images` / `twitter.images`** in the layout's\n `metadata`. The `opengraph-image.tsx` file convention supplies an absolute, hashed URL, and\n the Twitter card falls back to it.\n\n## Beyond the image — the rest of the convention\n\n- **Title + meta description** on every route (unique, page-specific).\n- **Structured data**: schema.org JSON-LD for the page's primary entity.\n- **`llms.txt`** at the site root — the machine-readable index for LLMs.\n- **`robots.txt` + `sitemap.xml`** — generated, not hand-maintained.\n- **Canonical URLs** absolute (a set `metadataBase` / `site`).\n- **Agent-readiness**: an MCP discovery card at `/.well-known/mcp.json` pointing at the MCP\n endpoint (mzizi.dev ships one for `https://mcp.mzizi.dev/mcp`).\n\n## Pre-ship checklist\n\n- [ ] Sharing the URL in Slack / WhatsApp / iMessage / X renders a preview **image**.\n- [ ] The OG image is **generated** (dynamic), carries the page's title + description + the site icon, and its URL is **absolute**.\n- [ ] No hardcoded `openGraph.images` / `twitter.images`.\n- [ ] OG text colours pass WCAG (≥ 7:1 body, ≥ 4.5:1 non-body) and APCA (Lc ≥ 75 large / ≥ 90 body).\n- [ ] `metadataBase` / `site` is set; canonical URL is absolute.\n- [ ] Title + meta description are unique per route.\n- [ ] `llms.txt`, `robots.txt`, `sitemap.xml` present; JSON-LD on primary entities.\n- [ ] Validate the card in a scraper (e.g. opengraph.xyz) after deploy.\n\n_The whole helix is Mzizi — and discoverability is the convention that makes each\nsurface legible to the machines that carry it. Ndiri nekuti tiri._",
|
|
221
221
|
"source": "mzizi-dev/agent-tools/mzizi-skills/skills/discoverability",
|
|
222
222
|
"files": []
|
|
223
223
|
},
|
|
224
224
|
{
|
|
225
225
|
"name": "digital-hygiene",
|
|
226
226
|
"category": "dev",
|
|
227
|
-
"description": "
|
|
227
|
+
"description": "Use this skill whenever an agent clones a repository, adds a git worktree, fans out sub-agents, builds or tests (pnpm, npm, vitest, cargo, Astro, wrangler), reviews a PR, or finishes a task. It keeps agent hosts and cloud sessions from filling their disks: check free disk with `df -h /` before starting, refuse to fan out below 50 GB on a shared host, and in a Claude Code cloud session treat df's Avail as the fixed allowance. Work only in a folder unique to you (a clone or git worktree with an owner marker), never touch a folder you did not create, share build caches, never commit build output. On close, audit uncommitted changes, stashes and local branches with no commit on any remote (a squash merge leaves commits local-only), then delete only your own clone. Ships hygiene.sh; every deleting command is a dry run unless given --apply. Never rm -rf an unchecked variable, never touch ~/GitHub, claude-* temp, browser profiles or another agent's folders, never delete staging.",
|
|
228
228
|
"body_mdx": "# Digital hygiene: leave the machine as you found it\n\n> **Covenant:** _A clone is borrowed space. Give it back when the work is done._\n\nOn 2026-10-05 an agent host ran out of disk. Its temp directory held **217 GB** in about\n**930 leftover agent clones**: roughly 92 GB of `node_modules` and 14 GB of Rust `target/`.\nNo agent had removed its clone when its work closed. When the disk filled, every agent on the\nmachine stopped mid-task, including the ones doing nothing wrong. Hygiene is not tidiness. It\nis what lets the next agent run at all, and Ubuntu applies here: what one agent leaves behind,\nevery other agent pays for.\n\nOn 2026-10-06 the other failure happened. Several agents on one release task cloned into one\nshared, readable folder, `$TMPDIR/rel1006`, and one of them ran `git checkout` in the others'\nclones. Their working trees moved under them mid-task. A readable folder name says what the task\nis; it does not say whose clone it is. So every clone now gets a folder that is unique to the\nagent that made it, and the script refuses to touch a clone another agent owns.\n\nOn 2026-10-07, in a Claude Code cloud session with a fixed disk allowance of about 38 GB, the\ngaps were different. The 50 GB rule could never be met, so agents either ignored it or stopped.\n`$TMPDIR` was unset, so a documented `\"$TMPDIR/<task>-…\"` meant `/<task>-…` at the filesystem\nroot. `/tmp` held 4.8 GB of litter that no clone audit sees: about 600 folders of Vitest module\ncopies that test runs never removed, a 472 MB stray clone and a 572 MB Node compile cache. A\nshared `CARGO_TARGET_DIR` served a stale test binary, so 38 tests ran instead of 42 and the run\nwas a false green. Code reviews fetched PR heads as `pr<N>` branches into shared clones, and the\nsession's stop hook then flagged already-merged work as unpushed. This skill now covers cloud\nsessions, worktrees, tool temp litter, the shared-target hazard and scratch review branches.\n\nThis skill has four parts: what to do **before** you start, **while** you work, **on close**,\nand in the **periodic sweep**. The safety rules apply to all of them. The script `hygiene.sh`\nin this skill folder runs the checks, the audit and the prune for you.\n\n## Get the script\n\n`hygiene.sh` is portable bash (3.2+) for macOS and Linux. Every command that deletes (`prune`,\n`tmp`, `close`, `sweep`) is a **dry run** unless you pass `--apply`; `close` and `sweep` still\nfetch first.\n`clone` and `worktree` always write: they make a clone or a worktree. `check` and `audit` only\nread.\n\n| Where you are | Path |\n| ------------------------------- | --------------------------------------------------------------------------------------------------------------------- |\n| `@nyuchi/mzizi-skills` from npm | `node_modules/@nyuchi/mzizi-skills/skills/digital-hygiene/hygiene.sh` |\n| The public `mzizi` plugin | `skills/digital-hygiene/hygiene.sh` in the plugin |\n| Only the MCP | `curl -fsSL https://unpkg.com/@nyuchi/mzizi-skills/skills/digital-hygiene/hygiene.sh -o \"${TMPDIR:-/tmp}/hygiene.sh\"` |\n\n```bash\nH=\"${TMPDIR:-/tmp}/hygiene.sh\" # or one of the paths above\nexport HYGIENE_AGENT_ID=\"$(od -An -N3 -tx1 /dev/urandom | tr -d ' \\n')\" # once per agent\nexport HYGIENE_ROOT=\"${TMPDIR:-/tmp}\" # name the root: --apply refuses a shared fallback root\nbash \"$H\" check # free disk; exits 1 below 50 GB (cloud: 25% of the allowance)\nD=\"$(bash \"$H\" clone <org>/<repo> --task <task>)\" # ${TMPDIR:-/tmp}/<task>-<repo>-<agent-id>\nW=\"$(bash \"$H\" worktree <clone> --task <task> --branch <b>)\" # or a worktree: …-<agent-id>-wt-<b>\n# (of your own marked clone; of a session's unmarked base clone with --shared)\nbash \"$H\" audit # every clone and worktree: CLEAN, KEEP or ACTIVE, with its owner\nbash \"$H\" prune # regenerable build folders it would remove\nbash \"$H\" tmp # orphaned tool temp folders in ${TMPDIR:-/tmp} it would remove\nbash \"$H\" close \"$D\" --apply # close your own clone (or worktree: git worktree remove)\nbash \"$H\" sweep --exclude \"$D\" # prune + tmp + delete clean idle clones (dry run)\n```\n\nOptions: `--root DIR` (default `$HYGIENE_ROOT`, else `$TMPDIR`, else `/tmp`; a deleting command\nrefuses that fallback unless only you can write in it, so name it), `--tmp DIR` (for\n`tmp`; default `$TMPDIR`, else `/tmp`), `--agent ID` (default `$HYGIENE_AGENT_ID`), `--task NAME`\nand `--branch B` (for `clone` and `worktree`), `--from REF` (where a new worktree branch or a\ndetached worktree starts), `--force-new` and `--shared` (see \"Worktrees\"), `--older-than MIN`\n(default 120),\n`--min-gb N` or `--min-pct P` (for `check`), `--exclude PATH` (repeatable), `--force-owner`,\n`--apply`. `clone` and `worktree` create a missing root with `mkdir -p` (the root only, never a\ntask folder), after the same checks as an existing root. PR lookups use `gh api` only, so Claude\nCode's built-in `gh` in a cloud session works; `HYGIENE_GH=none` turns them off. Exit codes: `0`\nok, `1` low disk, a refused path or a clone another agent owns (and from `close`, a clone left in\nplace only because a worktree of it, or of a clone inside it, is still checked out: close that\nworktree first), `2` a clone was kept because it holds unpublished work.\n\n**`$TMPDIR` can be unset**, as it is in a Claude Code cloud session. Then `\"$TMPDIR/x\"` is\n`/x`, at the filesystem root. Every command in this skill writes `\"${TMPDIR:-/tmp}\"`; write it\nthat way in your own commands too. `hygiene.sh` falls back to `/tmp` by itself, and `check`\nwarns when `$TMPDIR` is unset.\n\n## Cloud sessions (Claude Code on the web)\n\nA cloud session runs on a VM that Anthropic hosts, one per session, and the VM goes away when\nthe session ends. Nothing you leave there outlives the session. The risk is different: you fill\n**your own allowance** part-way through the work, and every agent in the session stops.\n\n- **How to tell.** Claude Code sets `CLAUDE_CODE_REMOTE=true` in a cloud session, and it is never\n `true` locally ([environment variables](https://code.claude.com/docs/en/env-vars)). The\n `CLAUDE_CODE_REMOTE_SESSION_ID` variable names the session. A Remote Control session is a local\n session that you steer from another device: it runs on your machine and follows the local\n rules.\n\n ```bash\n [ \"${CLAUDE_CODE_REMOTE:-}\" = true ] && echo \"cloud session\"\n ```\n\n- **The disk is a per-session allowance, so `df`'s \"Avail\" is what counts.** The filesystem is\n the host's. On 2026-10-07 `df -h /` showed a size of 252G with 29G used and 8.9G available,\n and the 252G meant nothing to the session. Used plus available, about 38 GB there, is the\n session's allowance. Anthropic documents about 30 GB of disk for hosted environments\n ([resource limits](https://code.claude.com/docs/en/cloud-environments#resource-limits)), so\n measure it; don't assume it.\n- **Thresholds.** The 50 GB rule can never be met in a cloud session. When\n `CLAUDE_CODE_REMOTE=true`, `hygiene.sh check` uses **25% of used + available** by default\n (about 9.5 GB of a 38 GB allowance), or the `--min-gb N` the session sets. It prints the\n allowance and how many heavy builds fit.\n- **Fan-out.** Allow **10 GB free for each concurrent heavy build**, such as a pnpm install\n with a build or test run, or a cargo build, **on top of the threshold**: the threshold is the\n floor the session keeps free, so builds may not spend it. `check` prints the room as\n (free − threshold) / 10 GB, rounded down. With 22 GB free over a 9.5 GB threshold, run one\n heavy build at a time; with room for 0, sweep first or run nothing heavy beside what runs.\n Agents that only read, review or write docs need no build budget. Check again before each\n wave of sub-agents, not only at the start.\n- **Where clones and worktrees go.** The session's repositories are usually cloned already (for\n example under `/workspace`). Make a `git worktree` of that clone per branch instead of a\n second clone: it shares the object store, so a worktree costs only its checkout. Keep every\n worktree, and any extra clone, under one root next to the clones and on the same disk:\n `export HYGIENE_ROOT=/workspace/wt`. Then `hygiene.sh worktree`, `clone`, `audit` and\n `sweep` all use it. The session's base clone has no owner marker, so add worktrees to it with\n `hygiene.sh worktree /workspace/<repo> --shared …`. The folder need not exist: `worktree` and `clone` create the root itself\n (`mkdir -p`, the root only), and `audit`, `prune` and `sweep` report a missing root as an\n error. By hand, `mkdir -p \"$HYGIENE_ROOT\"` once. Don't put clones in `/tmp`. Tools litter `/tmp`, and Claude Code keeps the\n session's own scratch there (`/tmp/claude-*`), which you must never touch.\n- **Give space back as you go.** Once your PR is open and CI is green, delete that worktree's\n `node_modules` (`hygiene.sh prune`, or by hand), which a reinstall regenerates. Run\n `hygiene.sh tmp` after test runs. If the allowance runs out anyway, free what you created\n first, then commit and push, and report that the session's disk is spent. A new session\n starts on a fresh machine.\n\n## Before you start\n\n1. **Check the disk.** Run `df -h /`, or `hygiene.sh check`. **On a shared host, do not fan out\n below 50 GB free.** In a cloud session, `check`'s threshold is 25% of the session's\n allowance, and you need 10 GB free above it per concurrent heavy build (see \"Cloud sessions\"). Below\n the threshold, don't spawn sub-agents and don't start parallel clones or builds. Run a sweep\n first (see below), or report the low disk to your supervisor. A fan-out on a nearly full\n disk fails all at once, part-way through every branch.\n2. **Clone only into a folder unique to you: `${TMPDIR:-/tmp}/<task>-<repo>-<short-id>`**, or a\n worktree (see \"Worktrees\" below). The\n short id is unique per agent, for example six random hex characters\n (`od -An -N3 -tx1 /dev/urandom | tr -d ' \\n'`). Set it once as `HYGIENE_AGENT_ID` and use\n it for every clone you make. Never clone into `~/GitHub`, which holds the owner's working\n copies. `hygiene.sh clone` does all of this: it creates the folder (and refuses one that\n already exists), clones into it, writes your agent id into `<clone>/.git/hygiene-owner` (inside\n `.git`, so `git clean -fdx` cannot remove it), and prints the path.\n\n ```bash\n D=\"$(bash \"$H\" clone <org>/<repo> --task <task-name>)\" # or by hand:\n D=\"${TMPDIR:-/tmp}/<task-name>-<repo>-$HYGIENE_AGENT_ID\"\n mkdir \"$D\" && git clone https://github.com/<org>/<repo>.git \"$D\" # mkdir, not mkdir -p\n printf '%s\\n' \"$HYGIENE_AGENT_ID\" >\"$D/.git/hygiene-owner\" # the marker: never skip it\n ```\n\n A clone with no marker has no owner the script can see, so another agent's sweep may\n delete it once it is clean and idle. A `git worktree` of your clone is owned by your clone's\n marker, unless it has its own marker in its git dir (`hygiene.sh worktree` writes one).\n - **Never cd into, check out, fetch into or modify a folder you did not create,** however\n its name reads and whoever's task it seems to be. A shared parent folder is fine\n (`${TMPDIR:-/tmp}/rel1006/` holding one clone per agent); a shared clone is not. If you need\n another agent's work, fetch its pushed branch into your own clone.\n - `mkdir` without `-p` is the claim: it fails when the folder exists, so you notice instead\n of moving into someone else's clone.\n\n3. **Share build caches; don't multiply them.** Each clone with its own caches costs gigabytes\n in every clone.\n - **Rust:** one `CARGO_TARGET_DIR` per machine or session, for example\n `export CARGO_TARGET_DIR=\"${TMPDIR:-/tmp}/cargo-target\"`. Every clone then reuses one\n compiled dependency tree instead of building its own `target/`.\n\n **The hazard: a shared target dir can run a stale test binary.** Cargo names a workspace\n crate's artifacts with a hash that leaves out where the workspace is on disk, so worktrees\n of one repository on different branches build to the same files. Its freshness check\n compares file times, so a binary that another worktree built from other source can look up\n to date. On 2026-10-07 an agent saw 38 tests run\n instead of 42, a false green, until `cargo clean -p <crate>`. So:\n - **Before the final verification run** of a crate you changed, run\n `cargo clean -p <crate>` (it removes only that crate's artifacts, and the dependency tree\n stays shared), then build and test. Check that the test count matches the tests you\n expect.\n - Or give a crate you edit its own target for the task:\n `CARGO_TARGET_DIR=\"$PWD/target\" cargo test -p <crate>`. Use the shared target for\n dependencies and for crates you only read. Prune the private `target/` when you close.\n\n - **pnpm:** the content-addressed store is shared by default. Keep it that way: never pass\n `--store-dir` into the clone, and don't use `node-linker=hoisted` copies when links will\n do.\n - **npm:** the cache (`~/.npm`) is shared. Use `npm ci --prefer-offline` and don't point\n `--cache` into the clone.\n - Install only what the task needs. `pnpm install --filter <pkg>...` installs one workspace\n package; a whole monorepo install for a docs fix is waste.\n\n### Worktrees: a first-class alternative to a clone\n\nA `git worktree` is a second checkout of a clone you already have. It shares the clone's object\nstore, so it costs only the checked-out files, and it is the right default when the repository\nis already on disk (a cloud session's `/workspace/<repo>`, or your own clone). The rules for\nclones apply to worktrees, plus three of their own:\n\n1. **Make it with an owner marker.** `hygiene.sh worktree <clone> --task <task> --branch <b>`\n claims `$HYGIENE_ROOT/<task>-<repo>-<agent-id>-wt-<b>` (`mkdir`, as for a clone; `/` in the\n branch becomes `-`, and a detached worktree ends in `-wt-<short sha>`). The `-wt-` suffix keeps\n it apart from your own clone for the same task. It adds the worktree, writes your agent id to\n its own git dir (`<clone>/.git/worktrees/<name>/hygiene-owner`), and prints the path.\n - `--branch <b>` checks out the local `<b>` when it exists. Otherwise, when `origin/<b>`\n exists, it creates `<b>` from `origin/<b>` and tracks it. Otherwise it creates `<b>` from\n `--from REF` (default: the clone's HEAD). Without `--branch` the worktree is detached at\n `--from` (or HEAD).\n - `--from` is never ignored. With a local `<b>`, the script refuses it, so drop `--from` or\n pick a new branch. With `origin/<b>` at another commit, it refuses too, unless you pass\n `--force-new`, which starts a new `<b>` from `--from` that tracks nothing.\n - Beside the marker it records the branch it created (close deletes it once it is\n published) and the commit the worktree started from (`hygiene-from`, marked `remote` when\n that commit was on the remote at that moment, for example a PR head just fetched into\n `FETCH_HEAD`).\n - If `git worktree add` fails part-way, or the marker cannot be written, the script runs\n `git worktree remove --force`, removes the folder and deletes the branch it created, then\n exits 1. It never leaves a registered worktree without a marker.\n\n By hand:\n\n ```bash\n W=\"$HYGIENE_ROOT/<task>-<repo>-$HYGIENE_AGENT_ID-wt-<branch>\"\n mkdir \"$W\" && git -C <clone> worktree add -b <branch> \"$W\" origin/staging\n printf '%s\\n' \"$HYGIENE_AGENT_ID\" >\"$(git -C \"$W\" rev-parse --absolute-git-dir)/hygiene-owner\"\n ```\n\n2. **Only from a clone that is yours or the session's.** Adding a worktree writes into the\n clone's `.git`, and closing it later runs `git worktree remove` and `git branch -D` there, so\n the clone passes the same checks as anything the script deletes under. The script refuses a\n clone under `~/GitHub` or on an `--exclude` path, a clone whose git data is there (it resolves\n the real git dir first), a clone whose marker names another agent, and a clone with no marker\n at all. A session's unmarked base clone (`/workspace/<repo>`) is\n shared by the session's agents: pass `--shared` to add worktrees to it, but never check out,\n commit in or fetch PR heads into the base checkout itself. `--shared` never lifts the other\n refusals.\n3. **Close it with `git worktree remove`, never `rm -rf`.** `rm -rf` leaves the worktree's\n entry under `<clone>/.git/worktrees/`, and its branch stays \"checked out\" there until\n `git worktree prune`. `hygiene.sh close <worktree> --apply` audits only what the worktree holds,\n which is its uncommitted changes and its checked-out branch. Stashes and the clone's other\n branches stay with the clone. If the audit is clean, the script runs\n `git worktree remove` and then deletes the branch it created. By hand:\n `git -C <clone> worktree remove <path>`, and `git -C <clone> worktree prune` after an\n accidental `rm`. A clone that still has worktrees checked out is `KEEP` in an audit, because\n deleting it would cut them off from their git data. `close` leaves it in place and exits `1`,\n not `2`, since nothing in it is unpublished. It names each worktree holding the clone, with\n its owner and whether it is locked: close your own worktrees first, and report one that is\n locked or another agent's instead of unlocking or removing it. A worktree that\n `git worktree remove` would refuse without `--force` (locked, holding a submodule, or with\n modified or untracked files) is never removed, and the dry run says so too.\n\n Before every git write (the fetch, `git worktree add` and `remove`, `git branch -D`) the\n script resolves the checkout's real git dir and common git dir and refuses `/`, your home and\n anything under `~/GitHub`. A worktree of a clone under `~/GitHub`, or a `--separate-git-dir`\n checkout whose git dir is there, is never fetched into, removed or deleted. An `--exclude`\n path also refuses writes to a clone's refs and config (the fetch, `git branch -D`), but not\n to a worktree's own entry under `<clone>/.git/worktrees/`: `sweep --exclude <your clone>`\n still removes that clone's finished worktrees, and keeps the branches they were on.\n\n## While you work\n\n- **Don't commit build output.** `node_modules/`, `target/`, `dist/`, `.astro/`, `.wrangler/`,\n `.turbo/`, `.next/` and `.vercel/output/` belong in `.gitignore`. If one shows up in\n `git status`, ignore it; don't add it.\n- **Delete what you made and no longer need.** That includes scratch builds, downloaded\n tarballs and one-off test clones, as soon as they have served their purpose.\n- **Check the disk again before anything large:** a release build, a full test matrix, or a\n second fan-out.\n- **Before every commit, check where you are.** Run `git status` and\n `git branch --show-current`, and confirm HEAD sits on your branch's upstream:\n\n ```bash\n git status && git branch --show-current\n git rev-parse --abbrev-ref '@{u}' # the upstream you expect, e.g. origin/feat/x\n git log --oneline '@{u}..HEAD' # only your own unpushed commits\n git log --oneline 'HEAD..@{u}' # empty, or pull first\n ```\n\n A branch you did not check out, a different upstream, or commits you did not write mean\n something else moved this clone. Stop and report it; don't commit on top.\n\n- **Review a PR in a throwaway worktree, never as a branch in a shared clone.** For a code\n review, `git fetch origin pull/<N>/head:pr<N>` in a shared clone leaves a `pr<N>` branch\n whose commits are on no remote branch. Stop hooks and audits then flag it as unpushed work\n long after the PR merged. Fetch the head without a branch, and check it out detached in a\n worktree of your own:\n\n ```bash\n git -C <clone> fetch origin \"pull/<N>/head\"\n R=\"$(bash \"$H\" worktree <clone> --task review-pr<N> --from FETCH_HEAD)\" # detached\n # ... review, build, test ...\n bash \"$H\" close \"$R\" --apply # git worktree remove\n ```\n\n `close` fetches first, which overwrites `FETCH_HEAD`, and each worktree keeps its own\n `FETCH_HEAD` in its git dir. So the script judges a review worktree by the commit it recorded\n at creation: published when it was fetched from the remote, or when its PR is merged or closed\n (a squash-merged or fork PR's head is on no remote branch), or while it is an open PR's head.\n\n `hygiene.sh audit` lists a `pr<N>` scratch branch (also `pr-<N>`, `pr/<N>` and\n `review/pr<N>`) as **safe to delete** when PR #N is merged or closed and the branch has nothing\n beyond the PR's head. It prints the `git branch -D` to run. A scratch branch with commits beyond\n the head is unpublished work, and the audit keeps it.\n\n- **Test runners leave temp folders.** Vitest 5.0.0 to 5.0.2 leave one folder per run in\n `${TMPDIR:-/tmp}`, and so does Vitest 4.x as Vite+ (`vite-plus`) runs it. It has a random 21-character name, holds only `client/` and `ssr/` module\n copies, and is 25 to 57 MB per run. Use Vitest 5.0.3 or later (Vite+ 1.1.0 or later), and run\n `hygiene.sh tmp` after a session of test runs: it finds the folders by their contents, whatever\n the version.\n\n## On close: when the PR is merged or abandoned\n\nClosing a clone is an audit first and a deletion second. **Never delete a clone you have not\naudited.** The audit asks one question: is there any work here that exists nowhere else?\n\n1. **Fetch**, so you judge against the remote as it is now: `git fetch --prune origin`. Even\n then, a remote-tracking ref is the clone's memory of the remote, not the remote: it outlives\n a branch deleted upstream whenever a fetch fails or never ran. Ask the remote itself\n (`git ls-remote origin`); a remote you cannot reach vouches for nothing.\n2. **Uncommitted changes:** `git status --porcelain --untracked-files=all --ignore-submodules=none`.\n Anything listed exists only here. Pass both options: `status.showUntrackedFiles=no` would hide\n untracked files, and `submodule.<name>.ignore` would hide a submodule moved off its commit.\n Status cannot see two more kinds of work: an edit to a file marked `--assume-unchanged` or\n `--skip-worktree` (`git ls-files -v`: a lowercase tag, or `S`), and **ignored files**\n (`git status --ignored`). An ignored `.env` or a folder of notes kept out of git is work;\n only build output (`node_modules`, `target`, `dist` and the like) is not.\n3. **Stashes:** `git stash list`. A stash is never on a remote.\n4. **Local refs whose commits are on no remote** — branches, and also tags, notes and\n remote-tracking refs (one whose branch was deleted upstream may be the only copy):\n\n ```bash\n git ls-remote origin | cut -f1 | sort -u | sed 's/^/^/' >/tmp/on-remote # what origin has now\n git for-each-ref --format='%(refname)' | while read -r r; do\n n=$(git rev-list --count --stdin \"$r\" </tmp/on-remote 2>/dev/null) || n=?\n [ \"$n\" = 0 ] || echo \"$r: $n commit(s) on no remote\"\n done\n ```\n\n (A commit the remote has that you have not fetched is not named here; `--stdin` skips\n nothing, so fetch first. A commit in `FETCH_HEAD` counts only when it came from the\n remote's own URL: `git fetch . <branch>` writes one too.)\n\n5. **Map each of those branches to its PR state.** Merged or closed counts as published:\n\n ```bash\n gh pr list -R <owner>/<repo> --head <branch> --state all --json number,state,title\n ```\n\n `hygiene.sh` asks `gh api repos/<owner>/<repo>/pulls?head=<owner>:<branch>`. That never finds\n a PR from a fork, so it then asks for the PRs associated with the branch's tip commit\n (`commits/<sha>/pulls`), and then scans the latest 100 PRs for one whose head ref is the\n branch and whose head commit is the tip or behind it. A tip that is an open PR's head is\n pushed, even when no remote-tracking ref has it.\n\n **A squash merge makes \"not on any remote\" meaningless as proof.** Squashing writes a new\n commit on the base branch, so the branch's own commits stay local-only even though the\n work shipped. Then the remote branch is deleted, and every squash-merged branch looks\n unpublished. So check in this order:\n - Is every commit on a remote? Then it is published.\n - Is there a PR from this branch that is `MERGED` or `CLOSED`, **and is the branch's tip that\n PR's head, or behind it**? Then it is published (merged, or deliberately abandoned). A\n commit made after the PR's head is not in the PR, whatever its state.\n - Is every unpublished commit already on a remote as **the same change**\n (`git patch-id --stable`, as `git cherry` compares; a rebase or a one-commit squash keeps\n the change)? Then it is published. A matching commit subject proves nothing: two\n different changes can carry one subject.\n - Otherwise it is **unpublished**. This includes an `OPEN` PR whose branch is behind your\n local commits.\n\n6. **Publish or report anything genuinely unpublished.** Push the branch and open a PR (into\n `staging` where the repo has one), or report it to your supervisor with the branch name,\n the commit subjects and why it was not published. Don't delete work to make a clone\n \"clean\".\n7. **Delete the clone, and only your own.** Delete it only after steps 1 to 6 come back\n clean, and then report what was freed. Never delete another agent's folder, even an empty\n one, even in a shared parent.\n\n`hygiene.sh close <clone> --apply` runs steps 1 to 5 and 7. It refuses, before it fetches or\nreads anything, a clone whose `.git/hygiene-owner` names another agent, or any marked clone when\nyou have given no agent id. A clone nested inside yours,\nsuch as a gitignored cache clone, is deleted with it, so it is audited too: unpublished work\nthere keeps the whole clone. It exits `2`, and deletes\nnothing, when step 6 is needed. A clone kept only because a worktree of it, or of a clone nested\nin it, is still checked out is not step 6: `close` exits `1` and says to close that worktree\nfirst. A submodule belongs to its parent, whatever the shape of its `.git` (a file pointing\ninto the parent's `.git/modules/`, or a folder, when `git submodule add` adopted a repository\nthat was already there). The script asks the parent: git's superproject, or a gitlink or\n`.gitmodules` path for it. It is audited as part of the parent, and `close` refuses it on its\nown.\n\n**Nothing goes with a folder unless everything in it may go.** Before a clone is deleted\n(`rm -rf`) or a worktree removed (`git worktree remove`, which also deletes everything inside\nit), the script finds every `.git` entry inside it, in gitignored folders, `node_modules` and\n`target` too, and gates each checkout the same way. Git has to read it as its own repository,\nits git dir has to be inside the folder, it may not be locked, and its own audit has to be clean\nand idle. A worktree of a clone outside the folder, such as a cache holding a\n`git worktree add` of `~/GitHub/live`, is never deleted with it: `git worktree remove` it\nagainst its own clone first, or report it. Another agent's clone inside it holds it the same\nway (`close` exits `1`: report it). Any refusal keeps the whole folder, with a reason that names\nthe checkout inside.\n\n- **A `.git` git does not read as its own** (a `.git` folder that lost its `HEAD`, a git dir that\n is gone) is kept for a human. Git skips such a folder and answers for the repository around\n it, so the script checks that the git dir git resolves is the checkout's own and that the\n checkout is its top level. `close` audits it and exits `2`, as `sweep` does.\n- **Git dirs not named `.git`** are gated too: a bare repository or mirror (`foo.git/`) and a\n deinit'd submodule's `.git/modules/<name>`. One goes only when it has no stash and every\n commit any of its refs or `HEAD` reaches is named by a remote it can reach (`git ls-remote`),\n never by its own remote-tracking refs. One with no reachable remote and any commit is kept:\n delete it by hand once you know it holds nothing of yours.\n- **A checkout found inside another runs nothing its config names.** A package that ships a\n `.git` in `node_modules`, a cache clone or a bare mirror has config anyone could have written,\n and `git ls-remote`, `git fetch` or even `git status` with it can run a command (an `ext::`\n remote URL, `core.sshCommand`, `remote.<name>.uploadpack`, `core.fsmonitor`, a filter). So the\n script never fetches into one, and asks its remotes only by URL (https or ssh, from an empty\n repository with every other protocol off), alongside what the clone around it was told by its\n own remotes. Any config key outside a short list of inert ones (`core.*` basics,\n `remote.*.url`/`fetch`, `branch.*`, `submodule.*.url`, …) keeps it unread, as `KEEP`\n (\"untrusted git config\"). Every git the script runs has `core.fsmonitor`, hooks, signature\n checks, the `ext`/`fd` transports and lazy fetches switched off, and no git runs on a scanned\n checkout at all until its config (and any `commondir`, which is followed, or refused for a\n bare git dir) has passed; a `core.worktree` pointing outside it or an\n `objects/info/alternates` keeps it. A `commondir` and a `.git` gitfile are\n read exactly as git reads them (the whole file, only a trailing `\\n`/`\\r`\n stripped); one with an embedded newline or CR, a NUL, or a byte outside\n printable ASCII is refused rather than parsed two ways, and a malformed\n `.git` file keeps its folder instead of being skipped. A registered submodule of your own clone is trusted while\n its config is inert.\n- **The dry run counts each byte once.** A sweep measures every checkout once before anything\n goes, and a checkout inside another is not counted again with the folder around it. The total\n is shown in exact KB, and it is what `--apply` frees.\n\n## The periodic sweep\n\nRun a sweep when `check` fails, before a large fan-out, and at the end of a long supervisor\nsession. It has three passes, and the dry run comes first:\n\n1. **Safe prune of regenerable folders.** These are `node_modules`, `target`, `.astro`,\n `.wrangler`, `.turbo` and `.next`, removed **only when older than N minutes** (default 120),\n so an agent mid-build is not hit. A sibling manifest has to prove what each folder is:\n `Cargo.toml` (or the cargo `CACHEDIR.TAG`) for `target`, `package.json` for `.next`,\n `.turbo` and `.astro`, and a `wrangler` config for `.wrangler`. A folder named `target` that\n holds notes is not a build cache. Pruning loses nothing, because the next install or build\n recreates the folder. A folder with a git checkout inside it (a `.git` anywhere in it) is\n skipped, since its checkout is not regenerable.\n2. **Orphaned tool temp folders** (`hygiene.sh tmp`, also run on its own) in `${TMPDIR:-/tmp}`\n (or `--tmp DIR`). A folder there is judged by its contents, not only its name, and only\n folders directly in the temp directory, owned by you and untouched for N minutes, are\n candidates:\n - **Vitest/Vite module copies:** a random name (16 to 32 letters, digits, `_` or `-`) holding\n only `client/` and/or `ssr/`. Each of those holds only files named by a 40-hex hash, and\n the files are Vite SSR module code.\n - **npm pack leftovers:** a `tmp.*` folder (from `mktemp -d`) holding only `*.tgz` files\n and/or an unpacked `package/` with a `package.json`.\n\n It never touches `claude-*` (Claude Code's temp and the session's scratchpad), browser\n profiles (`.org.chromium.*`, `*chrom*`, `puppeteer*`, `playwright*`, `*profile*`), symlinks,\n another user's folders, a folder with a git checkout inside it, or an `--exclude` path. It\n reports `node-compile-cache` (Node's\n shared compile cache) with its size and never removes it; delete it by hand when no `node`\n process runs. A stray clone in `/tmp` is a clone: `hygiene.sh audit --root /tmp` finds it.\n\n3. **Audit report of the clones and worktrees, then delete the clean ones.** Each gets one\n verdict:\n - `CLEAN`: nothing unpublished, idle. Deleted with `--apply`.\n - `KEEP`: unpublished work, with the reason. Never deleted: publish it or report it.\n - `ACTIVE`: a file changed inside the window. Another session is probably using it. Leave\n it.\n - `OTHER`: its `.git/hygiene-owner` names another agent. Not audited, not pruned, not deleted.\n\nThe sweep judges worktrees before clones. Removing a clean worktree frees its clone, which is\nthen judged without it (in a dry run, as if it were gone), so a clone is never reported as kept\nonly because the sweep had not reached its worktree yet. A clone held only by a worktree that is\nstill in use (active, or another agent's) is left in place and reported, but it is not counted\nas kept and does not make the sweep exit `2`. The dry run predicts `--apply` exactly: a worktree\nthat `git worktree remove` would refuse (locked, holding a submodule) is not counted as removed,\nso the clone it holds is not reported as deletable either. A submodule is never listed on its\nown; it goes only with its parent.\n\nA worktree is deleted with `git worktree remove`, never `rm -rf`. Prune, close and sweep leave\na clone or worktree another agent owns alone. A supervisor clearing up after\nagents that have finished passes `--force-owner`, which still deletes only `CLEAN`, idle\nclones and is still a dry run without `--apply`. A clone with no marker (made by hand, or\nbefore markers) is judged as before.\n\n```bash\nbash hygiene.sh sweep --exclude \"${TMPDIR:-/tmp}/<your-task>\" # read the report\nbash hygiene.sh sweep --exclude \"${TMPDIR:-/tmp}/<your-task>\" --apply # then act on it\n```\n\nReport the result: what was freed, how many clones were deleted, and every `KEEP` with its\nreason.\n\n## Safety rules (all of the above)\n\n- **Never `rm -rf` a path built from an unchecked variable.** If `$DIR` is empty or unset,\n `rm -rf \"$DIR/\"*` deletes from `/`. Use fixed base paths and let `find … -prune` produce the\n paths. Check every path before you delete it: it is absolute, it is inside the root, it is not\n the root itself, `/` or `$HOME`, and it is not under `~/GitHub`. Then use\n `rm -rf -- \"$path\"`. `hygiene.sh` routes every deletion through that check.\n- **Never touch `~/GitHub` working copies, `claude-*` temp folders, browser profiles, or another\n agent's folders.** The owner keeps live\n work in `~/GitHub`. The script refuses a root or a clone there. A clone you did not create\n is someone else's until they or your supervisor say otherwise: never cd into it, check it\n out, fetch into it, build in it or delete it. The script refuses a clone whose\n `.git/hygiene-owner` is not yours unless you pass `--force-owner`. Use `--exclude` for your own\n live clone.\n- **Never delete `staging`,** neither the branch on the remote nor a release PR's head branch.\n A clone is deleted locally; branches on the remote are the repository's business, not the\n sweep's.\n- **Delete only under a root you named, and never through a symlink.** In a temp folder other\n users can write in (`/tmp`, and often `$TMPDIR`), a path checked now and deleted later can be\n redirected: another user swaps a folder for a symlink in between. So `prune`, `close` and\n `sweep` with `--apply` refuse the fallback root unless it is a folder only you can write in;\n set `HYGIENE_ROOT` (or pass `--root`) to the folder your clones live in, which in a cloud\n session is `export HYGIENE_ROOT=\"${TMPDIR:-/tmp}\"`. Every deletion checks that each folder\n from the root down is a real folder you own, not a symlink, then deletes from inside the\n parent it entered with `cd -P` (agent-tools#279).\n- **Run nothing a repository's config names.** In a root other users can write in, a checkout\n is trusted only when `clone` or `worktree` marked it (`hygiene-owner`) and the marker, the\n checkout and its git dir are yours and writable by nobody else; anyone can plant a repository\n there, marker included. Trusted or not, no git runs in a checkout until every key in its\n config is inert (no `credential.helper`, `uploadpack`, `sshCommand`, `hooksPath` or filter)\n and its `hooks/` holds nothing but git's `*.sample` files: a checkout that fails is kept for\n a human. The remote calls reset\n `credential.helper` to your own helpers and turn the file transport off; set\n `HYGIENE_FILE_REMOTES=1` only for a local mirror you trust (agent-tools#279).\n- **Dry run first, then `--apply`.** Read what would go before anything goes.\n- **Report what was freed,** in GB, with the number of folders and clones and every clone that\n was kept and why. \"Cleaned up\" is not a report.\n\n## Checklist\n\n- [ ] `df -h /` before starting. On a shared host, 50 GB free or more before any fan-out. In a\n cloud session (`CLAUDE_CODE_REMOTE=true`), `hygiene.sh check` passes, and you have 10 GB\n free above its threshold per concurrent heavy build.\n- [ ] Every path is written `\"${TMPDIR:-/tmp}/…\"`, never a bare `\"$TMPDIR/…\"`.\n- [ ] Cloned into `${TMPDIR:-/tmp}/<task>-<repo>-<short-id>` (`hygiene.sh clone`), or a marked\n worktree (`hygiene.sh worktree`; in a cloud session under `HYGIENE_ROOT`), unique to you;\n never `~/GitHub`, never a folder another agent made.\n- [ ] PR reviews in a throwaway detached worktree, not `pr<N>` branches in a shared clone.\n- [ ] `cargo clean -p <crate>` before the final test run of a crate you changed, when\n `CARGO_TARGET_DIR` is shared.\n- [ ] `git status`, `git branch --show-current` and the upstream checked before every commit.\n- [ ] `CARGO_TARGET_DIR` shared; pnpm store and npm cache not redirected into the clone.\n- [ ] No build output committed.\n- [ ] On close: fetched, then checked the status, the stashes, and the branches on no remote.\n PR state looked up per branch, because of squash merges.\n- [ ] Unpublished work pushed into a PR, or reported.\n- [ ] Your own clone deleted, or your worktree removed with `git worktree remove`, nobody\n else's, and the amount freed reported.\n- [ ] `hygiene.sh tmp` run after test runs; orphaned tool temp folders removed.",
|
|
229
229
|
"source": "mzizi-dev/agent-tools/mzizi-skills/skills/digital-hygiene",
|
|
230
230
|
"files": [
|
|
@@ -240,7 +240,7 @@ export const BUNDLED_SKILLS = [
|
|
|
240
240
|
{
|
|
241
241
|
"name": "progress-report",
|
|
242
242
|
"category": "dev",
|
|
243
|
-
"description": "All dev work runs on a 10-minute progress-report loop. When
|
|
243
|
+
"description": "Use this skill whenever dev work starts, in any repo. All dev work runs on a 10-minute progress-report loop. When work starts, schedule a recurring report every 10 minutes (`/loop 10m` or CronCreate `*/10 * * * *` in Claude Code; the cron dies with the session; without cron, report at each checkpoint). Measure, don't guess: each report has 20-cell progress bars with a percentage and a one-line basis (plan steps done of total, features done, code migrated as lines in the new tree against the old, and an Overall bar labelled estimate when it is not exact) and per workstream open PRs, latest release, staging ahead of main, CI, and a live HTTP check for deployed sites. It ends with one 'Since last report:' line and a final 'Needs you:' line. When nothing changed, report a single line. Ticks are read-only: never publish, release, merge, delete or deploy from a tick unless the owner approved that exact action. No secrets or PII. Ships progress.sh (bar, pct, repo, lines, http).",
|
|
244
244
|
"body_mdx": "# Progress report: measured, every ten minutes\n\n> **Covenant:** _The owner should never have to ask \"where are we?\"_\n\nAll dev work runs on a 10-minute progress-report loop. The owner reads the reports to decide\nwhat to do next, so every number in them is measured, and every report ends with what the\nowner has to do. A report that guesses is worse than none, because it is believed.\n\nThis skill has four parts: **starting** the loop, **what a report holds**, **the rules for a\ntick**, and **stopping**. The script `progress.sh` in this skill folder renders the bars and\nreads the repo state for you.\n\n## Get the script\n\n`progress.sh` is portable bash (3.2+) for macOS and Linux. It is **read-only**: it reads git,\nthe GitHub API (through `gh`) and HTTP status codes, and never writes anything.\n\n| Where you are | Path |\n| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |\n| `@nyuchi/mzizi-skills` from npm | `node_modules/@nyuchi/mzizi-skills/skills/progress-report/progress.sh` |\n| The public `mzizi` plugin | `skills/progress-report/progress.sh` in the plugin |\n| Only the MCP | `curl -fsSL https://unpkg.com/@nyuchi/mzizi-skills/skills/progress-report/progress.sh -o \"${TMPDIR:-/tmp}/progress.sh\"` |\n\n```bash\nP=\"${TMPDIR:-/tmp}/progress.sh\" # or one of the paths above\nbash \"$P\" bar 78 \"Plan steps\" \"7 of 9\" # Plan steps ████████████████░░░░ 78% 7 of 9\nbash \"$P\" pct 7 9 # 78\nbash \"$P\" repo mzizi-dev/agent-tools # open PRs, latest release, staging vs main, CI\nbash \"$P\" -C \"${TMPDIR:-/tmp}/task/app\" lines origin/staging app/ # lines under app/ at that ref\nbash \"$P\" http https://mcp.mzizi.dev/ https://mzizi.dev # status code per deployed site\n```\n\n`lines` counts the committed tree at a ref (`git grep -c ''`, binary files skipped), never the\nworking copy, so two reports on the same commit give the same number. `repo` reads only, every\npage of each list. Its CI line takes the newest run of each GitHub Actions workflow, the\nchecks of other apps, and the commit statuses (deploy integrations often post only a status)\non the default branch's head, and names what failed. A workflow whose newest run was\ncancelled is shown as cancelled (`; 1 cancelled: CI`), neither green nor red, because nothing\nfinished it; read that as \"not verified\", never as passing. `pct` and\n`bar` never round unfinished work up to 100%.\n\n## Starting the loop\n\nWhen dev work starts, schedule the report before you write any code:\n\n- **Claude Code:** `/loop 10m <report prompt>`, or CronCreate with `*/10 * * * *`. The cron\n lives in the session: it dies when the session ends and expires after 7 days, so a long build\n re-arms it in each new session.\n- **Surfaces without cron** (a chat, a CI job, an agent with no scheduler): report at each\n natural checkpoint instead, such as a PR opened, CI finished, a merge, a release, or a\n blocker.\n\nThe report prompt names the work and where its measures come from, for example:\n\n```text\nProgress report for the Toddle rebuild (nyuchi/toddle-enhancement-extension#41).\nBars: plan steps from the checklist in #41; features from the feature table in docs/plan.md;\ncode migrated = lines in app/ vs src/ at origin/staging. Workstreams: the repo, the Chrome Web\nStore listing. Use the progress-report skill. Read-only.\n```\n\nWrite the measures down once, in the tracking issue (see \"Track big work in GitHub issues\").\nThen every tick measures the same thing the same way.\n\n## What a report holds\n\n### 1. Progress bars\n\nEach bar is 20 cells of `█` and `░`, a percentage, and a **one-line basis**: what was counted.\nUse `progress.sh bar` so every bar has the same width and alignment. Typical bars:\n\n| Bar | Basis (measured) |\n| ------------- | ------------------------------------------------------------------------------------ |\n| Plan steps | Steps done of total, from the repo's plan doc or the tracking issue's checklist. |\n| Features done | Features shipped of the features listed in the plan. |\n| Code migrated | Lines in the new tree against the old (`progress.sh lines`), at the same ref. |\n| Overall | How it combines the others. Labelled `(estimate)` and shown as `~N%` when not exact. |\n\nOnly show a bar you can measure. If there is no checklist, say so and don't invent a\ndenominator. A bar never reads 100% for unfinished work: `progress.sh` keeps a cell empty\nuntil the count is complete.\n\n### 2. Per workstream\n\nFor each repo, service or content stream the work touches:\n\n- **Open PRs**, the **latest release or tag**, **staging ahead of main**, and **CI** on the\n default branch: `progress.sh repo <owner/repo>`.\n- **Deployed sites:** a live HTTP check (`progress.sh http <url>…`). Report the status code\n you got; don't call a site live without one.\n- **Content**, when the work includes it (Intercom articles, Sanity documents, store\n listings): the **published** count against the **draft** count, read from the source's API\n or MCP.\n\n### 3. Since last report\n\nOne line: what changed since the previous tick (a PR merged, CI went green, a bar moved from\n68% to 74%).\n\n### 4. Needs you\n\nThe **last line** of every report: `Needs you:` and the owner actions and decisions that block\nthe work, each one specific enough to act on. Write `Needs you: nothing` when nothing does.\n\n### When nothing changed\n\nIf nothing changed since the last tick, the whole report is one line, such as\n`10:40 no change since 10:30 (PR #212 CI still running). Needs you: nothing.`\n\n## Example\n\nThe Toddle rebuild format, partway through (the numbers are illustrative):\n\n```text\nProgress report 14:20 (Toddle rebuild, nyuchi/toddle-enhancement-extension#41)\n\nFeatures rebuilt (UI) ████████████████████ 100% every visible feature is React in app/\nPlan steps ████████████████░░░░ 78% 7 of 9\nCode in app/ ██████████████░░░░░░ 69% 16,090 app/ · 7,266 src/\n───────────────────────────────────────────────\nOverall (estimate) ███████████████░░░░░ ~75% plan steps, weighted by code moved\n\nnyuchi/toddle-enhancement-extension\n open PRs 2 (#58 settings panel, #59 remove src/)\n latest release v1.6.0 (2026-10-05)\n staging vs main 4 ahead, 0 behind\n CI on main green (9 checks)\nChrome Web Store 1.6.0 published, 1.7.0 draft staged\n\nSince last report: #57 merged; code in app/ 64% → 69%.\nNeeds you: approve the 1.7.0 store submission (staged, not submitted).\n```\n\n## Rules for a tick\n\n- **Ticks are read-only by default.** Never publish, release, merge, delete or deploy from a\n report tick unless the owner already approved that specific action. A report observes the\n work; it does not do it. Installing an approved test build is fine when the owner set that\n up.\n- **Measure, don't guess.** Every number comes from a command, an API or a file you read in\n this tick. If a read fails, say `?` and why, rather than reusing an old number.\n- **Privacy.** No secrets, tokens or credential status, and no personal information (names of\n members of the public, emails, phone numbers) in a report. Name a secret by its name, never\n its value.\n- **Short.** A report is read on a phone between other things. Bars, workstreams, one \"Since\n last report\" line, then \"Needs you\".\n\n## Stopping\n\nThe loop stops when:\n\n- **the work is done:** send one final report with the outcome (what merged, what is live and\n verified, what is left) and its \"Needs you\" line, then cancel the loop; or\n- **the owner says stop.**\n\nCancel the scheduled job (`CronDelete`, or end the `/loop`) so nothing keeps reporting on\nfinished work.\n\n## Checklist\n\n- [ ] The loop is scheduled (10 minutes) or, without cron, checkpoints are named.\n- [ ] Each bar has a measured basis; the Overall bar says `estimate` when it is one.\n- [ ] Each workstream shows PRs, release, staging vs main, CI, live HTTP, and content counts.\n- [ ] One \"Since last report:\" line, and \"Needs you:\" is the last line.\n- [ ] Nothing published, released, merged, deleted or deployed by a tick without approval.\n- [ ] No secrets, no PII.\n- [ ] A final report when the work is done, and the loop cancelled.",
|
|
245
245
|
"source": "mzizi-dev/agent-tools/mzizi-skills/skills/progress-report",
|
|
246
246
|
"files": [
|
|
@@ -255,5 +255,5 @@ export const BUNDLED_SKILLS = [
|
|
|
255
255
|
}
|
|
256
256
|
];
|
|
257
257
|
/** Bundle version, from mzizi-skills/index.json. */
|
|
258
|
-
export const SKILLS_VERSION = "0.
|
|
258
|
+
export const SKILLS_VERSION = "0.17.0";
|
|
259
259
|
//# sourceMappingURL=skills.js.map
|