@pyreon/mcp 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -655,6 +655,8 @@ setSearch({ page: "2" })`,
655
655
  // @pyreon/head
656
656
  // ═══════════════════════════════════════════════════════════════════════════
657
657
 
658
+ // <gen-docs:api-reference:start @pyreon/head>
659
+
658
660
  'head/useHead': {
659
661
  signature: 'useHead(input: UseHeadInput | (() => UseHeadInput)): void',
660
662
  example: `// Static:
@@ -665,23 +667,58 @@ useHead(() => ({
665
667
  title: \`\${username()} — Profile\`,
666
668
  meta: [{ property: "og:title", content: username() }]
667
669
  }))`,
670
+ notes: 'Register head tags from any component in the tree. Pass a static `UseHeadInput` object for one-shot registration, or a `() => UseHeadInput` thunk for reactive re-registration when signal reads inside the thunk change. Calling `useHead()` outside a `HeadProvider` ancestor (CSR) or `renderWithHead()` invocation (SSR) is a silent no-op — it does not throw. See also: HeadProvider, renderWithHead.',
671
+ mistakes: `- Using \`\${...}\` in a \`titleTemplate\` string — the placeholder is \`%s\` (or pass a function form \`(title) => …\`)
672
+ - Calling \`useHead()\` outside any \`HeadProvider\` / \`renderWithHead()\` boundary — silent no-op, the entries simply go nowhere
673
+ - Wrapping the input in \`computed()\` instead of a thunk — pass a plain \`() => ({...})\` arrow; \`useHead\` registers its own effect
674
+ - Expecting \`</script>\` inside an inline script body to render verbatim — the SSR escaper rewrites it as \`<\/script>\` to prevent breaking out of the inline tag`,
668
675
  },
669
676
 
670
677
  'head/HeadProvider': {
671
- signature: '<HeadProvider>{children}</HeadProvider>',
672
- example: `// Client-side setup:
678
+ signature: '(props: HeadProviderProps) => VNodeChild',
679
+ example: `<HeadProvider>{children}</HeadProvider>
680
+
681
+ // Client-side setup:
673
682
  mount(
674
683
  <HeadProvider>
675
684
  <App />
676
685
  </HeadProvider>,
677
686
  document.getElementById("app")!
678
687
  )`,
688
+ notes: 'Client-side context provider that collects every `useHead()` call from descendants and syncs the resolved tags into the live `document.head` element. Mount once near the application root. Auto-creates a `HeadContextValue` when no `context` prop is passed; nested providers each own an independent context. See also: useHead, renderWithHead, createHeadContext.',
689
+ mistakes: `- Mounting two \`HeadProvider\` instances at sibling roots — each owns an independent context, so a \`useHead()\` deeper in tree A is invisible to tree B
690
+ - Forgetting to mount \`HeadProvider\` and expecting \`useHead()\` to still update \`document.head\` — silent no-op outside a provider`,
691
+ },
692
+
693
+ 'head/renderWithHead': {
694
+ signature: 'renderWithHead(app: VNode): Promise<{ html: string; head: string; htmlAttrs: string; bodyAttrs: string }>',
695
+ example: `import { renderWithHead } from '@pyreon/head'
696
+
697
+ const { html, head, htmlAttrs, bodyAttrs } = await renderWithHead(<App />)
698
+ const doc = \`<!doctype html><html\${htmlAttrs}><head>\${head}</head><body\${bodyAttrs}>\${html}</body></html>\``,
699
+ notes: 'SSR companion to `HeadProvider`. Renders the app to HTML via `renderToString` while collecting every `useHead()` call from the tree, then serializes the resolved tags into a single `head` string plus separate `htmlAttrs` / `bodyAttrs` strings. Async components that call `useHead()` in their body work — the renderer awaits suspended subtrees before serialization. See also: useHead, HeadProvider.',
700
+ mistakes: `- Awaiting \`renderWithHead\` and then NOT splicing \`head\` into the \`<head>\` element — every \`useHead()\` call quietly disappears
701
+ - Forgetting to interpolate \`htmlAttrs\` / \`bodyAttrs\` (the leading space is included in each string) — \`htmlAttrs.lang\` and \`bodyAttrs.class\` set via \`useHead\` won\'t reach the DOM`,
679
702
  },
680
703
 
704
+ 'head/createHeadContext': {
705
+ signature: '() => HeadContextValue',
706
+ example: `import { createHeadContext, HeadContext } from '@pyreon/head'
707
+
708
+ const ctx = createHeadContext()
709
+ provide(HeadContext, ctx)
710
+ // ... render tree that calls useHead() ...
711
+ const { tags, htmlAttrs, bodyAttrs } = ctx.resolve()`,
712
+ notes: 'Manual factory for a `HeadContextValue` — only needed when wiring up a custom SSR pipeline that bypasses `renderWithHead`, or when running multiple isolated head contexts in the same process. The value exposes `add` / `remove` / `resolve` / `resolveTitleTemplate` / `resolveHtmlAttrs` / `resolveBodyAttrs` for full programmatic control. See also: HeadProvider, renderWithHead.',
713
+ },
714
+ // <gen-docs:api-reference:end @pyreon/head>
715
+
681
716
  // ═══════════════════════════════════════════════════════════════════════════
682
717
  // @pyreon/server
683
718
  // ═══════════════════════════════════════════════════════════════════════════
684
719
 
720
+ // <gen-docs:api-reference:start @pyreon/server>
721
+
685
722
  'server/createHandler': {
686
723
  signature: 'createHandler(options: HandlerOptions): (req: Request) => Promise<Response>',
687
724
  example: `import { createHandler } from "@pyreon/server"
@@ -692,17 +729,26 @@ export default createHandler({
692
729
  clientEntry: "/src/entry-client.ts",
693
730
  mode: "stream", // or "string"
694
731
  })`,
732
+ notes: 'Build a production SSR handler from your `App`, `routes`, and optional template / client entry / middleware. The template is precompiled once at handler-creation (split into 4 parts to skip three string scans per request); a missing `<!--pyreon-app-->` placeholder throws at creation time, not per request. Middleware runs before render with `ctx.locals` for cross-middleware data passing — return a `Response` to short-circuit the chain. `mode: "stream"` uses `renderToStream` so Suspense boundaries flush out-of-order; `mode: "string"` uses `renderToString` (default). See also: prerender, island, useRequestLocals.',
733
+ mistakes: `- Omitting \`<!--pyreon-app-->\` from the custom template — throws at handler-creation, not per request
734
+ - Returning a \`Response\` from middleware and expecting downstream middleware to still run — the chain short-circuits on the first \`Response\`
735
+ - Reading \`ctx.locals\` from inside the component without \`useRequestLocals()\` — the component tree only sees locals when bridged through that hook
736
+ - Forgetting to escape user data inserted into a custom template — \`createHandler\` only escapes its own loader-data injection (\`</script>\` → \`<\/script>\`); your template content is your responsibility`,
695
737
  },
696
738
 
697
739
  'server/island': {
698
- signature:
699
- 'island(loader: () => Promise<ComponentFn>, options: { name: string; hydrate?: HydrationStrategy }): ComponentFn',
740
+ signature: 'island(loader: () => Promise<ComponentFn>, options: { name: string; hydrate?: HydrationStrategy }): ComponentFn',
700
741
  example: `const SearchBar = island(
701
742
  () => import("./SearchBar"),
702
743
  { name: "SearchBar", hydrate: "visible" }
703
744
  )
704
745
 
705
746
  // Hydration strategies: "load" | "idle" | "visible" | "media" | "never"`,
747
+ notes: 'Wrap a lazily-loaded component in a `<pyreon-island>` boundary with a hydration strategy. The rest of the page stays HTML-only; only the island fetches its JS bundle and hydrates. Strategies: `"load"` (immediate), `"idle"` (`requestIdleCallback`), `"visible"` (IntersectionObserver), `"media(query)"` (matchMedia), `"never"` (HTML-only, no JS). Props passed to islands are JSON-serialized — non-JSON values (functions, symbols, undefined, children) are stripped. See also: createHandler, hydrateIslands.',
748
+ mistakes: `- Passing function props (event handlers, callbacks) — silently stripped during JSON serialization, the island sees \`undefined\`
749
+ - Passing children to an island — stripped; islands cannot render arbitrary descendant trees from props
750
+ - Forgetting to call \`hydrateIslands({ Name: () => import("./Path") })\` on the client — islands render as HTML and never hydrate
751
+ - Using a duplicate \`name\` across two islands — the client-side registry collapses them, only one loader will fire`,
706
752
  },
707
753
 
708
754
  'server/prerender': {
@@ -712,7 +758,12 @@ export default createHandler({
712
758
  paths: ["/", "/about", "/blog/1", "/blog/2"],
713
759
  outDir: "./dist",
714
760
  })`,
761
+ notes: 'Static-site generator built on `createHandler`. Walks the `paths` array (or async generator), invokes the handler for each path, and writes the rendered HTML to `outDir/<path>.html`. The `onPage(path, html)` callback fires per page so callers can post-process or stream output. Validates `outDir` against path traversal (`../` segments are rejected). Errors per-page are collected in the result, not thrown. See also: createHandler.',
762
+ mistakes: `- Passing a relative \`outDir\` and being surprised when it resolves against \`process.cwd()\` — pass an absolute path for predictability
763
+ - Expecting per-page errors to throw — they\'re collected in \`result.errors\`; check the array after \`await\`
764
+ - Generating thousands of paths without batching — the function processes the array sequentially; if you need parallelism, batch the \`paths\` array yourself`,
715
765
  },
766
+ // <gen-docs:api-reference:end @pyreon/server>
716
767
 
717
768
  // ═══════════════════════════════════════════════════════════════════════════
718
769
  // @pyreon/runtime-dom
@@ -2127,6 +2178,8 @@ Posts.useTable({ columns: ['title', 'author'] })`,
2127
2178
  // @pyreon/lint
2128
2179
  // ═══════════════════════════════════════════════════════════════════════════
2129
2180
 
2181
+ // <gen-docs:api-reference:start @pyreon/lint>
2182
+
2130
2183
  'lint/lint': {
2131
2184
  signature: 'lint(options?: LintOptions): LintResult',
2132
2185
  example: `import { lint } from "@pyreon/lint"
@@ -2144,34 +2197,29 @@ lint({
2144
2197
  "pyreon/no-window-in-ssr": { exemptPaths: ["src/foundation/"] },
2145
2198
  },
2146
2199
  })`,
2147
- notes:
2148
- 'Programmatic API. 59 rules across 12 categories. Auto-loads .pyreonlintrc.json. Presets: recommended, strict, app, lib. Per-rule options via tuple form in config (`["error", { exemptPaths: [...] }]`) or `ruleOptionsOverrides`. Wrong-typed options surface on `result.configDiagnostics`. Uses oxc-parser with AST caching.',
2200
+ notes: '59 rules across 12 categories. Auto-loads `.pyreonlintrc.json`. Presets: `recommended`, `strict`, `app`, `lib`. Per-rule options via tuple form in config (`["error", { exemptPaths: [...] }]`) or `ruleOptionsOverrides`. Wrong-typed options surface on `result.configDiagnostics`. Uses `oxc-parser` with AST caching. See also: lintFile, getPreset, AstCache.',
2149
2201
  },
2150
2202
 
2151
2203
  'lint/lintFile': {
2152
- signature:
2153
- 'lintFile(filePath: string, sourceText: string, rules: Rule[], config: LintConfig, cache?: AstCache, configDiagnosticsSink?: ConfigDiagnostic[]): LintFileResult',
2204
+ signature: 'lintFile(filePath: string, sourceText: string, rules: Rule[], config: LintConfig, cache?: AstCache, configDiagnosticsSink?: ConfigDiagnostic[]): LintFileResult',
2154
2205
  example: `import { lintFile, allRules, getPreset, AstCache } from "@pyreon/lint"
2155
2206
 
2156
2207
  const cache = new AstCache()
2157
2208
  const config = getPreset("recommended")
2158
2209
  const configSink: ConfigDiagnostic[] = []
2159
2210
  const result = lintFile("app.tsx", source, allRules, config, cache, configSink)`,
2160
- notes:
2161
- 'Low-level single-file API. Optional AstCache for repeat runs (FNV-1a hash keyed). Optional `configDiagnosticsSink` collects malformed-option diagnostics; without it they print to stderr.',
2211
+ notes: 'Low-level single-file API. Optional `AstCache` for repeat runs (FNV-1a hash keyed). Optional `configDiagnosticsSink` collects malformed-option diagnostics; without it they print to stderr. See also: lint, AstCache.',
2162
2212
  },
2163
2213
 
2164
2214
  'lint/cli': {
2165
- signature:
2166
- 'pyreon-lint [--preset name] [--fix] [--format text|json|compact] [--quiet] [--watch] [--list] [--config path] [--ignore path] [--rule id=severity] [--rule-options id=\'{json}\'] [path...]',
2215
+ signature: `pyreon-lint [--preset name] [--fix] [--format text|json|compact] [--quiet] [--watch] [--list] [--config path] [--ignore path] [--rule id=severity] [--rule-options id='{json}'] [path...]`,
2167
2216
  example: `pyreon-lint --preset strict --quiet # CI mode
2168
2217
  pyreon-lint --fix # auto-fix
2169
2218
  pyreon-lint --watch src/ # watch mode
2170
2219
  pyreon-lint --list # list all 59 rules
2171
2220
  pyreon-lint --format json # machine-readable
2172
2221
  pyreon-lint --rule-options 'pyreon/no-window-in-ssr={"exemptPaths":["src/foundation/"]}' src/`,
2173
- notes:
2174
- "CLI entry. Config: .pyreonlintrc.json (reference schema/pyreonlintrc.schema.json for IDE autocomplete), package.json 'pyreonlint' field. Ignore: .pyreonlintignore + .gitignore. Watch: fs.watch recursive with 100ms debounce. `--rule-options id='{json}'` passes per-rule options on a single run.",
2222
+ notes: `CLI entry. Config: \`.pyreonlintrc.json\` (reference \`schema/pyreonlintrc.schema.json\` for IDE autocomplete) or \`package.json\`'s \`'pyreonlint'\` field. Ignore: \`.pyreonlintignore\` + \`.gitignore\`. Watch: \`fs.watch\` recursive with 100ms debounce. \`--rule-options id='{json}'\` passes per-rule options on a single run. See also: lint.`,
2175
2223
  },
2176
2224
 
2177
2225
  'lint/no-process-dev-gate': {
@@ -2184,8 +2232,7 @@ if (__DEV__) console.warn('hello')
2184
2232
  // @ts-ignore — provided by Vite/Rolldown at build time
2185
2233
  const __DEV__ = import.meta.env?.DEV === true
2186
2234
  if (__DEV__) console.warn('hello')`,
2187
- notes:
2188
- "The `typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'` pattern works in vitest (Node, `process` is defined) but is silently dead code in real Vite browser bundles because Vite does NOT polyfill `process` for the client. Every `console.warn` gated on the broken constant never fires for real users in dev mode — unit tests pass while users get nothing. Use `import.meta.env.DEV` instead — Vite/Rolldown literal-replace it at build time, prod tree-shakes the warning to zero bytes, and vitest sets it to `true` automatically. Server-only packages (`zero`, `core/server`, `core/runtime-server`, `vite-plugin`, `cli`, `lint`, `mcp`, `storybook`, `typescript`) and test files are exempt. Reference implementation: `packages/fundamentals/flow/src/layout.ts:warnIgnoredOptions`. The rule has an auto-fix that replaces the broken expression with `import.meta.env?.DEV === true`.",
2235
+ notes: `The \`typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'\` pattern works in vitest (Node, \`process\` is defined) but is silently dead code in real Vite browser bundles because Vite does NOT polyfill \`process\` for the client. Every \`console.warn\` gated on the broken constant never fires for real users in dev mode — unit tests pass while users get nothing. Use \`import.meta.env.DEV\` instead — Vite/Rolldown literal-replace it at build time, prod tree-shakes the warning to zero bytes, and vitest sets it to \`true\` automatically. Server-only packages (\`zero\`, \`core/server\`, \`core/runtime-server\`, \`vite-plugin\`, \`cli\`, \`lint\`, \`mcp\`, \`storybook\`, \`typescript\`) and test files are exempt. Reference implementation: \`packages/fundamentals/flow/src/layout.ts:warnIgnoredOptions\`. The rule has an auto-fix that replaces the broken expression with \`import.meta.env?.DEV === true\`. See also: require-browser-smoke-test.`,
2189
2236
  mistakes: `- Copying the \`typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'\` pattern from existing codebases — it works in Node but is dead in browser bundles
2190
2237
  - Trying to test with \`delete globalThis.process\` — vitest's own \`import.meta.env\` depends on \`process\`, so deleting it breaks the FIXED gate too (not because the gate is wrong, but because vitest can't resolve it)
2191
2238
  - Adding \`process: { env: { ... } }\` polyfills to vite.config.ts as a workaround — fix the source instead
@@ -2206,13 +2253,19 @@ if (__DEV__) console.warn('hello')`,
2206
2253
  ]
2207
2254
  }
2208
2255
  }`,
2209
- notes:
2210
- "Locks in the durability of the T1.1 browser smoke harness (PRs #224, #227, #229, #231). Every browser-categorized package MUST ship at least one \`*.browser.test.{ts,tsx}\` file under \`src/\`. Without this rule, new browser packages can quietly ship without smoke coverage and we drift back to the world before T1.1 — happy-dom silently masks environment-divergence bugs (PR #197 mock-vnode metadata drop, PR #200 \`typeof process\` dead code, multi-word event delegation bug). Default browser-package list mirrors \`.claude/rules/test-environment-parity.md\`. The rule fires once per package on its \`src/index.ts\`, walks the package directory looking for \`*.browser.test.*\`, and reports if none are found. Off in \`app\` preset because apps don't ship as packages with smoke obligations.",
2256
+ notes: `Locks in the durability of the T1.1 browser smoke harness (PRs #224, #227, #229, #231). Every browser-categorized package MUST ship at least one \`*.browser.test.{ts,tsx}\` file under \`src/\`. Without this rule, new browser packages can quietly ship without smoke coverage and we drift back to the world before T1.1 — happy-dom silently masks environment-divergence bugs (PR #197 mock-vnode metadata drop, PR #200 \`typeof process\` dead code, multi-word event delegation bug). Default browser-package list mirrors \`.claude/rules/test-environment-parity.md\`. The rule fires once per package on its \`src/index.ts\`, walks the package directory looking for \`*.browser.test.*\`, and reports if none are found. Off in \`app\` preset because apps don't ship as packages with smoke obligations. See also: no-process-dev-gate.`,
2211
2257
  mistakes: `- Adding a new browser-running package without a browser test — the rule will fail your PR
2212
2258
  - Hardcoding the browser-package list in the rule — the list lives in \`.claude/rules/browser-packages.json\` (single source of truth), not in the rule source
2213
2259
  - Disabling the rule globally — use \`exemptPaths\` to exempt specific packages still under construction
2214
2260
  - Shipping a \`sanity.browser.test.ts\` with \`expect(1).toBe(1)\` just to satisfy the rule — it passes but provides zero signal. The rule is a GATE, not a quality check; review actual contents on PR`,
2215
2261
  },
2262
+ // <gen-docs:api-reference:end @pyreon/lint>
2263
+
2264
+ // ═══════════════════════════════════════════════════════════════════════════
2265
+ // @pyreon/mcp
2266
+ // ═══════════════════════════════════════════════════════════════════════════
2267
+
2268
+ // <gen-docs:api-reference:start @pyreon/mcp>
2216
2269
 
2217
2270
  'mcp/get_browser_smoke_status': {
2218
2271
  signature: 'tool: get_browser_smoke_status — no args',
@@ -2220,18 +2273,103 @@ if (__DEV__) console.warn('hello')`,
2220
2273
  // "which Pyreon packages are missing browser smoke coverage?"
2221
2274
  // Tool walks packages/, matches against .claude/rules/browser-packages.json,
2222
2275
  // returns a coverage report.`,
2223
- notes:
2224
- "Companion to the `pyreon/require-browser-smoke-test` lint rule. Reports which browser-categorized Pyreon packages have at least one `*.browser.test.{ts,tsx}` file under `src/`. Uses the same `.claude/rules/browser-packages.json` single source of truth as the rule + the CI script. Lets an AI agent check coverage before writing a new browser package (so it adds a smoke test in the same PR) instead of discovering the failure when CI runs. Falls back with a clear message if the JSON isn't present (e.g. consumer apps that don't ship the Pyreon monorepo layout).",
2276
+ notes: `Companion to the \`pyreon/require-browser-smoke-test\` lint rule. Reports which browser-categorized Pyreon packages have at least one \`*.browser.test.{ts,tsx}\` file under \`src/\`. Uses the same \`.claude/rules/browser-packages.json\` single source of truth as the rule + the CI script. Lets an AI agent check coverage before writing a new browser package (so it adds a smoke test in the same PR) instead of discovering the failure when CI runs. Falls back with a clear message if the JSON isn't present (e.g. consumer apps that don't ship the Pyreon monorepo layout). See also: audit_test_environment.`,
2225
2277
  mistakes: `- Using the tool's output as a substitute for running the CI script — this tool only checks file existence, not the self-expiring-exemption check that \`bun run lint:browser-smoke\` performs`,
2226
2278
  },
2227
2279
 
2280
+ 'mcp/get_api': {
2281
+ signature: 'tool: get_api({ package: string; symbol: string }) → APIEntry',
2282
+ example: `// Agent-side
2283
+ get_api({ package: 'flow', symbol: 'createFlow' })
2284
+ get_api({ package: '@pyreon/router', symbol: 'useTypedSearchParams' })`,
2285
+ notes: `Look up any Pyreon API by \`package\` (e.g. \`"flow"\` or \`"@pyreon/flow"\`) and \`symbol\` (e.g. \`"createFlow"\`). Returns the canonical signature, example, foot-gun catalogue, and cross-references — drawn from \`api-reference.ts\`, which is regenerated from each package\'s \`manifest.ts\`. The single agent-facing entry point for "what does this API do and how do I avoid the common mistakes." See also: validate, get_pattern.`,
2286
+ },
2287
+
2288
+ 'mcp/validate': {
2289
+ signature: 'tool: validate({ code: string; filename?: string }) → Diagnostics[]',
2290
+ example: `validate({ code: \`
2291
+ function MyComp(props) {
2292
+ const { value } = props // → props-destructured
2293
+ return <For each={items}>{...}</For> // → for-missing-by
2294
+ }
2295
+ \` })`,
2296
+ notes: 'Two AST-based detectors run in parallel: `detectReactPatterns` flags "coming from React" mistakes (`useState`, `useEffect`, `className`, `onChange` on inputs, React-package imports), and `detectPyreonPatterns` flags "using Pyreon wrong" mistakes (`<For>` missing `by`, props destructured at component signature, `typeof process` dev gates, raw `addEventListener`, `Date.now() + Math.random()` IDs). Diagnostics are merged + sorted by line / column for top-down reading. See also: get_anti_patterns, migrate_react.',
2297
+ },
2298
+
2299
+ 'mcp/migrate_react': {
2300
+ signature: 'tool: migrate_react({ code: string; filename?: string }) → MigrationResult',
2301
+ example: `migrate_react({ code: \`
2302
+ import { useState, useEffect } from 'react'
2303
+ function Counter() {
2304
+ const [count, setCount] = useState(0)
2305
+ useEffect(() => { console.log(count) }, [count])
2306
+ return <button onClick={() => setCount(count + 1)}>{count}</button>
2307
+ }
2308
+ \` })`,
2309
+ notes: 'Convert React code to idiomatic Pyreon. Handles `useState` → `signal()`, `useEffect` → `effect()`, `className` → `class`, `onChange` → `onInput`, `useMemo` → `computed()`, React imports → Pyreon imports. Reports per-edit fixable diagnostics so callers can apply or review. See also: validate.',
2310
+ },
2311
+
2312
+ 'mcp/diagnose': {
2313
+ signature: 'tool: diagnose({ error: string }) → DiagnoseResult',
2314
+ example: `diagnose({ error: 'Cannot redefine property X on object [object Object]' })
2315
+ // → cause: configurable: false on a getter; fix: set configurable: true`,
2316
+ notes: 'Parse a Pyreon runtime / build error message into structured fix information: probable cause, recommended fix, related docs, and the `.claude/rules/anti-patterns.md` entry (if any) the error matches. Useful when an agent sees a stack trace and wants to skip the "search the codebase for similar errors" step. See also: validate, get_anti_patterns.',
2317
+ },
2318
+
2319
+ 'mcp/get_routes': {
2320
+ signature: 'tool: get_routes() → Route[]',
2321
+ example: `get_routes()
2322
+ // → [{ path: '/', name: 'home', hasLoader: true, params: [] }, ...]`,
2323
+ notes: 'List every route in the current project — path, loader presence, guards, params, and named-route name. Walks the project source from `process.cwd()` down. Cached per server instance with auto-invalidation on `cwd` change. See also: get_components.',
2324
+ },
2325
+
2326
+ 'mcp/get_components': {
2327
+ signature: 'tool: get_components() → ComponentInfo[]',
2328
+ example: `get_components()
2329
+ // → [{ name: 'Button', file: 'src/Button.tsx', props: ['onClick', 'children'], signals: ['count'] }, ...]`,
2330
+ notes: 'List every component in the current project with its props and signal usage. Same scanner as `get_routes`. Useful for an agent before generating new code that needs to reference existing components. See also: get_routes.',
2331
+ },
2332
+
2333
+ 'mcp/get_pattern': {
2334
+ signature: 'tool: get_pattern({ name?: string }) → PatternBody | string[]',
2335
+ example: `get_pattern({ name: 'controllable-state' })
2336
+ // → full canonical pattern body
2337
+ get_pattern({})
2338
+ // → [{ name: 'controllable-state', summary: '...' }, ...]`,
2339
+ notes: 'Fetch a canonical "how do I do X" pattern body from `docs/patterns/`. Eight foundational patterns ship: `dev-warnings`, `controllable-state`, `ssr-safe-hooks`, `signal-writes`, `keyed-lists`, `reactive-context`, `event-listeners`, `form-fields`. Omit `name` to list available patterns. Drop a new `docs/patterns/<slug>.md` file to add one — picked up on next call. See also: get_anti_patterns.',
2340
+ },
2341
+
2342
+ 'mcp/get_anti_patterns': {
2343
+ signature: `tool: get_anti_patterns({ category?: 'reactivity' | 'jsx' | 'context' | 'architecture' | 'testing' | 'lifecycle' | 'documentation' | 'all' }) → AntiPattern[]`,
2344
+ example: `get_anti_patterns({ category: 'reactivity' })
2345
+ // → ['Bare signal in JSX text', 'Stale closures', 'Destructuring props', ...]`,
2346
+ notes: 'Browse the anti-patterns catalog parsed from `.claude/rules/anti-patterns.md`. Each entry surfaces its `[detector: <code>]` tag inline so an agent can pair the catalog entry with the live static detector exposed by `validate`. Optional `category` filter; default returns all categories. See also: validate, get_pattern.',
2347
+ },
2348
+
2349
+ 'mcp/get_changelog': {
2350
+ signature: 'tool: get_changelog({ package?: string; limit?: number; includeDependencyUpdates?: boolean; since?: string }) → ChangelogEntry[]',
2351
+ example: `get_changelog({ package: 'flow', limit: 5 })
2352
+ get_changelog({ package: '@pyreon/router', since: '0.12.0' })`,
2353
+ notes: 'Recent release notes for any `@pyreon/*` package without scraping `git log`. Parses `packages/**/CHANGELOG.md` into version entries (`{ version, changes[], dependencyUpdates[], empty }`) and returns the N most recent substantive versions (default 5). Filters out ceremonial version bumps (pure dependency-update releases with no user-facing body) by default — opt back in with `includeDependencyUpdates: true`. `since: "0.12.0"` returns the delta from a known floor — useful when an agent knows the version it was trained against. See also: get_api.',
2354
+ },
2355
+
2356
+ 'mcp/audit_test_environment': {
2357
+ signature: `tool: audit_test_environment({ minRisk?: 'high' | 'medium' | 'low'; limit?: number }) → AuditReport`,
2358
+ example: `audit_test_environment({ minRisk: 'medium', limit: 10 })
2359
+ // → grouped report with HIGH / MEDIUM / LOW sections`,
2360
+ notes: `Scan every \`*.test.{ts,tsx}\` under \`packages/\` for the mock-vnode anti-pattern that caused PR #197\'s silent metadata drop. Files are classified HIGH / MEDIUM / LOW based on the balance of mock-vnode literals + helpers + helper-call sites vs real \`h()\` calls + \`@pyreon/core\` import. Three context-aware skips (helper-def vs binding discrimination, type-guard call-arg skip, template-string fixture mask) keep the false-positive rate low. Run before merging a new test file or after a framework change. See also: get_browser_smoke_status.`,
2361
+ },
2362
+ // <gen-docs:api-reference:end @pyreon/mcp>
2363
+
2364
+
2228
2365
  // ═══════════════════════════════════════════════════════════════════════════
2229
2366
  // @pyreon/ui-core
2230
2367
  // ═══════════════════════════════════════════════════════════════════════════
2231
2368
 
2369
+ // <gen-docs:api-reference:start @pyreon/ui-core>
2370
+
2232
2371
  'ui-core/PyreonUI': {
2233
- signature:
2234
- "PyreonUI(props: { theme?: Theme; mode?: 'light' | 'dark' | 'system'; inversed?: boolean; children: VNodeChild }): VNodeChild",
2372
+ signature: `(props: { theme?: Theme; mode?: 'light' | 'dark' | 'system'; inversed?: boolean; children: VNodeChild }) => VNodeChild`,
2235
2373
  example: `import { PyreonUI } from "@pyreon/ui-core"
2236
2374
  import { enrichTheme } from "@pyreon/unistyle"
2237
2375
 
@@ -2243,27 +2381,32 @@ const theme = enrichTheme({ colors: { primary: "#3b82f6" } })
2243
2381
 
2244
2382
  // mode="system" auto-detects OS dark mode via prefers-color-scheme
2245
2383
  // inversed flips the resolved mode (light↔dark)`,
2246
- notes:
2247
- "Unified provider replacing 3 separate providers (theme, mode, config). Calls init() internally. mode='system' uses matchMedia('(prefers-color-scheme: dark)') and reactively updates.",
2248
- mistakes: `- Using ThemeProvider + ModeProvider + ConfigProvider separately Use PyreonUI instead
2249
- - Forgetting enrichTheme() raw theme objects miss default breakpoints/spacing`,
2384
+ notes: `Unified provider replacing the previous theme / mode / config split (3 nested providers became 1). Accepts an enriched \`theme\` object (merge with defaults via \`enrichTheme()\`), a \`mode\` of \`'light' | 'dark' | 'system'\`, and an optional \`inversed\` flip. When \`mode='system'\`, the provider subscribes to \`matchMedia('(prefers-color-scheme: dark)')\` and re-resolves the mode reactively. Calls \`init()\` internally so consumers don\'t need to wire it up themselves. Whole-theme swaps (user-preference themes) propagate through the styler resolver and re-resolve CSS without remounting the VNode. See also: useMode, enrichTheme, init.`,
2385
+ mistakes: `- Using \`ThemeProvider\` + \`ModeProvider\` + \`ConfigProvider\` separately \`PyreonUI\` is the single replacement covering all three
2386
+ - Forgetting \`enrichTheme()\` raw theme objects miss default breakpoints / spacing / unit utilities
2387
+ - Destructuring \`props\` inside the provider components run once; destructuring captures values at setup. Read \`props.mode\` lazily inside reactive scopes
2388
+ - Re-augmenting the \`ThemeDefault\` / \`StylesDefault\` interfaces in your app — \`@pyreon/ui-theme\` already augments them; double-augmentation throws TS2320`,
2250
2389
  },
2251
2390
 
2252
2391
  'ui-core/useMode': {
2253
- signature: "useMode(): Signal<'light' | 'dark'>",
2392
+ signature: `useMode(): Signal<'light' | 'dark'>`,
2254
2393
  example: `import { useMode } from "@pyreon/ui-core"
2255
2394
 
2256
2395
  const mode = useMode()
2257
2396
  // mode() returns "light" or "dark" (resolved, reactive)
2258
2397
  // Reflects OS preference when PyreonUI mode="system"`,
2259
- notes:
2260
- "Returns the resolved mode as a reactive signal. When mode='system', reflects the OS preference. When inversed is true, the mode is flipped.",
2398
+ notes: `Returns the currently resolved mode as a reactive signal — \`'light'\` or \`'dark'\`. When the nearest \`PyreonUI\` ancestor uses \`mode='system'\`, the signal reflects the OS preference and updates when the user changes their system setting. When \`inversed\` is true on any ancestor, the mode is flipped before resolution. Component-scoped subscription — readers re-run only when the resolved mode actually changes. See also: PyreonUI.`,
2399
+ mistakes: `- Reading \`useMode()\` without calling it the value is a \`Signal\`; use \`mode()\` to read
2400
+ - Using \`useMode()\` outside any \`PyreonUI\` ancestor — falls back to a default but loses the reactive system / inversed handling`,
2261
2401
  },
2402
+ // <gen-docs:api-reference:end @pyreon/ui-core>
2262
2403
 
2263
2404
  // ═══════════════════════════════════════════════════════════════════════════
2264
2405
  // @pyreon/unistyle
2265
2406
  // ═══════════════════════════════════════════════════════════════════════════
2266
2407
 
2408
+ // <gen-docs:api-reference:start @pyreon/unistyle>
2409
+
2267
2410
  'unistyle/enrichTheme': {
2268
2411
  signature: 'enrichTheme(theme: PartialTheme): Theme',
2269
2412
  example: `import { enrichTheme } from "@pyreon/unistyle"
@@ -2274,10 +2417,101 @@ const theme = enrichTheme({
2274
2417
  })
2275
2418
 
2276
2419
  // Merges user overrides with default breakpoints, spacing, and units`,
2277
- notes:
2278
- 'Merges a partial theme with the full default theme (breakpoints, spacing, unit utilities). Always use when passing a theme to PyreonUI.',
2420
+ notes: 'Merge a partial theme with the full default theme (breakpoints, spacing, unit utilities, fallback colors). Always call this before passing a user theme to `PyreonUI` — raw theme objects miss the default breakpoints and spacing scale that the rest of the UI system reads from. Idempotent: enriching an already-enriched theme is a no-op. See also: breakpoints, createMediaQueries.',
2421
+ mistakes: `- Passing the raw partial theme to \`<PyreonUI theme={...}>\` without enriching — \`theme.breakpoints\` is undefined and every responsive prop falls back to the desktop value
2422
+ - Mutating the theme after passing it to \`PyreonUI\` — the styler resolver caches off the theme identity; clone + re-enrich for whole-theme swaps`,
2279
2423
  },
2280
2424
 
2425
+ 'unistyle/breakpoints': {
2426
+ signature: 'breakpoints(): Breakpoints',
2427
+ example: `import { breakpoints } from '@pyreon/unistyle'
2428
+
2429
+ const bp = breakpoints()
2430
+ // { xs: 0, sm: 640, md: 768, lg: 1024, xl: 1280, xxl: 1536 }`,
2431
+ notes: 'Return the default breakpoint set keyed by name (`xs`, `sm`, `md`, `lg`, `xl`, `xxl`) with min-width values in pixels. The same map is folded into `enrichTheme()` output, so most consumers read `theme.breakpoints` rather than calling this directly. Use it when you need the defaults outside a theme context (e.g. building a custom theme programmatically). See also: enrichTheme, createMediaQueries.',
2432
+ },
2433
+
2434
+ 'unistyle/createMediaQueries': {
2435
+ signature: 'createMediaQueries(breakpoints: Breakpoints): Record<string, string>',
2436
+ example: `import { createMediaQueries, breakpoints } from '@pyreon/unistyle'
2437
+
2438
+ const queries = createMediaQueries(breakpoints())
2439
+ // { xs: '@media (min-width: 0)', sm: '@media (min-width: 640px)', md: '@media (min-width: 768px)', ... }`,
2440
+ notes: 'Build a record of media-query strings keyed by breakpoint name. Each value is a `min-width` query — `xs` is `(min-width: 0)`, `sm` becomes `(min-width: 640px)`, and so on. Used internally by `makeItResponsive()`; expose to consumers when they need to compose custom CSS-in-JS rules outside the responsive-prop pipeline. See also: breakpoints, makeItResponsive.',
2441
+ },
2442
+
2443
+ 'unistyle/makeItResponsive': {
2444
+ signature: 'makeItResponsive<T>(options: { value: T | T[] | Record<string, T>; property: string; theme: Theme }): string',
2445
+ example: `import { makeItResponsive } from '@pyreon/unistyle'
2446
+
2447
+ makeItResponsive({ value: 16, property: 'padding', theme })
2448
+ // → 'padding: 16px;'
2449
+
2450
+ makeItResponsive({ value: [8, 12, 16], property: 'padding', theme })
2451
+ // → 'padding: 8px; @media (min-width: 640px) { padding: 12px } @media (min-width: 768px) { padding: 16px }'
2452
+
2453
+ makeItResponsive({ value: { xs: 8, md: 16, xl: 24 }, property: 'padding', theme })
2454
+ // → '@media (min-width: 0) { padding: 8px } @media (min-width: 768px) { padding: 16px } @media (min-width: 1280px) { padding: 24px }'`,
2455
+ notes: 'Resolve a responsive prop value to CSS for the current screen. Accepts three input shapes: single value (applies at all breakpoints), mobile-first array `[xs, sm, md, lg]` (each entry maps to the next breakpoint), or breakpoint object `{ xs: ..., md: ..., xl: ... }` (named keys map directly). The output is a CSS string with media queries already embedded; insert into a styled component template literal. See also: createMediaQueries, styles.',
2456
+ mistakes: `- Passing CSS-spec property names (\`borderTopWidth\`) — unistyle uses property-first naming (\`borderWidthTop\`); the responsive transformer expects the unistyle convention
2457
+ - Forgetting to pass an enriched theme — without \`theme.breakpoints\`, the array form falls back to the first value at every breakpoint`,
2458
+ },
2459
+
2460
+ 'unistyle/styles': {
2461
+ signature: 'styles(theme: Theme): string',
2462
+ example: `import { styles, enrichTheme } from '@pyreon/unistyle'
2463
+
2464
+ const theme = enrichTheme({ colors: { primary: '#3b82f6' } })
2465
+ const css = styles(theme)
2466
+ // → ':root { --color-primary: #3b82f6; --spacing-xs: 4px; ... }'`,
2467
+ notes: `Generate the CSS string for a complete theme — colors, spacing, fonts, breakpoints, the works. Used to produce the cascade of CSS variables / global declarations that backs every styled component. Most consumers don\'t call this directly; the \`PyreonUI\` provider invokes it internally on theme mount. See also: enrichTheme, extendCss.`,
2468
+ },
2469
+
2470
+ 'unistyle/alignContent': {
2471
+ signature: `alignContent(options: { alignX?: AlignXKey; alignY?: AlignYKey; direction?: 'row' | 'column' | 'inline' | 'rows' }): string`,
2472
+ example: `import { alignContent } from '@pyreon/unistyle'
2473
+
2474
+ alignContent({ alignX: 'center', alignY: 'start', direction: 'row' })
2475
+ // → 'justify-content: center; align-items: flex-start;'
2476
+
2477
+ alignContent({ alignX: 'spaceBetween', direction: 'inline' })
2478
+ // → 'justify-content: space-between;'`,
2479
+ notes: `Resolve \`alignX\` / \`alignY\` / \`direction\` shorthand to the matching flex / grid CSS (\`justify-content\`, \`align-items\`). The Element / Row / Column primitives use this internally — it\'s exposed for custom layout components that want the same alignment semantics. \`direction: "inline"\` maps to \`row\`; \`direction: "rows"\` maps to \`column\`. See also: makeItResponsive.`,
2480
+ },
2481
+
2482
+ 'unistyle/extendCss': {
2483
+ signature: 'extendCss(base: ExtendCss, override?: ExtendCss): ExtendCss',
2484
+ example: `import { extendCss } from '@pyreon/unistyle'
2485
+
2486
+ const base = { color: 'red', hover: { color: 'darkred' } }
2487
+ const extended = extendCss(base, { hover: { background: 'pink' } })
2488
+ // → { color: 'red', hover: { color: 'darkred', background: 'pink' } }`,
2489
+ notes: 'Extend a CSS definition (theme block, style descriptor) with overrides — deep-merges nested objects without losing the base. Used by rocketstyle dimension chains to layer dimension-specific CSS over a baseline. The base is not mutated; the result is a new object. See also: styles.',
2490
+ },
2491
+
2492
+ 'unistyle/stripUnit': {
2493
+ signature: 'stripUnit(value: string | number): number',
2494
+ example: `import { stripUnit } from '@pyreon/unistyle'
2495
+
2496
+ stripUnit('16px') // → 16
2497
+ stripUnit('1.5rem') // → 1.5
2498
+ stripUnit(16) // → 16`,
2499
+ notes: 'Strip the unit suffix from a CSS value and return the numeric part (`"16px"` → `16`, `"1.5rem"` → `1.5`). Returns the input unchanged when already a number. Useful for arithmetic on theme values declared as strings (`"16px"`) without manually parsing. See also: value, values.',
2500
+ },
2501
+
2502
+ 'unistyle/value': {
2503
+ signature: 'value(input: PropertyValue, fallback?: PropertyValue): UnitValue',
2504
+ example: `import { value } from '@pyreon/unistyle'
2505
+
2506
+ value(16) // → { value: 16, unit: 'px' }
2507
+ value('1.5rem') // → { value: 1.5, unit: 'rem' }
2508
+ value('50%') // → { value: 50, unit: '%' }
2509
+ value('garbage', 0) // → { value: 0, unit: 'px' }`,
2510
+ notes: 'Parse and validate a single property value into a `UnitValue` shape (`{ value, unit }`). Accepts numbers (treated as pixels), strings with units (`"16px"`, `"1rem"`, `"50%"`), or objects already in `UnitValue` form. Optional `fallback` is returned when the input is invalid. The companion `values()` does the same over an array. See also: stripUnit, values.',
2511
+ },
2512
+ // <gen-docs:api-reference:end @pyreon/unistyle>
2513
+
2514
+
2281
2515
  // ═══════════════════════════════════════════════════════════════════════════
2282
2516
  // @pyreon/storybook
2283
2517
  // ═══════════════════════════════════════════════════════════════════════════
@@ -2289,12 +2523,12 @@ const theme = enrichTheme({
2289
2523
  // <gen-docs:api-reference:start @pyreon/rx>
2290
2524
 
2291
2525
  'rx/rx': {
2292
- signature: 'Readonly<{ filter, map, sortBy, groupBy, keyBy, uniqBy, take, skip, last, chunk, flatten, find, mapValues, count, sum, min, max, average, distinct, scan, combine, debounce, throttle, search, pipe }>',
2526
+ signature: 'Readonly<{ filter, map, sortBy, groupBy, keyBy, uniqBy, take, skip, last, chunk, flatten, find, mapValues, first, compact, reverse, partition, takeWhile, dropWhile, unique, sample, count, sum, min, max, average, reduce, every, some, distinct, scan, combine, zip, merge, debounce, throttle, search, pipe }>',
2293
2527
  example: `const active = rx.filter(users, u => u.active) // Computed<User[]>
2294
2528
  const sorted = rx.sortBy(active, 'name') // Computed<User[]>
2295
2529
  const total = rx.sum(users, u => u.age) // Computed<number>
2296
2530
  const grouped = rx.groupBy(users, u => u.department) // Computed<Map<string, User[]>>`,
2297
- notes: 'Namespaced object exposing all 24 reactive transform functions plus `pipe`. Use `rx.filter(...)` for dot-notation style, or destructure individual functions for tree-shaking. Every function is overloaded: `Signal<T[]>` input produces `Computed<T[]>` that auto-tracks, plain `T[]` input produces a static result. See also: pipe, filter.',
2531
+ notes: 'Namespaced object exposing all 37 reactive transform functions plus `pipe`. Use `rx.filter(...)` for dot-notation style, or destructure individual functions for tree-shaking. Every function is overloaded: `Signal<T[]>` input produces `Computed<T[]>` that auto-tracks, plain `T[]` input produces a static result. See also: pipe, filter.',
2298
2532
  mistakes: `- Expecting \`rx.filter(signal, pred)\` to return a plain array — signal inputs always produce \`Computed\` outputs. Call the result to read: \`active()\`
2299
2533
  - Passing a signal accessor (\`() => items()\`) instead of the signal itself — pass \`items\` not \`() => items()\`; the function checks for \`.subscribe\` to detect signals`,
2300
2534
  },
@@ -2406,6 +2640,8 @@ setUrlRouter(router)
2406
2640
  // @pyreon/document-primitives
2407
2641
  // ═══════════════════════════════════════════════════════════════════════════
2408
2642
 
2643
+ // <gen-docs:api-reference:start @pyreon/document-primitives>
2644
+
2409
2645
  'document-primitives/extractDocNode': {
2410
2646
  signature: 'extractDocNode(templateFn: () => VNode, options?: ExtractOptions): DocNode',
2411
2647
  example: `import {
@@ -2414,52 +2650,239 @@ setUrlRouter(router)
2414
2650
  } from '@pyreon/document-primitives'
2415
2651
  import { download } from '@pyreon/document'
2416
2652
 
2417
- interface Resume { name: string; headline: string }
2418
-
2419
- function ResumeTemplate(props: { resume: () => Resume }) {
2420
- return (
2421
- // title and author accept reactive accessors — extractDocNode
2422
- // resolves them at extraction time, so each export click reads
2423
- // the LIVE value from the underlying signal
2424
- <DocDocument
2425
- title={() => \`\${props.resume().name} — Resume\`}
2426
- author={() => props.resume().name}
2427
- >
2428
- <DocPage>
2429
- <DocHeading level="h1">{() => props.resume().name}</DocHeading>
2430
- <DocText>{() => props.resume().headline}</DocText>
2431
- </DocPage>
2432
- </DocDocument>
2433
- )
2434
- }
2435
-
2436
- // One-step extraction. The two-step createDocumentExport(...).getDocNode()
2437
- // form is still exported for callers that want to pass the helper
2438
- // object around, but extractDocNode is the recommended form.
2439
- const tree = extractDocNode(() => <ResumeTemplate resume={store.resume} />)
2440
- await download(tree, 'resume.pdf')
2441
- await download(tree, 'resume.docx')
2442
- await download(tree, 'resume.html')
2443
- await download(tree, 'resume.md')`,
2444
- notes:
2445
- "18 primitives: DocDocument, DocPage, DocSection, DocRow, DocColumn, DocHeading, DocText, DocLink, DocImage, DocTable, DocList, DocListItem, DocCode, DocDivider, DocSpacer, DocButton, DocQuote, DocPageBreak. Same component tree renders in browser AND exports — primitives carry _documentType statics that extractDocumentTree (from @pyreon/connector-document) walks to produce a DocNode for @pyreon/document's render() to consume. DocDocument's title/author/subject accept either a string OR a `() => string` accessor; function values are stored in _documentProps and resolved at extraction time so reactive metadata works without `const initial = get()` workarounds. PR #197 also fixed a latent bug in extractDocumentTree: it now CALLS rocketstyle component functions to read post-attrs _documentProps, where before it only looked at the JSX vnode's props directly — every primitive's metadata was silently dropped during export until that fix landed.",
2446
- mistakes: `- Calling props.title() at the top of a template body to "fix" reactivity — components run ONCE at mount, so this captures the initial value forever. Pass the accessor through to DocDocument as-is: <DocDocument title={() => get().name}>
2447
- - DocRow direction: layout props (direction, gap) go in .attrs() not .theme(). Element accepts 'inline' | 'rows' | 'reverseInline' | 'reverseRows' — 'row' is NOT valid
2448
- - For text children reactivity, pass a signal accessor and read inside body: <DocText>{() => store.field()}</DocText>
2449
- - Don't declare runtime-filled fields (tag, _documentProps) in the rocketstyle .attrs<P>() generic — they leak as required JSX props
2450
- - Using createDocumentExport(...).getDocNode() in new code — prefer extractDocNode(fn) which is one call instead of two. createDocumentExport is kept for backward compat`,
2653
+ const tree = extractDocNode(() => (
2654
+ <DocDocument title="Quarterly Report" author="Aisha">
2655
+ <DocPage>
2656
+ <DocHeading level="h1">Q4 Results</DocHeading>
2657
+ <DocText>Revenue grew 23% YoY.</DocText>
2658
+ </DocPage>
2659
+ </DocDocument>
2660
+ ))
2661
+ await download(tree, 'report.pdf')
2662
+ await download(tree, 'report.docx')`,
2663
+ notes: `18 primitives: \`DocDocument\`, \`DocPage\`, \`DocSection\`, \`DocRow\`, \`DocColumn\`, \`DocHeading\`, \`DocText\`, \`DocLink\`, \`DocImage\`, \`DocTable\`, \`DocList\`, \`DocListItem\`, \`DocCode\`, \`DocDivider\`, \`DocSpacer\`, \`DocButton\`, \`DocQuote\`, \`DocPageBreak\`. Same component tree renders in browser AND exports — primitives carry \`_documentType\` statics that \`extractDocumentTree\` (from \`@pyreon/connector-document\`) walks to produce a \`DocNode\` for \`@pyreon/document\`\'s \`render()\` to consume. \`DocDocument\`\'s \`title\` / \`author\` / \`subject\` accept either a string OR a \`() => string\` accessor; function values are stored in \`_documentProps\` and resolved at extraction time so reactive metadata works without \`const initial = get()\` workarounds. PR #197 also fixed a latent bug in \`extractDocumentTree\`: it now CALLS rocketstyle component functions to read post-attrs \`_documentProps\`, where before it only looked at the JSX vnode\'s props directly — every primitive\'s metadata was silently dropped during export until that fix landed. See also: createDocumentExport.`,
2664
+ mistakes: `- Calling \`props.title()\` at the top of a template body to "fix" reactivity — components run ONCE at mount, so this captures the initial value forever. Pass the accessor through to DocDocument as-is: \`<DocDocument title={() => get().name}>\`
2665
+ - DocRow direction: layout props (direction, gap) go in \`.attrs()\` not \`.theme()\`. Element accepts \`'inline'\` | \`'rows'\` | \`'reverseInline'\` | \`'reverseRows'\` — \`'row'\` is NOT valid
2666
+ - For text children reactivity, pass a signal accessor and read inside body: \`<DocText>{() => store.field()}</DocText>\`
2667
+ - Don't declare runtime-filled fields (\`tag\`, \`_documentProps\`) in the rocketstyle \`.attrs<P>()\` generic — they leak as required JSX props
2668
+ - Using \`createDocumentExport(...).getDocNode()\` in new code — prefer \`extractDocNode(fn)\` which is one call instead of two. \`createDocumentExport\` is kept for backward compat`,
2451
2669
  },
2452
2670
 
2453
2671
  'document-primitives/createDocumentExport': {
2454
- signature:
2455
- 'createDocumentExport(templateFn: () => VNode): { getDocNode(): DocNode }',
2672
+ signature: 'createDocumentExport(templateFn: () => VNode): { getDocNode(): DocNode }',
2456
2673
  example: `// Two-step form (kept for backward compat). New code should
2457
2674
  // prefer the one-step extractDocNode helper.
2458
2675
  import { createDocumentExport } from '@pyreon/document-primitives'
2459
2676
 
2460
2677
  const helper = createDocumentExport(() => <Resume name="Aisha" />)
2461
2678
  const tree = helper.getDocNode()`,
2462
- notes:
2463
- "Wrapper around extractDocNode. The wrapper-object form is kept for callers that want to pass the helper around (e.g. to wrapper components that take a DocumentExport instance). New code should use extractDocNode(templateFn) which is one call instead of two.",
2464
- },
2679
+ notes: 'Wrapper around `extractDocNode`. The wrapper-object form is kept for callers that want to pass the helper around (e.g. to wrapper components that take a `DocumentExport` instance). New code should use `extractDocNode(templateFn)` which is one call instead of two. See also: extractDocNode.',
2680
+ },
2681
+
2682
+ 'document-primitives/DocDocument': {
2683
+ signature: '(props: { title?: string | (() => string); author?: string | (() => string); subject?: string | (() => string); children: VNodeChild }) => VNodeChild',
2684
+ example: `<DocDocument title="Quarterly Report" author="Aisha" subject="Q4 2025">
2685
+ <DocPage>...</DocPage>
2686
+ </DocDocument>
2687
+
2688
+ // Reactive metadata via accessor
2689
+ <DocDocument title={() => \`\${user().name} — Resume\`}>
2690
+ <DocPage>...</DocPage>
2691
+ </DocDocument>`,
2692
+ notes: 'Root container for a document tree — produces a `_documentType: "document"` node. Accepts optional metadata: `title`, `author`, `subject`. Each accepts either a plain string OR a `() => string` accessor; function values are stored in `_documentProps` and resolved at extraction time so each export call reads the LIVE value from any underlying signal. See also: DocPage, extractDocNode.',
2693
+ },
2694
+
2695
+ 'document-primitives/DocPage': {
2696
+ signature: `(props: { size?: string; orientation?: 'portrait' | 'landscape'; children: VNodeChild }) => VNodeChild`,
2697
+ example: `<DocDocument>
2698
+ <DocPage size="A4" orientation="portrait">
2699
+ <DocHeading level="h1">Page 1</DocHeading>
2700
+ </DocPage>
2701
+ <DocPage size="A4" orientation="landscape">
2702
+ <DocHeading level="h1">Page 2 — landscape</DocHeading>
2703
+ </DocPage>
2704
+ </DocDocument>`,
2705
+ notes: 'A page boundary inside a `DocDocument`. Paginated outputs (PDF, DOCX) treat each `DocPage` as a separate page; flow outputs (HTML, Markdown) render the contents inline with no page boundary. `size` and `orientation` configure paginated formats — common values: `"A4"`, `"Letter"`, `"Legal"`. See also: DocDocument, DocPageBreak.',
2706
+ },
2707
+
2708
+ 'document-primitives/DocSection': {
2709
+ signature: `(props: { direction?: 'column' | 'row'; children: VNodeChild }) => VNodeChild`,
2710
+ example: `<DocPage>
2711
+ <DocSection direction="column">
2712
+ <DocHeading level="h2">Introduction</DocHeading>
2713
+ <DocText>Background paragraph.</DocText>
2714
+ </DocSection>
2715
+ </DocPage>`,
2716
+ notes: 'Semantic grouping inside a page. Default `direction` is `"column"` (children stack vertically); `"row"` arranges them horizontally. Use to group related content for visual rhythm and for export targets that emit semantic section markers (HTML `<section>`, DOCX section breaks). See also: DocRow, DocColumn.',
2717
+ },
2718
+
2719
+ 'document-primitives/DocRow': {
2720
+ signature: '(props: { children: VNodeChild }) => VNodeChild',
2721
+ example: `<DocRow>
2722
+ <DocText>Name:</DocText>
2723
+ <DocText>Aisha Patel</DocText>
2724
+ </DocRow>`,
2725
+ notes: 'Horizontal layout container — children flow inline with a fixed 8px gap. Use for side-by-side content (label + value pairs, columns of metadata, button rows). Layout-only — no user-configurable props on this primitive; for columns with custom widths use `DocColumn` inside. See also: DocColumn, DocSection.',
2726
+ },
2727
+
2728
+ 'document-primitives/DocColumn': {
2729
+ signature: '(props: { width?: number | string; children: VNodeChild }) => VNodeChild',
2730
+ example: `<DocRow>
2731
+ <DocColumn width="30%">
2732
+ <DocText>Label</DocText>
2733
+ </DocColumn>
2734
+ <DocColumn width="70%">
2735
+ <DocText>Value</DocText>
2736
+ </DocColumn>
2737
+ </DocRow>`,
2738
+ notes: `A column inside a row layout. Optional \`width\` controls the column\'s share of the row — accepts a number (interpreted as pixels) or a string (\`"50%"\`, \`"1fr"\`). When omitted, columns share available width equally. Most common shape is \`<DocRow><DocColumn width="30%" /> <DocColumn width="70%" /></DocRow>\`. See also: DocRow, DocSection.`,
2739
+ },
2740
+
2741
+ 'document-primitives/DocHeading': {
2742
+ signature: `(props: { level?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; children: VNodeChild }) => VNodeChild`,
2743
+ example: `<DocHeading level="h1">Quarterly Report</DocHeading>
2744
+ <DocHeading level="h2">Q4 Results</DocHeading>
2745
+ <DocHeading level="h3">Revenue Breakdown</DocHeading>`,
2746
+ notes: 'Heading text — `level` (`"h1"` through `"h6"`) controls both visual size and the semantic level emitted to outputs (HTML `<h1>...<h6>`, DOCX heading styles, Markdown `#`...`######`). Default `level` is `"h1"`. Used for document structure that downstream tooling can build a TOC from. See also: DocText, DocSection.',
2747
+ },
2748
+
2749
+ 'document-primitives/DocText': {
2750
+ signature: '(props: { children: VNodeChild }) => VNodeChild',
2751
+ example: `<DocText>Static paragraph content.</DocText>
2752
+
2753
+ // Reactive children
2754
+ <DocText>{() => \`Hello, \${user().name}\`}</DocText>`,
2755
+ notes: 'Paragraph / inline text. The most common primitive — wraps any text content for the document. Children may be string literals OR signal accessors (`{() => store.field()}`) for reactive content. Visual styling (font weight, variant) is controlled via rocketstyle dimension props on the wrapping component definition. See also: DocHeading, DocLink.',
2756
+ },
2757
+
2758
+ 'document-primitives/DocLink': {
2759
+ signature: '(props: { href?: string; children: VNodeChild }) => VNodeChild',
2760
+ example: `<DocText>
2761
+ Read more on
2762
+ <DocLink href="https://pyreon.dev">our blog</DocLink>
2763
+ for the latest releases.
2764
+ </DocText>`,
2765
+ notes: 'Hyperlink within text. `href` is the URL — defaults to `"#"`. Outputs that support hyperlinks (HTML, PDF, DOCX, email) render this as a clickable link; flat outputs (plain text, certain Slack variants) render the link target inline as `text (href)`. See also: DocText.',
2766
+ },
2767
+
2768
+ 'document-primitives/DocImage': {
2769
+ signature: '(props: { src: string; alt?: string; width?: number; height?: number; caption?: string }) => VNodeChild',
2770
+ example: `<DocImage
2771
+ src="/charts/q4-revenue.png"
2772
+ alt="Revenue grew 23% in Q4"
2773
+ width={600}
2774
+ height={400}
2775
+ caption="Figure 1: Quarterly revenue, 2024-2025"
2776
+ />`,
2777
+ notes: 'An image embedded in the document. `src` is the image URL or data URI. `alt` is the accessible description (also used as fallback text in non-visual outputs). `width` / `height` constrain dimensions in pixels. Optional `caption` renders a caption beneath the image. See also: DocCode.',
2778
+ },
2779
+
2780
+ 'document-primitives/DocTable': {
2781
+ signature: '(props: { columns: TableColumn[]; rows: TableRow[]; headerStyle?: object; striped?: boolean; bordered?: boolean; caption?: string }) => VNodeChild',
2782
+ example: `<DocTable
2783
+ caption="Q4 results by region"
2784
+ bordered
2785
+ striped
2786
+ columns={[
2787
+ { key: 'region', label: 'Region', align: 'left' },
2788
+ { key: 'revenue', label: 'Revenue', align: 'right' },
2789
+ { key: 'growth', label: 'YoY Growth', align: 'right' },
2790
+ ]}
2791
+ rows={[
2792
+ { region: 'NA', revenue: '$12.4M', growth: '+23%' },
2793
+ { region: 'EU', revenue: '$8.7M', growth: '+18%' },
2794
+ { region: 'APAC', revenue: '$5.1M', growth: '+41%' },
2795
+ ]}
2796
+ />`,
2797
+ notes: 'Tabular data. `columns` defines the header cells (label, key, optional alignment). `rows` is an array of data rows keyed by column key. `striped` adds alternating row backgrounds; `bordered` adds cell borders; `caption` renders an accessible table caption. Both `rows` and `columns` are filtered before reaching the DOM via `.attrs(..., { filter: [...] })` because `HTMLTableElement.rows` / `.cells` are read-only DOM properties — assignment would crash. See also: DocList, DocSection.',
2798
+ },
2799
+
2800
+ 'document-primitives/DocList': {
2801
+ signature: '(props: { ordered?: boolean; children: VNodeChild }) => VNodeChild',
2802
+ example: `<DocList>
2803
+ <DocListItem>First bullet</DocListItem>
2804
+ <DocListItem>Second bullet</DocListItem>
2805
+ </DocList>
2806
+
2807
+ <DocList ordered>
2808
+ <DocListItem>First step</DocListItem>
2809
+ <DocListItem>Second step</DocListItem>
2810
+ </DocList>`,
2811
+ notes: 'Bulleted (default) or numbered (`ordered`) list. Children are typically `DocListItem` instances. Outputs map this to the right native list type — HTML `<ul>` / `<ol>`, Markdown `-` / `1.`, DOCX list styles. See also: DocListItem.',
2812
+ },
2813
+
2814
+ 'document-primitives/DocListItem': {
2815
+ signature: '(props: { children: VNodeChild }) => VNodeChild',
2816
+ example: `<DocList>
2817
+ <DocListItem>Top-level item</DocListItem>
2818
+ <DocListItem>
2819
+ Item with nested list
2820
+ <DocList>
2821
+ <DocListItem>Nested A</DocListItem>
2822
+ <DocListItem>Nested B</DocListItem>
2823
+ </DocList>
2824
+ </DocListItem>
2825
+ </DocList>`,
2826
+ notes: `Single item inside a \`DocList\`. Children may be plain text, \`DocText\`, nested \`DocList\` for sublists, or any other inline primitive. Visual marker (bullet vs number) is decided by the parent list\'s \`ordered\` prop, not by the item. See also: DocList.`,
2827
+ },
2828
+
2829
+ 'document-primitives/DocCode': {
2830
+ signature: '(props: { language?: string; children: VNodeChild }) => VNodeChild',
2831
+ example: `<DocCode language="typescript">{
2832
+ \`const flow = createFlow({
2833
+ nodes: [{ id: '1', position: { x: 0, y: 0 } }],
2834
+ edges: [],
2835
+ })\`
2836
+ }</DocCode>`,
2837
+ notes: 'Monospace code block. Optional `language` hint enables syntax highlighting in outputs that support it (HTML via Prism / Shiki, Markdown fenced code blocks with language tag). Whitespace is preserved verbatim — pass code as a single string child to keep newlines. See also: DocText.',
2838
+ },
2839
+
2840
+ 'document-primitives/DocDivider': {
2841
+ signature: '(props: { color?: string; thickness?: number }) => VNodeChild',
2842
+ example: `<DocText>Above the divider.</DocText>
2843
+ <DocDivider color="#e5e7eb" thickness={1} />
2844
+ <DocText>Below the divider.</DocText>`,
2845
+ notes: 'Horizontal rule — visual section separator. `color` controls the line color (any CSS color string); `thickness` controls the line thickness in pixels. Outputs map this to native dividers — HTML `<hr>`, Markdown `---`, DOCX horizontal rule. See also: DocSpacer.',
2846
+ },
2847
+
2848
+ 'document-primitives/DocSpacer': {
2849
+ signature: '(props: { height?: number }) => VNodeChild',
2850
+ example: `<DocSection>
2851
+ <DocHeading level="h2">Section A</DocHeading>
2852
+ <DocText>Content...</DocText>
2853
+ <DocSpacer height={32} />
2854
+ <DocHeading level="h2">Section B</DocHeading>
2855
+ <DocText>More content...</DocText>
2856
+ </DocSection>`,
2857
+ notes: 'Vertical whitespace — adds a blank vertical gap. `height` is in pixels (default 16). Use to space out content beyond what `DocSection` / `DocPage` margins provide. In flow outputs this becomes a styled blank block; in plain-text outputs, a sequence of newlines. See also: DocDivider.',
2858
+ },
2859
+
2860
+ 'document-primitives/DocButton': {
2861
+ signature: '(props: { href?: string; children: VNodeChild }) => VNodeChild',
2862
+ example: `<DocButton href="https://pyreon.dev/signup">
2863
+ Get started
2864
+ </DocButton>`,
2865
+ notes: 'Call-to-action button. Renders as a styled clickable element in HTML / email outputs (mail-safe button table layout for email), and as a labeled link in PDF / DOCX. `href` is the action URL — defaults to `"#"`. Visual style (variant) is controlled via rocketstyle dimensions on the component definition. See also: DocLink.',
2866
+ },
2867
+
2868
+ 'document-primitives/DocQuote': {
2869
+ signature: '(props: { borderColor?: string; children: VNodeChild }) => VNodeChild',
2870
+ example: `<DocQuote borderColor="#3b82f6">
2871
+ <DocText>"The best way to predict the future is to build it."</DocText>
2872
+ <DocText>— Aisha Patel, Q4 keynote</DocText>
2873
+ </DocQuote>`,
2874
+ notes: 'Block quote — sets off a quoted passage with an indented left border. `borderColor` controls the indicator stripe (any CSS color). Outputs map this to native quote styling — HTML `<blockquote>`, Markdown `> ...`, DOCX quote style. See also: DocText.',
2875
+ },
2876
+
2877
+ 'document-primitives/DocPageBreak': {
2878
+ signature: '() => VNodeChild',
2879
+ example: `<DocPage>
2880
+ <DocHeading level="h1">Section 1</DocHeading>
2881
+ <DocText>...long content...</DocText>
2882
+ <DocPageBreak />
2883
+ <DocHeading level="h1">Section 2 — new page</DocHeading>
2884
+ </DocPage>`,
2885
+ notes: 'Explicit page boundary inside a `DocPage`. Forces the renderer to start a new page at this point in paginated outputs (PDF, DOCX). In flow outputs (HTML, Markdown), it renders as visible whitespace or is omitted entirely. Use for explicit pagination control beyond what `DocPage` boundaries already provide. See also: DocPage.',
2886
+ },
2887
+ // <gen-docs:api-reference:end @pyreon/document-primitives>
2465
2888
  }