@pyreon/mcp 0.12.10 → 0.12.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -12750,7 +12750,7 @@ var StdioServerTransport = class {
12750
12750
 
12751
12751
  //#endregion
12752
12752
  //#region package.json
12753
- var version = "0.12.10";
12753
+ var version = "0.12.12";
12754
12754
 
12755
12755
  //#endregion
12756
12756
  //#region src/api-reference.ts
@@ -13307,7 +13307,10 @@ theme.remove() // delete from storage`,
13307
13307
  },
13308
13308
  "i18n/createI18n": {
13309
13309
  signature: "createI18n(options: { locale: string, messages: Record<string, Record<string, string>>, loader?, fallbackLocale?, pluralRules? }): I18nInstance",
13310
- example: `const i18n = createI18n({
13310
+ example: `// Full entry — includes JSX components (Trans, I18nProvider, useI18n)
13311
+ import { createI18n, useI18n } from '@pyreon/i18n'
13312
+
13313
+ const i18n = createI18n({
13311
13314
  locale: 'en',
13312
13315
  messages: { en: { greeting: 'Hello, {{name}}!' } },
13313
13316
  loader: (locale, ns) => import(\`./locales/\${locale}/\${ns}.json\`),
@@ -13315,8 +13318,18 @@ theme.remove() // delete from storage`,
13315
13318
 
13316
13319
  const { t, locale } = useI18n()
13317
13320
  t('greeting', { name: 'World' }) // "Hello, World!"
13318
- locale.set('fr') // switch reactively`,
13319
- notes: "Interpolation with {{name}}, pluralization with _one/_other suffixes. Namespace lazy loading. <Trans> component for rich JSX interpolation."
13321
+ locale.set('fr') // switch reactively
13322
+
13323
+ // Backend / non-JSX entry — @pyreon/i18n/core
13324
+ // Zero JSX dependencies, transitively only @pyreon/reactivity.
13325
+ // Use this on backends, edge workers, non-Pyreon frontends.
13326
+ import { createI18n } from '@pyreon/i18n/core'
13327
+ const backendI18n = createI18n({ locale: 'en', messages: { en: { hello: 'Hi' } } })
13328
+ backendI18n.t('hello')`,
13329
+ notes: "Interpolation with {{name}}, pluralization with _one/_other suffixes. Namespace lazy loading. <Trans> component for rich JSX interpolation. TWO ENTRY POINTS: `@pyreon/i18n` (full, with JSX components) vs `@pyreon/i18n/core` (framework-agnostic, zero JSX deps — use for backends and non-Pyreon consumers). Both return identical I18nInstance objects.",
13330
+ mistakes: `- Using \`@pyreon/i18n\` (the main entry) on a backend without a JSX-aware tsconfig — the bun condition resolves to source which transitively includes the Trans JSX component. Use \`@pyreon/i18n/core\` instead.
13331
+ - Reading the README example and importing from \`@pyreon/i18n\` in a non-Pyreon project — that path works for Pyreon UIs but the README now documents \`/core\` as the backend recommendation.
13332
+ - Trying to use \`<Trans>\` from \`@pyreon/i18n/core\` — it's intentionally not exported there. Import it from the main \`@pyreon/i18n\` entry instead.`
13320
13333
  },
13321
13334
  "document/createDocument": {
13322
13335
  signature: "createDocument(props?: DocumentProps): DocumentBuilder",
@@ -13332,37 +13345,113 @@ await doc.toNotion() // Notion blocks`,
13332
13345
  notes: "14+ output formats. JSX primitives: Document, Page, Heading, Text, Table, Image, List, Code, etc. Heavy renderers lazy-loaded."
13333
13346
  },
13334
13347
  "flow/createFlow": {
13335
- signature: "createFlow(config: { nodes: FlowNode[], edges: FlowEdge[], ... }): FlowInstance",
13336
- example: `const flow = createFlow({
13348
+ signature: "createFlow<TData = Record<string, unknown>>(config: FlowConfig<TData>): FlowInstance<TData>",
13349
+ example: `// Generic over node data shape — typed consumers get strong narrowing
13350
+ interface WorkflowData {
13351
+ kind: 'trigger' | 'filter' | 'transform' | 'notify'
13352
+ label: string
13353
+ }
13354
+
13355
+ const flow = createFlow<WorkflowData>({
13337
13356
  nodes: [
13338
- { id: '1', position: { x: 0, y: 0 }, data: { label: 'Start' } },
13339
- { id: '2', position: { x: 200, y: 100 }, data: { label: 'End' } },
13357
+ { id: '1', type: 'custom', position: { x: 0, y: 0 }, data: { kind: 'trigger', label: 'Start' } },
13358
+ { id: '2', type: 'custom', position: { x: 200, y: 100 }, data: { kind: 'notify', label: 'End' } },
13340
13359
  ],
13341
- edges: [{ id: 'e1', source: '1', target: '2' }],
13360
+ edges: [{ id: 'e1', source: '1', target: '2', animated: true }],
13342
13361
  })
13343
13362
 
13344
- flow.addNode({ id: '3', position: { x: 100, y: 200 }, data: { label: 'New' } })
13345
- await flow.layout('layered') // auto-layout via elkjs
13363
+ // node.data.kind narrows to the typed union, not unknown
13364
+ const trigger = flow.findNodes((n) => n.data.kind === 'trigger')
13365
+
13366
+ flow.addNode({ id: '3', type: 'custom', position: { x: 100, y: 200 }, data: { kind: 'transform', label: 'New' } })
13367
+ await flow.layout('layered', { direction: 'RIGHT', nodeSpacing: 50, layerSpacing: 100 }) // auto-layout via lazy-loaded elkjs
13368
+ // LayoutOptions applicability: direction/layerSpacing/edgeRouting apply to layered/tree only;
13369
+ // force/stress/radial/box/rectpacking silently ignore them. nodeSpacing applies to all algorithms.
13370
+ const json = flow.toJSON(); flow.fromJSON(json) // round-trip serialization
13371
+
13372
+ // Custom node renderer — every prop except id is a REACTIVE ACCESSOR
13373
+ function CustomNode(props: NodeComponentProps<WorkflowData>) {
13374
+ return (
13375
+ <div
13376
+ class={() => (props.selected() ? 'selected' : '')}
13377
+ style={() => \`cursor: \${props.dragging() ? 'grabbing' : 'grab'}\`}
13378
+ >
13379
+ {() => props.data().label}
13380
+ </div>
13381
+ )
13382
+ }
13346
13383
 
13347
- <Flow instance={flow}><Background /><Controls /><MiniMap /></Flow>`,
13348
- notes: "Signal-native nodes/edges. Auto-layout via elkjs (lazy-loaded). Pan/zoom via pointer events + CSS transforms. No D3."
13384
+ <Flow instance={flow} nodeTypes={{ custom: CustomNode }}>
13385
+ <Background variant="dots" />
13386
+ <Controls />
13387
+ <MiniMap />
13388
+ </Flow>`,
13389
+ notes: "Signal-native nodes/edges. Generic over node data shape: createFlow<TData> returns FlowInstance<TData> so node.data.kind narrows correctly. Defaults to Record<string, unknown> if no generic supplied. NodeComponentProps has THREE reactive accessors — data: () => TData, selected: () => boolean, dragging: () => boolean — read inside reactive scopes so the node patches in place when ANY underlying state changes. Each node mounts EXACTLY ONCE across the lifetime of the graph regardless of how many drags, selection clicks, or updateNode mutations happen. Internally <Flow> uses <For> keyed by node.id plus per-node accessors that read live state from instance.nodes() — so a 60fps drag in a 1000-node graph is O(1) instead of O(N) per frame. Auto-layout via elkjs (lazy-loaded, ~1.4MB chunk only on first .layout() call). Pan/zoom via pointer events + CSS transforms. No D3. JSX components are NOT generic at the call site (<Flow<MyData> /> is invalid JSX) — FlowProps.instance is typed as FlowInstance<any> so typed consumers can pass FlowInstance<MyData> without casting.",
13390
+ mistakes: `- Forgetting to declare @pyreon/runtime-dom in consumer app deps — flow's JSX emits _tpl() which needs runtime-dom imports
13391
+ - Reading props.data, props.selected, or props.dragging as plain values — they're ALL accessors, call them: props.data().kind, props.selected(), props.dragging()
13392
+ - Calling props.data() OUTSIDE a reactive scope — captures the value once at component setup, defeating reactivity. Read it inside JSX expression thunks, effect, or computed: {() => props.data().label}
13393
+ - Adding [key: string]: unknown index signature to your node data interface — no longer needed now that createFlow is generic. Just pass createFlow<MyData>(...)
13394
+ - Using direction: 'row' on flow's containing layout — Pyreon Element accepts 'inline'|'rows'|'reverseInline'|'reverseRows', not 'row'
13395
+ - Setting LayoutOptions.direction (or layerSpacing, or edgeRouting) on a force/stress/radial/box/rectpacking layout and expecting a directional result — these options are namespaced under ELK's layered/tree pipelines and silently ignored by the geometric algorithms. Switch the algorithm to 'layered' or 'tree' if you need a directional layout.
13396
+ - Missing the <Flow nodeTypes={{ key: Component }}> registration — node.type strings dispatch to that map`
13349
13397
  },
13350
13398
  "code/createEditor": {
13351
- signature: "createEditor(config: { value?: string, language?: string, theme?: string, minimap?: boolean, ... }): EditorInstance",
13399
+ signature: "createEditor(config: { value?: string, language?: EditorLanguage, theme?: EditorTheme, onChange?: (val: string) => void, minimap?: boolean, lineNumbers?: boolean, ... }): EditorInstance",
13352
13400
  example: `const editor = createEditor({
13353
13401
  value: '// hello',
13354
13402
  language: 'typescript',
13355
13403
  theme: 'dark',
13356
13404
  minimap: true,
13405
+ onChange: (next) => console.log('user edit:', next),
13357
13406
  })
13358
13407
 
13359
- editor.value() // reactive Signal<string>
13408
+ editor.value() // reactive Signal<string>, read inside JSX/effects
13409
+ editor.value.set('new') // write back into CodeMirror
13410
+ editor.cursor() // computed { line, col }
13411
+ editor.lineCount() // computed
13360
13412
  editor.goToLine(42)
13361
13413
  editor.insert('new code')
13414
+ editor.setDiagnostics([{ from: 0, to: 5, severity: 'error', message: '...' }])
13415
+
13416
+ <CodeEditor instance={editor} style="height: 400px" />
13417
+ <DiffEditor original="old" modified="new" language="typescript" />`,
13418
+ notes: "Built on CodeMirror 6 (~250KB vs Monaco's ~2.5MB). 19 languages via lazy-loaded grammars (declared as optionalDependencies). Two-way binding: editor.value is a writable Signal — pass onChange for editor → external, set editor.value for external → editor. For external↔editor binding with built-in loop prevention, use the higher-level `bindEditorToSignal({ editor, signal, serialize, parse })` helper instead of hand-rolling the flag pattern. <CodeEditor> auto-mounts and cleans up on unmount.",
13419
+ mistakes: `- Forgetting to declare @pyreon/runtime-dom in consumer app deps — <CodeEditor> JSX emits _tpl() which needs runtime-dom imports
13420
+ - Hand-rolling the applyingFromExternal/applyingFromEditor flag pattern for two-way binding — use the bindEditorToSignal helper instead, it handles the loop prevention correctly and is tested
13421
+ - Calling editor methods before mount — they no-op safely but changes don't persist
13422
+ - Setting both vim: true and emacs: true — emacs wins`
13423
+ },
13424
+ "code/bindEditorToSignal": {
13425
+ signature: "bindEditorToSignal<T>(options: { editor: EditorInstance, signal: SignalLike<T>, serialize: (val: T) => string, parse: (text: string) => T | null, onParseError?: (err: Error) => void }): { dispose: () => void }",
13426
+ example: `import { bindEditorToSignal, createEditor } from '@pyreon/code'
13427
+ import { signal } from '@pyreon/reactivity'
13428
+
13429
+ interface Doc { name: string; count: number }
13430
+ const data = signal<Doc>({ name: 'Alice', count: 1 })
13431
+
13432
+ const editor = createEditor({
13433
+ value: JSON.stringify(data(), null, 2),
13434
+ language: 'json',
13435
+ })
13362
13436
 
13363
- <CodeEditor instance={editor} />
13364
- <DiffEditor original="old" modified="new" />`,
13365
- notes: "Built on CodeMirror 6 (~250KB vs Monaco's ~2.5MB). loadLanguage() for lazy grammars. TabbedEditor for multi-file."
13437
+ const binding = bindEditorToSignal({
13438
+ editor,
13439
+ signal: data, // accepts Signal<T> or any SignalLike<T>
13440
+ serialize: (val) => JSON.stringify(val, null, 2),
13441
+ parse: (text) => {
13442
+ try { return JSON.parse(text) } catch { return null }
13443
+ },
13444
+ onParseError: (err) => console.warn(err.message),
13445
+ })
13446
+
13447
+ // Later, on unmount:
13448
+ binding.dispose()`,
13449
+ notes: "Replaces the recurring loop-prevention flag-pair boilerplate (applyingFromExternal / applyingFromEditor) that consumers had to hand-roll for two-way external↔editor binding. The helper manages both directions, breaks the format-on-input race via internal flags, catches parse errors, and returns a disposable. Accepts any SignalLike<T> (Pyreon Signal, custom store wrapper, etc.). The editor itself ALSO has internal CM↔signal loop guards — this helper adds the SECOND layer for the external↔editor boundary.",
13450
+ mistakes: `- Forgetting to call binding.dispose() on unmount — leaks both effects until the editor instance is GC'd
13451
+ - Non-deterministic serialize() — if serialize(parse(text)) returns a string structurally different from the input text, the helper dispatches redundant editor writes that fight the user's typing. JSON.stringify with consistent indentation is fine; pretty-printing that varies on every call is not
13452
+ - Throwing in parse() without an onParseError handler — the helper catches and silently no-ops if no handler is provided. Pass onParseError to surface parse errors in your UI
13453
+ - Returning a non-null value from parse() for malformed input — the helper writes whatever you return, including partial / corrupted state. Return null on parse failure, or throw with an error message
13454
+ - Using bindEditorToSignal AND a manual editor.value.set() loop in the same component — defeats the loop prevention. Pick one binding strategy per editor instance`
13366
13455
  },
13367
13456
  "hotkeys/useHotkey": {
13368
13457
  signature: "useHotkey(shortcut: string, handler: (e: KeyboardEvent) => void, options?: HotkeyOptions): void",
@@ -13423,7 +13512,7 @@ console.log(result.totalErrors, result.totalWarnings)
13423
13512
 
13424
13513
  // With config file auto-loading + rule overrides
13425
13514
  lint({ paths: ["."], ruleOverrides: { "pyreon/no-classname": "off" } })`,
13426
- notes: "Programmatic API. 55 rules across 12 categories. Auto-loads .pyreonlintrc.json. Presets: recommended, strict, app, lib. Uses oxc-parser with AST caching."
13515
+ notes: "Programmatic API. 58 rules across 12 categories. Auto-loads .pyreonlintrc.json. Presets: recommended, strict, app, lib. Uses oxc-parser with AST caching."
13427
13516
  },
13428
13517
  "lint/lintFile": {
13429
13518
  signature: "lintFile(filePath: string, sourceText: string, rules: Rule[], config: LintConfig, cache?: AstCache): LintFileResult",
@@ -13439,10 +13528,26 @@ const result = lintFile("app.tsx", source, allRules, config, cache)`,
13439
13528
  example: `pyreon-lint --preset strict --quiet # CI mode
13440
13529
  pyreon-lint --fix # auto-fix
13441
13530
  pyreon-lint --watch src/ # watch mode
13442
- pyreon-lint --list # list all 55 rules
13531
+ pyreon-lint --list # list all 58 rules
13443
13532
  pyreon-lint --format json # machine-readable`,
13444
13533
  notes: "CLI entry. Config: .pyreonlintrc.json, package.json 'pyreonlint' field. Ignore: .pyreonlintignore + .gitignore. Watch: fs.watch recursive with 100ms debounce."
13445
13534
  },
13535
+ "lint/no-process-dev-gate": {
13536
+ signature: "rule: pyreon/no-process-dev-gate (architecture, error, auto-fixable)",
13537
+ example: `// ❌ Wrong — dead code in real Vite browser bundles
13538
+ const __DEV__ = typeof process !== 'undefined' && process.env.NODE_ENV !== 'production'
13539
+ if (__DEV__) console.warn('hello')
13540
+
13541
+ // ✅ Correct — Vite literal-replaces import.meta.env.DEV at build time
13542
+ // @ts-ignore — provided by Vite/Rolldown at build time
13543
+ const __DEV__ = import.meta.env?.DEV === true
13544
+ if (__DEV__) console.warn('hello')`,
13545
+ 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`.",
13546
+ 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
13547
+ - 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)
13548
+ - Adding \`process: { env: { ... } }\` polyfills to vite.config.ts as a workaround — fix the source instead
13549
+ - Using the rule for server-only packages — they're correctly exempt because Node always has \`process\``
13550
+ },
13446
13551
  "ui-core/PyreonUI": {
13447
13552
  signature: "PyreonUI(props: { theme?: Theme; mode?: 'light' | 'dark' | 'system'; inversed?: boolean; children: VNodeChild }): VNodeChild",
13448
13553
  example: `import { PyreonUI } from "@pyreon/ui-core"
@@ -13629,6 +13734,58 @@ export const Primary: StoryObj<typeof meta> = {
13629
13734
  args: { variant: 'primary', label: 'Click me' },
13630
13735
  }`,
13631
13736
  notes: "Storybook renderer for Pyreon components. Re-exports h, Fragment, signal, computed, effect, mount for story convenience."
13737
+ },
13738
+ "document-primitives/extractDocNode": {
13739
+ signature: "extractDocNode(templateFn: () => VNode, options?: ExtractOptions): DocNode",
13740
+ example: `import {
13741
+ DocDocument, DocPage, DocHeading, DocText,
13742
+ extractDocNode,
13743
+ } from '@pyreon/document-primitives'
13744
+ import { download } from '@pyreon/document'
13745
+
13746
+ interface Resume { name: string; headline: string }
13747
+
13748
+ function ResumeTemplate(props: { resume: () => Resume }) {
13749
+ return (
13750
+ // title and author accept reactive accessors — extractDocNode
13751
+ // resolves them at extraction time, so each export click reads
13752
+ // the LIVE value from the underlying signal
13753
+ <DocDocument
13754
+ title={() => \`\${props.resume().name} — Resume\`}
13755
+ author={() => props.resume().name}
13756
+ >
13757
+ <DocPage>
13758
+ <DocHeading level="h1">{() => props.resume().name}</DocHeading>
13759
+ <DocText>{() => props.resume().headline}</DocText>
13760
+ </DocPage>
13761
+ </DocDocument>
13762
+ )
13763
+ }
13764
+
13765
+ // One-step extraction. The two-step createDocumentExport(...).getDocNode()
13766
+ // form is still exported for callers that want to pass the helper
13767
+ // object around, but extractDocNode is the recommended form.
13768
+ const tree = extractDocNode(() => <ResumeTemplate resume={store.resume} />)
13769
+ await download(tree, 'resume.pdf')
13770
+ await download(tree, 'resume.docx')
13771
+ await download(tree, 'resume.html')
13772
+ await download(tree, 'resume.md')`,
13773
+ 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.",
13774
+ 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}>
13775
+ - DocRow direction: layout props (direction, gap) go in .attrs() not .theme(). Element accepts 'inline' | 'rows' | 'reverseInline' | 'reverseRows' — 'row' is NOT valid
13776
+ - For text children reactivity, pass a signal accessor and read inside body: <DocText>{() => store.field()}</DocText>
13777
+ - Don't declare runtime-filled fields (tag, _documentProps) in the rocketstyle .attrs<P>() generic — they leak as required JSX props
13778
+ - Using createDocumentExport(...).getDocNode() in new code — prefer extractDocNode(fn) which is one call instead of two. createDocumentExport is kept for backward compat`
13779
+ },
13780
+ "document-primitives/createDocumentExport": {
13781
+ signature: "createDocumentExport(templateFn: () => VNode): { getDocNode(): DocNode }",
13782
+ example: `// Two-step form (kept for backward compat). New code should
13783
+ // prefer the one-step extractDocNode helper.
13784
+ import { createDocumentExport } from '@pyreon/document-primitives'
13785
+
13786
+ const helper = createDocumentExport(() => <Resume name="Aisha" />)
13787
+ const tree = helper.getDocNode()`,
13788
+ 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."
13632
13789
  }
13633
13790
  };
13634
13791