@pyreon/mcp 0.13.1 → 0.15.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.
@@ -271,10 +271,10 @@ const getMode = useContext(ModeCtx) // reactive: returns () => T`,
271
271
  example: `<Show when={isLoggedIn()} fallback={<LoginForm />}>
272
272
  <Dashboard />
273
273
  </Show>`,
274
- notes: 'Reactive conditional rendering. Mounts children when `when` is truthy, unmounts and shows `fallback` when falsy. More efficient than ternary for signal-driven conditions because it avoids re-evaluating the entire branch expression on every signal change — `Show` only transitions between mounted/unmounted when the boolean flips. See also: Switch, Match, For.',
274
+ notes: `Reactive conditional rendering. Mounts children when \`when\` is truthy, unmounts and shows \`fallback\` when falsy. More efficient than ternary for signal-driven conditions because it avoids re-evaluating the entire branch expression on every signal change — \`Show\` only transitions between mounted/unmounted when the boolean flips. \`when\` accepts BOTH a value (\`when={true}\`, \`when={signal()}\`) and an accessor (\`when={() => signal()}\`) — the framework normalizes via \`typeof === "function"\`. The accessor form is required for true reactivity (the framework re-evaluates it on signal change); a bare \`when={signal}\` reference works because the compiler's signal auto-call rewrites it to \`when={signal()}\`. See also: Switch, Match, For.`,
275
275
  mistakes: `- \`{cond() ? <A /> : <B />}\` — works but less efficient than \`<Show>\` for signal-driven conditions
276
276
  - \`<Show when={items().length}>\` — works (truthy check), but be explicit: \`<Show when={items().length > 0}>\`
277
- - \`<Show when={user}>\` without calling the signal must call: \`<Show when={user()}>\``,
277
+ - \`<Show when={signal}>\` (bare reference) — relies on the compiler's signal auto-call to rewrite to \`when={signal()}\`. Works defensively but use \`when={() => signal()}\` for explicit accessor semantics across the entire reactive lifecycle.`,
278
278
  },
279
279
 
280
280
  'core/Switch': {
@@ -299,7 +299,7 @@ const getMode = useContext(ModeCtx) // reactive: returns () => T`,
299
299
  <Match when={tab() === "home"}><Home /></Match>
300
300
  <Match when={tab() === "settings"}><Settings /></Match>
301
301
  </Switch>`,
302
- notes: 'A branch inside a `<Switch>`. Renders its children when `when` is truthy and it is the first truthy `<Match>` in the parent `<Switch>`. Must be a direct child of `<Switch>`. See also: Switch, Show.',
302
+ notes: 'A branch inside a `<Switch>`. Renders its children when `when` is truthy and it is the first truthy `<Match>` in the parent `<Switch>`. Must be a direct child of `<Switch>`. `when` accepts both a value and an accessor (same normalization as `<Show>`). See also: Switch, Show.',
303
303
  },
304
304
 
305
305
  'core/For': {
@@ -445,6 +445,35 @@ return <input ref={inputRef} />`,
445
445
  notes: 'Execute a function reading signals WITHOUT subscribing to them. Alias for `runUntracked` from `@pyreon/reactivity`. Use inside effects when you need a one-shot snapshot of a signal value without the effect re-running when that signal changes. See also: @pyreon/reactivity.',
446
446
  },
447
447
 
448
+ 'core/nativeCompat': {
449
+ signature: '<T>(fn: T) => T',
450
+ example: `// In a framework package:
451
+ export const RouterView = nativeCompat(function RouterView(props) {
452
+ provide(RouterContext, ...)
453
+ return <div>{children}</div>
454
+ })`,
455
+ notes: `Mark a Pyreon framework component as "self-managing" so compat layers (\`@pyreon/{react,preact,vue,solid}-compat\`) skip their wrapping and route the component through Pyreon's mount path. Use on every \`@pyreon/*\` JSX component whose setup body uses \`provide()\` / lifecycle hooks / signal subscriptions — wrapping breaks those by running the body inside the compat layer's render context instead of Pyreon's. Idempotent; non-function inputs pass through unchanged. The marker is a registry symbol (\`Symbol.for("pyreon:native-compat")\`), so framework and compat sides share it without an import dependency between them. See also: isNativeCompat, NATIVE_COMPAT_MARKER.`,
456
+ mistakes: `- Forgetting to mark a new framework JSX export — under compat mode, the component's \`provide()\` / \`onMount()\` calls fail with "called outside component setup" warnings and the rendered DOM silently breaks.
457
+ - Marking user-app components — only \`@pyreon/*\` framework components that already manage their own reactivity should be marked. User components in compat mode are SUPPOSED to be wrapped (that's how they get re-render-on-state-change semantics).`,
458
+ },
459
+
460
+ 'core/isNativeCompat': {
461
+ signature: '(fn: unknown) => boolean',
462
+ example: `// In a compat layer's jsx-runtime:
463
+ if (isNativeCompat(type)) return h(type, props)
464
+ return wrapCompatComponent(type)(props)`,
465
+ notes: 'Compat-layer-side: read whether a function has been marked as a Pyreon native framework component via `nativeCompat()`. Compat `jsx()` calls this to decide whether to skip the React/Vue/Solid/Preact-style wrapping. Always returns `false` for non-function inputs. See also: nativeCompat, NATIVE_COMPAT_MARKER.',
466
+ },
467
+
468
+ 'core/NATIVE_COMPAT_MARKER': {
469
+ signature: 'symbol',
470
+ example: `import { NATIVE_COMPAT_MARKER } from '@pyreon/core'
471
+
472
+ // Equivalent to nativeCompat(MyComponent):
473
+ ;(MyComponent as Record<symbol, boolean>)[NATIVE_COMPAT_MARKER] = true`,
474
+ notes: 'The well-known registry symbol (`Symbol.for("pyreon:native-compat")`) used to mark a component as a Pyreon native framework component. Most callers should use `nativeCompat()` / `isNativeCompat()` instead of touching the symbol directly; exported for advanced cases (e.g., a compat layer that wants to inspect the property without going through the helper). See also: nativeCompat, isNativeCompat.',
475
+ },
476
+
448
477
  'core/ExtractProps': {
449
478
  signature: 'type ExtractProps<T> = T extends ComponentFn<infer P> ? P : T',
450
479
  example: `const Greet: ComponentFn<{ name: string }> = ({ name }) => <h1>{name}</h1>
@@ -607,6 +636,58 @@ const User = () => {
607
636
  notes: `Access the data returned by the current route's \`loader\` function. The loader runs before the route component mounts; its return value is cached and available synchronously via this hook. Generic over the loader return type. See also: useMiddlewareData, useRoute.`,
608
637
  },
609
638
 
639
+ 'router/redirect': {
640
+ signature: 'redirect(url: string, status?: 301 | 302 | 303 | 307 | 308): never',
641
+ example: `// src/routes/app/_layout.tsx
642
+ import { redirect, type LoaderContext } from "@pyreon/router"
643
+
644
+ export async function loader(ctx: LoaderContext) {
645
+ // SSR: read from request headers; CSR: read from document.cookie
646
+ const cookie = ctx.request?.headers.get("cookie")
647
+ ?? (typeof document !== "undefined" ? document.cookie : "")
648
+ const sid = /(?:^|;\s*)sid=([^;]+)/.exec(cookie)?.[1]
649
+ if (!sid) redirect("/login")
650
+ const session = await getSession(sid)
651
+ if (!session) redirect("/login")
652
+ return { session }
653
+ }`,
654
+ notes: `Throw inside a route loader to redirect the navigation BEFORE the layout renders. On SSR (initial nav), the thrown error is converted by \`@pyreon/server\`'s handler into a real HTTP \`302\`/\`307\` \`Location:\` response — no layout HTML leaves the server. On CSR (subsequent nav), the redirect propagates through the navigate flow and triggers \`router.replace()\` before any matched route's component mounts. Replaces the fragile \`onMount + router.push()\` workaround for auth-gates under nested-layout dev SSR + hydration. Default status is \`307\` (Temporary Redirect, method-preserving). See also: notFound, useLoaderData, isRedirectError.`,
655
+ mistakes: `- Calling \`redirect()\` outside a loader (in a component body, an event handler, etc.) — the helper expects to be caught by the loader-runner. For imperative redirects from event handlers, use \`router.replace(target)\` instead.
656
+ - Forgetting to make \`LoaderContext.request\` access optional. It's populated only on SSR; CSR loaders see \`request: undefined\`. Read both: \`ctx.request?.headers.get('cookie') ?? document.cookie\`.
657
+ - Using \`redirect()\` for control-flow that should be a \`<Match>\` / \`<Show>\` conditional — the helper is for redirecting the URL, not for branching the rendered output.
658
+ - Returning \`redirect()\` instead of throwing it. The helper has return type \`never\` and throws — \`return redirect(...)\` is misleading and may suppress the throw under TS strict-null checks.
659
+ - Picking the wrong status. Default \`307\` preserves the request method (POST stays POST after redirect). Use \`302\`/\`303\` to force GET on the target. Use \`301\`/\`308\` for PERMANENT moves (browsers cache them aggressively).
660
+ - Assuming \`redirect()\` cancels every loader in a sibling chain. The first loader to throw wins; later loaders in the same \`Promise.allSettled\` batch may have already started executing before the redirect short-circuits. Treat them as best-effort.`,
661
+ },
662
+
663
+ 'router/isRedirectError': {
664
+ signature: 'isRedirectError(err: unknown): boolean',
665
+ example: `import { ErrorBoundary } from "@pyreon/core"
666
+ import { isRedirectError } from "@pyreon/router"
667
+
668
+ <ErrorBoundary fallback={(err, reset) => {
669
+ if (isRedirectError(err)) throw err // let the framework handle it
670
+ return <ErrorPage error={err} onReset={reset} />
671
+ }}>
672
+ <App />
673
+ </ErrorBoundary>`,
674
+ notes: 'Type guard for errors thrown by `redirect()`. Used internally by the router (CSR) and `@pyreon/server` (SSR) to distinguish redirect-control-flow errors from real failures. Useful in custom error boundaries that should let redirects pass through to the framework instead of catching them. See also: redirect, isNotFoundError, getRedirectInfo.',
675
+ },
676
+
677
+ 'router/getRedirectInfo': {
678
+ signature: 'getRedirectInfo(err: unknown): { url: string; status: 301 | 302 | 303 | 307 | 308 } | null',
679
+ example: `import { getRedirectInfo } from "@pyreon/router"
680
+
681
+ try {
682
+ await prefetchLoaderData(router, path, request)
683
+ } catch (err) {
684
+ const info = getRedirectInfo(err)
685
+ if (info) return new Response(null, { status: info.status, headers: { Location: info.url } })
686
+ throw err
687
+ }`,
688
+ notes: `Extract the redirect URL and status from a thrown RedirectError. Returns \`null\` for non-redirect errors. Used by \`@pyreon/server\`'s SSR handler to convert the thrown error into a 302/307 \`Response\`. See also: redirect, isRedirectError.`,
689
+ },
690
+
610
691
  'router/useSearchParams': {
611
692
  signature: 'useSearchParams<T>(defaults?: T): [get: () => T, set: (updates: Partial<T>) => Promise<void>]',
612
693
  example: `const [search, setSearch] = useSearchParams({ page: "1", sort: "name" })
@@ -655,6 +736,8 @@ setSearch({ page: "2" })`,
655
736
  // @pyreon/head
656
737
  // ═══════════════════════════════════════════════════════════════════════════
657
738
 
739
+ // <gen-docs:api-reference:start @pyreon/head>
740
+
658
741
  'head/useHead': {
659
742
  signature: 'useHead(input: UseHeadInput | (() => UseHeadInput)): void',
660
743
  example: `// Static:
@@ -665,23 +748,58 @@ useHead(() => ({
665
748
  title: \`\${username()} — Profile\`,
666
749
  meta: [{ property: "og:title", content: username() }]
667
750
  }))`,
751
+ 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.',
752
+ mistakes: `- Using \`\${...}\` in a \`titleTemplate\` string — the placeholder is \`%s\` (or pass a function form \`(title) => …\`)
753
+ - Calling \`useHead()\` outside any \`HeadProvider\` / \`renderWithHead()\` boundary — silent no-op, the entries simply go nowhere
754
+ - Wrapping the input in \`computed()\` instead of a thunk — pass a plain \`() => ({...})\` arrow; \`useHead\` registers its own effect
755
+ - 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
756
  },
669
757
 
670
758
  'head/HeadProvider': {
671
- signature: '<HeadProvider>{children}</HeadProvider>',
672
- example: `// Client-side setup:
759
+ signature: '(props: HeadProviderProps) => VNodeChild',
760
+ example: `<HeadProvider>{children}</HeadProvider>
761
+
762
+ // Client-side setup:
673
763
  mount(
674
764
  <HeadProvider>
675
765
  <App />
676
766
  </HeadProvider>,
677
767
  document.getElementById("app")!
678
768
  )`,
769
+ 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.',
770
+ 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
771
+ - Forgetting to mount \`HeadProvider\` and expecting \`useHead()\` to still update \`document.head\` — silent no-op outside a provider`,
772
+ },
773
+
774
+ 'head/renderWithHead': {
775
+ signature: 'renderWithHead(app: VNode): Promise<{ html: string; head: string; htmlAttrs: string; bodyAttrs: string }>',
776
+ example: `import { renderWithHead } from '@pyreon/head'
777
+
778
+ const { html, head, htmlAttrs, bodyAttrs } = await renderWithHead(<App />)
779
+ const doc = \`<!doctype html><html\${htmlAttrs}><head>\${head}</head><body\${bodyAttrs}>\${html}</body></html>\``,
780
+ 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.',
781
+ mistakes: `- Awaiting \`renderWithHead\` and then NOT splicing \`head\` into the \`<head>\` element — every \`useHead()\` call quietly disappears
782
+ - 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`,
783
+ },
784
+
785
+ 'head/createHeadContext': {
786
+ signature: '() => HeadContextValue',
787
+ example: `import { createHeadContext, HeadContext } from '@pyreon/head'
788
+
789
+ const ctx = createHeadContext()
790
+ provide(HeadContext, ctx)
791
+ // ... render tree that calls useHead() ...
792
+ const { tags, htmlAttrs, bodyAttrs } = ctx.resolve()`,
793
+ 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.',
679
794
  },
795
+ // <gen-docs:api-reference:end @pyreon/head>
680
796
 
681
797
  // ═══════════════════════════════════════════════════════════════════════════
682
798
  // @pyreon/server
683
799
  // ═══════════════════════════════════════════════════════════════════════════
684
800
 
801
+ // <gen-docs:api-reference:start @pyreon/server>
802
+
685
803
  'server/createHandler': {
686
804
  signature: 'createHandler(options: HandlerOptions): (req: Request) => Promise<Response>',
687
805
  example: `import { createHandler } from "@pyreon/server"
@@ -692,17 +810,26 @@ export default createHandler({
692
810
  clientEntry: "/src/entry-client.ts",
693
811
  mode: "stream", // or "string"
694
812
  })`,
813
+ 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.',
814
+ mistakes: `- Omitting \`<!--pyreon-app-->\` from the custom template — throws at handler-creation, not per request
815
+ - Returning a \`Response\` from middleware and expecting downstream middleware to still run — the chain short-circuits on the first \`Response\`
816
+ - Reading \`ctx.locals\` from inside the component without \`useRequestLocals()\` — the component tree only sees locals when bridged through that hook
817
+ - 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
818
  },
696
819
 
697
820
  'server/island': {
698
- signature:
699
- 'island(loader: () => Promise<ComponentFn>, options: { name: string; hydrate?: HydrationStrategy }): ComponentFn',
821
+ signature: 'island(loader: () => Promise<ComponentFn>, options: { name: string; hydrate?: HydrationStrategy }): ComponentFn',
700
822
  example: `const SearchBar = island(
701
823
  () => import("./SearchBar"),
702
824
  { name: "SearchBar", hydrate: "visible" }
703
825
  )
704
826
 
705
827
  // Hydration strategies: "load" | "idle" | "visible" | "media" | "never"`,
828
+ 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.',
829
+ mistakes: `- Passing function props (event handlers, callbacks) — silently stripped during JSON serialization, the island sees \`undefined\`
830
+ - Passing children to an island — stripped; islands cannot render arbitrary descendant trees from props
831
+ - Forgetting to call \`hydrateIslands({ Name: () => import("./Path") })\` on the client — islands render as HTML and never hydrate
832
+ - Using a duplicate \`name\` across two islands — the client-side registry collapses them, only one loader will fire`,
706
833
  },
707
834
 
708
835
  'server/prerender': {
@@ -712,7 +839,12 @@ export default createHandler({
712
839
  paths: ["/", "/about", "/blog/1", "/blog/2"],
713
840
  outDir: "./dist",
714
841
  })`,
842
+ 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.',
843
+ mistakes: `- Passing a relative \`outDir\` and being surprised when it resolves against \`process.cwd()\` — pass an absolute path for predictability
844
+ - Expecting per-page errors to throw — they\'re collected in \`result.errors\`; check the array after \`await\`
845
+ - Generating thousands of paths without batching — the function processes the array sequentially; if you need parallelism, batch the \`paths\` array yourself`,
715
846
  },
847
+ // <gen-docs:api-reference:end @pyreon/server>
716
848
 
717
849
  // ═══════════════════════════════════════════════════════════════════════════
718
850
  // @pyreon/runtime-dom
@@ -2127,6 +2259,8 @@ Posts.useTable({ columns: ['title', 'author'] })`,
2127
2259
  // @pyreon/lint
2128
2260
  // ═══════════════════════════════════════════════════════════════════════════
2129
2261
 
2262
+ // <gen-docs:api-reference:start @pyreon/lint>
2263
+
2130
2264
  'lint/lint': {
2131
2265
  signature: 'lint(options?: LintOptions): LintResult',
2132
2266
  example: `import { lint } from "@pyreon/lint"
@@ -2144,34 +2278,29 @@ lint({
2144
2278
  "pyreon/no-window-in-ssr": { exemptPaths: ["src/foundation/"] },
2145
2279
  },
2146
2280
  })`,
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.',
2281
+ notes: '62 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
2282
  },
2150
2283
 
2151
2284
  'lint/lintFile': {
2152
- signature:
2153
- 'lintFile(filePath: string, sourceText: string, rules: Rule[], config: LintConfig, cache?: AstCache, configDiagnosticsSink?: ConfigDiagnostic[]): LintFileResult',
2285
+ signature: 'lintFile(filePath: string, sourceText: string, rules: Rule[], config: LintConfig, cache?: AstCache, configDiagnosticsSink?: ConfigDiagnostic[]): LintFileResult',
2154
2286
  example: `import { lintFile, allRules, getPreset, AstCache } from "@pyreon/lint"
2155
2287
 
2156
2288
  const cache = new AstCache()
2157
2289
  const config = getPreset("recommended")
2158
2290
  const configSink: ConfigDiagnostic[] = []
2159
2291
  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.',
2292
+ 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
2293
  },
2163
2294
 
2164
2295
  '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...]',
2296
+ 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
2297
  example: `pyreon-lint --preset strict --quiet # CI mode
2168
2298
  pyreon-lint --fix # auto-fix
2169
2299
  pyreon-lint --watch src/ # watch mode
2170
- pyreon-lint --list # list all 59 rules
2300
+ pyreon-lint --list # list all 62 rules
2171
2301
  pyreon-lint --format json # machine-readable
2172
2302
  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.",
2303
+ 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
2304
  },
2176
2305
 
2177
2306
  'lint/no-process-dev-gate': {
@@ -2184,8 +2313,7 @@ if (__DEV__) console.warn('hello')
2184
2313
  // @ts-ignore — provided by Vite/Rolldown at build time
2185
2314
  const __DEV__ = import.meta.env?.DEV === true
2186
2315
  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`.",
2316
+ 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
2317
  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
2318
  - 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
2319
  - Adding \`process: { env: { ... } }\` polyfills to vite.config.ts as a workaround — fix the source instead
@@ -2206,13 +2334,19 @@ if (__DEV__) console.warn('hello')`,
2206
2334
  ]
2207
2335
  }
2208
2336
  }`,
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.",
2337
+ 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
2338
  mistakes: `- Adding a new browser-running package without a browser test — the rule will fail your PR
2212
2339
  - 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
2340
  - Disabling the rule globally — use \`exemptPaths\` to exempt specific packages still under construction
2214
2341
  - 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
2342
  },
2343
+ // <gen-docs:api-reference:end @pyreon/lint>
2344
+
2345
+ // ═══════════════════════════════════════════════════════════════════════════
2346
+ // @pyreon/mcp
2347
+ // ═══════════════════════════════════════════════════════════════════════════
2348
+
2349
+ // <gen-docs:api-reference:start @pyreon/mcp>
2216
2350
 
2217
2351
  'mcp/get_browser_smoke_status': {
2218
2352
  signature: 'tool: get_browser_smoke_status — no args',
@@ -2220,18 +2354,103 @@ if (__DEV__) console.warn('hello')`,
2220
2354
  // "which Pyreon packages are missing browser smoke coverage?"
2221
2355
  // Tool walks packages/, matches against .claude/rules/browser-packages.json,
2222
2356
  // 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).",
2357
+ 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
2358
  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
2359
  },
2227
2360
 
2361
+ 'mcp/get_api': {
2362
+ signature: 'tool: get_api({ package: string; symbol: string }) → APIEntry',
2363
+ example: `// Agent-side
2364
+ get_api({ package: 'flow', symbol: 'createFlow' })
2365
+ get_api({ package: '@pyreon/router', symbol: 'useTypedSearchParams' })`,
2366
+ 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.`,
2367
+ },
2368
+
2369
+ 'mcp/validate': {
2370
+ signature: 'tool: validate({ code: string; filename?: string }) → Diagnostics[]',
2371
+ example: `validate({ code: \`
2372
+ function MyComp(props) {
2373
+ const { value } = props // → props-destructured
2374
+ return <For each={items}>{...}</For> // → for-missing-by
2375
+ }
2376
+ \` })`,
2377
+ 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.',
2378
+ },
2379
+
2380
+ 'mcp/migrate_react': {
2381
+ signature: 'tool: migrate_react({ code: string; filename?: string }) → MigrationResult',
2382
+ example: `migrate_react({ code: \`
2383
+ import { useState, useEffect } from 'react'
2384
+ function Counter() {
2385
+ const [count, setCount] = useState(0)
2386
+ useEffect(() => { console.log(count) }, [count])
2387
+ return <button onClick={() => setCount(count + 1)}>{count}</button>
2388
+ }
2389
+ \` })`,
2390
+ 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.',
2391
+ },
2392
+
2393
+ 'mcp/diagnose': {
2394
+ signature: 'tool: diagnose({ error: string }) → DiagnoseResult',
2395
+ example: `diagnose({ error: 'Cannot redefine property X on object [object Object]' })
2396
+ // → cause: configurable: false on a getter; fix: set configurable: true`,
2397
+ 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.',
2398
+ },
2399
+
2400
+ 'mcp/get_routes': {
2401
+ signature: 'tool: get_routes() → Route[]',
2402
+ example: `get_routes()
2403
+ // → [{ path: '/', name: 'home', hasLoader: true, params: [] }, ...]`,
2404
+ 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.',
2405
+ },
2406
+
2407
+ 'mcp/get_components': {
2408
+ signature: 'tool: get_components() → ComponentInfo[]',
2409
+ example: `get_components()
2410
+ // → [{ name: 'Button', file: 'src/Button.tsx', props: ['onClick', 'children'], signals: ['count'] }, ...]`,
2411
+ 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.',
2412
+ },
2413
+
2414
+ 'mcp/get_pattern': {
2415
+ signature: 'tool: get_pattern({ name?: string }) → PatternBody | string[]',
2416
+ example: `get_pattern({ name: 'controllable-state' })
2417
+ // → full canonical pattern body
2418
+ get_pattern({})
2419
+ // → [{ name: 'controllable-state', summary: '...' }, ...]`,
2420
+ 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.',
2421
+ },
2422
+
2423
+ 'mcp/get_anti_patterns': {
2424
+ signature: `tool: get_anti_patterns({ category?: 'reactivity' | 'jsx' | 'context' | 'architecture' | 'testing' | 'lifecycle' | 'documentation' | 'all' }) → AntiPattern[]`,
2425
+ example: `get_anti_patterns({ category: 'reactivity' })
2426
+ // → ['Bare signal in JSX text', 'Stale closures', 'Destructuring props', ...]`,
2427
+ 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.',
2428
+ },
2429
+
2430
+ 'mcp/get_changelog': {
2431
+ signature: 'tool: get_changelog({ package?: string; limit?: number; includeDependencyUpdates?: boolean; since?: string }) → ChangelogEntry[]',
2432
+ example: `get_changelog({ package: 'flow', limit: 5 })
2433
+ get_changelog({ package: '@pyreon/router', since: '0.12.0' })`,
2434
+ 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.',
2435
+ },
2436
+
2437
+ 'mcp/audit_test_environment': {
2438
+ signature: `tool: audit_test_environment({ minRisk?: 'high' | 'medium' | 'low'; limit?: number }) → AuditReport`,
2439
+ example: `audit_test_environment({ minRisk: 'medium', limit: 10 })
2440
+ // → grouped report with HIGH / MEDIUM / LOW sections`,
2441
+ 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.`,
2442
+ },
2443
+ // <gen-docs:api-reference:end @pyreon/mcp>
2444
+
2445
+
2228
2446
  // ═══════════════════════════════════════════════════════════════════════════
2229
2447
  // @pyreon/ui-core
2230
2448
  // ═══════════════════════════════════════════════════════════════════════════
2231
2449
 
2450
+ // <gen-docs:api-reference:start @pyreon/ui-core>
2451
+
2232
2452
  'ui-core/PyreonUI': {
2233
- signature:
2234
- "PyreonUI(props: { theme?: Theme; mode?: 'light' | 'dark' | 'system'; inversed?: boolean; children: VNodeChild }): VNodeChild",
2453
+ signature: `(props: { theme?: Theme; mode?: 'light' | 'dark' | 'system'; inversed?: boolean; children: VNodeChild }) => VNodeChild`,
2235
2454
  example: `import { PyreonUI } from "@pyreon/ui-core"
2236
2455
  import { enrichTheme } from "@pyreon/unistyle"
2237
2456
 
@@ -2243,27 +2462,32 @@ const theme = enrichTheme({ colors: { primary: "#3b82f6" } })
2243
2462
 
2244
2463
  // mode="system" auto-detects OS dark mode via prefers-color-scheme
2245
2464
  // 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`,
2465
+ 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.`,
2466
+ mistakes: `- Using \`ThemeProvider\` + \`ModeProvider\` + \`ConfigProvider\` separately \`PyreonUI\` is the single replacement covering all three
2467
+ - Forgetting \`enrichTheme()\` raw theme objects miss default breakpoints / spacing / unit utilities
2468
+ - Destructuring \`props\` inside the provider components run once; destructuring captures values at setup. Read \`props.mode\` lazily inside reactive scopes
2469
+ - Re-augmenting the \`ThemeDefault\` / \`StylesDefault\` interfaces in your app — \`@pyreon/ui-theme\` already augments them; double-augmentation throws TS2320`,
2250
2470
  },
2251
2471
 
2252
2472
  'ui-core/useMode': {
2253
- signature: "useMode(): Signal<'light' | 'dark'>",
2473
+ signature: `useMode(): Signal<'light' | 'dark'>`,
2254
2474
  example: `import { useMode } from "@pyreon/ui-core"
2255
2475
 
2256
2476
  const mode = useMode()
2257
2477
  // mode() returns "light" or "dark" (resolved, reactive)
2258
2478
  // 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.",
2479
+ 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.`,
2480
+ mistakes: `- Reading \`useMode()\` without calling it the value is a \`Signal\`; use \`mode()\` to read
2481
+ - Using \`useMode()\` outside any \`PyreonUI\` ancestor — falls back to a default but loses the reactive system / inversed handling`,
2261
2482
  },
2483
+ // <gen-docs:api-reference:end @pyreon/ui-core>
2262
2484
 
2263
2485
  // ═══════════════════════════════════════════════════════════════════════════
2264
2486
  // @pyreon/unistyle
2265
2487
  // ═══════════════════════════════════════════════════════════════════════════
2266
2488
 
2489
+ // <gen-docs:api-reference:start @pyreon/unistyle>
2490
+
2267
2491
  'unistyle/enrichTheme': {
2268
2492
  signature: 'enrichTheme(theme: PartialTheme): Theme',
2269
2493
  example: `import { enrichTheme } from "@pyreon/unistyle"
@@ -2274,9 +2498,100 @@ const theme = enrichTheme({
2274
2498
  })
2275
2499
 
2276
2500
  // 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.',
2501
+ 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.',
2502
+ 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
2503
+ - Mutating the theme after passing it to \`PyreonUI\` — the styler resolver caches off the theme identity; clone + re-enrich for whole-theme swaps`,
2504
+ },
2505
+
2506
+ 'unistyle/breakpoints': {
2507
+ signature: 'breakpoints(): Breakpoints',
2508
+ example: `import { breakpoints } from '@pyreon/unistyle'
2509
+
2510
+ const bp = breakpoints()
2511
+ // { xs: 0, sm: 640, md: 768, lg: 1024, xl: 1280, xxl: 1536 }`,
2512
+ 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.',
2513
+ },
2514
+
2515
+ 'unistyle/createMediaQueries': {
2516
+ signature: 'createMediaQueries(breakpoints: Breakpoints): Record<string, string>',
2517
+ example: `import { createMediaQueries, breakpoints } from '@pyreon/unistyle'
2518
+
2519
+ const queries = createMediaQueries(breakpoints())
2520
+ // { xs: '@media (min-width: 0)', sm: '@media (min-width: 640px)', md: '@media (min-width: 768px)', ... }`,
2521
+ 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.',
2522
+ },
2523
+
2524
+ 'unistyle/makeItResponsive': {
2525
+ signature: 'makeItResponsive<T>(options: { value: T | T[] | Record<string, T>; property: string; theme: Theme }): string',
2526
+ example: `import { makeItResponsive } from '@pyreon/unistyle'
2527
+
2528
+ makeItResponsive({ value: 16, property: 'padding', theme })
2529
+ // → 'padding: 16px;'
2530
+
2531
+ makeItResponsive({ value: [8, 12, 16], property: 'padding', theme })
2532
+ // → 'padding: 8px; @media (min-width: 640px) { padding: 12px } @media (min-width: 768px) { padding: 16px }'
2533
+
2534
+ makeItResponsive({ value: { xs: 8, md: 16, xl: 24 }, property: 'padding', theme })
2535
+ // → '@media (min-width: 0) { padding: 8px } @media (min-width: 768px) { padding: 16px } @media (min-width: 1280px) { padding: 24px }'`,
2536
+ 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.',
2537
+ mistakes: `- Passing CSS-spec property names (\`borderTopWidth\`) — unistyle uses property-first naming (\`borderWidthTop\`); the responsive transformer expects the unistyle convention
2538
+ - Forgetting to pass an enriched theme — without \`theme.breakpoints\`, the array form falls back to the first value at every breakpoint`,
2539
+ },
2540
+
2541
+ 'unistyle/styles': {
2542
+ signature: 'styles(theme: Theme): string',
2543
+ example: `import { styles, enrichTheme } from '@pyreon/unistyle'
2544
+
2545
+ const theme = enrichTheme({ colors: { primary: '#3b82f6' } })
2546
+ const css = styles(theme)
2547
+ // → ':root { --color-primary: #3b82f6; --spacing-xs: 4px; ... }'`,
2548
+ 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.`,
2549
+ },
2550
+
2551
+ 'unistyle/alignContent': {
2552
+ signature: `alignContent(options: { alignX?: AlignXKey; alignY?: AlignYKey; direction?: 'row' | 'column' | 'inline' | 'rows' }): string`,
2553
+ example: `import { alignContent } from '@pyreon/unistyle'
2554
+
2555
+ alignContent({ alignX: 'center', alignY: 'start', direction: 'row' })
2556
+ // → 'justify-content: center; align-items: flex-start;'
2557
+
2558
+ alignContent({ alignX: 'spaceBetween', direction: 'inline' })
2559
+ // → 'justify-content: space-between;'`,
2560
+ 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.`,
2561
+ },
2562
+
2563
+ 'unistyle/extendCss': {
2564
+ signature: 'extendCss(base: ExtendCss, override?: ExtendCss): ExtendCss',
2565
+ example: `import { extendCss } from '@pyreon/unistyle'
2566
+
2567
+ const base = { color: 'red', hover: { color: 'darkred' } }
2568
+ const extended = extendCss(base, { hover: { background: 'pink' } })
2569
+ // → { color: 'red', hover: { color: 'darkred', background: 'pink' } }`,
2570
+ 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.',
2571
+ },
2572
+
2573
+ 'unistyle/stripUnit': {
2574
+ signature: 'stripUnit(value: string | number): number',
2575
+ example: `import { stripUnit } from '@pyreon/unistyle'
2576
+
2577
+ stripUnit('16px') // → 16
2578
+ stripUnit('1.5rem') // → 1.5
2579
+ stripUnit(16) // → 16`,
2580
+ 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.',
2581
+ },
2582
+
2583
+ 'unistyle/value': {
2584
+ signature: 'value(input: PropertyValue, fallback?: PropertyValue): UnitValue',
2585
+ example: `import { value } from '@pyreon/unistyle'
2586
+
2587
+ value(16) // → { value: 16, unit: 'px' }
2588
+ value('1.5rem') // → { value: 1.5, unit: 'rem' }
2589
+ value('50%') // → { value: 50, unit: '%' }
2590
+ value('garbage', 0) // → { value: 0, unit: 'px' }`,
2591
+ 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.',
2279
2592
  },
2593
+ // <gen-docs:api-reference:end @pyreon/unistyle>
2594
+
2280
2595
 
2281
2596
  // ═══════════════════════════════════════════════════════════════════════════
2282
2597
  // @pyreon/storybook
@@ -2289,12 +2604,12 @@ const theme = enrichTheme({
2289
2604
  // <gen-docs:api-reference:start @pyreon/rx>
2290
2605
 
2291
2606
  '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 }>',
2607
+ 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
2608
  example: `const active = rx.filter(users, u => u.active) // Computed<User[]>
2294
2609
  const sorted = rx.sortBy(active, 'name') // Computed<User[]>
2295
2610
  const total = rx.sum(users, u => u.age) // Computed<number>
2296
2611
  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.',
2612
+ 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
2613
  mistakes: `- Expecting \`rx.filter(signal, pred)\` to return a plain array — signal inputs always produce \`Computed\` outputs. Call the result to read: \`active()\`
2299
2614
  - Passing a signal accessor (\`() => items()\`) instead of the signal itself — pass \`items\` not \`() => items()\`; the function checks for \`.subscribe\` to detect signals`,
2300
2615
  },
@@ -2406,6 +2721,8 @@ setUrlRouter(router)
2406
2721
  // @pyreon/document-primitives
2407
2722
  // ═══════════════════════════════════════════════════════════════════════════
2408
2723
 
2724
+ // <gen-docs:api-reference:start @pyreon/document-primitives>
2725
+
2409
2726
  'document-primitives/extractDocNode': {
2410
2727
  signature: 'extractDocNode(templateFn: () => VNode, options?: ExtractOptions): DocNode',
2411
2728
  example: `import {
@@ -2414,52 +2731,239 @@ setUrlRouter(router)
2414
2731
  } from '@pyreon/document-primitives'
2415
2732
  import { download } from '@pyreon/document'
2416
2733
 
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`,
2734
+ const tree = extractDocNode(() => (
2735
+ <DocDocument title="Quarterly Report" author="Aisha">
2736
+ <DocPage>
2737
+ <DocHeading level="h1">Q4 Results</DocHeading>
2738
+ <DocText>Revenue grew 23% YoY.</DocText>
2739
+ </DocPage>
2740
+ </DocDocument>
2741
+ ))
2742
+ await download(tree, 'report.pdf')
2743
+ await download(tree, 'report.docx')`,
2744
+ 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.`,
2745
+ 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}>\`
2746
+ - DocRow direction: layout props (direction, gap) go in \`.attrs()\` not \`.theme()\`. Element accepts \`'inline'\` | \`'rows'\` | \`'reverseInline'\` | \`'reverseRows'\` — \`'row'\` is NOT valid
2747
+ - For text children reactivity, pass a signal accessor and read inside body: \`<DocText>{() => store.field()}</DocText>\`
2748
+ - Don't declare runtime-filled fields (\`tag\`, \`_documentProps\`) in the rocketstyle \`.attrs<P>()\` generic — they leak as required JSX props
2749
+ - Using \`createDocumentExport(...).getDocNode()\` in new code — prefer \`extractDocNode(fn)\` which is one call instead of two. \`createDocumentExport\` is kept for backward compat`,
2451
2750
  },
2452
2751
 
2453
2752
  'document-primitives/createDocumentExport': {
2454
- signature:
2455
- 'createDocumentExport(templateFn: () => VNode): { getDocNode(): DocNode }',
2753
+ signature: 'createDocumentExport(templateFn: () => VNode): { getDocNode(): DocNode }',
2456
2754
  example: `// Two-step form (kept for backward compat). New code should
2457
2755
  // prefer the one-step extractDocNode helper.
2458
2756
  import { createDocumentExport } from '@pyreon/document-primitives'
2459
2757
 
2460
2758
  const helper = createDocumentExport(() => <Resume name="Aisha" />)
2461
2759
  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
- },
2760
+ 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.',
2761
+ },
2762
+
2763
+ 'document-primitives/DocDocument': {
2764
+ signature: '(props: { title?: string | (() => string); author?: string | (() => string); subject?: string | (() => string); children: VNodeChild }) => VNodeChild',
2765
+ example: `<DocDocument title="Quarterly Report" author="Aisha" subject="Q4 2025">
2766
+ <DocPage>...</DocPage>
2767
+ </DocDocument>
2768
+
2769
+ // Reactive metadata via accessor
2770
+ <DocDocument title={() => \`\${user().name} — Resume\`}>
2771
+ <DocPage>...</DocPage>
2772
+ </DocDocument>`,
2773
+ 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.',
2774
+ },
2775
+
2776
+ 'document-primitives/DocPage': {
2777
+ signature: `(props: { size?: string; orientation?: 'portrait' | 'landscape'; children: VNodeChild }) => VNodeChild`,
2778
+ example: `<DocDocument>
2779
+ <DocPage size="A4" orientation="portrait">
2780
+ <DocHeading level="h1">Page 1</DocHeading>
2781
+ </DocPage>
2782
+ <DocPage size="A4" orientation="landscape">
2783
+ <DocHeading level="h1">Page 2 — landscape</DocHeading>
2784
+ </DocPage>
2785
+ </DocDocument>`,
2786
+ 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.',
2787
+ },
2788
+
2789
+ 'document-primitives/DocSection': {
2790
+ signature: `(props: { direction?: 'column' | 'row'; children: VNodeChild }) => VNodeChild`,
2791
+ example: `<DocPage>
2792
+ <DocSection direction="column">
2793
+ <DocHeading level="h2">Introduction</DocHeading>
2794
+ <DocText>Background paragraph.</DocText>
2795
+ </DocSection>
2796
+ </DocPage>`,
2797
+ 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.',
2798
+ },
2799
+
2800
+ 'document-primitives/DocRow': {
2801
+ signature: '(props: { children: VNodeChild }) => VNodeChild',
2802
+ example: `<DocRow>
2803
+ <DocText>Name:</DocText>
2804
+ <DocText>Aisha Patel</DocText>
2805
+ </DocRow>`,
2806
+ 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.',
2807
+ },
2808
+
2809
+ 'document-primitives/DocColumn': {
2810
+ signature: '(props: { width?: number | string; children: VNodeChild }) => VNodeChild',
2811
+ example: `<DocRow>
2812
+ <DocColumn width="30%">
2813
+ <DocText>Label</DocText>
2814
+ </DocColumn>
2815
+ <DocColumn width="70%">
2816
+ <DocText>Value</DocText>
2817
+ </DocColumn>
2818
+ </DocRow>`,
2819
+ 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.`,
2820
+ },
2821
+
2822
+ 'document-primitives/DocHeading': {
2823
+ signature: `(props: { level?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; children: VNodeChild }) => VNodeChild`,
2824
+ example: `<DocHeading level="h1">Quarterly Report</DocHeading>
2825
+ <DocHeading level="h2">Q4 Results</DocHeading>
2826
+ <DocHeading level="h3">Revenue Breakdown</DocHeading>`,
2827
+ 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.',
2828
+ },
2829
+
2830
+ 'document-primitives/DocText': {
2831
+ signature: '(props: { children: VNodeChild }) => VNodeChild',
2832
+ example: `<DocText>Static paragraph content.</DocText>
2833
+
2834
+ // Reactive children
2835
+ <DocText>{() => \`Hello, \${user().name}\`}</DocText>`,
2836
+ 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.',
2837
+ },
2838
+
2839
+ 'document-primitives/DocLink': {
2840
+ signature: '(props: { href?: string; children: VNodeChild }) => VNodeChild',
2841
+ example: `<DocText>
2842
+ Read more on
2843
+ <DocLink href="https://pyreon.dev">our blog</DocLink>
2844
+ for the latest releases.
2845
+ </DocText>`,
2846
+ 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.',
2847
+ },
2848
+
2849
+ 'document-primitives/DocImage': {
2850
+ signature: '(props: { src: string; alt?: string; width?: number; height?: number; caption?: string }) => VNodeChild',
2851
+ example: `<DocImage
2852
+ src="/charts/q4-revenue.png"
2853
+ alt="Revenue grew 23% in Q4"
2854
+ width={600}
2855
+ height={400}
2856
+ caption="Figure 1: Quarterly revenue, 2024-2025"
2857
+ />`,
2858
+ 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.',
2859
+ },
2860
+
2861
+ 'document-primitives/DocTable': {
2862
+ signature: '(props: { columns: TableColumn[]; rows: TableRow[]; headerStyle?: object; striped?: boolean; bordered?: boolean; caption?: string }) => VNodeChild',
2863
+ example: `<DocTable
2864
+ caption="Q4 results by region"
2865
+ bordered
2866
+ striped
2867
+ columns={[
2868
+ { key: 'region', label: 'Region', align: 'left' },
2869
+ { key: 'revenue', label: 'Revenue', align: 'right' },
2870
+ { key: 'growth', label: 'YoY Growth', align: 'right' },
2871
+ ]}
2872
+ rows={[
2873
+ { region: 'NA', revenue: '$12.4M', growth: '+23%' },
2874
+ { region: 'EU', revenue: '$8.7M', growth: '+18%' },
2875
+ { region: 'APAC', revenue: '$5.1M', growth: '+41%' },
2876
+ ]}
2877
+ />`,
2878
+ 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.',
2879
+ },
2880
+
2881
+ 'document-primitives/DocList': {
2882
+ signature: '(props: { ordered?: boolean; children: VNodeChild }) => VNodeChild',
2883
+ example: `<DocList>
2884
+ <DocListItem>First bullet</DocListItem>
2885
+ <DocListItem>Second bullet</DocListItem>
2886
+ </DocList>
2887
+
2888
+ <DocList ordered>
2889
+ <DocListItem>First step</DocListItem>
2890
+ <DocListItem>Second step</DocListItem>
2891
+ </DocList>`,
2892
+ 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.',
2893
+ },
2894
+
2895
+ 'document-primitives/DocListItem': {
2896
+ signature: '(props: { children: VNodeChild }) => VNodeChild',
2897
+ example: `<DocList>
2898
+ <DocListItem>Top-level item</DocListItem>
2899
+ <DocListItem>
2900
+ Item with nested list
2901
+ <DocList>
2902
+ <DocListItem>Nested A</DocListItem>
2903
+ <DocListItem>Nested B</DocListItem>
2904
+ </DocList>
2905
+ </DocListItem>
2906
+ </DocList>`,
2907
+ 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.`,
2908
+ },
2909
+
2910
+ 'document-primitives/DocCode': {
2911
+ signature: '(props: { language?: string; children: VNodeChild }) => VNodeChild',
2912
+ example: `<DocCode language="typescript">{
2913
+ \`const flow = createFlow({
2914
+ nodes: [{ id: '1', position: { x: 0, y: 0 } }],
2915
+ edges: [],
2916
+ })\`
2917
+ }</DocCode>`,
2918
+ 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.',
2919
+ },
2920
+
2921
+ 'document-primitives/DocDivider': {
2922
+ signature: '(props: { color?: string; thickness?: number }) => VNodeChild',
2923
+ example: `<DocText>Above the divider.</DocText>
2924
+ <DocDivider color="#e5e7eb" thickness={1} />
2925
+ <DocText>Below the divider.</DocText>`,
2926
+ 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.',
2927
+ },
2928
+
2929
+ 'document-primitives/DocSpacer': {
2930
+ signature: '(props: { height?: number }) => VNodeChild',
2931
+ example: `<DocSection>
2932
+ <DocHeading level="h2">Section A</DocHeading>
2933
+ <DocText>Content...</DocText>
2934
+ <DocSpacer height={32} />
2935
+ <DocHeading level="h2">Section B</DocHeading>
2936
+ <DocText>More content...</DocText>
2937
+ </DocSection>`,
2938
+ 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.',
2939
+ },
2940
+
2941
+ 'document-primitives/DocButton': {
2942
+ signature: '(props: { href?: string; children: VNodeChild }) => VNodeChild',
2943
+ example: `<DocButton href="https://pyreon.dev/signup">
2944
+ Get started
2945
+ </DocButton>`,
2946
+ 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.',
2947
+ },
2948
+
2949
+ 'document-primitives/DocQuote': {
2950
+ signature: '(props: { borderColor?: string; children: VNodeChild }) => VNodeChild',
2951
+ example: `<DocQuote borderColor="#3b82f6">
2952
+ <DocText>"The best way to predict the future is to build it."</DocText>
2953
+ <DocText>— Aisha Patel, Q4 keynote</DocText>
2954
+ </DocQuote>`,
2955
+ 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.',
2956
+ },
2957
+
2958
+ 'document-primitives/DocPageBreak': {
2959
+ signature: '() => VNodeChild',
2960
+ example: `<DocPage>
2961
+ <DocHeading level="h1">Section 1</DocHeading>
2962
+ <DocText>...long content...</DocText>
2963
+ <DocPageBreak />
2964
+ <DocHeading level="h1">Section 2 — new page</DocHeading>
2965
+ </DocPage>`,
2966
+ 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.',
2967
+ },
2968
+ // <gen-docs:api-reference:end @pyreon/document-primitives>
2465
2969
  }