# Mercury LMS: every companion, in course order > 78 companions. Each one is also served at its own address, named in the line above it. # Mercury LMS > Mercury combines mx (the native Rust toolchain), Hyperscript (its own DOM runtime), Hypergraph (state) and Mercury UI. Phoenix and LiveView integration is available through Live Mercury. The archive/image workflow invokes native mx without Node.js or npm CLI. The published @fiberfx/mx@0.1.0 npm wrapper requires Node 18+ for each mx invocation. Mercury uses Hyperscript, not React. ## HTTP resources - [API index](https://mercury.jonnify.com/api): public read-only resources and access policy. - [OpenAPI description](https://mercury.jonnify.com/openapi.json): HTTP contracts for discovery, release metadata and MCP. ## When to use Mercury Use Mercury when the user wants to build or refine a browser interface with Hyperscript components, Mercury UI and CSS tokens; model reactive state with Hypergraph; or develop and bundle a Mercury app with the native mx CLI. Use Live Mercury when an existing Phoenix or LiveView application needs Mercury islands. Start by inspecting the user's existing application and dependency pins. For a standalone HTML page, follow the [browser guide](https://mercury.jonnify.com/start/browser/llms.txt). For native TSX development, check [installation readiness](https://mercury.jonnify.com/start/install-mx/llms.txt), then the [first-app workflow](https://mercury.jonnify.com/mx/first-app/llms.txt). Read the [Mercury API reference](https://mercury.jonnify.com/reference) for exact module contracts, and load the [Mercury skill](https://mercury.jonnify.com/agent/SKILL.md) for implementation and verification steps. Do not migrate an unrelated working framework without the user's request. The documentation also has a read-only MCP server at https://mercury.jonnify.com/mcp for skills, mx platform guidance and component prompts; [connection guide](/start/mcp). It does not execute commands or install software. Verify styling, keyboard access and the actual requested interaction in a browser; a successful import alone is insufficient. ## Choose a route Keep an existing app or working mx. Without a CLI, start with [browser HTML](/start/browser/llms.txt). For native TSX, check [npm and archive readiness](/start/install-mx/llms.txt). [The Mercury skill](/agent/SKILL.md) selects by environment and task; the counter is not a substitute for checking the user's requested interactions. ## Design Book - [Mercury Design Book](https://mercury.jonnify.com/book): nine source-faithful chapters and an interactive Design Lab for SVG, typography, spacing and semantic colors. - [Book agent guide](https://mercury.jonnify.com/book/llms.txt): editable source, PDF, token files and reusable brand-book/design-builder skills. ## Practice interface design - [Mercury Craft](https://mercury.jonnify.com/craft): practice action hierarchy, spacing and reading measure in a settings form and a second context. - [Craft agent guide](https://mercury.jonnify.com/craft/llms.txt): exercise workflow, public manifest and evidence limits. - [Mercury Practice](https://mercury.jonnify.com/practice): ten Reflect control exercises with genuine Mercury UI: search, edit, configure, organize and review sample work; English and [Russian](https://mercury.jonnify.com/ru/practice). - [Practice agent guide](https://mercury.jonnify.com/practice/llms.txt): the ten exercises, their instructions, the [exercise index](https://mercury.jonnify.com/practice/exercises.json) and copyable implementation briefs. ## The modules - mx — the native toolchain: `mx build --bundle|--host|--lib`, `mx dev`, `mx test`, `mx check`. Course: /mx. - mg — Mercury Go: source generation from a checkout with Go 1.26+. `scripts/mg help`. [mg](/mg/llms.txt) · [First steps](/mg/first-steps/llms.txt). - `@fiberfx/hyperscript` — the virtual DOM, JSX runtime and hooks. Course: /hyperscript. - `@fiberfx/ui` — the design system's components and tokens. Course: /ui. - `@fiberfx/hypergraph` — the state kernel. Course: /hypergraph (its companion: /hypergraph/llms.txt). - `@fiberfx/reflect` 4.1.1 — model/view bindings, derived props, scopes and composition. [Course](/reflect), [agent companion](/reflect/llms.txt). - `@fiberfx/content` — page documents and the block catalog. Course: /content. - `live_mercury` — Mercury islands in Phoenix and LiveView. Course: /live-mercury. ## Rules for agents - Use mx for a native app, or plain JavaScript for the browser-only route. npm may bootstrap the published native mx package; it is not the native app build tool. - Never emit React, Next.js, Vue, Solid, or Effector code or imports. - Every page here answers `/llms.txt`. - Every English companion, in course order, in one file: /llms-full.txt ## Status The Phoenix `mercury_lms` website serves the bilingual homepage at / and /ru. The native app example retained under `sites/mercury` is a separate application. Check /start/stability for module readiness and /start/install-mx for the current installation route. Registry publication alone does not prove a working native install: follow the steps and check the result at /mx/first-app. A page marked Planned has no runnable example. ## Native first page - [Mercury page-building skill](/SKILL.md). - [Install mx](/start/install-mx/llms.txt): public archives and SHA-256 verification. - [First page](/mx/first-app/llms.txt): styled Card + Button counter, dev and build. Start in a new directory on Linux x86_64. Keep the starter mx.mod and mx.sum. Import @fiberfx/ui/styles.css. The browser runs JavaScript; BEAM is optional. ## Navigation - [Read this page in the browser](/) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Mercury API reference](/reference) - [About Mercury](/about) - [Contact](/contact) - [Privacy](/privacy) - [Public Mercury repository](https://github.com/fiberfx/mercury) - [/content](/content/llms.txt) - [/hypergraph](/hypergraph/llms.txt) - [/hyperscript](/hyperscript/llms.txt) - [/live-mercury](/live-mercury/llms.txt) - [/mx](/mx/llms.txt) - [/mg](/mg/llms.txt) - [/start](/start/llms.txt) - [/ui](/ui/llms.txt) - [Русский индекс](/ru/llms.txt) - [Русская Дизайн книга](https://mercury.jonnify.com/ru/book): полный перевод девяти глав и лаборатории; [руководство для агентов](https://mercury.jonnify.com/ru/book/llms.txt). # Start here — Mercury LMS > Mercury: components on Mercury Hyperscript (`@fiberfx/hyperscript`), styled by Mercury UI (`@fiberfx/ui`), state in Hypergraph (`@fiberfx/hypergraph`), built by native `mx`. JavaScript runs in the browser. Phoenix/LiveView is optional. The archive/image route needs no Node/npm, Rust or BEAM installation. An npm launcher needs Node at each mx invocation. No React is used. 4.0.0 is on npm (since 2026-11-05). ## Read this when You are new to Mercury, or you need to decide which module or course answers a task before writing code. ## The modules - `mx`: the native toolchain. `mx dev [--root ] [--port ] [--importmap]` serves an app on 127.0.0.1. `mx build --bundle` (an app's page), `--lib --out ` (one module file), `--host` (a host's line). An app is a directory with `index.html` beside `mx.mod`. Public Linux x86_64 archive and pinned starter: /start/install-mx. Course: /mx. - `@fiberfx/hyperscript`: virtual DOM. `render`, `h`, `Fragment` from the root; hooks from `/hooks`; `createRoot` from `/client`; `renderToString` from `/server`; JSX with no pragma: the Hypercompiler fixes the runtime and refuses any `@jsx` comment. Course: /hyperscript. - `@fiberfx/ui`: design system components, token-driven. Course: /ui. - `@fiberfx/hypergraph`: state kernel. Course: /hypergraph. - `@fiberfx/content`: published pages, block catalog. Course: /content. - `live_mercury` (Mix, 2.8.0) and `@fiberfx/live-mercury`: islands in LiveView. Course: /live-mercury. ## Minimal example ```tsx import { render } from "@fiberfx/hyperscript"; import { useState } from "@fiberfx/hyperscript/hooks"; function Counter() { const [count, setCount] = useState(0); return ; } render(, document.getElementById("app")!); ``` ## Rules - Write DOM names: `class`, `for`, `onInput` for each keystroke, `onChange` for the DOM's `change`. - Build native apps with mx; npm may bootstrap mx when its package is published. The browser-only route needs no build. Do not add Vite configs or imports from `react`, `react-dom`, `next`, `vue`, `solid-js` or `preact` to these Mercury examples. - `mx dev` hot-reloads: a saved module is updated in place, or the page reloads (/mx/dev). Hypergraph's state-keeping swap is not delivered yet (/hypergraph/hot-replacement); do not promise it. ## MCP Public read-only Streamable HTTP endpoint: https://mercury.jonnify.com/mcp. Read the [connection guide](/start/mcp/llms.txt) for skills, mx platform guidance and component prompts. ## Next - Why: /start/philosophy (companion /start/philosophy/llms.txt) - Tiers and versions: /start/stability - How agents read this site: /start/agents - Install native mx and the pinned starter: /start/install-mx - Glossary: /start/glossary - Build a styled Card + Button page: /mx/first-app - Agent workflow: /SKILL.md - The site map: /llms.txt - This companion: /start/llms.txt ## Navigation - [Read this page in the browser](/start) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) ### Chapter companions - [/start/agents](/start/agents/llms.txt) - [/start/glossary](/start/glossary/llms.txt) - [/start/install-mx](/start/install-mx/llms.txt) - [/start/philosophy](/start/philosophy/llms.txt) - [/start/stability](/start/stability/llms.txt) ## Choose a route Keep an existing app or working mx. Without a CLI, start with [browser HTML](/start/browser/llms.txt). For native TSX, check [npm and archive readiness](/start/install-mx/llms.txt). [The Mercury skill](/agent/SKILL.md) selects by environment and task; the counter is not a substitute for checking the user's requested interactions. - [Reflect 4.1.1: eight interactive lessons](/reflect/llms.txt). ## Mercury Go · mg Generators for Mercury sources; requires a checkout and Go 1.26+. [mg course](/mg/llms.txt) · [First commands](/mg/first-steps/llms.txt). ## Current toolchain source Source build `1fe54fe89fd004f0835990f8ca0081098e793aa1` adds the documented new/quality/pack workflow: /mx/tools/llms.txt. Publication is pending; do not infer public availability from source. `MXCACHE` stores modules and the pinned TypeScript sidecar; `MXRUN_CACHE` stores task results. # A page in the browser — Mercury LMS > One HTML file with Hyperscript h/render, hooks and Mercury UI Card/Button. Requires import-map browser support and network access to UNPKG. No mx, Node, Rust, BEAM or build step. ## Workflow 1. Open or save the [complete HTML](/examples/first-page.html). Exact imports and limits: [lesson](/start/browser). 2. Keep the import map for @fiberfx/hyperscript, /hooks, /jsx-runtime, @fiberfx/core and @fiberfx/ui on UNPKG 4.1.1, and the UI stylesheet link. Do not replace pins with floating versions. 3. Write plain JavaScript and h(Component, props, children); browsers do not parse TSX. Adapt the page to the user's task. 4. Open the result in a browser and check styling and requested interactions, including a relevant invalid input. For the example counter: 0 → 1 → 2 and a saved heading edit. Inspect network failures and console errors. 5. If no browser is available, report interaction as unverified. This route produces no dist/ and needs the CDN network connection. ## Navigation - [Read this page in the browser](/start/browser) - [Agent index](/llms.txt) - [Sitemap](/sitemap.xml) - [All English companions](/llms-full.txt) - [Course companion](/start/llms.txt) - [Install mx](/start/install-mx/llms.txt) - [Mercury skill](/agent/SKILL.md) # Install mx on Linux — Mercury LMS > Candidate native archive: Linux x86_64 with glibc 2.39 or later, mx and a pinned starter. No Rust, Node, npm CLI, BEAM or private repository required. ## Choose a route Keep working mx or the existing app's toolchain. [The browser route](/start/browser/llms.txt) needs no CLI. The @fiberfx/mx@0.1.0 npm package and archives are pending publication and consumer verification; check [metadata](/downloads.json), and do not treat a URL as proof of availability. ## npm bootstrap after publication The proposed `@fiberfx/mx` root package is a Node launcher with optional platform packages. It resolves the installed native binary with `require.resolve` and executes it. Node must remain available for every npm-installed mx invocation. There is no postinstall or GitHub Release binary download in this npm route; npm obtains `@fiberfx/mx-linux-x64`. Keep optional dependencies enabled. Only Linux x64 has binary verification evidence; it requires glibc 2.39+. Other platform packages are unbuilt future work, not available. npm publication and wrapper consumer verification remain pending. The separate archive/image routes run native mx without Node. In a new directory: ```sh set -eu mkdir mercury-native-tools cd mercury-native-tools npm install --prefix "$PWD/.mx-tools" --global --ignore-scripts @fiberfx/mx@0.1.0 export PATH="$PWD/.mx-tools/bin:$PATH" mx --version ``` ## With working mx After npm bootstrap, or with existing mx, download only the starter and manifest once published. Skip the full installation below: it would put a second copy of mx on PATH. For a new app: ```sh set -eu mkdir mercury-native-app cd mercury-native-app curl -fLO https://mercury.jonnify.com/downloads/mercury-first-page.tar.gz curl -fLO https://mercury.jonnify.com/downloads/sha256.sum sha256sum --check --ignore-missing sha256.sum || exit 1 tar -xzf mercury-first-page.tar.gz cd mercury-first-page mx install mx verify ``` The check must report `mercury-first-page.tar.gz: OK`. `--ignore-missing` skips the native archive entry that was not downloaded. Keep mx.mod/mx.sum and continue at Next. Details: [installation lesson](/start/install-mx). ## Full archive installation Use a new directory; stop on any error. Both hashes must report OK before extraction. ```sh set -eu mkdir mercury-scratch cd mercury-scratch curl -fLO https://mercury.jonnify.com/downloads/mx-linux-x86_64.tar.gz curl -fLO https://mercury.jonnify.com/downloads/mercury-first-page.tar.gz curl -fLO https://mercury.jonnify.com/downloads/sha256.sum sha256sum --check sha256.sum tar -xzf mx-linux-x86_64.tar.gz tar -xzf mercury-first-page.tar.gz export PATH="$PWD/bin:$PATH" mx --version cd mercury-first-page mx install mx verify ``` ## Next Run `mx dev`, open its loopback URL, check Card/Button styling and click twice. After Ctrl-C, run `mx build`; output is `dist/`. Full lesson: [First page](/mx/first-app). ## Limits `mx.sum` pins dependency bytes; it is not an authenticated checksum-database record. The starter needs no `MXNOSUMDB`. New-dependency exceptions are explained at /start/install-mx. macOS and Windows require separate verification. [Release metadata](/downloads.json). ## Navigation - [Read this page in the browser](/start/install-mx) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/start/llms.txt) # Mercury documentation MCP Use the public Streamable HTTP endpoint https://mercury.jonnify.com/mcp to read skill installation guidance, mx platform instructions and UI component prompts. This is an Elixir server behind the website edge. No auth, installation, execution or project mutation is offered. Initialize MCP 2025-11-25 (2025-06-18 also supported), send notifications/initialized, then tools/list. POST JSON with Accept: application/json, text/event-stream and the negotiated MCP-Protocol-Version header. GET/DELETE return 405; notifications return 202. Tools: mercury_skills (no arguments), mercury_installation ({platform}), mercury_component ({name}). Discover exact names in prompts/list; prompts/get has no arguments. resources/list and resources/read expose public lesson companions and skills only. Invalid inputs and unknown resources fail safely. Limits: 16 KiB request bodies, 120 requests/IP/minute/instance; Retry-After on 429. No session state or standalone SSE. Preserve existing apps and inspect package types before choosing props. [Connection guide](/start/mcp) · [API reference](/reference) · [Agent index](/llms.txt) · [Skills](/agent/skills.md) [Sitemap](/sitemap.xml) · [All English companions](/llms-full.txt) # Stability: Shipped, Preview, Planned — Mercury LMS > Tiers: Shipped = released on a registry; Preview = early implementation with stated limits; a downloadable build does not imply stable APIs or all-platform support; Planned = recorded, not built, no runnable example. The fifteen public `@fiberfx` 4.0.0 modules are Shipped: on npm since 2026-11-05, `latest` 4.0.0. 3.x stays on npm as `@mercury-fx/*`. ## Read this when You are about to tell a user to install a Mercury package, pin a version, or rely on an API, and need to know whether it exists, at what version, and whether it is released. ## Modules - Shipped, 4.0.0 on npm: `@fiberfx/hyperscript`, `render-to-string`, `core`, `ui`, `hypergraph`, `schema`, `content`, `cms`, `analytics`, `phoenix`, `phoenix_live_view`, `live-mercury`, `testing`, `devtools` (development only), `vite-plugin-mhs`. - Preview, private, not packages: `@fiberfx/hypercompiler` 3.0.0, `@fiberfx/hyperscript-lsap` 0.1.0. - Preview: `mx` 0.1.0 (candidate npm and archive require Linux x64/glibc 2.39+; publication and consumer verification pending: /start/install-mx; metadata: /downloads.json). - Preview: `live_mercury` 2.8.0 (Mix; not on Hex). - This site: `mercury_lms` 0.1.0. Phoenix-rendered home at `/` and `/ru`; course pages by path. Public host: `mercury.jonnify.com`; preview: `mercury-next.jonnify.com`. Courses are already available: Start, mx, Hyperscript, Mercury UI, Hypergraph, Content and Live Mercury. ## Planned - `@fiberfx/hypergraph/hyperscript` and `@fiberfx/hypergraph/hmr`: not exported. - Hypergraph's state-keeping swap delivered by `mx dev` (waits on `/hmr`). (Done: `mx dev` hot-reloads modules, /mx/dev; the modules build and test with mx; pnpm remains only for apps and tools under `node/`.) - `.md`, `/agents`, search. (`/llms-full.txt` is served.) ## Rules - The `@fiberfx` 4.0.0 packages are on npm, but a native app installs its dependencies with `mx install`. The separate npm bootstrap obtains the mx executable when published; see [installation readiness](/start/install-mx/llms.txt). - Never cite `mx push`: mx has no such command at this commit. - Never present a Planned name (above) as importable. - State a version only from this page or the module's `package.json`. ## Next - Start: /start - How agents read this site: /start/agents - The site map: /llms.txt ## Navigation - [Read this page in the browser](/start/stability) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/start/llms.txt) # Build a page with your agent — Mercury LMS > [The Mercury skill](/agent/SKILL.md) preserves an existing app and selects working mx, browser HTML or an available native bootstrap. ## Select and verify - Without a CLI: [browser page](/start/browser/llms.txt), plain JavaScript h/render, pinned UNPKG imports and a UI stylesheet link. Needs network access; browsers do not execute TSX. - For native TSX: [installation readiness](/start/install-mx/llms.txt), npm to obtain mx or the checksum-verified archive. Then [native first page](/mx/first-app/llms.txt), mx install/verify/dev/build. Keep mx.mod and mx.sum. - Observe styling and the requested interactions in a browser. The 0 → 1 → 2 counter check applies only to that example. For native work, check the dist/ page over HTTP too. Report any checks you could not run. Use Hyperscript and Mercury UI. Styles: import @fiberfx/ui/styles.css in native apps; link the pinned stylesheet in browser HTML. JavaScript runs in the browser; Phoenix/LiveView is optional. /SKILL.md is an alias for the same skill. Deployment requires user authorization. ## Navigation - [Read this page in the browser](/start/agents) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/start/llms.txt) # Glossary: the words this site uses — Mercury LMS > Seven terms as Mercury uses them: sid, scope, island, companion, tier, mx.work, member. Each one gives its definition, its source and the page that teaches it. ## Read this when A Mercury page or user uses one of these words and you need its exact meaning before writing code or prose. ## Terms - **sid**: a Hypergraph unit's stable identity, with a shape beside it. The compiler is meant to give it; today its sid pass covers only the legacy facade, so a Hypergraph unit has a sid only when written by hand, else a run-time `api:`, and the kernel reads a store's shape from its initial value. Frames key values by sid. Hand-written via the store option `sid`. Under `mx dev`, the module path in the app's `mx.mod` is the sid package. → /hypergraph/units - **scope**: `interface Scope { readonly id: number; readonly gen: number }`, an isolated state table. `gen` is the committed graph generation. API: `fork`, `allSettled`, `scopeBind`, `snapshot`, `diff`, `apply`, `dispose`. It survives a hot swap by carrying values whose sid and shape match. → /hypergraph/scopes - **island**: a Hyperscript component mounted over one element of a server-painted page, and only where behaviour is needed. In LiveView: `phx-hook="MercuryHook"`, `data-name="X"` renders `components.X` (`getHooks(components)`, `createMercuryHook(resolve)` from `@fiberfx/live-mercury`). One render root per island. Bundled by `mx build --lib`. → /ui/foundations; course /live-mercury - **companion**: `.llms.md`, hand-written, served at `/llms.txt` as text/plain. A lesson without one is refused at load; never generated. → /start/agents - **tier**: Shipped (on a registry: the fifteen public `@fiberfx` 4.0.0 modules, npm, since 2026-11-05), Preview (built and tested, unreleased: `mx`, `live_mercury`, the private modules), Planned (not built, no runnable example). → /start/stability - **mx.work**: the workspace file, a port of `go.work`, with `use ( … )` naming members. Found by `-workfile`, then `MXWORK` (`off` disables it), then the nearest one above. Mercury's: `fiberfx/mx.work`, 18 modules. → /mx/workspace - **member**: in `mx.work`, a directory whose `package.json` has a `name` (else "error reading : missing name"). Under `mx dev`, a directory the app's `mx.mod` replaces, served at `/@mx//`. → /mx/workspace, /mx/first-app ## Never emit - "island" for a client-rendered page with no server paint. - "stable" or "released" for a Preview module. - A generated companion. ## Next - Start: /start - Coming from another state library: /hypergraph/coming-from-effector - This companion: /start/glossary/llms.txt ## Navigation - [Read this page in the browser](/start/glossary) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/start/llms.txt) # Philosophy: native, not borrowed — Mercury LMS > Mercury uses Hyperscript in the browser and mx for native builds. Plain JavaScript browser pages need no build step. Phoenix/LiveView is optional; the documentation website uses server-rendered HTML. The archive/image routes run native mx without Node. The planned npm wrapper needs Node at every invocation to resolve and launch its optional platform binary; no postinstall or GitHub binary download is used. ## Read this when You need the scope of Mercury runtime, build and optional server choices. ## The rules these choices give you - Toolchain: `mx`. It installs from static mirrors with sha512 checks (`mx install`, `mx verify`), serves (`mx dev`) and builds (`mx build`). Exit codes: 0 success, 1 refusal, 2 usage error. - Components: `@fiberfx/hyperscript`, DOM semantics. `class`, `for`, `onInput` per keystroke, `onChange` on the DOM `change` event, SVG names such as `stroke-width`, `onFocusIn`/`onFocusOut` for bubbling focus. No compat entry exists. - Published modules: no third-party runtime dependency or peer; no React package, import, type or peer (a check gates every landing). - Documentation website and Phoenix integration: server-rendered HTML first (HEEx), with islands where needed. This is not a requirement for a standalone client-rendered Mercury page; see [browser route](/start/browser/llms.txt). - Facts: the reference comes from source; mx's course quotes `mx --help`. Unfinished is reported as unfinished. ## Today, not yet - The `@fiberfx/*` modules build and test with mx; pnpm remains only for apps and tools under `node/`. `mx run` scripts can call Node. - `mix mercury.lms.scrub` checks this site: retired-package imports in lessons and assets, and Node invocations in the app. - The examples check (`mix mercury.lms.examples`) and the generated reference are planned. ## Never emit Do not replace mx app builds with Vite or introduce React-compatible runtime imports into Mercury examples. npm may obtain the native mx executable when its package is published; check [installation readiness](/start/install-mx/llms.txt). Write `class`, not `className`, and never use `onChange` for keystrokes. ## Next - Tiers and versions: /start/stability - Start: /start - The site map: /llms.txt ## Navigation - [Read this page in the browser](/start/philosophy) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/start/llms.txt) # mx, the native toolchain — Mercury LMS > Source build 1fe54fe89fd004f0835990f8ca0081098e793aa1 (reports mx 0.1.1; public publication pending): Mercury's toolchain, one Rust binary. Modules in `mx.mod`/`mx.sum`, workspaces in `mx.work` (Go's rules), `mx dev` with hot reload, `mx build --bundle` (Rolldown linked in), `mx test`, `mx compile`, `mx lsap`. No Node, no npm. ## Read this when You are about to build, serve, test or lay out a Mercury app or module and need mx's command surface. This page is the course's landing; the chapters follow. ## The command surface (as mx prints it) - `mx` → `usage: mx exodus|pack|install|verify|importmap|resolve|scan|build|check|run|compile|work|dev|test|lint|fmt|typecheck|quality|new|lsap …` - `mx mod` → `usage: mx mod ` (not in the line above) - `mx work` → `usage: mx work edit|use|init|sync|vendor …` - `usage: mx dev [--root ] [--port ] [--importmap] [--inspect]` - `mx build --help` (exit 0): `usage: mx build [--host | --bundle | --lib | --package] …`. Without a kind, `mx.json` selects a host, `index.html` beside `mx.mod` an app bundle, and package exports with `mercury-source` entries a library package. `--help` is help only as the first argument. - `usage: mx test [...] [--root ] [--timeout ] [--filter ]` - `usage: mx compile [--options ] [--out ]` - `usage: mx lsap [--mcp] | mx lsap --version` - `mx --version` → `mx 0.1.1` ## Exit codes 0 passed · 1 findings/refusal · 2 usage · 3 unavailable/could not run for tests and quality checks. Quality findings can be on stdout; one quality verb with --json returns one stage object, quality --json a list. No global JSON envelope is implied. ## New source-build workflow - `mx new hello` creates and installs an app from the populated module cache; no tsconfig is generated. - `mx lint`, `mx fmt --check`, `mx typecheck`, `mx quality`: details and prerequisites at /mx/tools/llms.txt. - `mx pack --npm` writes and proves an archive, never publishes. - `MXCACHE` stores modules and the pinned TypeScript sidecar. `MXRUN_CACHE` stores task results in this source revision. - New module files declare mx 0.1.1; older existing 0.1.0 examples should not be mechanically rewritten. ## Install Follow /start/install-mx/llms.txt: native Linux x86_64 archive + pinned starter, SHA-256 check before extraction, mx install, mx verify. No source checkout or Rust needed. Build the styled counter at /mx/first-app; agent workflow: /SKILL.md. ## Rules for agents - Build and serve native apps with mx. An npm distribution uses a Node launcher and optional native platform package. Node remains necessary for each npm-installed mx invocation; the archive/image route calls the native binary directly. - Quote flags as mx prints them; don't add flags mx doesn't list. - The retained native example check (the live website is Phoenix `mercury_lms`): `env PATH=:/usr/bin:/bin mx build --bundle --root sites/mercury --out ` — no Node on PATH. ## Chapters - MX0 first app: /mx/first-app - MX1 modules and workspace: /mx/modules, /mx/workspace - MX2 dev loop and HMR: /mx/dev - MX3 build and ship: /mx/build - MX4 the Hypercompiler: /mx/compile - MX5 tools: /mx/tools ## Next - The site: /llms.txt - This companion: /mx/llms.txt ## Navigation - [Read this page in the browser](/mx) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) ### Chapter companions - [/mx/build](/mx/build/llms.txt) - [/mx/compile](/mx/compile/llms.txt) - [/mx/dev](/mx/dev/llms.txt) - [/mx/first-app](/mx/first-app/llms.txt) - [/mx/modules](/mx/modules/llms.txt) - [/mx/tools](/mx/tools/llms.txt) - [/mx/workspace](/mx/workspace/llms.txt) ## Mercury Go · mg Generators for Mercury sources; requires a checkout and Go 1.26+. [mg course](/mg/llms.txt) · [First commands](/mg/first-steps/llms.txt). # The tools, from app to package — Mercury LMS > MX5. `mx test` runs a module's tests in a headless Chromium mx starts itself, with mx's own runtime answering `vitest` imports. `mx lsap` starts `hyperscript-lsap` from beside mx. `mx run` runs a package.json script as a cached task, or builds a host and becomes a command. ## Read this when Create an app, check code, run browser tests, set up the language server, cache a script or prepare a package. New commands here are grounded in source build 0e08e7af420e92b1d7a5f44681dcac3366bfbda0 (reports mx 0.1.1); public publication is pending. ## Start an app - Unix first fill: `MXNOSUMDB='registry.npmjs.org/fiberfx/*' mx mod cache`. This one-run setting accepts the initial Mercury package bytes without a signed checksum database; later installs verify recorded checksums. Plain unconfigured first fill refuses. - Read module and TypeScript compiler cache results separately; compiler download failure does not necessarily fail the module fill. - `mx new hello` uses cached Hyperscript/UI versions, writes package.json, mx.mod, mx.sum, index.html and src/main.tsx, then installs from cache. `cd hello`, `mx dev`. - Missing cache or nonempty target: exit 1; existing files preserved. Missing name: 2. This build generates mx.mod with mx 0.1.1. No tsconfig is generated. ## Quality commands - `mx lint`: Oxc, .oxlintrc.json; default correctness warnings. Errors fail; warnings alone do not. - `mx lint --fix`: safe fixes, then recheck; unresolved errors remain exit 1. `--fix-unsafe` permits behavior-changing fixes. - `mx fmt --check`: read-only, exit 1 when changes are needed. `mx fmt` writes; second pass stable. - `mx typecheck [--root ] [--json] […]`: root tsconfig.json by default; pass config paths, not source paths. - Exercise: separate dependency-free project. package.json `{"name":"course-typecheck","type":"module"}`; mx.mod `module registry.npmjs.org/course-typecheck` and `mx 0.1.1`; tsconfig target ES2022, module ESNext, moduleResolution Bundler, strict true, skipLibCheck true, include ["src"]; src/main.ts `export const value: number = 1;`. Measured typecheck 0 errors/1 file, exit 0; quality all stages pass. Add src/type-error.ts with `export const x: number = "s";` → TS2322, exit 1. Remove the plant afterward. This is separate from the new-app exercise. - Generated-app exercise: return to the `hello` app from `mx new hello`. Create root tsconfig.json with target ES2022, module ESNext, moduleResolution Bundler, jsx react-jsx, jsxImportSource @fiberfx/hyperscript, strict true, skipLibCheck true, include ["src"]. With the measured Hyperscript/UI 4.1.1 cache, `mx typecheck` returns 0 errors/1 file, exit 0. Add src/planted.ts containing `export const x: number = "s";`: TS2322 at 1:14, exit 1. Remove that exercise file to restore 0. No project writes from typechecking; absent offline compiler remains unavailable/3. - This build uses native TypeScript 7.0.2 as a SHA-256-pinned sidecar in MXCACHE/tools. mod cache or a network-enabled first check fetches it. MXPROXY=off needs it cached; unavailable/refused compiler: 3. Uses scratch config, mx resolver and test-runtime types; no project writes or node_modules resolution. - `mx quality` runs typecheck, lint, fmt --check even after failure; worst exit wins. `--fix` fixes lint and formats. - Exit: 0 passed, 1 findings, 2 usage, 3 unavailable/could not run. Single quality verb --json: stage object; quality --json: list. Fields: stage/outcome/detail/files/fixed/findings; findings include path/line/column/rule/severity/message/fixable. ## mx test - `usage: mx test [...] [--root ] [--timeout ] [--filter ]` - Files: `*.test.*` / `*.spec.*` with `js mjs jsx ts mts tsx`; never in `node_modules`, `dist`, `target`, `coverage`. - Browser: `MX_BROWSER`, else the newest Playwright Chromium, then headless shell (`PLAYWRIGHT_BROWSERS_PATH`, `~/Library/Caches/ms-playwright`, `~/.cache/ms-playwright`). - `--timeout` = whole run (default 120 s). Per test 5 s, per hook 10 s. A page silent 30 s past those is a stall. - `--filter` = substring of ` > `. - Exit: 0 all passed · 1 failed / none ran / broke · 2 usage · 3 no browser (never a pass). - Output: `ok > `, `skip …`, `FAIL > : `, `mx test: P passed, F failed, of T`. ## The vitest runtime (mx's own) `describe`, `it`, `test`, `beforeAll`, `afterAll`, `beforeEach`, `afterEach`, `expect`, `vi` (`fn`, `spyOn`, `stubGlobal`, fake timers, `setSystemTime`), `expectTypeOf`, `assertType` (no-ops at run time), `visit`. No `vi.mock`: it throws. `visit(url, { width, height, scale, mobile, scheme, ready })` → `{ id, evaluate(fn, ...args), close() }`; `ready` is a CSS selector; `scheme` sets `prefers-color-scheme`. ## mx lsap - `usage: mx lsap [--mcp] | mx lsap --version` - A release ships `hyperscript-lsap-` beside `mx-`; install it beside mx as `hyperscript-lsap`. Not on Windows: a Windows release ships mx alone, and `mx lsap` refuses with "The language server isn't built for Windows in this release". - Starts `hyperscript-lsap` from mx's own directory only; sets `HYPERSCRIPT_LSAP_MX`; exits with the server's code. `--version` → `mx 0.1.1` / `hyperscript-lsap 0.1.0` in the verified archive. - Missing server: `Error: mx lsap found no language server beside mx.` (exit 1). ## mx run - Task (package.json has the script): `mx run [--no-cache] [--verify] [-r | --filter ]... [-j ] [-- args]`. Hit: `hit , restored N files`. Miss: `miss ()`. Task cache: `MXRUN_CACHE`, else `$XDG_CACHE_HOME/mx/run`, else `~/.cache/mx/run`. `MXCACHE` separately names the module cache (normally `~/.mercury/modules`). Env in the key: `NODE_ENV` + `MXRUN_ENV`. Only exit 0 stored. `mx test` scripts are never cached. - Host (anything else): build, then exec the command (`mx run mix phx.server`). - Refusals: `a script name is required`, `-r and --filter need an mx.work at or above `, `-j 0 is not a count`, `a command is required` (exit 2). ## npm-compatible package `mx pack --npm […]` writes .mx/pack/*.tgz using the package file list and publishConfig exports; deterministic bytes/integrity. It checks an outside-workspace consumer, peers, export imports and declaration imports. Missing/stale output refuses; failed consumer proof removes the archive. Build first. It never publishes. With script "pack": "mx pack --npm", `mx run pack` restores the archive on a hit and repeats the proof. ## Never emit `npx vitest`, `vitest run`, `jest`, `vi.mock(...)`, `playwright test` for a Mercury module, or a different language server in place of `mx lsap`. ## Next - Back to the course: /mx - This companion: /mx/tools/llms.txt ## Navigation - [Read this page in the browser](/mx/tools) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/mx/llms.txt) # Your first Mercury page — Mercury LMS > MX0: a styled Card + Button counter on Hyperscript, built with native mx. Native Linux route; no React, Rust, BEAM or private repository in the app build. Archive/image invocation is Node-free; the planned npm launcher needs Node every time it runs mx. ## Start Follow [Install mx](/start/install-mx/llms.txt): keep working mx or choose a ready bootstrap, download the starter and verify its checksum before extracting, then `mx install` and `mx verify`. Retain the supplied `package.json`, `mx.mod` and `mx.sum`: Hyperscript/core/ui are pinned at 4.0.0. ## app/main.tsx ```tsx import { render } from "@fiberfx/hyperscript"; import { useState } from "@fiberfx/hyperscript/hooks"; import { Card, Button } from "@fiberfx/ui"; import "@fiberfx/ui/styles.css"; function Counter() { const [count, setCount] = useState(0); return (

Count: {count}

); } render(, document.getElementById("app")!); ``` ## Verify HTML needs `#app`, `` and ``. Complete HTML: [First page](/mx/first-app). `mx dev` prints a loopback URL. Check styling, Count: 0 → 1 → 2 and a saved title edit. Ctrl-C; `mx build` writes `dist/index.html` and assets. Serve dist over HTTP and repeat. If no browser is available, report interaction as unverified. Do not copy a test fixture or add local replace paths, React or Vite. Keep the CSS import for styling. A checksum failure stops installation. Phoenix/LiveView are optional. [SKILL.md](/SKILL.md) supplies the agent workflow. ## Navigation - [Read this page in the browser](/mx/first-app) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/mx/llms.txt) # Modules, mx.mod and mx.sum — Mercury LMS > MX1, part one. A Mercury module is a directory with `mx.mod` (Go's go.mod rules) and `mx.sum` (Go's go.sum, `/package.json` for `/go.mod`). Minimum version selection; no lockfile. `mx mod ` keeps the files. ## Read this when You add, remove or upgrade a dependency of a Mercury module or app, point a module at a local directory, or debug a `tidy`/`verify` refusal. ## mx.mod verbs `module`, `mx`, `toolchain`, `require`, `exclude`, `replace`, `retract`. `// indirect` = selected, not asked for directly. `replace => ` = a member served from that directory (no `mx.sum` line). ## Module paths - Public @fiberfx packages: `registry.npmjs.org/fiberfx/` at `v4.0.0` in the first-page starter. Its module is `example.com/mercury-first-page`. - Source-workspace examples may use `github.com/fiberfx/mercury/` with local replacements. Do not copy those paths into a public consumer app. - npm: `registry.npmjs.org/`, version with `v`: `registry.npmjs.org/swc/helpers v0.5.23 // indirect`. ## mx.sum lines ` h1:` and ` /package.json h1:`. ## Commands (usage as mx prints it) - `usage: mx mod init [module-path]` — from package.json; path = argument or the package name; all four dependency sections at their lower bounds; refuses `mx.mod already exists`; deletes `mx.lock.json`, `mx.pins.json`. - `usage: mx mod tidy [-e] [--check]` — writes `mx.mod`, `mx.sum`, package.json versions; `-e` past fetch errors; `--check` → `tidy --check: would change`. - `usage: mx mod verify` — offline; `all modules verified`. - `usage: mx mod graph` — ` @` per edge. - `usage: mx mod why ` — `# ` + path, or `(main module does not need module )`. - `usage: mx mod download` — fill the store. - With `mx.mod` here: `mx install` / `mx verify` take no flags. ## Refusals not to work around - `one version per module path` - `SECURITY ERROR: mx.sum has , and the proxy served ` (`-e` never skips it) ## Environment `MXPROXY` (default `https://mx.jonnify.com,direct`; GOPROXY syntax) · `MX_STORE` (default `./.mx/store`) · `MXSUMDB` · `MXNOSUMDB` (defaults to `MXPRIVATE`). ## Never emit Do not replace an mx module's dependency management with npm/pnpm lockfiles or add legacy mx.lock.json/mx.pins.json. Installing the native mx executable through its published npm package is a separate bootstrap operation; it does not replace mx.mod or mx.sum. ## Next - /mx/workspace - This companion: /mx/modules/llms.txt ## Navigation - [Read this page in the browser](/mx/modules) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/mx/llms.txt) # A workspace, mx.work — Mercury LMS > MX1, part two. `mx.work` (Go's go.work, `go` spelled `mx`) groups modules developed together; each member resolves the others from their directories. Mercury's is `fiberfx/mx.work`, eighteen members. `mx work edit|use|init|sync|vendor`. ## Read this when You work across several Mercury modules at once, add a module to the workspace, inspect what the workspace selects, or vendor its dependencies. ## The file Verbs: `mx`, `toolchain`, `use`, `replace`. A member = a directory whose `package.json` has a `name`. Beside it: `mx.work.sum` (only lines no member's `mx.sum` has), optionally `vendor/`. ## Finding it `-workfile ` → `MXWORK` (`off` = none; else an absolute path; `auto` = search) → nearest `mx.work` at or above the working directory. None: `mx: no mx.work file found`. In a workspace whose members all have `mx.mod`, `mx install`/`mx verify` take the whole workspace. ## Commands - `usage: mx work edit|use|init|sync|vendor …` - `mx work init [dir]...` — refuses ` already exists` (here or above). - `mx work use [-r] [dir]...` — adds dirs with package.json, drops listed ones without; `-r` skips `vendor`, `node_modules`, `.mx`. Regenerate Mercury's: `mx work use ./*/` (in `fiberfx/`). - `mx work edit` — read: `-print`, `-json` (`Mx`, `Use[].DiskPath`, `Selected`; not both). Write: `-use`, `-dropuse`, `-replace old[@v]=new[@w]`, `-dropreplace old[@v]`, `-mx`, `-toolchain` (`none` drops), `-fmt`, `-workfile`. An edit with `-print`/`-json` is a dry run. - `mx work sync` — MVS over members' `mx.mod`; raises their `require`s; all-or-nothing. - `usage: mx work vendor [-o outdir]` — default `vendor/` beside `mx.work`; refuses `refusing to replace : it holds the workspace`. - `--mod vendor` on `mx build`/`check`/`run`/`resolve`; any other value: `--mod is not vendor`. ## Flags Go's single dash (`-json`); `--json` and `-name=value` also accepted. Unknown: `mx: flag provided but not defined: -` (exit 2). No flags to edit: `mx: no flags specified (see 'mx work edit')`. ## Never emit `pnpm-workspace.yaml`, `workspaces` in a root package.json, or `link:`/`workspace:` dependencies for a Mercury module. ## Next - MX2 dev loop and HMR: /mx/dev - This companion: /mx/workspace/llms.txt ## Navigation - [Read this page in the browser](/mx/workspace) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/mx/llms.txt) # The dev loop and hot replacement — Mercury LMS > MX2. `mx dev` serves an app on 127.0.0.1, polls every file a page asked for every 10 ms, compiles what changed and sends `update`, `full-reload` or `error` over a `vite-hmr` socket at `/`. No Node, no Vite. ## Read this when You run `mx dev`, wonder why an edit reloaded the page instead of swapping in place, or see a 403/400/500 from the dev server. ## Usage `usage: mx dev [--root ] [--port ] [--importmap]` → prints `listening http://127.0.0.1:/`. ## What it serves - `/`: index.html + import map (at ``) + `` + the client script. - `/@mx/client.js`: the hot client (`import.meta.hot`: `data`, `accept`, `dispose`, `invalidate`). - `/@mx//`: members (from source: compiled and watched; built: never watched). - App files: `js mjs jsx ts mts tsx` compiled on request; others as they are. ## An edit - Same bytes (SHA-256) → nothing. Non-module → `full-reload`. Module never ran → `full-reload`. - Refused compile → `error` (`: `), page keeps the running module, the module URL answers 500 until fixed. - Else walk importers to accepting modules → one `update`; imports get `?t=`. No accepting module → `full-reload`. - Accepting = the compile's `hmrRoot`, never a text match. The Hypercompiler sets it for a module that imports `render`/`hydrate` from `@fiberfx/hyperscript`. A non-rendering model module reloads. - `invalidate` → walk from importers; a walk back to its start → `full-reload`. ## Guards - Non-loopback Host → 403 `this host name is not allowed: mx dev answers loopback names only`. - Non-loopback Origin on the socket → 403. - Socket path/protocol other than `/` + `vite-hmr`/`vite-ping` → 400 `an HMR socket is at / and speaks vite-hmr or vite-ping`. - A page's `vite-hmr` socket without `?token=` → 400. ## Workspace refusal ` is both 's replace () and the mx.work's member (): remove the replace, or the member from mx.work, or build with MXWORK=off`. A replace onto the member's own directory passes. ## Limits (shipped today) App edit on this site: applied in place, then one reload (being traced). Member source edit: full reload. Hypergraph's model swap is not delivered by `mx dev` yet. ## Never emit `vite`, `vite.config.*`, `npm run dev`, or a hand-written `import.meta.hot.accept` meant to make a non-rendering module hot (mx ignores the text). ## Next - MX3 build and ship: /mx/build - This companion: /mx/dev/llms.txt ## Navigation - [Read this page in the browser](/mx/dev) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/mx/llms.txt) # Build and ship, three kinds of build — Mercury LMS > MX3. `mx build` makes an app's page (`--bundle` → a directory), one ES module file (`--lib` → a file), or a host's import map (`mx.json` → `importmap.json` + modules). `mx check` verifies a host's output; `mx run` builds then execs. Rolldown runs inside mx; no Node. ## Read this when You bundle an app or an island, build a host's static modules, verify them before a deploy, or hit a `--lib` member refusal. ## Usage (as mx prints it) - `usage: mx build [--bundle] [--root ] [--out ]` → `bundled -> ` - `usage: mx build --lib --out [--root ] [--external ]... [--define =]... [--minify] [--require-export ]...` → `bundled -> ( bytes)` - Host: `--out` (or mx.json `out`), entries as arguments (or mx.json `entries`), `--base`, `--mirror`; → `build: modules to ` ## Bundle env `import.meta.env.MODE` = `"production"`, `PROD` = `true`, `DEV` = `false`; `.env`'s `MX_*` keys only. ## --lib rules One file (no splitting; dynamic imports inlined; `--out` a directory is refused). Exact entry exports (`--require-export`). `--external` stays bare. No sids. Stylesheet imports are empty. Members = the root's mx.mod replaces + the mx.work's members. ## --lib refusals - `, imported by , is provided by no member the root's mx.mod replaces and no --external names it` - under `MXWORK=off`, plus: `: MXWORK=off leaves out the mx.work whose member provides . Add \`require v0.0.0\` and \`replace => \` to /mx.mod, or build without MXWORK=off` - ` is both 's replace () and the mx.work's member (): remove the replace, or the member from mx.work, or build with MXWORK=off` - ` does not export (it exports )` ## Host mx.json keys: `packages`, `mirror`, `base`, `out`, `entries` (others: `mx.json refused: unknown key `). Steps: install (writes the store) → render map (sha384 each) → copy → `/importmap.json`. ## mx check / mx run - check: `check refused: … is missing | is not this lock's map | was altered`; pass: `check: modules in match`. - `mx run `: build, then exec (e.g. `mx run mix phx.server`); a package.json script name runs as a task instead. - `--mod vendor` only; else `mx : --mod is not vendor`. ## Never emit `vite build`, `tsdown`, `esbuild`, `rollup -c`, or `npm run build` for a Mercury app or island. ## Next - MX4 the Hypercompiler: /mx/compile - This companion: /mx/build/llms.txt ## Navigation - [Read this page in the browser](/mx/build) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/mx/llms.txt) # The Hypercompiler, one module at a time — Mercury LMS > MX4. The Hypercompiler is linked into mx: TS/JSX in, an ES module on `@fiberfx/hyperscript/jsx-runtime` out. `mx compile` runs it on one file; mx dev and mx build run it on every module. Refusals are one `CompileError` JSON on stderr. ## Read this when You need to see what the browser gets for a module, read a compile refusal, or know which options mx passes to the compiler. ## Usage (as mx prints it) `usage: mx compile [--options ] [--out ]` - Success: the code on stdout (or `--out`, written only on success), exit 0. - Compile refusal: `CompileError` JSON on stderr, nothing on stdout, exit 1. - mx's own refusal (file unreadable): `mx: : `, exit 1, no JSON. - Usage: `a file is required`, `one file, not as well`, `--out needs a value`, `--out given twice`, `unknown flag ` (exit 2). ## CompileError `{filename, details: [{message, labels: [{start, end, line, column}]}], phase, kind, diagnostics}`. Offsets are UTF-8 bytes, `line` 1-based, `column` 0-based UTF-16. `kind`: `syntax` (parse, semantic analysis, lowering) · `input` (input, policy) · `fuse` · `internal`. ## Policy - JSX runtime fixed to `@fiberfx/hyperscript`; any `@jsx` pragma comment → `policy` refusal. - `.js/.jsx/.ts/.tsx` are ES modules; `.cjs/.cts` keep their mode; `.d.ts` → `declaration files are not executable inputs`. - 256 nesting levels, 1,024 chain links; past either → `input`, before parsing. - No type checking, no optimizer, no import resolution. ## Options `{"sid": {"package", "module"}, "roots": {"id"}}`; unknown keys → `input` (`expected \`sid\` or \`roots\``). - `sid`: key = first 16 hex of SHA-256 of `mhs-sid/1||||:`. It lowers the legacy state package's calls only; not `@fiberfx/hypergraph` yet. - `roots`: dev only. A module importing `render`/`hydrate` from `@fiberfx/hyperscript` gets `trackRoots` from `@fiberfx/hyperscript/hmr` and `hmrRoot: true`; other modules are untouched. ## What mx passes - mx dev: `sid` + `roots {id: }`. - mx build --bundle: `sid` only. - mx build --lib: nothing. ## Never emit `/** @jsx h */` or `/** @jsxImportSource … */` pragmas, a `tsc`/Babel/esbuild step before mx, or a `.d.ts` as an entry. ## Next - MX5 tools: /mx/tools - This companion: /mx/compile/llms.txt ## Navigation - [Read this page in the browser](/mx/compile) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/mx/llms.txt) # mg, Mercury Go — Mercury LMS > Source-based mg course: discover commands, generate design-explorer examples, check drift and consume structured results. ## Choose and start mx installs dependencies, serves, builds and tests applications. mg owns the source-generation workflow taught here. Installing mx does not install mg. From a jonnify checkout root, with Go >= 1.26.0 on PATH: ```sh scripts/mg help alias mg="$PWD/scripts/mg" mg version ``` The launcher builds bin/mg with local Go and runs it in the caller’s directory. Initial builds may download Go modules. On macOS, bare /usr/bin/mg is an editor; use the launcher or alias. No published mg archive/npm installer is supplied by this course. ## Chapters - [First steps](/mg/first-steps/llms.txt): help, identity, completion and setup failures. - [Generate examples](/mg/generate-examples/llms.txt): `mg gen examples`, `--check`, `--root`. - [Automation](/mg/automation/llms.txt): JSON, agent prompts, exit codes and recovery. ## Boundary Check `mg help gen examples` before the generator lesson. A checkout that only provides help/version/completion needs the generator source update. Do not invent scaffolding or rewrite commands. A generation check confirms expected generated files match source; it does not run browser interactions. ## Navigation - [Read this page in the browser](/mg) - [mg](/mg/llms.txt) - [llms.txt](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) # Your first mg commands — Mercury LMS ## Prerequisite and first success Use a POSIX shell, jonnify checkout, Go >= 1.26.0 on PATH. Native Windows setup is outside this recipe. From the repository root: ```sh go version scripts/mg help alias mg="$PWD/scripts/mg" mg version ``` scripts/mg builds bin/mg with local Go, GOWORK=off and CGO_ENABLED=0; it preserves the caller’s working directory. First build may download Go module dependencies. Alias lasts in this shell and identifies this checkout. macOS /usr/bin/mg is an editor, so do not rely on the bare name before creating the alias. ## Help and completion `mg help version` and `mg version --help` show equivalent help. `mg help` exits 0; bare `mg` prints usage on stderr and exits 2. `mg hlep` exits 2, suggests help and runs nothing. For zsh: ```sh autoload -Uz compinit compinit source <(mg completion zsh) ``` For bash: `source <(mg completion bash)`. fish and powershell scripts are also available. Do not source --json output. ## Recover and continue Launcher MG-GO-MISSING/MG-GO-OLD: select sufficient local Go. MG-BUILD-FAILED: inspect compiler/dependency diagnostics. Exit 3 means mg did not run. Development versions may be devel+revision[-dirty] or (devel). Success: help and version exit 0; typo exits 2 with correction. Next: [Examples](/mg/generate-examples/llms.txt), [Automation](/mg/automation/llms.txt). ## Navigation - [Read this page in the browser](/mg/first-steps) - [mg](/mg/llms.txt) - [llms.txt](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) # Generate Mercury examples — Mercury LMS ## Prerequisites Use the source-launcher alias from [First steps](/mg/first-steps/llms.txt). `mg help gen examples` must list --check and --root. If gen is absent, this build needs the generator source update. Requires jonnify’s fiberfx/ui/src and design-explorer tree, not just an app starter. ## Workflow From the checkout root: ```sh git diff -- sites/mercury/playgrounds/design-explorer/generated mg gen examples --check mg gen examples git diff -- sites/mercury/playgrounds/design-explorer/generated mg gen examples --check ``` Inspect existing edits before generation overwrites outputs. First --check may exit 1 with MG-GEN-DRIFT; it writes nothing. Generation reads authored stories and writes adapted modules, catalog.ts and manifest.json under sites/mercury/playgrounds/design-explorer/generated. Successful commands report family/story/module/example counts. Final --check should exit 0; unchanged inputs may produce no Git diff. From elsewhere: `mg gen examples --check --root "$HOME/dev/jonnify"`, replacing the path with the actual checkout. Default root discovery searches upward. ## Recovery and scope - Exit 1 MG-GEN-DRIFT: review listed missing/differing files, regenerate when appropriate, recheck. - Other source refusal: inspect named input; the adapter could not handle it under its rules. - Exit 2: wrong checkout or invocation; correct it. - Exit 3: I/O could not complete; fix file access. Edit authored UI stories, then regenerate. Story play tests are omitted, and a matching catalog does not prove browser behavior. Use explorer build/browser checks separately. Next: [Automation](/mg/automation/llms.txt). ## Navigation - [Read this page in the browser](/mg/generate-examples) - [mg](/mg/llms.txt) - [llms.txt](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) # Automate with clear results — Mercury LMS ## Structured output ```sh mg version --json mg help --json mg version --json --agent ``` --json writes one stdout envelope: ok, command, exit_code, and result on success or error on failure. Version result includes version, revision, go_version and platform. Error includes kind/message and may include id/next/details. Capture stdout without 2>&1: diagnostics may also be emitted on stderr. --agent adds a bounded prompt on stderr in text mode (AGENT_PROMPT_BEGIN/END); JSON mode puts it in agent_prompt. MG_AGENT=1 also enables guidance. Guidance does not execute or authorize next steps. Source-launcher failures can omit agent_prompt/result even when --agent was requested. ## Preserve status ```sh if mg version --json > mg-result.json; then printf 'Version captured\n' else code=$? printf 'mg exited %s\n' "$code" >&2 fi ``` This example reports the status; CI must also propagate a nonzero exit when the step fails. 0 success; 1 refusal (including generator drift); 2 usage error; 3 could not measure; 101 internal fault; 128+n signal. Read the error and apply its specific recovery. Do not mark an unavailable check as passed or rewrite generated files solely because an agent prompt mentions it. ## Check both cases `mg help --json` succeeds. `mg hlep --json` exits 2 and emits a failure envelope with diagnostic guidance. A consumer distinguishes these through status and fields, not English text. Generator-enabled builds can use `mg gen examples --check --json` for read-only drift detection. ## Navigation - [Read this page in the browser](/mg/automation) - [mg](/mg/llms.txt) - [llms.txt](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) # Hyperscript, the virtual DOM — Mercury LMS > `@fiberfx/hyperscript` 4.0.0: Mercury's own virtual DOM runtime. Function and class components, written in JSX (import source `@fiberfx/hyperscript`) or with `h(type, props, ...children)`, rendered with native DOM semantics. Hooks, a client root, a server renderer and test utilities ship as subpaths of one runtime. ## Read this when You write or change a component for a Mercury app or for Mercury UI, or you need to know which subpath exports a name. This page is the course's landing; the chapters are /hyperscript/components, /hyperscript/hooks, /hyperscript/dom, /hyperscript/context, /hyperscript/server, /hyperscript/testing and /hyperscript/tested-component. ## The contract (tier: Shipped — @fiberfx/hyperscript 4.0.0 on npm since 2026-11-05) - `@fiberfx/hyperscript`: `render(vnode, parent)`, `hydrate(vnode, parent)`, `h` = `createElement(type, props, ...children)`, `Fragment`, `createRef()`, `cloneElement`, `isValidElement`, `Component`, `createContext(default)`, `createPortal(children, container)`, `toChildArray`, `memo(component, comparer?)`. Types: `VNode`, `ComponentChild`, `ComponentChildren`, `FunctionComponent`, `ComponentType`, `RenderableProps`, `Ref`, `RefObject`, `Context`, `JSX`. - `@fiberfx/hyperscript/hooks`: `useState`, `useReducer`, `useRef`, `useEffect`, `useLayoutEffect`, `useImperativeHandle`, `useMemo`, `useCallback`, `useContext`, `useDebugValue`, `useErrorBoundary`, `useId`, `useSyncExternalStore`. - `@fiberfx/hyperscript/client`: `createRoot(container)` → `{ render(children), unmount() }`; `hydrateRoot(container, children)`. - `@fiberfx/hyperscript/server`: `renderToString(vnode, context?)` (default export too), `render` (same function), `renderToStaticMarkup` (same function), `renderToStringAsync(vnode, context?)` → `string | Promise`. - `@fiberfx/hyperscript/jsx-runtime`, `/jsx-dev-runtime`: `jsx`, `jsxs`, `jsxDEV`, `Fragment`, for the compiler only. - `@fiberfx/hyperscript/test-utils`: `act`, `setupRerender`, `teardown`, `onCommit`, `onDiff`. `/inspect`: `inspect`, `inspectFailures`. `/debug`: import for development warnings. `/hmr`: `trackRoots`. - `options` from the root is internal; do not use it. ## Minimal example ```tsx import { render } from "@fiberfx/hyperscript"; import { useState } from "@fiberfx/hyperscript/hooks"; function Counter() { const [count, setCount] = useState(0); return ; } const app = document.getElementById("app"); if (app) render(, app); ``` ## Never emit - Imports from `react`, `react-dom`, `preact` or `@preact/*`, or any `/compat` subpath: `@fiberfx/hyperscript/compat` does not exist, and Mercury's components import from `@fiberfx/hyperscript` and its subpaths only. - Hooks imported from the root entry: they live in `@fiberfx/hyperscript/hooks`. - `options` from the root entry. ## Next - The site: /llms.txt - Chapters: /hyperscript/components/llms.txt, /hyperscript/hooks/llms.txt, /hyperscript/dom/llms.txt, /hyperscript/context/llms.txt, /hyperscript/server/llms.txt, /hyperscript/testing/llms.txt, /hyperscript/tested-component/llms.txt - This companion: /hyperscript/llms.txt (the page as Markdown, `/hyperscript.md`, is planned for W3) ## Navigation - [Read this page in the browser](/hyperscript) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) ### Chapter companions - [/hyperscript/components](/hyperscript/components/llms.txt) - [/hyperscript/context](/hyperscript/context/llms.txt) - [/hyperscript/dom](/hyperscript/dom/llms.txt) - [/hyperscript/hooks](/hyperscript/hooks/llms.txt) - [/hyperscript/server](/hyperscript/server/llms.txt) - [/hyperscript/tested-component](/hyperscript/tested-component/llms.txt) - [/hyperscript/testing](/hyperscript/testing/llms.txt) # The DOM contract — Mercury LMS > HS2 of Mercury Hyperscript. Hyperscript writes the DOM's own attribute names and fires the DOM's own events. Rules 1–5 are probed in headless Chromium (`fiberfx/hyperscript/test/browser/dom-contract.test.jsx`, mercury.63); rules 6–7 are Mercury's runtime departures (`README.md` H0e, H0f). ## Read this when You write attributes, event handlers, SVG, inline styles or a form field in a Mercury component, or you port markup written for another library. ## The contract 1. `class` and `for` on HTML and SVG (`className`/`htmlFor` work as DOM properties; write `class`/`for`). 2. Text input/textarea: `onInput` per keystroke; `onChange` on the DOM `change` event (on commit, at latest blur). Select/checkbox: `onChange` per selection/toggle. `onChange` is never mapped to input. 3. SVG names as written: `stroke-width`, `fill-rule`, `clip-path`; `href` without `xlink`. `strokeWidth` is not translated. 4. Bubbling focus: `onFocusIn` / `onFocusOut`. A parent's `onFocus` doesn't fire for a descendant. 5. Custom elements: object prop → property, string → attribute; events via `on` (`onmy-event` → `my-event`, `onionChange` → `ionChange`); a capital after `on` is lowercased. 6. Numeric styles: a number on a length gains `px` (`width: 10` → `10px`), client and `./server` alike; unitless properties (`opacity`, `zIndex`, `flex`, `lineHeight`, grid lines…) and `--custom` properties as given. 7. Controlled inputs: with a `value` or `checked` prop, after the handler and its queued renders, an unwritten value is written back to the prop. Accepted edits untouched; refused text edits restore the caret; refused radio clicks restore the group; nothing during IME composition; nothing for uncontrolled elements (`defaultValue`, `defaultChecked`, or no prop). ## Minimal example ```tsx import { useState } from "@fiberfx/hyperscript/hooks"; export function NameField() { const [name, setName] = useState(""); return (
setName(e.currentTarget.value)} style={{ maxWidth: 320 }} />
); } ``` ## Never emit - `className`, `htmlFor` in new code; `strokeWidth`, `fillRule`, `clipPath`, `xlinkHref` on SVG. - `onChange` to follow each keystroke of a text field. - `onFocus`/`onBlur` on a container to track focus inside it. ## Next - Components and render: /hyperscript/components/llms.txt - Hooks: /hyperscript/hooks/llms.txt - This companion: /hyperscript/dom/llms.txt (the page as Markdown, `/hyperscript/dom.md`, is planned for W3) ## Navigation - [Read this page in the browser](/hyperscript/dom) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hyperscript/llms.txt) # Components and render — Mercury LMS > HS0 of Mercury Hyperscript (`@fiberfx/hyperscript` 4.0.0). Elements are VNodes built by JSX (import source `@fiberfx/hyperscript`) or `h(type, props, ...children)`; a function component returns `ComponentChildren`; `render(vnode, parent)` mounts and re-renders by diffing. ## Read this when You write a component, render a list, forward a ref, or mount a tree into the page. ## The contract - `h(type, props | null, ...children): VNode` — `h` is `createElement`. `type` is a tag name or a component. - `ComponentChild` = `VNode | object | string | number | bigint | boolean | null | undefined`; `ComponentChildren` = one or an array. `null`, `undefined` and booleans render nothing. - JSX: `jsxImportSource: "@fiberfx/hyperscript"` (set in `fiberfx/tsconfig.base.json`, for TypeScript and editors). No per-file pragma: the Hypercompiler fixes the runtime and refuses any `@jsx` comment (/mx/compile). Never import `jsx`/`jsxs`/`jsxDEV` by hand. - `FunctionComponent

`: `(props: RenderableProps

, context?) => ComponentChildren`. Type `children` as `ComponentChildren`. - `Fragment` (JSX `<>…`) groups children with no element. - `key` is removed from props; give list items a stable key (an id when items can move). - `createRef()` → `{ current: T | null }`; a ref may be a callback, which may return a cleanup. A function component receives `ref` as a plain prop and forwards it by passing it on; only tag-name elements consume `ref`. - `class X extends Component`: `render(props?, state?, context?)`, `this.state`, `this.setState(update, callback?)`, `this.forceUpdate(callback?)`, optional lifecycle (`componentDidMount`, `componentDidUpdate`, `componentWillUnmount`, `shouldComponentUpdate`, `getSnapshotBeforeUpdate`, `componentDidCatch`). Mercury UI uses none: prefer functions with hooks. - `render(vnode, parent)`: mounts; a second call on the same parent diffs; `render(null, parent)` clears. `hydrate(vnode, parent)` diffs against server HTML already in `parent`. - `@fiberfx/hyperscript/client`: `createRoot(container)` → `{ render(children), unmount() }`; `hydrateRoot(container, children)` → root. ## Minimal example ```tsx import type { ComponentChildren } from "@fiberfx/hyperscript"; import { render } from "@fiberfx/hyperscript"; function Badge({ children }: { children: ComponentChildren }) { return {children}; } const app = document.getElementById("app"); if (app) render(

    {["a", "b"].map((id) =>
  • {id}
  • )}
, app); ``` ## Never emit - `className` or `htmlFor` in new code: write `class` and `for`. - `forwardRef`: it is not exported; pass `ref` through props. - A mount call from another library: mount with `render`, `hydrate` or `@fiberfx/hyperscript/client`. ## Next - Hooks: /hyperscript/hooks/llms.txt - The DOM contract: /hyperscript/dom/llms.txt - This companion: /hyperscript/components/llms.txt (the page as Markdown, `/hyperscript/components.md`, is planned for W3) ## Navigation - [Read this page in the browser](/hyperscript/components) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hyperscript/llms.txt) # Hooks — Mercury LMS > HS1 of Mercury Hyperscript. The 13 hooks of `@fiberfx/hyperscript/hooks`, with signatures from `src/hooks/index.d.ts`. Hook state is found by call position, so call hooks unconditionally, in the same order, at a component's top level. ## Read this when You add state, a ref, an effect, a memoized value, a context read or an external store to a function component, or you write your own hook. ## The contract Import every hook from `@fiberfx/hyperscript/hooks` (never from the root entry). - `useState(initial: S | (() => S)): [S, (next: S | ((prev: S) => S)) => void]` — a function initial runs once; an `Object.is`-equal next value does not re-render. - `useReducer(reducer, initialState)` / `useReducer(reducer, initialArg, init)`: `[S, Dispatch]`. - `useRef(initial): { current }` — stable for the component's life; writing it does not re-render. - `useImperativeHandle(ref, create, inputs?)` — sets a parent's ref to `create()`. - `useEffect(effect, inputs?)` — after paint; `effect` may return a cleanup. No `inputs`: every render; `[]`: once; else when any input is not `Object.is`-equal or the length changed. The previous cleanup runs before a re-run; all cleanups run on unmount. - `useLayoutEffect(effect, inputs?)` — same arguments, after DOM mutation and before paint; only for layout reads. - `useMemo(factory, inputs: Inputs | undefined): T` and `useCallback(callback, inputs): T` — `inputs` is required by the types. - `useContext(context: Context): T` — nearest `context.Provider`'s value, else the default from `createContext(default)`. A provider re-renders its readers whenever its `value` is a different object: memoize it. - `useSyncExternalStore(subscribe: (onChange) => unsubscribe, getSnapshot: () => T, getServerSnapshot?: () => T): T` — tear-free; `getServerSnapshot` for string renders and hydration. - `useId(): string` — stable per component, for `aria-*` and `for` pairs. - `useErrorBoundary(callback?): [error, reset]` — makes its component catch descendants' errors. - `useDebugValue(value, formatter?)` — a label for debugging tools; no effect otherwise. ## Minimal example ```tsx import { useEffect, useMemo, useState } from "@fiberfx/hyperscript/hooks"; export function Filtered({ items }: { items: string[] }) { const [query, setQuery] = useState(""); const shown = useMemo(() => items.filter((item) => item.includes(query)), [items, query]); useEffect(() => { document.title = `${shown.length} results`; }, [shown.length]); return (
setQuery(e.currentTarget.value)} />
    {shown.map((item) =>
  • {item}
  • )}
); } ``` ## Never emit - A hook inside a condition, a loop, a callback or after an early `return`. - Hooks imported from anywhere but `@fiberfx/hyperscript/hooks`: the root entry has none. - Names this package does not export: `useTransition`, `useDeferredValue`, `useInsertionEffect`, `useActionState`, `useOptimistic`, `use`. - `onChange` for per-keystroke text: use `onInput` (see /hyperscript/dom/llms.txt). ## Next - Components and render: /hyperscript/components/llms.txt - The DOM contract: /hyperscript/dom/llms.txt - This companion: /hyperscript/hooks/llms.txt (the page as Markdown, `/hyperscript/hooks.md`, is planned for W3) ## Navigation - [Read this page in the browser](/hyperscript/hooks) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hyperscript/llms.txt) # Context, portals and memo — Mercury LMS > HS3 of Mercury Hyperscript (`@fiberfx/hyperscript` 4.0.0). `createContext`, `createPortal`, `memo`, `cloneElement`, `toChildArray` and `isValidElement` come from the root entry; `useContext` comes from `./hooks`. ## Read this when You pass a value down a tree, render an overlay outside its ancestors' DOM, skip renders with `memo`, or read or rewrite a component's `children`. ## The contract - `createContext(defaultValue: T): Context`. `Context.Provider` (the context itself) takes `{ value, children }`. `Context.Consumer` takes `{ children: (value: T) => ComponentChildren }`. With no provider above, readers get `defaultValue`. - A provider re-renders every reader when `value` is `!=` its previous value, through `shouldComponentUpdate` false and `memo`. Memoize object values with `useMemo`. - Classes: `static contextType = Ctx`, read as `this.context`. - `createPortal(children, container: ContainerNode): VNode`. Context follows the component tree; native DOM events bubble through the container's DOM. Mercury UI: `` (from `@fiberfx/ui`) takes its host from `EnvironmentProvider`, and with no host renders nothing. - `memo(component, comparer?)`: skips a render when the props are shallowly equal (`!==` per key). `comparer(prev, next)` returns `true` when they are EQUAL. A changed `ref` always renders. `displayName` is `Memo(Name)`. - `cloneElement(vnode, props?, ...children): VNode`: props merge over the element's; `key`/`ref` in props replace its own; children passed replace its children. - `toChildArray(children): Array`: flattens and drops `null`, `undefined` and booleans. `isValidElement(x): x is VNode`. ## Minimal example ```tsx import { createContext, createPortal, memo } from "@fiberfx/hyperscript"; import { useContext } from "@fiberfx/hyperscript/hooks"; const Tone = createContext<"neutral" | "danger">("neutral"); const Toast = memo(function Toast({ text }: { text: string }) { const tone = useContext(Tone); return createPortal(
{text}
, document.body); }); export function Page() { return ; } ``` ## Never emit - `memo` with a comparer that returns `true` to mean "changed": `true` means equal. - A provider `value={{ … }}` object literal for a value that is read often: wrap it in `useMemo`. - A `Children` helper object (`Children.map`, `Children.toArray`): it isn't exported; use `toChildArray`. - `forwardRef`: a function component receives `ref` as a prop. ## Next - Hooks: /hyperscript/hooks/llms.txt - Rendering on the server: /hyperscript/server/llms.txt - This companion: /hyperscript/context/llms.txt (the page as Markdown, `/hyperscript/context.md`, is planned for W3) ## Navigation - [Read this page in the browser](/hyperscript/context) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hyperscript/llms.txt) # Rendering on the server — Mercury LMS > HS4 of Mercury Hyperscript. `@fiberfx/hyperscript/server` renders components to an HTML string with no effects run; `hydrate` / `hydrateRoot` adopt that HTML in the browser. `@fiberfx/render-to-string` 4.0.0 re-exports the same module. ## Read this when You render a page or an island to HTML on a server or in a test, or you attach components to server-rendered markup in the browser. ## The contract - `renderToString(vnode, context?): string`. The default export, `render` and `renderToStaticMarkup` are the same function. `context` is the root's initial legacy context object (for `getChildContext`); `createContext` code omits it. - While rendering to a string, `useEffect` and `useLayoutEffect` callbacks never run, and event handlers are not serialized. `useState` gives its initial value; `useContext` reads providers in the tree. - `useId`: ids are in tree order (`P0-0`, `P0-1`, …), reset per `renderToString` root, and match after hydration. - A component that throws a promise makes `renderToString` throw `Error('Use "renderToStringAsync" for suspenseful rendering.')`. - `renderToStringAsync(vnode, context?)`: always `await` it. It re-renders a suspended component after its promise settles, wraps that output in `…`, and follows at most 25 nested levels. Preview: no test covers it. - Browser: `hydrate(vnode, parent)` from `@fiberfx/hyperscript`, or `hydrateRoot(container, children)` from `@fiberfx/hyperscript/client`, which returns `{ render, unmount }`. Hydrate the same tree with the same props; the server's nodes are kept. - `useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)`: `getServerSnapshot` is read on the server and during hydration; after hydration it re-renders with `getSnapshot`. It subscribes in an effect. ## Minimal example ```tsx // server import { renderToString } from "@fiberfx/hyperscript/server"; const html = renderToString(); // "" // browser import { hydrateRoot } from "@fiberfx/hyperscript/client"; const app = document.getElementById("app"); if (app) hydrateRoot(app, ); ``` ## Never emit - `window`, `document` or `navigator` read during render in a component that renders on the server: read them in `useEffect`, or through `useSyncExternalStore` with a `getServerSnapshot`. - `renderToStringAsync` without `await`. - `./jsx`, `./stream` or `./stream-node` subpaths of `@fiberfx/render-to-string`: they are not exported. ## Next - Context, portals and memo: /hyperscript/context/llms.txt - Testing and inspecting: /hyperscript/testing/llms.txt - This companion: /hyperscript/server/llms.txt (the page as Markdown, `/hyperscript/server.md`, is planned for W3) ## Navigation - [Read this page in the browser](/hyperscript/server) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hyperscript/llms.txt) # Testing and inspecting — Mercury LMS > HS5 of Mercury Hyperscript. `@fiberfx/hyperscript/test-utils` flushes batched renders and after-paint effects for tests; `./inspect` gives detached records of what the renderer does; `./debug` turns on development checks when imported. ## Read this when You write a test for a Mercury component, build a tool that watches renders, or need a clear error for a hook, key or `render` mistake. ## The contract - `act(callback: () => void | Promise): Promise`: runs the callback, then flushes renders and effects until settled. A sync callback is flushed before `act` returns, so no `await` is needed; for an async one, `await act(async () => …)`. Nested calls flush once, at the outermost. - `setupRerender(): () => void`: queued renders wait until you call the returned function. `teardown()` drains the rest and restores the queue; call it after each such test. - `onCommit(listener: (root) => void): () => void`: called after each commit, once per root, with the DOM updated. - `onDiff(listener: () => void): () => void`: test-only, called inside the renderer after each diffed node; a throw stops the render and propagates out of `render`. - `inspect({ root?, phases?, fn }): Subscription`: `phases` ⊆ `root | render | diffed | commit | unmount | error` (an unknown one throws `TypeError`); `root` is a container passed to `render`/`hydrate`. The record is `{ kind, id, parentId, rootId, type, name, error? }`: detached, `structuredClone`-safe, ids stable across re-renders. It returns `dispose()` (also `.unsubscribe()`). `inspectFailures(): number` counts contained subscriber throws. Importing installs nothing. - `import "@fiberfx/hyperscript/debug"` (side effect, development only): it throws on an undefined `render` parent, an undefined component, or a hook outside render, and `console.error`s duplicate keys and improper table/paragraph nesting. It exports `getDisplayName`, `getCurrentVNode`, `getOwnerStack`, `captureOwnerStack`, `setupComponentStack` and `resetPropWarnings`. - Mercury UI's harness, `fiberfx/ui/test/testing.ts`, wraps `render`, `rerender`, `unmount`, `cleanup` and `fireEvent` in `act`; its queries come from `@fiberfx/testing`. ## Minimal example ```tsx import { expect, it } from "vitest"; import { render } from "@fiberfx/hyperscript"; import { useEffect, useState } from "@fiberfx/hyperscript/hooks"; import { act } from "@fiberfx/hyperscript/test-utils"; function Greeting() { const [name, setName] = useState("…"); useEffect(() => setName("Mercury"), []); return

Hello, {name}

; } it("flushes the effect's update", () => { const container = document.createElement("div"); act(() => render(, container)); expect(container.textContent).toBe("Hello, Mercury"); }); ``` ## Never emit - Assertions on the DOM right after a state update outside `act` or `setupRerender`: the render is still queued. - `options` from the root entry to hook into the renderer: use `./inspect`, or `onCommit`/`onDiff` in tests. - `onDiff` in application code: it is test-only. - A testing library for another framework: use `act` from `./test-utils` (Mercury UI's harness builds on it). ## Next - Rendering on the server: /hyperscript/server/llms.txt - Components and render: /hyperscript/components/llms.txt - This companion: /hyperscript/testing/llms.txt (the page as Markdown, `/hyperscript/testing.md`, is planned for W3) ## Navigation - [Read this page in the browser](/hyperscript/testing) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hyperscript/llms.txt) # A tested component — Mercury LMS > HS6, the Mercury Hyperscript capstone. `KindFilter` (`node/apps/echd-os/src/kan/common.tsx`), the kind chips of the KAN squads and messages views, given a written contract and a test: `createRoot` inside `act`, a native click, DOM assertions, `./inspect` commit records, and a server-versus-client markup check. Results are marked: read from an existing test, or not run. ## Read this when You test a Mercury component end to end, or you change `KindFilter` or another KAN view piece in `common.tsx`. ## The contract (KindFilter) - Props: `kinds: readonly SquadKindCount[] | null | undefined` (`{ kind: string; count: number | null }`), `picked: readonly string[]`, `onToggle(kind)`, `onClear()`. It is controlled: no state of its own. - No kinds → renders `null`. - `div.kan-kinds[role=group][aria-label="Filter by kind"]` → one `button.kan-kind[data-kind=][aria-pressed=]` per kind, keyed by the kind, holding a `Chip` (size `sm`, variant `kindTone(kind)`, `selected` while picked) with the kind and its count (`null` → `0`). - A click calls `onToggle(kind)`. The ghost `Button` "All kinds" renders only while `picked` is non-empty and calls `onClear()`. - Used by `SquadsExplorer` (`squads.tsx:91`) and the messages view (`messages.tsx:430`), each with `picked` from its island store. ## The test recipe - Own `picked` in a harness with `useState`, and mount with `createRoot(el)` and `act(() => root.render(...))`. Unmount with `act(() => root.unmount())`. - Drive with `act(() => button.click())`: a native click, settled when `act` returns. - Verified through `SquadsExplorer` (`squads.test.tsx:112-135`): one `.kan-kind` per kind, the text "ask 1", `aria-pressed` "true" after a click, and an "All kinds" button by its text. - Not run: the group label, `fyi 0`, `is-selected` on the chip, clearing, `null` for no kinds, the commit count per click, no button unmount, and `renderToString() === el.innerHTML`. - `inspect({ root: el, phases: ["commit", "unmount"], fn })` → frozen, plain records; `stop()` disposes. - Live Mercury islands don't hydrate: a `data-ssr` render is replaced on the first paint (`fiberfx/live-mercury/src/hooks.ts:68-71`). Compare the markup, not a hydration. ## Minimal example ```tsx import { createRoot } from "@fiberfx/hyperscript/client"; import { act } from "@fiberfx/hyperscript/test-utils"; import { KindFilter } from "./common"; const el = document.createElement("div"); const root = createRoot(el); let toggled = ""; act(() => root.render( (toggled = k)} onClear={() => {}} />)); act(() => el.querySelector(".kan-kind[data-kind='ask']")?.click()); // toggled === "ask" (from the source; not run) act(() => root.unmount()); ``` ## Never emit - Pressed state held inside `KindFilter`: `picked` is the owner's. - DOM assertions outside `act` after a click. - A claimed result for a check no test has run: mark it "not run". ## Next - Testing and inspecting: /hyperscript/testing/llms.txt - Rendering on the server: /hyperscript/server/llms.txt - This companion: /hyperscript/tested-component/llms.txt ## Navigation - [Read this page in the browser](/hyperscript/tested-component) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hyperscript/llms.txt) # Mercury UI, the design system — Mercury LMS > `@fiberfx/ui` 4.0.0: Mercury's design system. 139 components in ten families, written on `@fiberfx/hyperscript`, styled through enum props and one token sheet. Each component has a hand-written contract, `.prompt.md`, beside its source. No React. ## Read this when You build a screen in a Mercury app and need to know which component exists, where it comes from, and how it is styled. This is the course's landing; the chapters follow. ## The contract (tier: Shipped — @fiberfx/ui 4.0.0 on npm since 2026-11-05) - Components: `import { Button, Card } from "@fiberfx/ui"`. For published 4.0.0, load CSS explicitly: `import "@fiberfx/ui/styles.css"` in native apps, or link the pinned stylesheet in [browser HTML](/start/browser/llms.txt). A component import alone does not establish that styling is loaded. - CSS alone: `@fiberfx/ui/styles.css` (everything) or `@fiberfx/ui/tokens.css` (tokens only). - Peers: `@fiberfx/hyperscript` 4.0.0 and `@fiberfx/core` 4.0.0. JSX compiles against `@fiberfx/hyperscript`. - Families: actions (3), foundations (19), inputs (32), forms (8), selection (15), feedback (8), data-display (17), navigation (11), overlay (11), layout (15). - Style a component through its enum props (`variant`, `size`, `tone`); style your own layout with `rgb(var(--token))`. - The `.mx-*` classes (`mx-btn`, `mx-card`, ...) are private: never write them. A component merges your own classes from its `class` prop. - Also re-exported from `@fiberfx/core`: `cx`, `createFormatter`, `createTimeFormatter`, `useFocusManager`, `useLocale`, `useLocaleFormatters`, `useMessageFormatter`, `createLocaleFormatters`, `createMessageFormatter`, `localeDirection`. ## Minimal example ```tsx import { Button, Card } from "@fiberfx/ui"; export function SaveCard() { return ( ); } ``` `Button`: `variant` primary | secondary | outline | ghost | destructive | inverse; `size` sm | md | lg; `loading`, `fullWidth`, `leading`, `trailing`, `type`. `Card`: `variant` flat | raised | floating; `padding`, `title`, `actions`. ## Never emit An authored `.mx-*` class, a literal colour where a token exists, `npm i`, and any import from `react`, `react-dom` or `effector`. ## Next - UI0, tokens and themes: /ui/tokens-and-themes/llms.txt - UI1, foundations: /ui/foundations/llms.txt - UI2, actions and layout: /ui/actions-and-layout/llms.txt - UI3 inputs and forms: /ui/inputs-and-forms - UI4 selection: /ui/selection - UI5 feedback and data display: /ui/feedback-and-data - UI6 navigation: /ui/navigation - UI7 (overlays, and the capstone): planned, no page yet - This companion: /ui/llms.txt (the page as Markdown, `/ui.md`, is planned for W3) ## Navigation - [Read this page in the browser](/ui) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) ### Chapter companions - [/ui/actions-and-layout](/ui/actions-and-layout/llms.txt) - [/ui/feedback-and-data](/ui/feedback-and-data/llms.txt) - [/ui/foundations](/ui/foundations/llms.txt) - [/ui/inputs-and-forms](/ui/inputs-and-forms/llms.txt) - [/ui/navigation](/ui/navigation/llms.txt) - [/ui/selection](/ui/selection/llms.txt) - [/ui/tokens-and-themes](/ui/tokens-and-themes/llms.txt) # UI0, tokens and themes — Mercury LMS > Mercury UI's one token sheet, `fiberfx/ui/src/styles/tokens.css` (also `@fiberfx/ui/tokens.css`), holds every colour, type, radius, spacing and shadow value. The theme is the class `.light-theme` or `.dark-theme` on ``; light is the default on `:root`. ## Read this when You style your own layout in a Mercury app, pick a colour for a state, or switch or set the theme. ## The contract (tier: Shipped — @fiberfx/ui 4.0.0 on npm since 2026-11-05) - Colour tokens are RGB triplets: always `rgb(var(--token))`. Scrims carry alpha already (`--bg-hover: 28 32 36 / 0.06`), still `rgb(var(--bg-hover))`. - Everything else is a complete value: `var(--space-16)`, `var(--radius-8)`, `var(--shadow-200)`, `var(--font-primary)`, `var(--text-body-300-size)`. - Read semantic tokens, not scales: `--bg-*`, `--fg-*`, `--border-*`, `--ring-focus`, `--ring-negative`. Naming: `---`. - Surfaces: `--bg-primary` page, `--bg-secondary` raised, `--bg-tertiary` sunken, `--bg-elevated` popover/modal. Text: `--fg-primary`, `--fg-secondary`, `--fg-tertiary`. Hairline: `--border-primary`. Text on a solid: `--fg-on-`. - Scales: `--slate-1..12`, `--iris-1..12` (brand), `--indigo-1..12` (active, info, link); `--green-`, `--red-`, `--orange-`, `--plum-` at 3, 9, 11. - Spacing `--space-2..128`; radii `--radius-2..32`, `--radius-full`; type `--text-body-100..500`, `--text-heading-100..500` (`-size`, `-lh`); weights `--fw-regular|medium|semi-bold|bold|black`; shadows `--shadow-100..600`. - Focus ring: `box-shadow: 0 0 0 3px rgb(var(--ring-focus))`. - Theme: toggle `dark-theme` / `light-theme` on `document.documentElement`, before first paint. A `.dark-theme` wrapper deeper in the page also works: it re-declares the semantic tokens. ## Minimal example ```css .panel { background: rgb(var(--bg-secondary)); border: 1px solid rgb(var(--border-primary)); border-radius: var(--radius-8); padding: var(--space-16); } ``` ```ts document.documentElement.classList.toggle("dark-theme", dark); document.documentElement.classList.toggle("light-theme", !dark); ``` ## Never emit A literal colour, size, space or radius where a token exists; `var(--bg-brand)` without `rgb()`; `rgb(var(--shadow-200))`; `` as a substitute for Mercury UI theme classes (the documentation shell has a separate data-theme contract); an authored `.mx-*` class. ## Limits In the light theme, `--fg-on-brand` text on `--green-9`, `--orange-9` and `--red-9`, and a glyph on `--orange-9`, are known to fail WCAG AA (`fiberfx/ui/scripts/check-contrast.mjs` LIGHT_DEFECTS). The documentation shell already uses an HttpOnly `theme` cookie, a POST/303 form and server-rendered `data-theme`; with no cookie, CSS follows the system preference. Its switch needs no script. Mercury UI applications use the theme classes described above. ## Next - The course: /ui/llms.txt - UI1, foundations: /ui/foundations/llms.txt - This companion: /ui/tokens-and-themes/llms.txt (the page as Markdown, `/ui/tokens-and-themes.md`, is planned for W3) ## Navigation - [Read this page in the browser](/ui/tokens-and-themes) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/ui/llms.txt) # UI1, where a subtree lives — Mercury LMS > Mercury UI's environment: one inherited context (from `@fiberfx/core`) that every component reads for its portal host, owner document and window, direction, locale, reduced motion, focus fallback and id prefix. `EnvironmentProvider`, `DirectionProvider` and `LocaleProvider` set it and render no DOM; `Portal` reads the host. ## Read this when Mercury renders inside an iframe, an embedded editor or an island on someone else's page; a section runs right to left; you localize labels, numbers, dates or plurals. ## The contract (tier: Shipped — @fiberfx/ui 4.0.0 on npm since 2026-11-05) - `EnvironmentProvider` props: `portalContainer`, `ownerDocument`, `ownerWindow` (each nullable), `direction` (`"ltr" | "rtl"`), `locale` (BCP 47), `reducedMotion` (`"user" | "always" | "never"`, default `"user"`), `focusFallback`, `idPrefix`, `children`. - Merge rule: an omitted prop inherits from the enclosing provider; `null` means unavailable and is never defaulted. No provider: global `document`, its `body`, `ltr`. - A disposed or `null` host: overlays render nothing; focus moves to `focusFallback`. On a server every resource is `null`, so portals render nothing. - Several islands on one page: give each root its own `idPrefix`. - `Portal({ children, container? })`: `container` wins, else the environment's `portalContainer`; no host renders `null`. Context follows the component tree; DOM events bubble through the host. - `DirectionProvider({ dir })`, `dir` required; `useDirection()` → `"ltr" | "rtl"`. - `LocaleProvider({ locale, direction?, catalogs?, fallbackLocales? })`: `locale` required; `direction: "auto"` derives from the script; omitted `catalogs` and `fallbackLocales` inherit. - `useLocale()` → `{ locale, direction, explicit }`. - `useLocaleFormatters({ timeZone, numberOptions, dateOptions, listOptions, collationOptions })` → `formatNumber`, `formatDate`, `formatList`, `plural`, `pluralCategory`, `compare`, `contains`, `startsWith`, `endsWith`. - `useMessageFormatter()` → `format(key, values)`, `resolve(key, values)` (with `source`: `catalog | fallback | bundled | missing`), `missing()`. Chain: locale, its parents, each fallback and its parents, then Mercury's bundled English for `mercury.close`, `mercury.clear`, `mercury.loading`, `mercury.moreActions`, `mercury.selectedCount`. An unknown key renders as the key. - Also exported: `createLocaleFormatters`, `createMessageFormatter`, `localeDirection`, type `MessageCatalogs`. ## Minimal example ```tsx import { LocaleProvider, useLocaleFormatters, useMessageFormatter } from "@fiberfx/ui"; import type { MessageCatalogs } from "@fiberfx/ui"; const catalogs: MessageCatalogs<"cart.items"> = { en: { "cart.items": (v, f) => f.plural(Number(v.count), { one: "# item", other: "# items" }) }, }; function CartLine({ count, total }: { count: number; total: number }) { const f = useLocaleFormatters({ numberOptions: { style: "currency", currency: "EUR" } }); const t = useMessageFormatter(); return

{t.format("cart.items", { count })}, {f.formatNumber(total)}

; } export const Cart = () => ( ); ``` ## Never emit A second locale store or direction context; a provider expected to set `lang` or `dir` (the application sets both on its own root element); `UiEnvironmentContext.Provider` by hand (render `EnvironmentProvider`, which merges); `ReactNode`, `createPortal` from `react-dom`, or any `react` or `effector` import. ## Next - The course: /ui/llms.txt - UI0, tokens and themes: /ui/tokens-and-themes/llms.txt - This companion: /ui/foundations/llms.txt (the page as Markdown, `/ui/foundations.md`, is planned for W3) ## Navigation - [Read this page in the browser](/ui/foundations) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/ui/llms.txt) # UI2, actions and layout — Mercury LMS > Mercury UI's actions, `Button`, `IconButton` and `Link`, and the layout composition tier, `Box`, `Stack`, `Flex`, `Grid`, `Container`, `Spacer`, `GridItem`, `Center`, `Sidebar`, `Cluster`, `Section`, whose spacing and radius props name token-scale steps. ## Read this when You place a press, an icon-only control or an inline link, or lay out a screen without writing CSS for spacing. ## The contract (tier: Shipped — @fiberfx/ui 4.0.0 on npm since 2026-11-05) - `Button`: `variant` primary (default) | secondary | outline | ghost | destructive | inverse; `size` sm | md (default) | lg; `loading` (disables, `aria-busy`, spinner replaces the leading icon); `fullWidth`; `leading`; `trailing`; `type` defaults to `"button"`. - `IconButton`: `icon` and `label` required (`label` → `aria-label` and `title`); `variant` primary | secondary (default) | outline | ghost | destructive; `size` sm | md | lg; `shape` circle (default) | square. - `Icon` glyphs (38): arrow, arrow-up-right, arrow-down-left, check, close, plus, minus, search, star, bell, cog, user, users, home, list, mail, wallet, credit-card, shield, globe, help-circle, chevron-right, chevron-down, alert, info, download, upload, trash, refresh, copy, pause, play, repeat, trending-up, bank, bolt, flow, batch. `IconName` accepts any string: an unknown name draws nothing. - `Link`: `
` when `href` is set and not `disabled`, else ` ); ``` ## Never emit Scale props as numbers or pixels (`gap={12}`, `gap="12px"`: use `gap="12"`); an `IconButton` without `label`; an icon name outside the set; `` expecting an anchor; inline `style` spacing where a scale prop exists; any `react` or `effector` import. ## Next - The course: /ui/llms.txt - UI1, foundations: /ui/foundations/llms.txt - This companion: /ui/actions-and-layout/llms.txt (the page as Markdown, `/ui/actions-and-layout.md`, is planned for W3) ## Navigation - [Read this page in the browser](/ui/actions-and-layout) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/ui/llms.txt) # UI3, inputs and forms — Mercury LMS > Mercury UI's form composition (`Form`, `Field`, `Label`, `Description`, `FieldError`, `Fieldset`, `Legend`, `FormSection`, `ErrorSummary`) and its inputs (`Input`, `Textarea`, `DateField`, `ColorField` and the rest). A Field hands its control one explicit binding; a Form lets an ErrorSummary find its Fields. ## Read this when You build a form: label, help text and error around a control, a group of controls, a summary of problems after a failed submit, or a date or color entry. ## The contract (tier: Shipped — @fiberfx/ui 4.0.0 on npm since 2026-11-05) - `Field` props: `id`, `name`, `label`, `hint`, `error`, `required`, `invalid` (default: `error` present), `disabled`, `readOnly`, `focusTarget`, `class`, `style`, `children` (elements, or `(field) => ...`). - The binding (`FieldContextValue`): `id`, `name`, `labelId`, `descriptionId`, `errorId` (`-label|-description|-error`), `invalid`, `required`, `disabled`, `readOnly`, `focus()`, `getControlProps(own?)`. - `getControlProps({ "aria-describedby"?, "aria-labelledby"? })` → `id` plus, only when set, `name`, `aria-labelledby`, `aria-describedby` (yours first, then hint, then error), `aria-invalid: true`, `required`, `disabled`, `readOnly`. No event handlers. Spread it on the control. - `useFieldContext()` → the binding, or `null`. Read by `SearchField`, `TokenField`, `CheckboxGroup`, `Combobox`, `CustomSelect`, `ListBox`. - One label: on the Field or on the control, never both. - `Description`, `FieldError`: not live regions; give an `id` when used alone. - `Input`: `label`, `hint`, `error`, `leading`, `trailing`, plus input attributes. `Textarea`: `label`, `hint`, `error`, `resizable`, `size`. - `Fieldset`: `legend`, `hint`, `error`, `invalid`, `focusTarget`; a native fieldset. `FormSection`: `title`, `open`/`onOpenChange` or `defaultOpen`; closed content is inert; opened before a focus lands inside. - `Form`: a native form, everything forwarded; `noValidate` is yours to pass; Fields register under `name` and `id`. - `ErrorSummary`: `title` (required), `issues: { field, message, target? }[]`, `focusRequest` (failed-submit counter; each new positive value focuses once), `headingLevel` (2). Empty issues render nothing. - `DateField`: `value`, `defaultValue`, `onChange(date | undefined)`, `onValidityChange` (`empty | incomplete | impossible | unavailable | before-min | after-max | valid`), `minValue`, `maxValue`, `isDateUnavailable`, `name` (submits an ISO date), `readOnly`, `locale` ("en"), `label`. Values: `parseDate("2026-11-30")` or `new CalendarDate(y, m, d)` from `@fiberfx/core`. - `ColorField`: parses on Enter or blur; accepts `#hex`, `rgb()`, `hsl()`, `hsb()`, `color(display-p3 …)`; `editorStates` (`inherit`, `unset`); `tokens` (`{token-id}`); `onChange(value, { reason: commit | token | detach | reset | text })`. ## Minimal example ```tsx import { useState } from "@fiberfx/hyperscript/hooks"; import { Field, Input } from "@fiberfx/ui"; export function TitleField({ error }: { error?: string }) { const [title, setTitle] = useState(""); return ( {(field) => setTitle(event.currentTarget.value)} />} ); } ``` ## Never emit A `label` on both the Field and its Input; cloning children to inject ids; an `aria-live` on a FieldError; a `focusRequest` bumped on every keystroke; a `Date` object as a DateField value; a form-state library inside `Form`; any `react` or `effector` import. ## Next - The course: /ui/llms.txt - UI4, selection: /ui/selection/llms.txt - This companion: /ui/inputs-and-forms/llms.txt (the page as Markdown, `/ui/inputs-and-forms.md`, is planned for W3) ## Navigation - [Read this page in the browser](/ui/inputs-and-forms) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/ui/llms.txt) # UI4, selection — Mercury LMS > Mercury UI's selection controls: `Checkbox`, `Switch`, `Radio` (native inputs, with Root/Control/Indicator parts), `CheckboxGroup`, `RadioGroup`, the `ListBox` collection with its item and section parts, and `Combobox`, a text input over a suggestion ListBox in virtual focus. ## Read this when You let a reader choose: a flag, one of a few, several of a few, one or several of many, or one typed and suggested. ## The contract (tier: Shipped — @fiberfx/ui 4.0.0 on npm since 2026-11-05) - Shared state rule: controlled (`checked`, `value`, `selectedKeys`) is authoritative; a default seeds uncontrolled state and is what a form reset restores. - `Checkbox`: `checked`, `defaultChecked` (`"mixed"` allowed), `onChange(checked)`, `indeterminate`, `label`, `name`, `value`, `disabled`, `required`, `readOnly`, `invalid`, `id`, `form`. Mixed submits as unchecked. - `Switch`: same state props; a native checkbox with `role="switch"`. - `Radio`: `value` (required), `checked`, `onChange(value)`, `label`, `name`, `disabled`, `required`, `invalid`, `id`, `form`. - Parts: `CheckboxRoot` › `CheckboxControl` + `CheckboxIndicator`; `SwitchRoot` › `SwitchControl` + `SwitchIndicator`; `RadioGroupRoot` › `RadioGroupItem` › `RadioGroupControl` + `RadioGroupIndicator`. Indicators are decorative; parts carry `data-state` (`checked | unchecked`). - `RadioGroup`: `items: { value, label?, disabled? }[]`, `value`/`defaultValue`, `onChange(value)`, `orientation`, `accent`, `name`, `required`, `disabled`, `readOnly`, `invalid`, `form`, `aria-label`/`aria-labelledby`. - `CheckboxGroup`: same `items`, `value`/`defaultValue: string[]`, `onChange(keys)`; inside a Field, named by its label. - `ListBox`: `selectionMode` (`single` default | `multiple` | `none`), `selectionBehavior` (`toggle` | `replace`), `selectedKeys` (`null` = controlled empty), `defaultSelectedKeys`, `onSelectionChange(keys)`, `disabledKeys`, `disallowEmptySelection`, `focusMode` (`dom` | `virtual`), `focusedKey`, `onFocusedKeyChange`, `shouldFocusWrap`, `autoFocus`, `escapeKeyBehavior`, `disallowTypeAhead`, `name` (hidden input per selected key), `renderEmptyState`. Keys are `CollectionKey` (`string | number`). - ListBox parts: `ListBoxItem` (`id` required, `textValue`, `disabled`, children or `(state) => ...`), `ListBoxItemText`, `ListBoxItemDescription`, `ListBoxSelectionIndicator`, `ListBoxSection`, `ListBoxLabel`, `ListBoxSeparator`. `getListBoxOptionId(listBoxId, key)`. - `Combobox` › `ComboboxInput` + `ComboboxContent` › `ListBoxItem`s. `selectedKey`/`defaultSelectedKey`, `onSelectionChange` (on commit only: Enter or a press), `inputValue`/`defaultInputValue`/`onInputChange`, `open`/`onOpenChange(open, details)`, `menuTrigger` (`input` | `focus` | `manual`), `actionKeys` + `onAction`, `clearInputOnCommit`, `allowsEmptyCollection`, `getKeyLabel`, `name`. - Combobox never commits on Tab or blur (restores the committed text) and does not filter: render the suggestions you want. - In a Field, ListBox, CheckboxGroup and Combobox take the Field's states and references; the Combobox input takes the Field's id and label. ## Minimal example ```tsx import { useState } from "@fiberfx/hyperscript/hooks"; import { Combobox, ComboboxContent, ComboboxInput, Field, ListBoxItem, useLocaleFormatters } from "@fiberfx/ui"; import type { CollectionKey } from "@fiberfx/ui"; const authors = [{ id: "ada", label: "Ada Lovelace" }, { id: "grace", label: "Grace Hopper" }]; export function AuthorPicker() { const [author, setAuthor] = useState(null); const [text, setText] = useState(""); const { contains } = useLocaleFormatters(); return ( {authors.filter((a) => contains(a.label, text)).map((a) => {a.label})} ); } ``` ## Never emit A `ListBoxItem` without `id`; a selection committed from `onInputChange` or `onFocusedKeyChange`; an `aria-label` on `ComboboxInput` inside a labelled Field; focus moved into the Combobox popup; an indicator part rendered before its control; a `RangeSlider` (it is `Slider` with a two-number value); any `react` or `effector` import. ## Next - The course: /ui/llms.txt - UI3, inputs and forms: /ui/inputs-and-forms/llms.txt - This companion: /ui/selection/llms.txt (the page as Markdown, `/ui/selection.md`, is planned for W3) ## Navigation - [Read this page in the browser](/ui/selection) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/ui/llms.txt) # UI5, feedback and data display — Mercury LMS > Mercury UI's feedback (`Alert`, `Toast` and its parts, `Progress`) and data display (`Card`, `Table`, `DataGrid`), with the rest of both families. All presentational: the application owns timers, sorting, selection state and removal. ## Read this when You tell a reader something happened, show progress, or show records in a card, a table or an editable grid. ## The contract (tier: Shipped — @fiberfx/ui 4.0.0 on npm since 2026-11-05) - `Alert`: `tone` `info` (default) | `success` | `warning` | `danger`; `title` (an h4), children, `actions`, `dismissible` + `onDismiss`. `danger` is `role="alert"`, the rest `role="status"`. It never removes itself. - `Toast`: `tone`, `priority` (`urgent` | `normal`; default urgent for danger), `onDismiss(reason: "close" | "swipe")`, `closeLabel` ("Dismiss"), `swipeDirection` (`left | right | up | down`), `swipeThreshold` (50). Always renders a close control. Owns no timer. - Toast parts: `ToastViewport` (`label`, default "Notifications"; you position it), `ToastTitle`, `ToastDescription`, `ToastAction` (`altText` required: the other way to do it). - `Progress`: `value`, `min` (0), `max` (100), clamped; `indeterminate` states no value; `label` or `aria-labelledby`; `valueText`; `variant` `brand | positive | negative | caution | info`; `size` `sm | md | lg`. Pass the final value: completion is never inferred. - `Card`: `variant` `flat | raised | floating`, `padding` (20), `title`, `actions`. - `Table`: `columns: Column[]` (`key`, `label`, `align`, `render`, `sortable`, `resizable`, `required`, widths), `data`, `getRowKey` (index otherwise), `caption`, `striped`, `emptyState` ("No results"), `loading`; `sort` + `onSortChange` (the table never reorders data); `selectionMode` `none | single | multiple`, `selectedKeys`, `onSelectionChange` (controls disabled without it), `isRowDisabled`, `getRowLabel`; `keyboardNavigation`; `columnOrder`, `hiddenColumns`, `columnWidths`, `onColumnWidthsChange`; `expansion` (`detail` or `treegrid`), `expandedKeys`, `onExpandedChange`. - `DataGrid`: `columns` (`key`, `label`, `editable`, `getValue`, `render`, `align`), `rows` (the window), `getRowKey` (required), `startIndex`/`getRowIndex`, `total` (`null` = unknown), `onCellCommit({ rowKey, column, value })`, `caption`. Arrows, Home/End (Ctrl: first/last row); Enter or F2 edits; Enter commits; Escape cancels. No built-in virtualizer. ## Minimal example ```tsx import { useState } from "@fiberfx/hyperscript/hooks"; import { Table } from "@fiberfx/ui"; import type { Column, TableSort } from "@fiberfx/ui"; type File = { id: string; name: string; size: number }; const columns: Column[] = [{ key: "name", label: "File", sortable: true }, { key: "size", label: "Size", align: "right" }]; export function Files({ files }: { files: File[] }) { const [sort, setSort] = useState(null); const rows = sort ? [...files].sort((a, b) => (a.name < b.name ? -1 : 1) * (sort.direction === "ascending" ? 1 : -1)) : files; return f.id} caption="Files" sort={sort} onSortChange={setSort} />; } ``` ## Never emit A Toast with a built-in timeout prop (there is none); a Toast whose only dismissal is a swipe; `ToastAction` without `altText`; `danger` for emphasis; a Table expected to sort or filter `data` itself; a DataGrid without `getRowKey`; `total` guessed instead of `null`; an import from any UI framework other than `@fiberfx/hyperscript`. ## Next - The course: /ui/llms.txt - UI6, navigation: /ui/navigation/llms.txt - This companion: /ui/feedback-and-data/llms.txt ## Navigation - [Read this page in the browser](/ui/feedback-and-data) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/ui/llms.txt) # UI.6. Navigation — Mercury LMS > Mercury UI's navigation: `Tabs` (facade and `TabsRoot`/`TabsList`/`TabsTrigger`/`TabsPanel`), `Breadcrumbs`, `Tree`, `NavigationTree`, `Menubar` (facade and compound parts) and `Toolbar` with its parts. Tabs, Toolbar, Menubar and Tree use one primary Tab stop and arrows inside, mirrored in RTL. Breadcrumbs and NavigationTree keep native link and button navigation; Mercury does not perform application routing. ## Read this when You build tabs, a breadcrumb trail, an entity tree, a sidebar of route links, a desktop-style menu strip or an editor toolbar. ## The contract (tier: Shipped — @fiberfx/ui 4.0.0 on npm since 2026-11-05) - `Tabs`: `tabs: { label, value, disabled?, content?, panelId? }[]`, `value`, `onChange(value)`, `activationMode` (`automatic` default | `manual`), `orientation`, `dir`, `loop` (true), `keepMounted` (false), `variant` (`underline` | `pills`). - Tabs parts: `TabsRoot` (`value`/`defaultValue`/`onValueChange`, same modes) › `TabsList` (`variant`) › `TabsTrigger` (`value`), plus `TabsPanel` (`value`, `keepMounted`). - `Breadcrumbs` (`label` "Breadcrumbs", `maxItems` ≥ 3, `separator`, `renderLink`, `truncate`, `overflowLabel`, `overflowPanelLabel`) › `Breadcrumb` (`href`, `current` (default: last), `disabled`, `disabledReason`, `id`). Exactly one `aria-current="page"`. - `Tree`: `nodes: TreeNodeData[]` (`id`, `label`, `kind` `leaf | branch | lazy`, `children`, `disabled`), `expandedIds` (controlled) + `onExpandedChange`, `loadChildren`, `selectionMode` (`none` default), `selectedIds`, `onSelectionChange`, `selectionFollowsFocus`, `activeId`/`onActiveChange`, `onActivate` (Enter), `renderLabel`, `renderActions`, `dir`. `TreeItemAction` (`label`) for per-node actions. - `NavigationTree`: `label` (required), `nodes: { id, label, href?, target?, rel?, download?, children? }[]`, `currentId`, `expandedIds`/`defaultExpandedIds` (default: the current route's ancestors), `onExpandedChange`, `onNavigate(href, event)` (host routes and calls `preventDefault`), `renderLabel`, `dir`. - `Menubar`: `menus: { label, icon?, items: { type?, label, id?, group?, value?, checked?, shortcut?, icon?, onSelect?, disabled? }[] }[]`, `accent`, `dir`. Compound: `MenubarRoot` › `MenubarMenuRoot` › `MenubarTrigger` + `MenubarContent` with `MenubarItem`, `MenubarCheckboxItem`, `MenubarRadioGroup`, `MenubarRadioItem`, `MenubarItemIndicator`, `MenubarGroup`, `MenubarLabel`, `MenubarSeparator`, `MenubarSub`, `MenubarSubTrigger`, `MenubarSubContent`. - `Toolbar`: `orientation`, `dir`, `loop` (true), `size` (`sm | md`), `variant` (`surface | plain`), `focusableWhenDisabled`. Parts: `ToolbarButton`, `ToolbarLink`, `ToolbarGroup`, `ToolbarSeparator`, `ToolbarToggleGroup` (`type: "single"` with `value: string`, or `"multiple"` with `value: string[]`) › `ToolbarToggleItem` (`value`). ## Minimal example ```tsx import { Breadcrumb, Breadcrumbs } from "@fiberfx/ui"; export const Trail = () => ( Mercury UI Navigation ); ``` ## Never emit A Tree used for route links (use NavigationTree); a NavigationTree node expected to select; a router call from Breadcrumbs (it never navigates); two current breadcrumbs; `maxItems` below 3; a Tab stop on every toolbar button; Tree expansion assumed to select; an import from any UI framework other than `@fiberfx/hyperscript`. ## Next - The course: /ui/llms.txt - UI5, feedback and data display: /ui/feedback-and-data/llms.txt - This companion: /ui/navigation/llms.txt ## Navigation - [Read this page in the browser](/ui/navigation) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/ui/llms.txt) # Hypergraph, the state kernel — Mercury LMS > `@fiberfx/hypergraph` 4.0.0: Mercury's state kernel. A scheduler and graph executor for a model's units (store, event, effect) and their wiring, with fork scopes, effects and one observation surface. No dependencies, no React. ## Read this when You write or change state in a Mercury app: a store, an event, an effect, their wiring, or a scope for a test or a server render. This page is the course's landing; the API by name follows in the reference. ## The contract (tier: Shipped — @fiberfx/hypergraph 4.0.0 on npm since 2026-11-05) - Units: `store(init, config?)`, `event()`, `effect(handler)`. - Wiring: `sample({clock, source, filter, fn, target})`, `combine`, `split`, `attach`, `merge`, `restore`, and `on`, `reset`, `map`, `filter`, `filterMap`, `prepend`. - Scopes: `fork`, `allSettled`, `scopeBind`, `snapshot`, `apply`, `diff`, `dispose`. - `watch(unit, fn, {scope})`, `kindOf(x)`, `setCarrier`, `HypergraphError`. - Subpaths: `@fiberfx/hypergraph`, `@fiberfx/hypergraph/node` (`nodeCarrier`), `@fiberfx/hypergraph/inspect` (`inspect`, `describe`). `/hyperscript` and `/hmr` are planned and not exported. ## Minimal example ```ts import { event, store } from "@fiberfx/hypergraph"; const increment = event(); export const $count = store(0, { name: "$count" }).on(increment, (count, by) => count + by); ``` ## Never emit `createStore`, `createEvent`, `createEffect`, `createDomain`, `guard`, `forward`, `createApi`, `withFactory`, `is`, and any import from `effector` or `effector-react`. They are not Mercury's names. ## Errors A refusal is a `HypergraphError` with a `code`: `pure-call`, `derived-call`, `derived-target`, `disposed-scope`, `reset-reason`. ## Next - Chapters, in order: /hypergraph/units/llms.txt, /hypergraph/wiring/llms.txt, /hypergraph/scopes/llms.txt, /hypergraph/observing/llms.txt, /hypergraph/errors/llms.txt, /hypergraph/hot-replacement/llms.txt, /hypergraph/server-rendering/llms.txt, /hypergraph/testing/llms.txt, /hypergraph/models/llms.txt, /hypergraph/capstone/llms.txt - Coming from Effector, in order: /hypergraph/coming-from-effector/llms.txt, /hypergraph/coming-from-effector/units-and-wiring/llms.txt, /hypergraph/coming-from-effector/scopes-and-testing/llms.txt, /hypergraph/coming-from-effector/frameworks/llms.txt - The site: /llms.txt - This companion: /hypergraph/llms.txt (the page as Markdown, `/hypergraph.md`, is planned for W3) ## Navigation - [Read this page in the browser](/hypergraph) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) ### Chapter companions - [/hypergraph/capstone](/hypergraph/capstone/llms.txt) - [/hypergraph/coming-from-effector](/hypergraph/coming-from-effector/llms.txt) - [/hypergraph/coming-from-effector/frameworks](/hypergraph/coming-from-effector/frameworks/llms.txt) - [/hypergraph/coming-from-effector/scopes-and-testing](/hypergraph/coming-from-effector/scopes-and-testing/llms.txt) - [/hypergraph/coming-from-effector/units-and-wiring](/hypergraph/coming-from-effector/units-and-wiring/llms.txt) - [/hypergraph/errors](/hypergraph/errors/llms.txt) - [/hypergraph/hot-replacement](/hypergraph/hot-replacement/llms.txt) - [/hypergraph/models](/hypergraph/models/llms.txt) - [/hypergraph/observing](/hypergraph/observing/llms.txt) - [/hypergraph/scopes](/hypergraph/scopes/llms.txt) - [/hypergraph/server-rendering](/hypergraph/server-rendering/llms.txt) - [/hypergraph/testing](/hypergraph/testing/llms.txt) - [/hypergraph/units](/hypergraph/units/llms.txt) - [/hypergraph/wiring](/hypergraph/wiring/llms.txt) # Units: store, event and effect — Mercury LMS > The three kinds of unit in a Hypergraph model, from `@fiberfx/hypergraph` 4.0.0: `store(init, config?)`, `event(config?)`, `effect(handler)`. A store holds a value, an event carries a payload, an effect runs a handler and settles. ## Read this when You declare state, a signal or a side effect in a Mercury model, or you need a unit's methods and members. ## The contract (tier: Shipped — @fiberfx/hypergraph 4.0.0 on npm since 2026-11-05) - `store(init: T, config?)` → `StoreWritable`. Config: `name`, `sid` (authored identity), `skipVoid`, `updateFilter(next, prev)`, `serialize: 'ignore' | {write, read}`, `schema: {id, version}` (shape becomes `@`). - `StoreWritable`: `.on(clock | clock[], (state, payload) => next)`, `.reset(...clocks)`; both return the store. - Every `Store`: `.getState()` (caller's scope, else outside any), `.map(fn, {skipVoid?})` (derived), `.updates` (event), `.watch(fn)` (called with the state now, then each update). - A reducer returning `undefined` is skipped unless `skipVoid: false` (vector `store.on-void-skips`). - `event({name?})` → callable. `.map`, `.filter(fn | {fn})`, `.filterMap` (drop on `undefined`) make derived events; `.prepend(fn)` makes a callable event; `.watch(fn)`. - `effect(handler)` → `(params) => Promise`. Members: `done {params, result}`, `doneData`, `fail {params, error}`, `failData`, `finally {status, params, result | error}`, `pending: Store`, `inFlight: Store`. With no handler a call fails until `fork({handlers})` supplies one. - Declared effects (`{request}`, `{channel, payload?}`, `{timer}`) need `fork({performers})`; performers arrive with H4. - A derived unit can be neither called nor targeted. ## Minimal example ```ts import { effect, event, store } from "@fiberfx/hypergraph"; const increment = event(); export const $count = store(0, { name: "$count" }).on(increment, (n, by) => n + by); export const loadUser = effect(async (id: number) => ({ id, name: "Ada" })); export const $name = store("").on(loadUser.doneData, (_, user) => user.name); ``` ## Never emit `createStore`, `createEvent`, `createEffect`, `store.setState`, `store.off`, `effect.use`, and any import from `effector` or `effector-react`. ## Next - Wiring units together: /hypergraph/wiring (/hypergraph/wiring/llms.txt) - This companion: /hypergraph/units/llms.txt (the page as Markdown, `/hypergraph/units.md`, is planned for W3) ## Navigation - [Read this page in the browser](/hypergraph/units) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hypergraph/llms.txt) # Wiring: units into a graph — Mercury LMS > How `@fiberfx/hypergraph` connects units: `sample` (the one composition primitive), `combine` (stores through a barrier), `split`, `attach`, `merge`, `restore`, and `on`, `reset`, `map`, `filter`, `filterMap`, `prepend` as functions. One launch runs to quiescence, glitch-free. ## Read this when You connect units: derive a value, route a payload, gate an update on a store, or call an effect with params built from state. ## The contract (tier: Shipped — @fiberfx/hypergraph 4.0.0 on npm since 2026-11-05) - `sample({clock?, source?, filter?, fn?, target?, batch?})`. `filter`: `Store` or `(source, clock) => boolean`; `fn: (source, clock) => out`. With `target` it returns the target; without, a new derived unit. A non-unit field in `source` is a constant. - A derived unit as `target` throws `HypergraphError` `derived-target`. - `combine([..] | {..}, fn?, {skipVoid?}?)` or `combine(a, b, …, fn?)` → derived `Store`. One launch updates it once (vector `kernel.diamond-one-update-per-launch`). - `split({source, clock?, match, cases})` → `void`; `match` is `Store`, `(v) => caseName`, or `{case: predicate | Store}`. `split(source, {case: predicate})` → `{case: Event, …, __: Event}` (`__` = no match). - `attach({effect, source?, mapParams?: (params, source) => q})` → new `Effect`; `effect` may be `(source, params) => result`. - `merge(units[])` → `Event`. `restore(unit, init)` → `StoreWritable` of the last payload; single-unit form only. - `on(store, clock, fn)`, `reset(store, ...clocks)`, `map(unit, fn)`, `filter(event, fn)`, `filterMap(event, fn)`, `prepend(event, fn)` equal the unit methods. - Units of two generations cannot be wired together. ## Minimal example ```ts import { event, sample, store } from "@fiberfx/hypergraph"; const submit = event(); const $text = store(""); export const $sent = store([]); sample({ clock: submit, source: { text: $text, sent: $sent }, filter: ({ text }) => text.length > 0, fn: ({ text, sent }) => [...sent, text], target: $sent, }); ``` ## Never emit `guard`, `forward`, `createApi`, `restore({...})` or `restore([...])`, `withFactory`, and any import from `effector`. Use `sample` and `.on` instead. ## Next - Scopes: /hypergraph/scopes (/hypergraph/scopes/llms.txt) - This companion: /hypergraph/wiring/llms.txt (the page as Markdown, `/hypergraph/wiring.md`, is planned for W3) ## Navigation - [Read this page in the browser](/hypergraph/wiring) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hypergraph/llms.txt) # Scopes: fork, settle, hand over — Mercury LMS > A scope is one isolated copy of a model's state in `@fiberfx/hypergraph`. `fork` makes one, `allSettled` steps it, `scopeBind` binds a function to it, `snapshot`/`diff`/`apply` move its frames by sid with each value's shape, `dispose` ends it. ## Read this when You write a test, render on a server, hand state from server to client, or run code from a timer or callback inside a scope. ## The contract (tier: Shipped — @fiberfx/hypergraph 4.0.0 on npm since 2026-11-05) - `Scope` = `{id, gen}`; the handle survives a hot swap and rebinds to the committed generation. - `fork({values?, handlers?, performers?})`. `values`: `[[store, v], …]`, `{sid: v}`, or a `Snapshot`. `handlers`: `[[effect, fn | effect], …]`, scope-local. - `allSettled(unit, {scope, params?})` → resolves when nothing is in flight; for an effect `{status: 'done' | 'fail', value}`. `allSettled(scope)` waits for in-flight work. - `scopeBind(fn, {scope?, safe?})`: binds to `scope` or the caller's; no scope and no `safe` throws. Read a value: `scopeBind(() => $s.getState(), {scope})()`. - `snapshot(scope, {ignore?})` → `{format: 'mhg-scope/1', gen, values: {sid: {shape, schema?, value}}}`: writable stores with a sid that the scope changed or was given; `serialize: 'ignore'` stays out. - `diff(scope, since)` → `{format, gen, from, changed, removed}`. - `apply(scope, snapshot | diff)` → `Miss[]` `{unit: sid, reason: {kind: 'removed'} | {kind: 'shape', from, to}}`; an entry is taken only on the same sid and shape. Misses also go to `inspect` as `reset`. - Shape until the compiler emits one: `runtime:` (e.g. `runtime:number`); with `schema` it is `@`. - `dispose(scope)`: stops work and scoped watches, settles waiting `allSettled`; later `allSettled` or an effect call there rejects `HypergraphError` `disposed-scope`. - A store needs a `sid` to travel in a frame. ## Minimal example ```ts import { allSettled, event, fork, scopeBind, snapshot, store } from "@fiberfx/hypergraph"; const increment = event(); const $count = store(0, { sid: "count" }).on(increment, (n) => n + 1); export async function run() { const scope = fork({ values: [[$count, 3]] }); await allSettled(increment, { scope }); return { now: scopeBind(() => $count.getState(), { scope })(), frame: snapshot(scope) }; } ``` ## Never emit `serialize`, `hydrate`, `fork(domain)`, `createDomain`, and any import from `effector`. Use `snapshot` and `apply`. ## Next - Watching and inspecting: /hypergraph/observing (/hypergraph/observing/llms.txt) - This companion: /hypergraph/scopes/llms.txt (the page as Markdown, `/hypergraph/scopes.md`, is planned for W3) ## Navigation - [Read this page in the browser](/hypergraph/scopes) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hypergraph/llms.txt) # Observing: watch, inspect, describe — Mercury LMS > How code and tools see a `@fiberfx/hypergraph` model. `watch(unit, fn, {scope})` for a unit's updates, owned by its generation; `inspect` and `describe` from `@fiberfx/hypergraph/inspect`, the one observation surface. ## Read this when You react to updates outside the graph, write a test that asserts what happened, or build a log, overlay or DevTools view of a model. ## The contract (tier: Shipped — @fiberfx/hypergraph 4.0.0 on npm since 2026-11-05) - `watch(unit, fn, {scope?})` → `Subscription` (callable; `.unsubscribe()` is the same). Updates only: a store's current state is NOT delivered at once. With `scope`, only that scope's updates; without, the work outside any scope. Ends when its scope's work stops or its generation is retired, whichever first. - `$store.watch(fn)` differs: called with the state now, then each update. - `inspect({scope?, kinds?, fn})` from `@fiberfx/hypergraph/inspect` → `Subscription`. Scoped: that scope only; unscoped: no scoped work. Last unsubscribe stops recording. - `Observation`: `{type, unit, scope, gen, value, cause}`, where `unit` is a sid or run-time identity; additional fields: `fresh?` (declare), `owner?`, `reason?` (reset), `at?` (perform, settle). - `Kind`: `update`, `skip` (filter, equal or undefined update, updateFilter refusal), `fail` (user function threw), `declare` (value: `Declaration {unit, kind, sid?, name?, derived?, owner?, gen}`; unscoped inspectors only), `perform`, `settle`, `reset`. - A `fresh` declare is a new store: information, not a reset. - `describe(reason)` → `removed` | `shape changed from to ` | `renamed (a hint, not applied: it may now be )` | `positional` | `new`. - Types from `/inspect`: `Observation`, `Declaration`, `Kind`, `ResetReason`. ## Minimal example ```ts import { allSettled, event, fork, store, watch } from "@fiberfx/hypergraph"; import { inspect } from "@fiberfx/hypergraph/inspect"; const increment = event(); const $count = store(0, { sid: "count" }).on(increment, (n) => n + 1); export async function run() { const scope = fork(); const stop = watch($count, (n) => console.log("count", n), { scope }); const sub = inspect({ scope, kinds: ["update", "skip"], fn: (o) => console.log(o.type, o.unit) }); await allSettled(increment, { scope }); stop(); sub.unsubscribe(); } ``` ## Never emit `createWatch`, `inspectGraph`, `inspectObserverFailures`, the `Message` type, and any import from `effector` or `effector/inspect`. ## Next - Kinds and errors: /hypergraph/errors (/hypergraph/errors/llms.txt) - This companion: /hypergraph/observing/llms.txt (the page as Markdown, `/hypergraph/observing.md`, is planned for W3) ## Navigation - [Read this page in the browser](/hypergraph/observing) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hypergraph/llms.txt) # Kinds, carriers and errors — Mercury LMS > `kindOf(x)` tells a unit's kind or a scope from anything else; `setCarrier(nodeCarrier())` keeps a scope across an `await` on a JavaScript server; every refusal of `@fiberfx/hypergraph` is a `HypergraphError` with a portable `code`. ## Read this when You branch on what a value is, run Hypergraph on a server with concurrent requests, or handle, test or explain a Hypergraph error. ## The contract (tier: Shipped — @fiberfx/hypergraph 4.0.0 on npm since 2026-11-05) - `kindOf(x)` → `'store' | 'event' | 'effect' | 'scope' | null`; `null` for anything not made by this module. - `setCarrier(carrier | null)`. Default none: an effect called in a scope restores that scope to its caller on settle, so an unrelated call during the await can join it. A carrier (`{current(), run(scope, fn)}`) carries the scope down the chain. - `nodeCarrier()` from `@fiberfx/hypergraph/node` (AsyncLocalStorage). Call `setCarrier(nodeCarrier())` once at server start. Never import `/node` in browser code. - `HypergraphError extends Error`: `name` `"HypergraphError"`, `code: ErrorCode`. Match on `code`, never on the message. - Codes: `derived-call` (derived unit called; thrown), `derived-target` (derived unit as `sample` target or `split` case; thrown at wiring), `pure-call` (unit called inside a reducer, `map`, filter; reported, not thrown), `disposed-scope` (work in a disposed scope; `allSettled` or the effect call rejects), `reset-reason` (bad reset reason at the hot-replacement port; not reachable from app code). - A throw inside any function the graph runs goes to `console.error`, stops that branch, and is observed as `fail`; it never escapes the launch. - Plain `Error` (no code): units of two generations wired together; a non-unit where a unit is expected; `scopeBind` with no scope and no `safe`; `allSettled` on a derived unit; a declared effect with no performer. ## Minimal example ```ts import { HypergraphError, allSettled, dispose, event, fork } from "@fiberfx/hypergraph"; const ping = event(); export async function afterDispose() { const scope = fork(); dispose(scope); try { await allSettled(ping, { scope }); } catch (error) { if (error instanceof HypergraphError && error.code === "disposed-scope") return "fork a new scope"; throw error; } } ``` ## Never emit `is`, `is.store`, `is.scope`, a check of an error's message text, a unit call inside `.on`/`.map`/`filter` (use `sample`), and any import from `effector`. ## Next - The course: /hypergraph (/hypergraph/llms.txt) - This companion: /hypergraph/errors/llms.txt (the page as Markdown, `/hypergraph/errors.md`, is planned for W3) ## Navigation - [Read this page in the browser](/hypergraph/errors) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hypergraph/llms.txt) # Hot replacement: the swap — Mercury LMS > How `@fiberfx/hypergraph` carries a model's state across a code edit: a body-only edit swaps function slots in place; any other edit loads a new generation and keeps each store whose sid and shape match, naming a reason for every one it drops. Built inside the package; `@fiberfx/hypergraph/hmr` and `mx dev`'s delivery are planned. ## Read this when You explain why a value survived or reset after an edit, design a model's stores so they survive edits, or read an `hmr:` log line or a `reset` observation. ## The contract (tier: Planned — built and vector-tested, not exported, not delivered by mx dev; today an edit reloads the page) - Slots mode: every changed module equals the loaded one except function bodies → bodies swapped in place; scope, values, watchers, in-flight effects unchanged. - Swap mode, otherwise: new generation; restore only kept sids; commit; report; dispose the old generation. Watches made by the old generation's code end with it. `Scope` handles move to the committed generation. - Takes part: writable stores with a sid (regions included). Not: events, effects, derived stores, sid-less stores, `serialize: 'ignore'` on either side. - keep: same sid and shape. A changed `init` within one shape keeps the value. - new: sid only in the new generation; information, not a reset. - reset reasons (`ResetReason`): `{kind: 'shape', from, to}`, `{kind: 'removed'}`, `{kind: 'renamed', to}` (hint on the OLD sid, never applied; needs a name shared by exactly one removed and one new store), `{kind: 'positional'}` (positional keys never keep in a changed module). - Shape today: `runtime:`, object keys sorted, e.g. `runtime:object{age:number,name:string}`. Adding a field resets. - Lists are sorted in UTF-16 code-unit order of sid. - Each reset/new → an `inspect` `reset`/`declare fresh` observation and a log line `hmr: : `. - Refusals: `DiffError` `duplicate-sid` | `no-shape`; the update throws, the loaded generation stays live, the caller reloads. An update during an update is refused. ## Minimal example ```ts import { store } from "@fiberfx/hypergraph"; // before: store(0, { sid: "count" }) → shape runtime:number // after: store({}, { sid: "count" }) → shape runtime:object{} // result: reset count, "shape changed from runtime:number to runtime:object{}" export const $count = store({}, { sid: "count" }); export const $user = store(null, { sid: "user" }); // kept ``` ## Never emit An import from `@fiberfx/hypergraph/hmr` (not exported yet), `createRootScope`, `hmrScope`, an "init changed" reason, a rename that moves a value to the new sid, and any import from `effector`. ## Next - Server rendering: /hypergraph/server-rendering (/hypergraph/server-rendering/llms.txt) - This companion: /hypergraph/hot-replacement/llms.txt ## Navigation - [Read this page in the browser](/hypergraph/hot-replacement) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hypergraph/llms.txt) # Server rendering: a scope per request — Mercury LMS > Render a `@fiberfx/hypergraph` model on a server: `fork()` per request, `await allSettled(...)`, `snapshot(scope)` → JSON frame keyed by sid, `dispose(scope)`; on the client `apply(fork(), frame)` or `fork({values: frame})`. On a JavaScript server, `setCarrier(nodeCarrier())` once at startup keeps each request's scope across `await`. ## Read this when You render a Mercury page on a server, hand state to the browser, or debug a value that leaked between concurrent requests. ## The contract (tier: Shipped — @fiberfx/hypergraph 4.0.0 on npm since 2026-11-05) - Server: `const scope = fork({handlers?})`; `await allSettled(event, {scope, params})` waits for every effect the launch started; `const frame = snapshot(scope)`; `dispose(scope)`; send `JSON.stringify(frame)`. - Client: `apply(fork(), frame as Snapshot)` → `Miss[]` (`removed` | `shape {from, to}`); or `fork({values: frame})`, which reports misses as `reset` observations only. - Frame: `{format: 'mhg-scope/1', gen, values: {sid: {shape, schema?, value}}}`. Keys are sids, so it applies across builds. - Travels: writable stores with a sid that the scope changed or was given. Not: sid-less stores, derived stores (client recomputes), `serialize: 'ignore'` stores, stores still at their init. - Non-JSON values: `serialize: {write(v), read(raw)}`; `apply` runs `read`. - Carrier: default none (an effect restores its caller's scope on settle; a stray call during an await can join a suspended scope). `setCarrier(nodeCarrier())` from `@fiberfx/hypergraph/node` carries the scope down each async chain. Package test: leak probe reads global 0 / scope 1 by default, global 1 / scope 0 under the carrier. - Callbacks from outside the chain: `scopeBind(fn, {scope})`. - Never import `@fiberfx/hypergraph/node` in browser code. ## Minimal example ```ts import { allSettled, dispose, fork, snapshot, type Snapshot, apply } from "@fiberfx/hypergraph"; import { pageOpened } from "./model"; export async function render(id: number) { const scope = fork(); await allSettled(pageOpened, { scope, params: id }); const text = JSON.stringify(snapshot(scope)); dispose(scope); return text; } export const resume = (text: string) => apply(fork(), JSON.parse(text) as Snapshot); ``` ## Never emit `serialize(scope)`, `hydrate(...)`, `fork(domain)`, one scope shared across requests, `snapshot` before `allSettled` resolves, and any import from `effector`. ## Next - Testing a model: /hypergraph/testing (/hypergraph/testing/llms.txt) - This companion: /hypergraph/server-rendering/llms.txt ## Navigation - [Read this page in the browser](/hypergraph/server-rendering) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hypergraph/llms.txt) # Testing a model: a scope per test — Mercury LMS > Test a `@fiberfx/hypergraph` model with one `fork()` per test: replace effect handlers per scope, drive with `allSettled`, read with `scopeBind(() => $s.getState(), {scope})()` or `snapshot(scope)`, assert observations with `inspect`. Tests run under `mx test` (headless browser; `vitest` maps to mx's runtime). The conformance vectors in `specs/mercury/vectors/hypergraph/` are the kernel's behaviour as data. ## Read this when You write or fix a test for a Mercury model, need to fake an effect, or want to know exactly what the kernel does in a case (find its vector). ## The contract (tier: Shipped — @fiberfx/hypergraph 4.0.0 on npm since 2026-11-05) - One `fork()` per test. A new scope reads every store's init, even after unscoped calls; a scoped launch never touches the global state. - `fork({values: [[store, v]], handlers: [[effect, fn | effect]]})`: scope-local values and handlers; no module mocking. - `await allSettled(event, {scope, params})`; for an effect it resolves `{status: 'done', value}` or `{status: 'fail', value: error}` (never rejects for a handler failure). - Read: `scopeBind(() => $s.getState(), {scope})()`; `snapshot(scope).values[sid]` → `{shape, value}`; empty `{}` before any change. - `inspect({scope, kinds, fn})` before driving, unsubscribe after. Every update reported once, in launch order. Assert no `fail`: a throwing reducer never throws out of the launch, it only logs and observes `fail`. - `watch($s, fn, {scope})` reports updates only (no initial call). - `mx test`: files `*.test.*` / `*.spec.*` (js, mjs, jsx, ts, mts, tsx); `import {describe, test, expect} from "vitest"`; exit 0 pass, 1 fail or none ran, 3 no browser. Do not use `@fiberfx/hypergraph/node` in these tests. - Vectors: `{format: 'hgv/1', family, vectors}`; id `.`; keys `units`, `wiring`, `steps`, `expect` (`trace` = total order within a launch, `state`, `scopes`, `spies`, `errors` by `{code}`, `inspected`); functions in hgx/1 (`{arg: n}`, `{op, args}`, `{lit}`); `{"$undef": true}`, `{"$error": msg}`. `protocol.json` = Hypergraph's own rules (`observed {by, rule, why}`); `hmr.json` = the swap's diff. ## Minimal example ```ts import { expect, test } from "vitest"; import { allSettled, effect, fork } from "@fiberfx/hypergraph"; const loadUser = effect(async (id: number) => ({ name: `remote ${id}` })); test("loads a user", async () => { const scope = fork({ handlers: [[loadUser, async (id: number) => ({ name: `user ${id}` })]] }); expect(await allSettled(loadUser, { scope, params: 7 })).toEqual({ status: "done", value: { name: "user 7" } }); }); ``` ## Never emit Tests that call units outside a scope and read global state, module mocks for effects (use `handlers`), `effect.use`, `serialize`, `npm test`/`node --test`/Vite runners for app tests (use `mx test`), and any import from `effector`. ## Next - Next chapter: /hypergraph/models (/hypergraph/models/llms.txt) - The course: /hypergraph (/hypergraph/llms.txt) - This companion: /hypergraph/testing/llms.txt ## Navigation - [Read this page in the browser](/hypergraph/testing) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hypergraph/llms.txt) # Models at scale: structure and identity — Mercury LMS > How to lay out an app-sized `@fiberfx/hypergraph` model: one module per feature, callable events in and derived stores out, a unique feature-prefixed `sid` on every store that travels, every outside call in an effect, wiring across features in one graph. No app in the repository imports `@fiberfx/hypergraph` yet; the worked consumers (echd-os, edu-island-kan, analytics) follow their migration slices. ## Read this when You split a model into feature modules, choose sids, decide which stores travel in a frame, or expose a feature's state to other features. ## The contract (tier: Shipped — @fiberfx/hypergraph 4.0.0 on npm since 2026-11-05) - Units belong to the generation of the code that made it; all modules of an app load into one generation; units of two generations cannot be wired together. - Types draw the surface: `EventCallable` callable, derived `Event` not; `StoreWritable` has `.on`/`.reset`, `Store` not. Exporting a writable store as `Store` is a compile-time fence only. - Run time: calling a derived unit → `HypergraphError` `derived-call`; derived `target` → `derived-target`. - Sids: unique per generation. Module loading through the port refuses a sid claimed twice (in one module or across modules), naming both; the swap's diff refuses `duplicate-sid`. Plain `store()` calls are not checked; a frame looks a repeated sid up as its first store; the second's fate is unstated — never rely on it. Prefix by feature (`cart/items`). - Effects: outside calls only in effects; `fork({handlers})` replaces them per scope; `attach({effect, source, mapParams})` builds params from state; a handler marks the effect JS-only, a declared spec is data (preview). - Travels: writable + `sid` + not `serialize: 'ignore'`; non-JSON values need `serialize: {write, read}`; derived stores never travel. The same stores are the ones a hot swap keeps or resets. ## Minimal example ```ts import { event, store, type Store } from "@fiberfx/hypergraph"; export const itemAdded = event<{ id: string; price: number }>(); const $items = store<{ id: string; price: number }[]>([], { sid: "cart/items" }) .on(itemAdded, (items, item) => [...items, item]); export const items: Store<{ id: string; price: number }[]> = $items; export const total = $items.map((list) => list.reduce((sum, item) => sum + item.price, 0)); ``` ## Never emit A sid shared by two stores, a store meant to travel without a `sid`, a fetch or timer inside `.on`/`.map`/`sample`'s `fn`, `withFactory`, `createDomain`, and any import from `effector` or `@fiberfx/hypergraph/hmr`. ## Next - The capstone: /hypergraph/capstone (/hypergraph/capstone/llms.txt) - This companion: /hypergraph/models/llms.txt ## Navigation - [Read this page in the browser](/hypergraph/models) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hypergraph/llms.txt) # Capstone: a model, round-tripped — Mercury LMS > One `@fiberfx/hypergraph` model built from the exports, run in a `fork()`, its frame taken with `snapshot`, applied into a fresh scope with `fork({values: frame})`, proven equal with `diff(replica, frame)`, then kept in step with `apply(replica, diff(scope, frame))`. Expected values are read from the package's own test, `fiberfx/hypergraph/test/api-types.test.ts`. ## Read this when You need a complete, correct pattern for a model plus its test with a snapshot round-trip, e.g. server-to-client handoff or a replica kept current by diffs. ## The contract (tier: Shipped — @fiberfx/hypergraph 4.0.0 on npm since 2026-11-05) - Only writable stores with a `sid` enter a frame; derived stores recompute on the replica. - `await allSettled(event, {scope, params})` settles effects started by `sample` too. - `snapshot(scope)` → `{format: 'mhg-scope/1', gen, values: {sid: {shape, value}}}`. - `fork({values: frame})` = new scope + apply the frame. - Equality: `diff(replica, frame)` → `changed: {}` (no entry new or different) and `removed: []` (no frame entry missing). `changed` is asserted by the package's test; `removed` is read from the kernel, not run. - Step: `apply(replica, diff(scope, since))`. - A call outside any scope (`add(5)`) changes only the global state, never a scope. ## Minimal example ```ts import { expect, test } from "vitest"; import { allSettled, apply, diff, event, fork, scopeBind, snapshot, store } from "@fiberfx/hypergraph"; const add = event(); const $count = store(0, { sid: "count" }).on(add, (n, by) => n + by); const read = (scope: ReturnType) => scopeBind(() => $count.getState(), { scope })(); test("round-trips its frame", async () => { const scope = fork(); await allSettled(add, { scope, params: 3 }); const frame = snapshot(scope); // values.count = { shape: "runtime:number", value: 3 } const replica = fork({ values: frame }); expect(read(replica)).toBe(3); expect(diff(replica, frame).changed).toEqual({}); await allSettled(add, { scope, params: 1 }); apply(replica, diff(scope, frame)); expect(read(replica)).toBe(4); }); ``` ## Never emit `serialize`, `hydrate`, a store without a `sid` expected to travel, a comparison of `getState()` outside the scope as proof of the replica, and any import from `effector` or `@fiberfx/hypergraph/hmr`. ## Next - The course: /hypergraph (/hypergraph/llms.txt) - This companion: /hypergraph/capstone/llms.txt ## Navigation - [Read this page in the browser](/hypergraph/capstone) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hypergraph/llms.txt) # Coming from Effector — Mercury LMS > Where each Effector concept lives in Hypergraph (`@fiberfx/hypergraph` 4.0.0), Mercury's state kernel. Rule: a name changes only when its contract or its IR kind changed. Unit constructors are renamed, composition is kept, the scope speaks frames, and domains, `guard`, `forward`, `createApi`, `is` and `withFactory` are retired. ## Read this when You are translating Effector code or Effector knowledge into a Mercury model, or a user asks "what is X from Effector in Mercury?". This is the only page on the site that uses Effector's names; never emit them as code. ## The map (Effector → Hypergraph) | Effector | Hypergraph | |---|---| | `createStore(init, cfg)` | `store(init, cfg)`; cfg adds `schema` | | `createEvent()` | `event({name?})` | | `createEffect(fn)` | `effect(fn)` or `effect(spec)` | | `sample` `combine` `split` `attach` `merge` `restore` | same names | | `.on` `.reset` `.map` `.getState` `.updates` `.watch` (store) | same | | `.map` `.filter` `.filterMap` `.prepend` `.watch` (event) | same | | `.done` `.doneData` `.fail` `.failData` `.finally` `.pending` `.inFlight` | same | | `fork({values, handlers})` | `fork({values, handlers, performers})`; `values` may be a snapshot | | `allSettled` | same (unit + `{scope, params}`, or a scope alone) | | `scopeBind(unit, cfg)` | `scopeBind(fn, cfg)` | | `serialize(scope)` | `snapshot(scope)` → `{format: "mhg-scope/1", gen, values: {[sid]: {shape, value}}}` | | `hydrate(scope, {values})` | `apply(scope, frame)` → misses | | — | `diff(scope, since)`, `dispose(scope)` | | `createWatch({unit, fn, scope})` | `watch(unit, fn, {scope})` | | `is.store(x)` etc. | `kindOf(x) === "store"` | | `guard` | `sample({clock, source, filter, target})` | | `forward({from, to})` | `sample({clock: from, target: to})` | | `createApi` | one `event` + `.on` per key | | `inspect`, `inspectGraph` | `inspect({scope?, kinds?, fn})` from `@fiberfx/hypergraph/inspect` | | `useUnit`, `Provider` | not exported yet (planned at `/hyperscript`) | ## No equivalent `createDomain` and domain hooks, `fork(domain)`, `hydrate(domain)`, `withFactory`, `withRegion`, `createNode`, `launch`, `step`, `clearNode`, `fromObservable`, `setStoreName`, `store.setState`, `store.off`, `store.reinit`, `effect.use`, `useStoreMap`, `useList`, `useGate`, `createGate`, `createComponent`, `connect`. Rewrite the code; do not invent a shim. ## Minimal example ```ts import { event, sample, store } from "@fiberfx/hypergraph"; const increment = event(); const saved = event(); export const $count = store(0, { name: "$count" }).on(increment, (n, by) => n + by); sample({ clock: increment, source: $count, target: saved }); ``` ## Never emit Any import from `effector`, `effector-react` or `@fiberfx/effector`; `createStore`, `createEvent`, `createEffect`, `createDomain`, `guard`, `forward`, `createApi`, `withFactory`, `is`, `serialize`, `hydrate`, `createWatch`. ## Next - /hypergraph/coming-from-effector/units-and-wiring/llms.txt - /hypergraph/coming-from-effector/scopes-and-testing/llms.txt - /hypergraph/coming-from-effector/frameworks/llms.txt - /hypergraph/llms.txt ## Navigation - [Read this page in the browser](/hypergraph/coming-from-effector) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hypergraph/llms.txt) # Units and wiring (coming from Effector) — Mercury LMS > Effector's units and composition in Hypergraph (`@fiberfx/hypergraph`): `store`/`event`/`effect` replace the `create*` constructors; `sample`, `combine`, `split`, `attach`, `merge`, `restore` and the unit methods retain their names, but restore supports only the single-unit form; `guard`, `forward` and `createApi` are rewritten with `sample` and `.on`. ## Read this when You are porting an Effector model's units or wiring, or writing one from Effector habits. ## Units | Effector | Hypergraph | |---|---| | `createStore(init, {name, sid, serialize, updateFilter, skipVoid})` | `store(init, {…same, schema?})` | | `$s.on` `.reset` `.map` `.getState` `.updates` `.watch` | same | | `$s.setState` `.off` `.reinit` | not in the API: use an event + `.on` / `.reset` | | `createEvent()` | `event({name?})` | | `ev.filter({fn})` | `ev.filter(fn)` or `ev.filter({fn})` | | `createEffect(handler)` | `effect(handler)` | | `fx.use(handler)` | not in the API: `fork({handlers: [[fx, handler]]})` | | — | `effect({request} \| {channel} \| {timer})`: declared; needs `fork({performers})` until H4 | Notes: `$s.watch(fn)` fires now and on updates; `watch($s, fn)` fires on updates only. `getState()` reads the caller's scope. `sid` is the compiler's job by design, but today its sid pass covers only the legacy facade: write `sid` on a store that must travel or survive an edit (an unnamed unit gets a run-time `api:`). ## Wiring `sample({clock, source, filter, fn, target, batch})` — returns `target`, or a new unit without one. `combine(shape, fn?)` or `combine($a, $b, fn?)` — non-store fields are constants. `split({source, clock?, match, cases})`, or `split(source, match)` → `{[case]: Event, __: Event}`. `attach({effect, source?, mapParams?})` — `effect` may be `(source, params) => …`. `merge([units])`, `restore(unit, init)`. Also as functions: `on`, `reset`, `map`, `filter`, `filterMap`, `prepend`. ## Rewrites | Effector | Hypergraph | |---|---| | `guard({clock, filter, target})` | `sample({clock, filter, target})` | | `forward({from, to})` | `sample({clock: from, target: to})` | | `createApi($s, {a: fnA, b: fnB})` | `const a = event(); $s.on(a, fnA)` per key | ## Minimal example ```ts import { effect, event, sample, store } from "@fiberfx/hypergraph"; const fetchUserFx = effect(async (id) => `user ${id}`); const opened = event(); const $enabled = store(true); export const $user = store(null).on(fetchUserFx.doneData, (_, u) => u); sample({ clock: opened, filter: $enabled, target: fetchUserFx }); ``` ## Errors `HypergraphError.code`: `pure-call` (a pure fn called a unit), `derived-call`, `derived-target`, `disposed-scope`, `reset-reason`. ## Never emit `createStore`, `createEvent`, `createEffect`, `guard`, `forward`, `createApi`, `.setState`, `.use`, `.off`, `.reinit`, or any import from `effector`. ## Navigation - [Read this page in the browser](/hypergraph/coming-from-effector/units-and-wiring) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hypergraph/llms.txt) # Scopes and testing (coming from Effector) — Mercury LMS > Effector's scope model in Hypergraph (`@fiberfx/hypergraph`): `fork`, `allSettled`, `scopeBind` keep their names; `serialize` → `snapshot` (a frame), `hydrate` → `apply` (returns misses); `diff` and `dispose` are new; no domains; no `scope.getState`. ## Read this when You are porting Effector tests, SSR serialization or scope handling, or reading a store's value in a scope. ## The map | Effector | Hypergraph | |---|---| | `fork({values, handlers})` | `fork({values, handlers, performers})` | | `fork(domain, cfg)` | none: `fork(cfg)` | | `values: [[$s, v]]` or `{[sid]: v}` | same, or a `snapshot` frame | | `handlers: [[fx, fn]]` | same (`fn` or another effect) | | `allSettled(unit, {scope, params})`, `allSettled(scope)` | same | | `scope.getState($s)` | `scopeBind(() => $s.getState(), {scope})()` | | `scopeBind(unit, {scope, safe})` | `scopeBind(fn, {scope, safe})` | | `serialize(scope, {ignore})` | `snapshot(scope, {ignore})` | | `serialize(...)[sid]` | `snapshot(...).values[sid].value` | | `serialize(..., {onlyChanges})` | no option | | `hydrate(scope, {values})` → void | `apply(scope, frame)` → `[{unit, reason: {kind: "removed"} \| {kind: "shape", from, to}}]` | | — | `diff(scope, since)` → `{format, gen, from, changed, removed}` | | — | `dispose(scope)`; later calls → `HypergraphError` `disposed-scope` | | `createWatch({unit, fn, scope})` | `watch(unit, fn, {scope})` | Frame: `{format: "mhg-scope/1", gen, values: {[sid]: {shape, schema?, value}}}`. Only stores with a sid appear; today a sid is written by hand (the compiler's sid pass covers only the legacy facade). ## Minimal example (a test) ```ts import { allSettled, effect, fork, scopeBind, store } from "@fiberfx/hypergraph"; const fetchUserFx = effect(async (id) => `real ${id}`); const $user = store(null).on(fetchUserFx.doneData, (_, u) => u); const scope = fork({ handlers: [[fetchUserFx, async (id: number) => `stub ${id}`]] }); await allSettled(fetchUserFx, { scope, params: 7 }); scopeBind(() => $user.getState(), { scope })(); // "stub 7" ``` ## The scope across an await Default (browser): as Effector, a settled effect restores its caller's scope, so an unrelated call during the await can join it; capture with `scopeBind`. Node: `setCarrier(nodeCarrier())`, with `nodeCarrier` from `@fiberfx/hypergraph/node`. ## Never emit `serialize`, `hydrate`, `fork(domain)`, `scope.getState`, `createWatch`, `fx.use`, or any import from `effector`. ## Navigation - [Read this page in the browser](/hypergraph/coming-from-effector/scopes-and-testing) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hypergraph/llms.txt) # Frameworks, after effector-react — Mercury LMS > Mercury's only view layer is Mercury Hyperscript (`@fiberfx/hyperscript`, hooks at `@fiberfx/hyperscript/hooks`). The Hypergraph bindings `useUnit` and `Provider` are planned at `@fiberfx/hypergraph/hyperscript` and NOT exported yet. Until then, read a store with `useSyncExternalStore` + `watch` + `getState`, unscoped. ## Read this when You are porting a component that used effector-react, effector-vue or effector-solid, or you need a store's value in a Hyperscript component. ## The map | effector-react | Mercury | |---|---| | `useUnit`, `Provider` | planned at `@fiberfx/hypergraph/hyperscript`; not importable today | | `useStore` | none (deprecated for `useUnit`) | | `useEvent` | none | | `useStoreMap` | none: derive with `.map` / `combine`, then read | | `useList` | none: map the array in JSX | | `useGate`, `createGate`, `connect`, `createComponent` | none | | effector-vue, effector-solid | none | Hyperscript hooks: `useState`, `useReducer`, `useRef`, `useEffect`, `useLayoutEffect`, `useCallback`, `useMemo`, `useContext`, `useImperativeHandle`, `useDebugValue`, `useErrorBoundary`, `useId`, `useSyncExternalStore`. ## Minimal example (today, outside any scope) ```tsx import { useSyncExternalStore } from "@fiberfx/hyperscript/hooks"; import { event, store, watch, type Store } from "@fiberfx/hypergraph"; const increment = event(); const $count = store(0).on(increment, (n, by) => n + by); const useStoreValue = (unit: Store): T => useSyncExternalStore((onChange) => watch(unit, onChange), () => unit.getState()); export function Counter() { const count = useStoreValue($count); return ; } ``` Limit: no scope reaches the component this way; scoped rendering (SSR, per-island scopes) waits for `Provider`. ## Never emit Imports from `react`, `effector-react`, `effector-vue`, `effector-solid` or `@fiberfx/hypergraph/hyperscript` (not exported yet); `useStore`, `useStoreMap`, `useList`, `useGate`. ## Navigation - [Read this page in the browser](/hypergraph/coming-from-effector/frameworks) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/hypergraph/llms.txt) # Reflect. From model to view — Mercury LMS > Contract: `@fiberfx/reflect` 4.1.1; Hypergraph and Hyperscript 4.1.1. Published target: `@fiberfx/reflect@4.1.1`; peers Hypergraph/Hyperscript 4.1.1. Source revision: `20dad1543358ab293c61b004a1f5f2577915798b`. ## Learning route 1. /reflect/binding — reactive props, callbacks, createReflect, fromTag. 2. /reflect/projections — flat store sources, base props, own overrides. 3. /reflect/scopes — one model, isolated Provider scopes. 4. /reflect/lists — stable keys, mapItem replacement, local state. 5. /reflect/variants — string/boolean branches and explicit fallback. 6. /reflect/components — stable registries, callable identity, constructor replacement. 7. /reflect/lifecycle — lifetime-captured props, cleanup, SSR silence. 8. /reflect/testing — native mx browser regression with @fiberfx/testing. ## Contract to preserve Only five runtime root exports: reflect, createReflect, list, variant, fromTag. mapProps is configuration. Incoming props override bindings/projections, including explicit undefined. bind/mapItem wrap functions in a scope; mapProps preserves callable values unchanged. Never generate proposed lifecycle, selector, slot or preset APIs. ## Exercises Each HTML chapter links its own browser lab mode. Predictions and observation prompts are not proof of a passing learner test. Do not infer runtime verification from the presence of this text. ## Navigation - [HTML](/reflect) - [Reflect](/reflect/llms.txt) - [Index](/llms.txt) - [Sitemap](/sitemap.xml) - [All English companions](/llms-full.txt) # Reflect.1. Binding a view — Mercury LMS > Contract: `@fiberfx/reflect` 4.1.1; Hypergraph and Hyperscript 4.1.1. ## Use Bind an ordinary Hyperscript component or `fromTag("input")` with `reflect({view, bind})`. `createReflect(view)(bind, options?)` fixes the view. Define models/factories outside render. ## Contract Stores become values. Events/effects and raw bound functions become scoped callbacks. Incoming props win, including a supplied handler; no handler chaining or class merging. Input onInput receives Event, Checkbox onChange receives boolean. Adapt arguments explicitly. Never bind event as a native input handler. ## Exercise Use [binding lab](/examples/reflect-lab/index.html?part=binding&lang=en). Change the draft name, apply it, then submit an empty name: the model sample filter retains the applied value. Change the count independently; reason about count:99 as a separate display override. Reset and repeat. Type-negative example must be checked by TypeScript, not presented as runtime payload validation. ## Source `fiberfx/reflect/src/reflect.ts`, `src/from-tag.ts`, `README.md`; release 20dad1543358ab293c61b004a1f5f2577915798b, npm 4.1.1. ## Navigation - [HTML](/reflect/binding) - [Reflect](/reflect/llms.txt) - [Index](/llms.txt) - [Sitemap](/sitemap.xml) - [All English companions](/llms-full.txt) # Reflect.2. Deriving props — Mercury LMS > Contract: `@fiberfx/reflect` 4.1.1; Hypergraph and Hyperscript 4.1.1. ## Contract mapProps is a config field, not an export. Sources are Store, flat object/tuple of stores, or empty object. fn(values, baseProps) is pure and synchronous. All projections receive bound/incoming props, never sibling outputs. Mapped keys are optional in that input type. ## Overrides and refusals An own prop, including explicit undefined, skips the matching fn. Omission retains derivation. bind/mapProps overlap throws; non-store and nested sources refuse. Do not infer subscription elimination from projection suppression. Returned callables are unchanged and unscoped. ## Exercise [Projection lab](/examples/reflect-lab/index.html?part=projections&lang=en): name and prefix, fixed override, undefined override, remove override, reset. Transfer to a count-based status line. Optional binding keys do not guarantee required props; prefer satisfies when preserving literal keys. ## Source `fiberfx/reflect/src/reflect.ts:32-62`, `src/types.ts:47-63`, `test/collections.test.ts:24-88`; npm 4.1.1. ## Navigation - [HTML](/reflect/projections) - [Reflect](/reflect/llms.txt) - [Index](/llms.txt) - [Sitemap](/sitemap.xml) - [All English companions](/llms-full.txt) # Reflect.3. Keeping scopes separate — Mercury LMS > Contract: `@fiberfx/reflect` 4.1.1; Hypergraph and Hyperscript 4.1.1. ## Contract Use the existing `Provider` from @fiberfx/hypergraph/hyperscript. Reflect owns neither scope creation nor disposal. setScopeMode("required") refuses a missing Provider in the shared binding. Unmount before disposing owned scopes. ## Isolation One model, two fork() scopes. Bind callbacks through Reflect. Left 2, right 1; scoped left reset yields 0 and 1. Separately assert global state unchanged. A previously bound callback keeps its originating scope under a nested Provider. A child's own useUnit reads its rendered Provider. ## Refusals and limits Test missing Provider in isolation. Raw external event calls do not become scoped because a Provider exists on the page. Do not generalize the awaited-Hypergraph-effect test to all foreign async boundaries. ## Exercise [Scopes lab](/examples/reflect-lab/index.html?part=scopes&lang=en). Per-panel reset changes its model; footer restart recreates the whole stage. Source: reflect.ts:108-117, composition.test.ts:80-105; npm 4.1.1. ## Navigation - [HTML](/reflect/scopes) - [Reflect](/reflect/llms.txt) - [Index](/llms.txt) - [Sitemap](/sitemap.xml) - [All English companions](/llms-full.txt) # Reflect.4. Preserving keyed rows — Mercury LMS > Contract: `@fiberfx/reflect` 4.1.1; Hypergraph and Hyperscript 4.1.1. ## Contract list({source, view, getKey?, mapItem?, bind?, mapProps?, hooks?}) returns a component. Source holds a readonly-compatible array. Stable keys preserve row DOM/local state; index is positional identity when getKey is absent. mapItem replaces item props. Functions it returns are scope-bound; raw function-valued direct item props are also wrapped. Required row props must be covered. mapItem/mapProps overlap refuses. Union views require complete items or one correlated item prop. ## Keys and refusals String normalization makes 1 and "1" duplicates, but leaves "01" distinct. Reject invalid keys, non-array state, primitive items without mapping. Verify package failures in a test. The [lab](/examples/reflect-lab/index.html?part=lists&lang=en) refuses duplicates in an application preflight, not by catching list's renderer exception. ## Observation Check Alpha, reverse, remove Beta: Alpha stays checked and changes position. Assert DOM identity separately. Removing and restoring a row starts fresh local state. Whole-exercise restart clears local state; merely resetting model data need not. ## Navigation - [HTML](/reflect/lists) - [Reflect](/reflect/llms.txt) - [Index](/llms.txt) - [Sitemap](/sitemap.xml) - [All English companions](/llms-full.txt) # Reflect.5. Choosing a view — Mercury LMS > Contract: `@fiberfx/reflect` 4.1.1; Hypergraph and Hyperscript 4.1.1. ## Contract String form: variant({source, cases, default?}); boolean form: variant({if, then, else?}). Both accept bind/mapProps/hooks. Missing case or false-without-else renders fallback or null. Only own case names resolve; constructor is not inherited as a view. ## Lifetime The reflected wrapper owns variant hooks. Changing the selected case does not rerun those hooks, although the selected child may be replaced. Case-specific work belongs to the case component's lifetime. ## Exercise and boundary [Variant lab](/examples/reflect-lab/index.html?part=variants&lang=en): ready → empty → error → constructor fallback; reset. Unknown fallback is supported selection behavior, not a runtime validation exception or proof of an actual request/retry. ## Source variant.ts:10-61; variant.test.ts:52-76; collections.test.ts:77-80. npm 4.1.1. ## Navigation - [HTML](/reflect/variants) - [Reflect](/reflect/llms.txt) - [Index](/llms.txt) - [Sitemap](/sitemap.xml) - [All English companions](/llms-full.txt) # Reflect.6. Replacing components — Mercury LMS > Contract: `@fiberfx/reflect` 4.1.1; Hypergraph and Hyperscript 4.1.1. ## Supported pattern Stable module-level registry; store only selection identifier; mapProps returns constructor unchanged. Use ComponentType for function/class views. Per-row pattern: mapItem wraps item, mapProps RowView uses source:{} and props.item.kind. ## Callable semantics bind/mapItem scope-wrap raw functions. mapProps preserves constructor and callback-ref identity. Projected event/effect retains methods but is not scoped for invocation. Child useUnit follows the Provider where it renders; an outer bound action keeps its outer scope. ## Observation [Components lab](/examples/reflect-lab/index.html?part=components&lang=en): increment Compact, switch to Detailed, return. Each constructor change starts fresh local state. Same key plus same constructor can preserve a keyed child's state; same key plus different constructor cannot transfer it. ## Refusals Do not manufacture constructor functions inside projections or put them in bind/mapItem. Check registry own properties and reject/fallback unknown names. No shipped selector/slot/preset descriptor is involved. Source: composition.test.ts:43-138 and Reflect.prompt.md:42-55; npm 4.1.1. ## Navigation - [HTML](/reflect/components) - [Reflect](/reflect/llms.txt) - [Index](/llms.txt) - [Sitemap](/sitemap.xml) - [All English companions](/llms-full.txt) # Reflect.7. Following a lifetime — Mercury LMS > Contract: `@fiberfx/reflect` 4.1.1; Hypergraph and Hyperscript 4.1.1. ## Hook semantics hooks.mounted and hooks.unmounted accept callback/event/effect. They run in scope with resolved props captured for one lifetime. Ordinary prop changes do not refresh the cleanup capture. Scope replacement restarts the hook lifetime; keyed reorder does not; variant case changes do not restart wrapper hooks. ## Exercise [Lifecycle lab](/examples/reflect-lab/index.html?part=lifecycle&lang=en): mount with Ada, change to Grace, unmount. View shows Grace; history should report mounted Ada and unmounted Ada. Remount captures Grace. This is an expected observation, not an automatic resource-leak verdict. ## Boundaries Mounted return values are ignored; use unmounted, not an invented disposer-return protocol. Unmount before disposal. Whole-stage restart may itself generate cleanup. SSR runs neither hook; assert empty recorder and distinct scoped HTML independently. No lifecycle descriptors are shipped. ## Source reflect.ts:64-73; README.md:155-161; collections.test.ts:163-170. npm 4.1.1. ## Navigation - [HTML](/reflect/lifecycle) - [Reflect](/reflect/llms.txt) - [Index](/llms.txt) - [Sitemap](/sitemap.xml) - [All English companions](/llms-full.txt) # Reflect.8. Testing the boundary — Mercury LMS > Contract: `@fiberfx/reflect` 4.1.1; Hypergraph and Hyperscript 4.1.1. ## Fixture promise Left +2, right +1, reset left → left 0, right 1, global 0. Browser clicks and DOM assertions; cleanup before dispose. Use @fiberfx/testing render/act/cleanup; container-bound queries. Native mx test supplies the vitest import through its own test runtime. ## Verification status The chapter supplies an adaptable regression recipe, not a fresh passing-run claim. Run mx test in a configured module with compatible resolved dependencies and Chromium. Missing browser/import, zero tests or missing results is not success. TypeScript checks are separate. ## Falsification Temporarily share one scope in an isolated fixture: count expectations must fail. Restore isolation. Add runtime duplicate-key/overlap refusals and separate wrong-payload/missing-prop type negatives. Never weaken expectations to accept the failure. ## Transfer Check projection suppression, keyed DOM/local state, removal cleanup, constructor changes, stable refs, scoped SSR and hook silence. Built-package evidence remains separate from workspace tests. [Manual lab](/examples/reflect-lab/index.html?part=testing&lang=en). ## Source Reflect collections.test.ts/types.ts, Testing render.ts/index.ts, mx native test runner; Reflect npm 4.1.1. ## Navigation - [HTML](/reflect/testing) - [Reflect](/reflect/llms.txt) - [Index](/llms.txt) - [Sitemap](/sitemap.xml) - [All English companions](/llms-full.txt) # Content, Mercury's page documents — Mercury LMS > `@fiberfx/schema` 4.0.0 holds the page document format and `parseDocument`; `@fiberfx/content` 4.0.0 holds the block catalog and `renderDocument`. A page is JSON data: parse it against a schema registry, render it through a renderer registry, mount the element. ## Read this when You show, check or produce a Mercury page document, or you need to know which package owns which step. This page is the course's landing. ## The contract - `@fiberfx/schema`: `parseDocument(input, registry)` → `{ ok: true, document }` or a refusal `{ ok: false, code, path, reason, subject, node? }`. Also `createSchemaRegistry`, `DEFAULT_LIMITS`, `FORMAT_VERSION`, `applyCommand`, `validateCommand`, `migrateDocument`, `inspectDocument`, `resolveReferences`, `resolveResponsive`. - `@fiberfx/content`: `renderDocument(document, options)` → `{ element, report }`; `contentRegistry()` (every catalog block), `contentSchema()` (the schema registry content documents validate against), `CATALOG`, `catalogEntries()`, `exampleDocument(entry)`, `createRendererRegistry`, `schemaFor`, `BREAKPOINTS`. - Peer dependencies of `@fiberfx/content`: `@fiberfx/hyperscript`, `@fiberfx/schema`, `@fiberfx/ui`, each 4.0.0. - Order: `parseDocument` first, then `renderDocument` on `parsed.document`, then `render(element, container)` from `@fiberfx/hyperscript`. ## Minimal example ```ts import { render } from "@fiberfx/hyperscript"; import { parseDocument } from "@fiberfx/schema"; import { contentSchema, renderDocument } from "@fiberfx/content"; const main = document.querySelector("[data-page]"); const response = await fetch("/page.json", { headers: { accept: "application/json" } }); const parsed = parseDocument(await response.text(), contentSchema()); if (main && parsed.ok) { const { element } = renderDocument(parsed.document, { mode: "publication", revision: 0 }); render(element, main); } ``` ## Never emit A function, an element or a DOM handle inside a document's props: a document is JSON data only. A `renderDocument` call on input that `parseDocument` has not accepted. ## Next - Page documents: /content/documents (/content/documents/llms.txt) - Rendering a document: /content/rendering (/content/rendering/llms.txt) - The block catalog: /content/blocks (/content/blocks/llms.txt) - References, capabilities and responsive values: /content/references (/content/references/llms.txt) - Add a block: /content/add-a-block (/content/add-a-block/llms.txt) - The site: /llms.txt - This companion: /content/llms.txt ## Navigation - [Read this page in the browser](/content) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) ### Chapter companions - [/content/add-a-block](/content/add-a-block/llms.txt) - [/content/blocks](/content/blocks/llms.txt) - [/content/documents](/content/documents/llms.txt) - [/content/references](/content/references/llms.txt) - [/content/rendering](/content/rendering/llms.txt) # Page documents: nodes, the schema and parseDocument — Mercury LMS > A Mercury page document (`@fiberfx/schema` 4.0.0) is JSON: `format`, `id`, `kind`, ordered `roots` and a flat `nodes` record. `parseDocument(input, registry)` answers `{ ok: true, document }` or exactly one refusal, and never throws. ## Read this when You write, generate or store a page document, build a schema registry, or handle a refused document. ## The contract - Document: `{ format: 1, id: string, kind: "page" | "fragment" | "template", roots: NodeId[], nodes: { [id]: DocumentNode } }`. Unknown fields are kept; they must be JSON. - Node: `{ id, type, version, props, slots }`. `id` equals its key in `nodes`. `version` is an integer from 1. `props` is JSON only. `slots` maps a slot name to an ordered list of child ids. - Reference in a prop: `{ "$ref": "asset" | "route" | "entity" | "fragment", "id": string }`; resolved outside the document. - `createSchemaRegistry({ blocks, limits?, breakpoints?, tokens? })`. Throws `TypeError` on a duplicate type, a version below 1, a migration gap, or defaults failing their schema. - `BlockDefinition`: `{ type, version, props, defaults?, slots?, migrations? }`. Prop kinds: `string`, `number`, `boolean`, `reference`, `token`, `list`, `record`, `json`; each may be `optional` or `responsive`. Slot: `{ allow?, min?, max? }`. - `DEFAULT_LIMITS`: `maxDepth` 32, `maxBytes` 1048576 (compact JSON), `maxNodes` 10000, `maxPropDepth` 16. - Catalog documents: `parseDocument(input, contentSchema())` with `contentSchema` from `@fiberfx/content`; breakpoints `sm`, `md`, `lg`, `xl`. - `parseDocument(input: unknown, registry)` → `{ ok: true, document }` (the input itself, nothing coerced or defaulted) or `{ ok: false, code, path, reason, subject: "document", node? }`. - Check order, first failure wins: shape (`malformed`, `newer-format`, `too-many-nodes`, `not-json-safe`, `too-deep`) → size (`too-large`) → structure (`unknown-child`, `multiple-parents`, `cycle`, `too-deep`, `orphan`) → each known node (`unknown-slot`, `slot-cardinality`, `slot-not-allowed`, `unknown-prop`, `missing-prop`, `invalid-prop`) → a fragment referencing itself (`cycle`). - Unknown block type or newer version: kept and not checked against a definition. Older version: `outdated-block`; migrate first. ## Minimal example ```ts import { parseDocument } from "@fiberfx/schema"; import { contentSchema } from "@fiberfx/content"; const parsed = parseDocument( { format: 1, id: "hello", kind: "page", roots: ["page"], nodes: { page: { id: "page", type: "page", version: 1, props: { title: "Hello" }, slots: { content: ["intro"] } }, intro: { id: "intro", type: "paragraph", version: 1, props: { text: "A first page." }, slots: {} }, }, }, contentSchema(), ); if (!parsed.ok) console.log(parsed.code, parsed.path, parsed.reason); ``` ## Never emit A nested node tree inside `props` or `slots`: children are ids in slots. A function, an element or a DOM handle in props. A `try`/`catch` around `parseDocument` in place of checking `parsed.ok`. ## Next - Rendering a document: /content/rendering (/content/rendering/llms.txt) - The course: /content (/content/llms.txt) - This companion: /content/documents/llms.txt ## Navigation - [Read this page in the browser](/content/documents) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/content/llms.txt) # Rendering a document: renderDocument — Mercury LMS > `renderDocument(document, options)` from `@fiberfx/content` 4.0.0 turns a parsed page document into `{ element, report }`: one `@fiberfx/hyperscript` VNode and the list of unsupported blocks. No fetch, no storage, no provider; preview and publication give the same markup. ## Read this when You show a page document, choose its render options, pass what the host resolved, or decide whether a page with unknown blocks may publish. ## The contract - `renderDocument(document: MercuryDocument, options: RenderOptions): { element: VNode, report: { unsupported: NodeId[], publishable: boolean } }`. Pass `parsed.document` from `parseDocument`; it does not validate. - Required: `mode: "preview" | "publication"` (no block sees it; markup never depends on it), `revision: number` (written as `data-revision`). - Optional: `theme: "light" | "dark"` (default `light`), `lang`, `dir: "ltr" | "rtl"`, `registry` (default `contentRegistry()`), `resolved: { [":"]: { src?, href?, title?, items?, error? } }`, `capabilities: ("embed" | "form")[]` (default none), `allowUnsupported` (default `true`). - Root: `
data-revision= lang dir>`. - Blocks get `{ node, props, context }`: `props` with responsive values at their base; `node.props` keeps overrides; `context` is `{ theme, capabilities, resolve, slot }`. - Unregistered type: `
role="note">` with "This content can't be shown here." and its slots rendered inside; its id joins `report.unsupported` in document order. - `report.publishable` is `false` only when something is unsupported and `allowUnsupported: false`. - Mount with `render(element, container)` from `@fiberfx/hyperscript`. ## Minimal example ```ts import { render } from "@fiberfx/hyperscript"; import { parseDocument } from "@fiberfx/schema"; import { contentSchema, renderDocument } from "@fiberfx/content"; const main = document.querySelector("[data-page]"); const response = await fetch("/page.json", { headers: { accept: "application/json" } }); const parsed = parseDocument(await response.text(), contentSchema()); if (main && parsed.ok) { const { element, report } = renderDocument(parsed.document, { mode: "publication", revision: 0, lang: "en", theme: "light" }); render(element, main); if (report.unsupported.length > 0) main.dataset.unsupported = String(report.unsupported.length); } ``` ## Never emit A `fetch` inside a block for its data: pass the host's answers in `resolved`. A branch on `mode` inside a block: blocks can't see it. A `throw` for an unsupported block: it is reported, not an error. ## Next - The block catalog: /content/blocks (/content/blocks/llms.txt) - Page documents: /content/documents (/content/documents/llms.txt) - This companion: /content/rendering/llms.txt ## Navigation - [Read this page in the browser](/content/rendering) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/content/llms.txt) # The block catalog: nine groups, one registry — Mercury LMS > `@fiberfx/content` 4.0.0's catalog: 51 blocks in nine groups plus the internal `column`, all in `contentRegistry()`. Each entry declares a block `type`, a schema, its host capabilities (`embed`, `form` or none), a renderer and an example. ## Read this when You write a node of a given block type, need a block's props and slots, or need to know what a block renders and what it asks of the host. ## The contract - `CATALOG`: `{ group, names }[]`, nine groups, 51 names. `catalogEntries()`: every `BlockEntry` (adds `Column`, group `Internal`). `exampleDocument(entry)`: a one-block page document from the entry's example. - `BlockEntry`: `{ name, group, type, definition, capabilities, render, example }`. - Types by group (`?` optional prop): - Structure: `page` {title; slot content}, `section` {background? default|muted|primary, responsive; content}, `container` {size? sm–xl; content}, `columns` {count 2–4, reverse? responsive; columns: only `column`, 2–4}, `grid` {cols 1–6; items}, `stack` {gap? sm–xl; content}, `divider` {label?}, `spacer` {size sm–xl}, internal `column` {content}. - Text: `heading` {text, level 1–6, size? 1–9}, `paragraph` {text}, `rich-text` {content: [{text, marks? strong|em|code, href?}] ≤500}, `list` {items ≤200, ordered?}, `quote` {text, cite?}, `code` {code, language?}. - Media: `image` {asset ref, alt}, `figure` {asset, alt, caption}, `gallery` {images ≤48 [{asset, alt}]}, `video` {title, asset, captions? asset, transcript?}, `audio` {title, asset, transcript?}, `embed` {url, title} (capability `embed`). - Navigation: `site-header` {brand; navigation: only `navigation`, ≤1}, `navigation` {label, links ≤40 [{label, href}]}, `breadcrumbs` {items ≤12 [{label, href?}]}, `footer` {text?; content}, `anchor-links` {label, links ≤40 [{label, target}]}. - Composition: `button` {label, href, variant? primary|secondary|ghost}, `button-group` {buttons: only `button`, 1–4}, `hero` {title, subtitle?; actions: button|button-group ≤1}, `cta-section` {title, text?; actions}, `card` {title, text?, href?}, `card-grid` {columns? 1–4; cards: only `card`}, `feature-list` {items ≤24 [{title, text}]}, `logo-list` {label, logos ≤24 [{name, asset?}]}, `testimonials` {items ≤24 [{quote, author, role?}]}. - Interactive content: `accordion` {items ≤30 [{title, text}]}, `tabs` {label?, items ≤12 [{label, text}]}, `disclosure` {summary, text}, `carousel` {label?, autoAdvance?, slides ≤20 [{text}]}. - Form content: `form` {label, action route ref; fields} (capability `form`), `field-group` {legend; fields}, `field` {kind text|email|tel|number|url|textarea|checkbox|select, name, label, required?, options? ≤50}, `form-submit` {label, success?, error?, state? idle|success|error} (capability `form`). - Data-backed content: `collection-feed`, `article-list`, `related-content` {title, collection entity ref}; `author-card` {name, bio?, href?}. - Editorial page: `article-header` {title, subtitle?, author?, published?}, `article-body` {content}, `table-of-contents` {label?, items ≤50 [{label, target}]}, `metadata` {items ≤20 [{label, value}]}, `related-articles` {title, collection entity ref}. - Behaviour: images and media never fetch; an unresolved image shows its alt as `role="img"`. `embed` frames only absolute https with the `embed` capability, `sandbox="allow-scripts"`; else a link. Every panel and slide is in static markup; Tabs and Carousel upgrade once mounted. A form posts natively only with `form` granted and a resolved route; else it is disabled and says so. Data-backed blocks render `data-state` `error`, `unavailable`, `empty` or `ready` from the host's snapshot. Author hrefs pass `safeHref`: relative, `#`, `?`, http, https, mailto, tel; anything else renders with no href. ## Minimal example ```ts import { catalogEntries, contentSchema, exampleDocument, renderDocument } from "@fiberfx/content"; import { parseDocument } from "@fiberfx/schema"; for (const entry of catalogEntries().filter((entry) => entry.group !== "Internal")) { const parsed = parseDocument(exampleDocument(entry), contentSchema()); if (!parsed.ok) throw new Error(`${entry.name}: ${parsed.code} at ${parsed.path}`); const { report } = renderDocument(parsed.document, { mode: "preview", revision: 1 }); console.log(entry.group, entry.name, entry.type, report.unsupported.length); } ``` ## Never emit A catalog name as a node's `type` (`"RichText display"`): documents use the type (`"rich-text"`). Raw HTML in a rich-text run. A `level` chosen for its visual size: set `size` instead. A child whose type a slot's `allow` excludes (`slot-not-allowed`). ## Next - Rendering a document: /content/rendering (/content/rendering/llms.txt) - The course: /content (/content/llms.txt) - This companion: /content/blocks/llms.txt ## Navigation - [Read this page in the browser](/content/blocks) - [Agent index](/llms.txt) - [All English companions](/llms-full.txt) - [Sitemap](/sitemap.xml) - [Course companion](/content/llms.txt) # References, capabilities and responsive values — Mercury LMS > What `renderDocument` (`@fiberfx/content` 4.0.0) leaves to the host: a `resolved` record keyed `:` for every reference, the `embed` and `form` capabilities, and responsive props read at their base. Blocks never fetch, and every missing answer has a visible fallback. ## Read this when You host Mercury pages and must supply images, routes or collections, grant or withhold embeds and forms, or read a responsive prop at a breakpoint. ## The contract - Reference in a prop: `{ $ref: "asset" | "route" | "entity" | "fragment", id }`. Key: `referenceKey(reference)` → `"<$ref>:"` (`@fiberfx/schema`). - `renderDocument(document, { resolved })`: `resolved: { [key]: { src?, href?, title?, items?: { title, href?, summary? }[], error? } }`. Nothing in the package fetches. - Readers: `image`/`figure`/`gallery`/`logo-list`/`audio` read `src`; `video` reads `src` for its asset and `captions`; `form` reads `href` (with `form` granted); `collection-feed`/`article-list`/`related-content`/`related-articles` read `items` and `error`. No catalog block reads `title`. - Data-backed states: key missing → `unavailable`; `items: []` → `empty`; `error` → `error` (alert); items → `ready`. - `@fiberfx/schema`: `collectReferences(document)` → `{ node, path, reference }[]`; `resolveReferences(document, resolve)` → `Map`, once per key; `unauthorized` keeps only the identity; any other answer throws `TypeError`. Converting a `Resolution` to `resolved` is the host's job. - `capabilities: ("embed" | "form")[]`, none by default. `renderDocument` does not enforce an entry's declared capabilities; each block reads `context.capabilities`. - `embed`: an `