@particle-academy/fancy-flow 0.33.3 → 0.35.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.
Files changed (36) hide show
  1. package/README.md +2 -2
  2. package/dist/{capabilities-BLOeLACV.d.cts → capabilities-BtJQTbnw.d.cts} +1 -1
  3. package/dist/{capabilities-BH7zmSQQ.d.ts → capabilities-CkhKj_U_.d.ts} +1 -1
  4. package/dist/engine.d.cts +7 -238
  5. package/dist/engine.d.ts +7 -238
  6. package/dist/index.d.cts +7 -7
  7. package/dist/index.d.ts +7 -7
  8. package/dist/layout/index.d.cts +1 -1
  9. package/dist/layout/index.d.ts +1 -1
  10. package/dist/llm/prism.d.cts +2 -2
  11. package/dist/llm/prism.d.ts +2 -2
  12. package/dist/llm/vercel-ai.d.cts +2 -2
  13. package/dist/llm/vercel-ai.d.ts +2 -2
  14. package/dist/registry/index.d.cts +6 -6
  15. package/dist/registry/index.d.ts +6 -6
  16. package/dist/{registry-Dd1Q66uP.d.ts → registry-Df2-bTXZ.d.ts} +1 -1
  17. package/dist/{registry-qv4gRiuQ.d.cts → registry-Ru5jIS5H.d.cts} +1 -1
  18. package/dist/rich-input.cjs +4 -2
  19. package/dist/rich-input.cjs.map +1 -1
  20. package/dist/rich-input.d.cts +14 -2
  21. package/dist/rich-input.d.ts +14 -2
  22. package/dist/rich-input.js +4 -3
  23. package/dist/rich-input.js.map +1 -1
  24. package/dist/{run-cohort-DitsaYgS.d.cts → run-cohort-Bjv2ZU6g.d.cts} +1 -1
  25. package/dist/{run-cohort-CV7EcwGG.d.ts → run-cohort-BnP6ErHi.d.ts} +1 -1
  26. package/dist/runtime/index.d.cts +3 -3
  27. package/dist/runtime/index.d.ts +3 -3
  28. package/dist/schema/index.d.cts +1 -1
  29. package/dist/schema/index.d.ts +1 -1
  30. package/dist/{types-DEmDSByY.d.cts → types-D6lsIGoe.d.ts} +251 -2
  31. package/dist/{types-Bj_ZHwqG.d.ts → types-D8Dezh9v.d.cts} +251 -2
  32. package/dist/{types-CMSrWVYM.d.cts → types-sOmpCitB.d.cts} +1 -1
  33. package/dist/{types-CMSrWVYM.d.ts → types-sOmpCitB.d.ts} +1 -1
  34. package/dist/ux.d.cts +2 -2
  35. package/dist/ux.d.ts +2 -2
  36. package/package.json +6 -3
@@ -1,6 +1,6 @@
1
1
  import { LanguageModel } from 'ai';
2
- import { a as LlmRouteRequest, L as LlmClient } from '../capabilities-BH7zmSQQ.js';
3
- import '../types-CMSrWVYM.js';
2
+ import { a as LlmRouteRequest, L as LlmClient } from '../capabilities-CkhKj_U_.js';
3
+ import '../types-sOmpCitB.js';
4
4
  import '@xyflow/react';
5
5
 
6
6
  /**
@@ -1,12 +1,12 @@
1
- import { N as NodeKindDefinition, d as PortSpec } from '../types-DEmDSByY.cjs';
2
- export { C as ConfigField, g as CredentialConfigField, D as DocumentConfigField, E as ExpressionConfigField, J as JsonConfigField, K as KeyValueConfigField, L as LEGACY_PAUSE_PREFIXES, a as NodeCategory, h as NumberConfigField, b as PAUSE_PREFIX, P as PauseAwaiting, c as PauseSignal, R as RenderBodyContext, j as RepeaterConfigField, k as RepeaterRowField, S as SelectConfigField, l as SwitchConfigField, T as TextConfigField, m as TextareaConfigField, e as decodePause, f as encodePause, i as isPause, p as pauseForHuman } from '../types-DEmDSByY.cjs';
3
- import { P as PortDescriptor, a as FlowNode, N as NodeExecutor } from '../types-CMSrWVYM.cjs';
1
+ import { d as NodeKindDefinition, j as PortSpec } from '../types-D8Dezh9v.cjs';
2
+ export { a as ConfigField, q as CredentialConfigField, D as DocumentConfigField, E as ExpressionConfigField, J as JsonConfigField, K as KeyValueConfigField, L as LEGACY_PAUSE_PREFIXES, c as NodeCategory, r as NumberConfigField, h as PAUSE_PREFIX, P as PauseAwaiting, i as PauseSignal, R as RenderBodyContext, t as RepeaterConfigField, u as RepeaterRowField, w as SelectConfigField, x as SwitchConfigField, T as TextConfigField, y as TextareaConfigField, m as decodePause, n as encodePause, o as isPause, p as pauseForHuman } from '../types-D8Dezh9v.cjs';
3
+ import { P as PortDescriptor, b as FlowNode, N as NodeExecutor } from '../types-sOmpCitB.cjs';
4
4
  import { Connection, Edge, NodeProps, NodeTypes } from '@xyflow/react';
5
- import { b as LlmRoute } from '../capabilities-BLOeLACV.cjs';
6
- export { C as CapabilityId, L as LlmClient, c as LlmRouteChoice, a as LlmRouteRequest, W as WorkflowResolution, d as WorkflowResolutionFailure, e as WorkflowResolver, f as capabilityStatus, g as getLlmClient, h as getWorkflowResolver, i as isResolutionFailure, r as registerLlmClient, j as registerWorkflowResolver } from '../capabilities-BLOeLACV.cjs';
5
+ import { b as LlmRoute } from '../capabilities-BtJQTbnw.cjs';
6
+ export { C as CapabilityId, L as LlmClient, c as LlmRouteChoice, a as LlmRouteRequest, W as WorkflowResolution, d as WorkflowResolutionFailure, e as WorkflowResolver, f as capabilityStatus, g as getLlmClient, h as getWorkflowResolver, i as isResolutionFailure, r as registerLlmClient, j as registerWorkflowResolver } from '../capabilities-BtJQTbnw.cjs';
7
7
  import * as react from 'react';
8
8
  import { ComponentType, ReactNode } from 'react';
9
- export { c as categoryAccent, d as defaultConfigFor, g as getNodeKind, k as kindIds, l as listNodeKinds, o as onNodeKindsChanged, r as registerNodeKind, a as resolveKindId, v as validateConfig } from '../registry-qv4gRiuQ.cjs';
9
+ export { c as categoryAccent, d as defaultConfigFor, g as getNodeKind, k as kindIds, l as listNodeKinds, o as onNodeKindsChanged, r as registerNodeKind, a as resolveKindId, v as validateConfig } from '../registry-Ru5jIS5H.cjs';
10
10
 
11
11
  /**
12
12
  * Connection validation — the single rule that decides whether an edge between
@@ -1,12 +1,12 @@
1
- import { N as NodeKindDefinition, d as PortSpec } from '../types-Bj_ZHwqG.js';
2
- export { C as ConfigField, g as CredentialConfigField, D as DocumentConfigField, E as ExpressionConfigField, J as JsonConfigField, K as KeyValueConfigField, L as LEGACY_PAUSE_PREFIXES, a as NodeCategory, h as NumberConfigField, b as PAUSE_PREFIX, P as PauseAwaiting, c as PauseSignal, R as RenderBodyContext, j as RepeaterConfigField, k as RepeaterRowField, S as SelectConfigField, l as SwitchConfigField, T as TextConfigField, m as TextareaConfigField, e as decodePause, f as encodePause, i as isPause, p as pauseForHuman } from '../types-Bj_ZHwqG.js';
3
- import { P as PortDescriptor, a as FlowNode, N as NodeExecutor } from '../types-CMSrWVYM.js';
1
+ import { d as NodeKindDefinition, j as PortSpec } from '../types-D6lsIGoe.js';
2
+ export { a as ConfigField, q as CredentialConfigField, D as DocumentConfigField, E as ExpressionConfigField, J as JsonConfigField, K as KeyValueConfigField, L as LEGACY_PAUSE_PREFIXES, c as NodeCategory, r as NumberConfigField, h as PAUSE_PREFIX, P as PauseAwaiting, i as PauseSignal, R as RenderBodyContext, t as RepeaterConfigField, u as RepeaterRowField, w as SelectConfigField, x as SwitchConfigField, T as TextConfigField, y as TextareaConfigField, m as decodePause, n as encodePause, o as isPause, p as pauseForHuman } from '../types-D6lsIGoe.js';
3
+ import { P as PortDescriptor, b as FlowNode, N as NodeExecutor } from '../types-sOmpCitB.js';
4
4
  import { Connection, Edge, NodeProps, NodeTypes } from '@xyflow/react';
5
- import { b as LlmRoute } from '../capabilities-BH7zmSQQ.js';
6
- export { C as CapabilityId, L as LlmClient, c as LlmRouteChoice, a as LlmRouteRequest, W as WorkflowResolution, d as WorkflowResolutionFailure, e as WorkflowResolver, f as capabilityStatus, g as getLlmClient, h as getWorkflowResolver, i as isResolutionFailure, r as registerLlmClient, j as registerWorkflowResolver } from '../capabilities-BH7zmSQQ.js';
5
+ import { b as LlmRoute } from '../capabilities-CkhKj_U_.js';
6
+ export { C as CapabilityId, L as LlmClient, c as LlmRouteChoice, a as LlmRouteRequest, W as WorkflowResolution, d as WorkflowResolutionFailure, e as WorkflowResolver, f as capabilityStatus, g as getLlmClient, h as getWorkflowResolver, i as isResolutionFailure, r as registerLlmClient, j as registerWorkflowResolver } from '../capabilities-CkhKj_U_.js';
7
7
  import * as react from 'react';
8
8
  import { ComponentType, ReactNode } from 'react';
9
- export { c as categoryAccent, d as defaultConfigFor, g as getNodeKind, k as kindIds, l as listNodeKinds, o as onNodeKindsChanged, r as registerNodeKind, a as resolveKindId, v as validateConfig } from '../registry-Dd1Q66uP.js';
9
+ export { c as categoryAccent, d as defaultConfigFor, g as getNodeKind, k as kindIds, l as listNodeKinds, o as onNodeKindsChanged, r as registerNodeKind, a as resolveKindId, v as validateConfig } from '../registry-Df2-bTXZ.js';
10
10
 
11
11
  /**
12
12
  * Connection validation — the single rule that decides whether an edge between
@@ -1,4 +1,4 @@
1
- import { N as NodeKindDefinition } from './types-Bj_ZHwqG.js';
1
+ import { d as NodeKindDefinition } from './types-D6lsIGoe.js';
2
2
 
3
3
  /**
4
4
  * registerNodeKind — install a node kind in the global registry. Returns
@@ -1,4 +1,4 @@
1
- import { N as NodeKindDefinition } from './types-DEmDSByY.cjs';
1
+ import { d as NodeKindDefinition } from './types-D8Dezh9v.cjs';
2
2
 
3
3
  /**
4
4
  * registerNodeKind — install a node kind in the global registry. Returns
@@ -28,7 +28,7 @@ function isPageDoc(value) {
28
28
  const v = value;
29
29
  return Array.isArray(v.sections) && typeof v.nodes === "object" && v.nodes !== null;
30
30
  }
31
- function useFancyCmsForRichInput(options = {}) {
31
+ function registerFancyCmsForRichInput(options = {}) {
32
32
  return registerRichInputAdapter({
33
33
  FauxClient: reactFancy.FauxClient,
34
34
  // Render the page at a real desktop width and scale it down, rather than
@@ -47,10 +47,12 @@ function useFancyCmsForRichInput(options = {}) {
47
47
  })
48
48
  });
49
49
  }
50
- useFancyCmsForRichInput();
50
+ registerFancyCmsForRichInput();
51
+ var useFancyCmsForRichInput = registerFancyCmsForRichInput;
51
52
 
52
53
  exports.emptyRichInputDoc = emptyRichInputDoc;
53
54
  exports.isPageDoc = isPageDoc;
55
+ exports.registerFancyCmsForRichInput = registerFancyCmsForRichInput;
54
56
  exports.useFancyCmsForRichInput = useFancyCmsForRichInput;
55
57
  //# sourceMappingURL=rich-input.cjs.map
56
58
  //# sourceMappingURL=rich-input.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/registry/rich-input.tsx","../src/rich-input.tsx"],"names":["emptyDoc","FauxClient","createElement","CmsPage","Editor"],"mappings":";;;;;;;;AA+CA,IAAI,OAAA,GAAmC,IAAA;AACvC,IAAM,SAAA,uBAAgB,GAAA,EAAgB;AAG/B,SAAS,yBAAyB,IAAA,EAAoC;AAC3E,EAAA,OAAA,GAAU,IAAA;AACV,EAAA,KAAA,MAAW,CAAA,IAAK,WAAW,CAAA,EAAE;AAC7B,EAAA,OAAO,MAAM;AACX,IAAA,IAAI,YAAY,IAAA,EAAM;AACpB,MAAA,OAAA,GAAU,IAAA;AACV,MAAA,KAAA,MAAW,CAAA,IAAK,WAAW,CAAA,EAAE;AAAA,IAC/B;AAAA,EACF,CAAA;AACF;;;ACvBO,SAAS,iBAAA,CAAkB,KAAK,YAAA,EAAuB;AAC5D,EAAA,OAAOA,oBAAS,EAAE,CAAA;AACpB;AAGO,SAAS,UAAU,KAAA,EAAkC;AAC1D,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,UAAU,OAAO,KAAA;AACxD,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,QAAQ,CAAA,IAAK,OAAO,CAAA,CAAE,KAAA,KAAU,QAAA,IAAY,CAAA,CAAE,KAAA,KAAU,IAAA;AACjF;AAQO,SAAS,uBAAA,CAAwB,OAAA,GAMpC,EAAC,EAAe;AAClB,EAAA,OAAO,wBAAA,CAAyB;AAAA,gBAC9BC,qBAAA;AAAA;AAAA;AAAA;AAAA,IAIA,UAAA,EAAY,EAAE,OAAA,EAAS,SAAA,EAAW,OAAO,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,IAAA,EAAK;AAAA;AAAA;AAAA,IAIxE,gBAAgB,CAAC,GAAA,KACf,SAAA,CAAU,GAAG,IACTC,mBAAA,CAAcC,kBAAA,EAAS,EAAE,GAAA,EAAK,UAAU,OAAA,CAAQ,QAAA,EAAU,MAAM,OAAA,CAAQ,IAAA,EAAM,CAAA,GAC9E,IAAA;AAAA;AAAA,IAGN,cAAc,CAAC,EAAE,OAAO,QAAA,EAAS,KAC/BD,oBAAcE,iBAAA,EAAQ;AAAA,MACpB,YAAA,EAAc,SAAA,CAAU,KAAK,CAAA,GAAI,QAAQ,iBAAA,EAAkB;AAAA,MAC3D,QAAA;AAAA,MACA,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,MAAM,OAAA,CAAQ;AAAA,KACf;AAAA,GACJ,CAAA;AACH;AAEA,uBAAA,EAAwB","file":"rich-input.cjs","sourcesContent":["import type { ComponentType, ReactNode } from \"react\";\n\n/**\n * Rich user input — the injection point.\n *\n * `rich_user_input` pauses a run on a fully authored page (long-form content,\n * required reading + confirmation, multi-section forms) rather than a flat\n * field list. That page IS a fancy-cms document — fancy-flow defines no\n * document schema of its own and never re-implements one.\n *\n * The wiring ships in the box. Import the subpath once and the node lights up:\n *\n * ```ts\n * import \"@particle-academy/fancy-flow/rich-input\";\n * ```\n *\n * That module registers fancy-cms's `PageDoc` + `CmsPage` renderer + `Editor`\n * against this seam. It lives on a separate entry so `@particle-academy/\n * fancy-cms-ui` and `@particle-academy/react-fancy` stay OPTIONAL peers — most\n * flows never use a rich input, and a workflow editor should not drag a CMS\n * into every install.\n *\n * The seam stays public so a host can substitute a different document engine,\n * but that is the escape hatch, not the expected path.\n *\n * Until something registers, the node still registers and still round-trips\n * its config — it renders a \"how to enable\" body rather than an empty card.\n */\nexport type RichInputAdapter = {\n /**\n * react-fancy's `FauxClient` (or any component with the same shape) — a\n * frame that mimics a browser window or device and scales its content down\n * to fit. Used to preview the authored page inside the node card.\n */\n FauxClient?: ComponentType<any>;\n /**\n * Props for the frame — e.g. `{ variant: \"browser\", width: 1280, scale: \"fit\" }`\n * so a full-width page renders at its real width and scales down into the\n * node card instead of reflowing to a cramped layout that misrepresents it.\n */\n frameProps?: Record<string, unknown>;\n /** Render the stored document read-only, for the in-node preview. */\n renderDocument?: (doc: unknown) => ReactNode;\n /** Editor mounted in the config panel via `renderDocumentField`. */\n renderEditor?: (props: { value: unknown; onChange: (next: unknown) => void }) => ReactNode;\n};\n\nlet adapter: RichInputAdapter | null = null;\nconst listeners = new Set<() => void>();\n\n/** Install the host's document editor + preview frame. Returns an unregister fn. */\nexport function registerRichInputAdapter(next: RichInputAdapter): () => void {\n adapter = next;\n for (const l of listeners) l();\n return () => {\n if (adapter === next) {\n adapter = null;\n for (const l of listeners) l();\n }\n };\n}\n\n/** The registered adapter, or null when the host hasn't wired one. */\nexport function getRichInputAdapter(): RichInputAdapter | null {\n return adapter;\n}\n\n/** True once a host has wired an adapter — i.e. the node is usable. */\nexport function isRichInputEnabled(): boolean {\n return adapter !== null && (adapter.renderDocument !== undefined || adapter.renderEditor !== undefined);\n}\n\n/** Subscribe to adapter changes (so nodes re-render when it lands). */\nexport function onRichInputAdapterChanged(fn: () => void): () => void {\n listeners.add(fn);\n return () => listeners.delete(fn);\n}\n\n/**\n * RichInputPreview — the node card body. Shows the authored page inside a\n * FauxClient frame so an author can see, at a glance on the canvas, what the\n * person hitting this step will be looking at.\n *\n * Degrades in two steps rather than one: no adapter at all → install hint;\n * adapter but nothing authored yet → an empty frame with the step title. A\n * blank node body would read as \"broken\" in both cases.\n */\nexport function RichInputPreview({ config }: { config: Record<string, unknown> }) {\n const a = getRichInputAdapter();\n const title = typeof config.title === \"string\" && config.title.trim() !== \"\" ? config.title : \"Untitled step\";\n const doc = config.document;\n\n if (!a || (!a.renderDocument && !a.renderEditor)) {\n return (\n <div className=\"ff-rich-preview\">\n <span className=\"ff-rich-preview__title\">{title}</span>\n <div className=\"ff-rich-preview__unavailable\">\n Add <code>@particle-academy/fancy-cms-ui</code> +{\" \"}\n <code>@particle-academy/react-fancy</code>, then{\" \"}\n <code>import \"@particle-academy/fancy-flow/rich-input\"</code>.\n </div>\n </div>\n );\n }\n\n const body = doc === undefined || doc === null\n ? <p className=\"ff-rich-preview__unavailable\">Nothing authored yet.</p>\n : a.renderDocument?.(doc) ?? null;\n\n const Frame = a.FauxClient;\n\n return (\n <div className=\"ff-rich-preview\">\n <span className=\"ff-rich-preview__title\">{title}</span>\n <div className=\"ff-rich-preview__frame\">\n {Frame ? <Frame {...(a.frameProps ?? { variant: \"browser\" })}>{body}</Frame> : body}\n </div>\n </div>\n );\n}\n","/**\n * `@particle-academy/fancy-flow/rich-input` — wires `rich_user_input` to the\n * real CMS.\n *\n * Import this entry once, anywhere in your app, and the node lights up:\n *\n * ```ts\n * import \"@particle-academy/fancy-flow/rich-input\";\n * ```\n *\n * ## Why this is a separate entry\n *\n * The document a rich input step shows IS a fancy-cms page — same `PageDoc`,\n * same renderer, same editor. fancy-flow declares no document schema of its\n * own and never re-implements one; duplicating the model would guarantee the\n * two drift, and a step authored in fancy-flow would stop being a thing\n * fancy-cms could open.\n *\n * But most flows never use a rich input, and a workflow editor should not drag\n * a CMS into every install. So the dependency lives HERE, on an opt-in\n * subpath, instead of in the main entry. `@particle-academy/fancy-cms-ui` and\n * `@particle-academy/react-fancy` are optional peers: required to import this\n * module, irrelevant if you never do.\n */\nimport { createElement } from \"react\";\nimport { CmsPage, Editor, emptyDoc, type PageDoc } from \"@particle-academy/fancy-cms-ui\";\nimport { FauxClient } from \"@particle-academy/react-fancy\";\nimport { registerRichInputAdapter } from \"./registry/rich-input\";\n\nexport type { PageDoc };\n\n/**\n * A blank page for a step that hasn't been authored yet.\n *\n * `Editor` takes a `defaultValue`, not a nullable doc — so a never-authored\n * step needs a real empty document to open into rather than a blank screen.\n */\nexport function emptyRichInputDoc(id = \"rich-input\"): PageDoc {\n return emptyDoc(id);\n}\n\n/** True when a stored value is a fancy-cms page document. */\nexport function isPageDoc(value: unknown): value is PageDoc {\n if (value === null || typeof value !== \"object\") return false;\n const v = value as Partial<PageDoc>;\n return Array.isArray(v.sections) && typeof v.nodes === \"object\" && v.nodes !== null;\n}\n\n/**\n * Register fancy-cms as the document engine for `rich_user_input`.\n *\n * Called automatically on import. Exported so a host can re-register after\n * swapping in a custom element registry, and so the call is testable.\n */\nexport function useFancyCmsForRichInput(options: {\n /** Custom element registry — pass the SAME one you give `CmsPage` at runtime,\n * or the edit canvas renders your node types as blank placeholders. */\n registry?: Parameters<typeof CmsPage>[0][\"registry\"];\n /** Data context that `{ $bind }` props preview against. */\n data?: Parameters<typeof CmsPage>[0][\"data\"];\n} = {}): () => void {\n return registerRichInputAdapter({\n FauxClient,\n // Render the page at a real desktop width and scale it down, rather than\n // letting it reflow into a card-sized viewport — a preview that reflows\n // shows a layout the person hitting the step will never see.\n frameProps: { variant: \"browser\", width: 1280, scale: \"fit\", dots: true },\n\n // Preview: the CMS's own renderer, so the node card shows exactly what the\n // person hitting this step will see. Styles are scoped by the frame.\n renderDocument: (doc) =>\n isPageDoc(doc)\n ? createElement(CmsPage, { doc, registry: options.registry, data: options.data })\n : null,\n\n // Authoring: the CMS's own WYSIWYG editor, in the config panel.\n renderEditor: ({ value, onChange }) =>\n createElement(Editor, {\n defaultValue: isPageDoc(value) ? value : emptyRichInputDoc(),\n onChange: onChange as (doc: PageDoc) => void,\n registry: options.registry,\n data: options.data,\n }),\n });\n}\n\nuseFancyCmsForRichInput();\n"]}
1
+ {"version":3,"sources":["../src/registry/rich-input.tsx","../src/rich-input.tsx"],"names":["emptyDoc","FauxClient","createElement","CmsPage","Editor"],"mappings":";;;;;;;;AA+CA,IAAI,OAAA,GAAmC,IAAA;AACvC,IAAM,SAAA,uBAAgB,GAAA,EAAgB;AAG/B,SAAS,yBAAyB,IAAA,EAAoC;AAC3E,EAAA,OAAA,GAAU,IAAA;AACV,EAAA,KAAA,MAAW,CAAA,IAAK,WAAW,CAAA,EAAE;AAC7B,EAAA,OAAO,MAAM;AACX,IAAA,IAAI,YAAY,IAAA,EAAM;AACpB,MAAA,OAAA,GAAU,IAAA;AACV,MAAA,KAAA,MAAW,CAAA,IAAK,WAAW,CAAA,EAAE;AAAA,IAC/B;AAAA,EACF,CAAA;AACF;;;ACvBO,SAAS,iBAAA,CAAkB,KAAK,YAAA,EAAuB;AAC5D,EAAA,OAAOA,oBAAS,EAAE,CAAA;AACpB;AAGO,SAAS,UAAU,KAAA,EAAkC;AAC1D,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,UAAU,OAAO,KAAA;AACxD,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,QAAQ,CAAA,IAAK,OAAO,CAAA,CAAE,KAAA,KAAU,QAAA,IAAY,CAAA,CAAE,KAAA,KAAU,IAAA;AACjF;AAQO,SAAS,4BAAA,CAA6B,OAAA,GAMzC,EAAC,EAAe;AAClB,EAAA,OAAO,wBAAA,CAAyB;AAAA,gBAC9BC,qBAAA;AAAA;AAAA;AAAA;AAAA,IAIA,UAAA,EAAY,EAAE,OAAA,EAAS,SAAA,EAAW,OAAO,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,IAAA,EAAK;AAAA;AAAA;AAAA,IAIxE,gBAAgB,CAAC,GAAA,KACf,SAAA,CAAU,GAAG,IACTC,mBAAA,CAAcC,kBAAA,EAAS,EAAE,GAAA,EAAK,UAAU,OAAA,CAAQ,QAAA,EAAU,MAAM,OAAA,CAAQ,IAAA,EAAM,CAAA,GAC9E,IAAA;AAAA;AAAA,IAGN,cAAc,CAAC,EAAE,OAAO,QAAA,EAAS,KAC/BD,oBAAcE,iBAAA,EAAQ;AAAA,MACpB,YAAA,EAAc,SAAA,CAAU,KAAK,CAAA,GAAI,QAAQ,iBAAA,EAAkB;AAAA,MAC3D,QAAA;AAAA,MACA,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,MAAM,OAAA,CAAQ;AAAA,KACf;AAAA,GACJ,CAAA;AACH;AAEA,4BAAA,EAA6B;AAatB,IAAM,uBAAA,GAA0B","file":"rich-input.cjs","sourcesContent":["import type { ComponentType, ReactNode } from \"react\";\n\n/**\n * Rich user input — the injection point.\n *\n * `rich_user_input` pauses a run on a fully authored page (long-form content,\n * required reading + confirmation, multi-section forms) rather than a flat\n * field list. That page IS a fancy-cms document — fancy-flow defines no\n * document schema of its own and never re-implements one.\n *\n * The wiring ships in the box. Import the subpath once and the node lights up:\n *\n * ```ts\n * import \"@particle-academy/fancy-flow/rich-input\";\n * ```\n *\n * That module registers fancy-cms's `PageDoc` + `CmsPage` renderer + `Editor`\n * against this seam. It lives on a separate entry so `@particle-academy/\n * fancy-cms-ui` and `@particle-academy/react-fancy` stay OPTIONAL peers — most\n * flows never use a rich input, and a workflow editor should not drag a CMS\n * into every install.\n *\n * The seam stays public so a host can substitute a different document engine,\n * but that is the escape hatch, not the expected path.\n *\n * Until something registers, the node still registers and still round-trips\n * its config — it renders a \"how to enable\" body rather than an empty card.\n */\nexport type RichInputAdapter = {\n /**\n * react-fancy's `FauxClient` (or any component with the same shape) — a\n * frame that mimics a browser window or device and scales its content down\n * to fit. Used to preview the authored page inside the node card.\n */\n FauxClient?: ComponentType<any>;\n /**\n * Props for the frame — e.g. `{ variant: \"browser\", width: 1280, scale: \"fit\" }`\n * so a full-width page renders at its real width and scales down into the\n * node card instead of reflowing to a cramped layout that misrepresents it.\n */\n frameProps?: Record<string, unknown>;\n /** Render the stored document read-only, for the in-node preview. */\n renderDocument?: (doc: unknown) => ReactNode;\n /** Editor mounted in the config panel via `renderDocumentField`. */\n renderEditor?: (props: { value: unknown; onChange: (next: unknown) => void }) => ReactNode;\n};\n\nlet adapter: RichInputAdapter | null = null;\nconst listeners = new Set<() => void>();\n\n/** Install the host's document editor + preview frame. Returns an unregister fn. */\nexport function registerRichInputAdapter(next: RichInputAdapter): () => void {\n adapter = next;\n for (const l of listeners) l();\n return () => {\n if (adapter === next) {\n adapter = null;\n for (const l of listeners) l();\n }\n };\n}\n\n/** The registered adapter, or null when the host hasn't wired one. */\nexport function getRichInputAdapter(): RichInputAdapter | null {\n return adapter;\n}\n\n/** True once a host has wired an adapter — i.e. the node is usable. */\nexport function isRichInputEnabled(): boolean {\n return adapter !== null && (adapter.renderDocument !== undefined || adapter.renderEditor !== undefined);\n}\n\n/** Subscribe to adapter changes (so nodes re-render when it lands). */\nexport function onRichInputAdapterChanged(fn: () => void): () => void {\n listeners.add(fn);\n return () => listeners.delete(fn);\n}\n\n/**\n * RichInputPreview — the node card body. Shows the authored page inside a\n * FauxClient frame so an author can see, at a glance on the canvas, what the\n * person hitting this step will be looking at.\n *\n * Degrades in two steps rather than one: no adapter at all → install hint;\n * adapter but nothing authored yet → an empty frame with the step title. A\n * blank node body would read as \"broken\" in both cases.\n */\nexport function RichInputPreview({ config }: { config: Record<string, unknown> }) {\n const a = getRichInputAdapter();\n const title = typeof config.title === \"string\" && config.title.trim() !== \"\" ? config.title : \"Untitled step\";\n const doc = config.document;\n\n if (!a || (!a.renderDocument && !a.renderEditor)) {\n return (\n <div className=\"ff-rich-preview\">\n <span className=\"ff-rich-preview__title\">{title}</span>\n <div className=\"ff-rich-preview__unavailable\">\n Add <code>@particle-academy/fancy-cms-ui</code> +{\" \"}\n <code>@particle-academy/react-fancy</code>, then{\" \"}\n <code>import \"@particle-academy/fancy-flow/rich-input\"</code>.\n </div>\n </div>\n );\n }\n\n const body = doc === undefined || doc === null\n ? <p className=\"ff-rich-preview__unavailable\">Nothing authored yet.</p>\n : a.renderDocument?.(doc) ?? null;\n\n const Frame = a.FauxClient;\n\n return (\n <div className=\"ff-rich-preview\">\n <span className=\"ff-rich-preview__title\">{title}</span>\n <div className=\"ff-rich-preview__frame\">\n {Frame ? <Frame {...(a.frameProps ?? { variant: \"browser\" })}>{body}</Frame> : body}\n </div>\n </div>\n );\n}\n","/**\n * `@particle-academy/fancy-flow/rich-input` — wires `rich_user_input` to the\n * real CMS.\n *\n * Import this entry once, anywhere in your app, and the node lights up:\n *\n * ```ts\n * import \"@particle-academy/fancy-flow/rich-input\";\n * ```\n *\n * ## Why this is a separate entry\n *\n * The document a rich input step shows IS a fancy-cms page — same `PageDoc`,\n * same renderer, same editor. fancy-flow declares no document schema of its\n * own and never re-implements one; duplicating the model would guarantee the\n * two drift, and a step authored in fancy-flow would stop being a thing\n * fancy-cms could open.\n *\n * But most flows never use a rich input, and a workflow editor should not drag\n * a CMS into every install. So the dependency lives HERE, on an opt-in\n * subpath, instead of in the main entry. `@particle-academy/fancy-cms-ui` and\n * `@particle-academy/react-fancy` are optional peers: required to import this\n * module, irrelevant if you never do.\n */\nimport { createElement } from \"react\";\nimport { CmsPage, Editor, emptyDoc, type PageDoc } from \"@particle-academy/fancy-cms-ui\";\nimport { FauxClient } from \"@particle-academy/react-fancy\";\nimport { registerRichInputAdapter } from \"./registry/rich-input\";\n\nexport type { PageDoc };\n\n/**\n * A blank page for a step that hasn't been authored yet.\n *\n * `Editor` takes a `defaultValue`, not a nullable doc — so a never-authored\n * step needs a real empty document to open into rather than a blank screen.\n */\nexport function emptyRichInputDoc(id = \"rich-input\"): PageDoc {\n return emptyDoc(id);\n}\n\n/** True when a stored value is a fancy-cms page document. */\nexport function isPageDoc(value: unknown): value is PageDoc {\n if (value === null || typeof value !== \"object\") return false;\n const v = value as Partial<PageDoc>;\n return Array.isArray(v.sections) && typeof v.nodes === \"object\" && v.nodes !== null;\n}\n\n/**\n * Register fancy-cms as the document engine for `rich_user_input`.\n *\n * Called automatically on import. Exported so a host can re-register after\n * swapping in a custom element registry, and so the call is testable.\n */\nexport function registerFancyCmsForRichInput(options: {\n /** Custom element registry — pass the SAME one you give `CmsPage` at runtime,\n * or the edit canvas renders your node types as blank placeholders. */\n registry?: Parameters<typeof CmsPage>[0][\"registry\"];\n /** Data context that `{ $bind }` props preview against. */\n data?: Parameters<typeof CmsPage>[0][\"data\"];\n} = {}): () => void {\n return registerRichInputAdapter({\n FauxClient,\n // Render the page at a real desktop width and scale it down, rather than\n // letting it reflow into a card-sized viewport — a preview that reflows\n // shows a layout the person hitting the step will never see.\n frameProps: { variant: \"browser\", width: 1280, scale: \"fit\", dots: true },\n\n // Preview: the CMS's own renderer, so the node card shows exactly what the\n // person hitting this step will see. Styles are scoped by the frame.\n renderDocument: (doc) =>\n isPageDoc(doc)\n ? createElement(CmsPage, { doc, registry: options.registry, data: options.data })\n : null,\n\n // Authoring: the CMS's own WYSIWYG editor, in the config panel.\n renderEditor: ({ value, onChange }) =>\n createElement(Editor, {\n defaultValue: isPageDoc(value) ? value : emptyRichInputDoc(),\n onChange: onChange as (doc: PageDoc) => void,\n registry: options.registry,\n data: options.data,\n }),\n });\n}\n\nregisterFancyCmsForRichInput();\n\n/**\n * @deprecated Renamed to `registerFancyCmsForRichInput`. This alias still works\n * and will not be removed in 0.x.\n *\n * The `use` prefix was wrong: this registers a document engine and returns a\n * dispose function — it is not a React hook and calls none. React reserves\n * `use*` for hooks, and the name made `react-hooks/rules-of-hooks` report the\n * module-scope call on import as a hook called outside a component. A linter\n * being misled by a name is the mild version; a reader assuming hook rules\n * apply, or that it must be called during render, is the real cost.\n */\nexport const useFancyCmsForRichInput = registerFancyCmsForRichInput;\n"]}
@@ -16,12 +16,24 @@ declare function isPageDoc(value: unknown): value is PageDoc;
16
16
  * Called automatically on import. Exported so a host can re-register after
17
17
  * swapping in a custom element registry, and so the call is testable.
18
18
  */
19
- declare function useFancyCmsForRichInput(options?: {
19
+ declare function registerFancyCmsForRichInput(options?: {
20
20
  /** Custom element registry — pass the SAME one you give `CmsPage` at runtime,
21
21
  * or the edit canvas renders your node types as blank placeholders. */
22
22
  registry?: Parameters<typeof CmsPage>[0]["registry"];
23
23
  /** Data context that `{ $bind }` props preview against. */
24
24
  data?: Parameters<typeof CmsPage>[0]["data"];
25
25
  }): () => void;
26
+ /**
27
+ * @deprecated Renamed to `registerFancyCmsForRichInput`. This alias still works
28
+ * and will not be removed in 0.x.
29
+ *
30
+ * The `use` prefix was wrong: this registers a document engine and returns a
31
+ * dispose function — it is not a React hook and calls none. React reserves
32
+ * `use*` for hooks, and the name made `react-hooks/rules-of-hooks` report the
33
+ * module-scope call on import as a hook called outside a component. A linter
34
+ * being misled by a name is the mild version; a reader assuming hook rules
35
+ * apply, or that it must be called during render, is the real cost.
36
+ */
37
+ declare const useFancyCmsForRichInput: typeof registerFancyCmsForRichInput;
26
38
 
27
- export { emptyRichInputDoc, isPageDoc, useFancyCmsForRichInput };
39
+ export { emptyRichInputDoc, isPageDoc, registerFancyCmsForRichInput, useFancyCmsForRichInput };
@@ -16,12 +16,24 @@ declare function isPageDoc(value: unknown): value is PageDoc;
16
16
  * Called automatically on import. Exported so a host can re-register after
17
17
  * swapping in a custom element registry, and so the call is testable.
18
18
  */
19
- declare function useFancyCmsForRichInput(options?: {
19
+ declare function registerFancyCmsForRichInput(options?: {
20
20
  /** Custom element registry — pass the SAME one you give `CmsPage` at runtime,
21
21
  * or the edit canvas renders your node types as blank placeholders. */
22
22
  registry?: Parameters<typeof CmsPage>[0]["registry"];
23
23
  /** Data context that `{ $bind }` props preview against. */
24
24
  data?: Parameters<typeof CmsPage>[0]["data"];
25
25
  }): () => void;
26
+ /**
27
+ * @deprecated Renamed to `registerFancyCmsForRichInput`. This alias still works
28
+ * and will not be removed in 0.x.
29
+ *
30
+ * The `use` prefix was wrong: this registers a document engine and returns a
31
+ * dispose function — it is not a React hook and calls none. React reserves
32
+ * `use*` for hooks, and the name made `react-hooks/rules-of-hooks` report the
33
+ * module-scope call on import as a hook called outside a component. A linter
34
+ * being misled by a name is the mild version; a reader assuming hook rules
35
+ * apply, or that it must be called during render, is the real cost.
36
+ */
37
+ declare const useFancyCmsForRichInput: typeof registerFancyCmsForRichInput;
26
38
 
27
- export { emptyRichInputDoc, isPageDoc, useFancyCmsForRichInput };
39
+ export { emptyRichInputDoc, isPageDoc, registerFancyCmsForRichInput, useFancyCmsForRichInput };
@@ -11,7 +11,7 @@ function isPageDoc(value) {
11
11
  const v = value;
12
12
  return Array.isArray(v.sections) && typeof v.nodes === "object" && v.nodes !== null;
13
13
  }
14
- function useFancyCmsForRichInput(options = {}) {
14
+ function registerFancyCmsForRichInput(options = {}) {
15
15
  return registerRichInputAdapter({
16
16
  FauxClient,
17
17
  // Render the page at a real desktop width and scale it down, rather than
@@ -30,8 +30,9 @@ function useFancyCmsForRichInput(options = {}) {
30
30
  })
31
31
  });
32
32
  }
33
- useFancyCmsForRichInput();
33
+ registerFancyCmsForRichInput();
34
+ var useFancyCmsForRichInput = registerFancyCmsForRichInput;
34
35
 
35
- export { emptyRichInputDoc, isPageDoc, useFancyCmsForRichInput };
36
+ export { emptyRichInputDoc, isPageDoc, registerFancyCmsForRichInput, useFancyCmsForRichInput };
36
37
  //# sourceMappingURL=rich-input.js.map
37
38
  //# sourceMappingURL=rich-input.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/rich-input.tsx"],"names":[],"mappings":";;;;;AAqCO,SAAS,iBAAA,CAAkB,KAAK,YAAA,EAAuB;AAC5D,EAAA,OAAO,SAAS,EAAE,CAAA;AACpB;AAGO,SAAS,UAAU,KAAA,EAAkC;AAC1D,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,UAAU,OAAO,KAAA;AACxD,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,QAAQ,CAAA,IAAK,OAAO,CAAA,CAAE,KAAA,KAAU,QAAA,IAAY,CAAA,CAAE,KAAA,KAAU,IAAA;AACjF;AAQO,SAAS,uBAAA,CAAwB,OAAA,GAMpC,EAAC,EAAe;AAClB,EAAA,OAAO,wBAAA,CAAyB;AAAA,IAC9B,UAAA;AAAA;AAAA;AAAA;AAAA,IAIA,UAAA,EAAY,EAAE,OAAA,EAAS,SAAA,EAAW,OAAO,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,IAAA,EAAK;AAAA;AAAA;AAAA,IAIxE,gBAAgB,CAAC,GAAA,KACf,SAAA,CAAU,GAAG,IACT,aAAA,CAAc,OAAA,EAAS,EAAE,GAAA,EAAK,UAAU,OAAA,CAAQ,QAAA,EAAU,MAAM,OAAA,CAAQ,IAAA,EAAM,CAAA,GAC9E,IAAA;AAAA;AAAA,IAGN,cAAc,CAAC,EAAE,OAAO,QAAA,EAAS,KAC/B,cAAc,MAAA,EAAQ;AAAA,MACpB,YAAA,EAAc,SAAA,CAAU,KAAK,CAAA,GAAI,QAAQ,iBAAA,EAAkB;AAAA,MAC3D,QAAA;AAAA,MACA,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,MAAM,OAAA,CAAQ;AAAA,KACf;AAAA,GACJ,CAAA;AACH;AAEA,uBAAA,EAAwB","file":"rich-input.js","sourcesContent":["/**\n * `@particle-academy/fancy-flow/rich-input` — wires `rich_user_input` to the\n * real CMS.\n *\n * Import this entry once, anywhere in your app, and the node lights up:\n *\n * ```ts\n * import \"@particle-academy/fancy-flow/rich-input\";\n * ```\n *\n * ## Why this is a separate entry\n *\n * The document a rich input step shows IS a fancy-cms page — same `PageDoc`,\n * same renderer, same editor. fancy-flow declares no document schema of its\n * own and never re-implements one; duplicating the model would guarantee the\n * two drift, and a step authored in fancy-flow would stop being a thing\n * fancy-cms could open.\n *\n * But most flows never use a rich input, and a workflow editor should not drag\n * a CMS into every install. So the dependency lives HERE, on an opt-in\n * subpath, instead of in the main entry. `@particle-academy/fancy-cms-ui` and\n * `@particle-academy/react-fancy` are optional peers: required to import this\n * module, irrelevant if you never do.\n */\nimport { createElement } from \"react\";\nimport { CmsPage, Editor, emptyDoc, type PageDoc } from \"@particle-academy/fancy-cms-ui\";\nimport { FauxClient } from \"@particle-academy/react-fancy\";\nimport { registerRichInputAdapter } from \"./registry/rich-input\";\n\nexport type { PageDoc };\n\n/**\n * A blank page for a step that hasn't been authored yet.\n *\n * `Editor` takes a `defaultValue`, not a nullable doc — so a never-authored\n * step needs a real empty document to open into rather than a blank screen.\n */\nexport function emptyRichInputDoc(id = \"rich-input\"): PageDoc {\n return emptyDoc(id);\n}\n\n/** True when a stored value is a fancy-cms page document. */\nexport function isPageDoc(value: unknown): value is PageDoc {\n if (value === null || typeof value !== \"object\") return false;\n const v = value as Partial<PageDoc>;\n return Array.isArray(v.sections) && typeof v.nodes === \"object\" && v.nodes !== null;\n}\n\n/**\n * Register fancy-cms as the document engine for `rich_user_input`.\n *\n * Called automatically on import. Exported so a host can re-register after\n * swapping in a custom element registry, and so the call is testable.\n */\nexport function useFancyCmsForRichInput(options: {\n /** Custom element registry — pass the SAME one you give `CmsPage` at runtime,\n * or the edit canvas renders your node types as blank placeholders. */\n registry?: Parameters<typeof CmsPage>[0][\"registry\"];\n /** Data context that `{ $bind }` props preview against. */\n data?: Parameters<typeof CmsPage>[0][\"data\"];\n} = {}): () => void {\n return registerRichInputAdapter({\n FauxClient,\n // Render the page at a real desktop width and scale it down, rather than\n // letting it reflow into a card-sized viewport — a preview that reflows\n // shows a layout the person hitting the step will never see.\n frameProps: { variant: \"browser\", width: 1280, scale: \"fit\", dots: true },\n\n // Preview: the CMS's own renderer, so the node card shows exactly what the\n // person hitting this step will see. Styles are scoped by the frame.\n renderDocument: (doc) =>\n isPageDoc(doc)\n ? createElement(CmsPage, { doc, registry: options.registry, data: options.data })\n : null,\n\n // Authoring: the CMS's own WYSIWYG editor, in the config panel.\n renderEditor: ({ value, onChange }) =>\n createElement(Editor, {\n defaultValue: isPageDoc(value) ? value : emptyRichInputDoc(),\n onChange: onChange as (doc: PageDoc) => void,\n registry: options.registry,\n data: options.data,\n }),\n });\n}\n\nuseFancyCmsForRichInput();\n"]}
1
+ {"version":3,"sources":["../src/rich-input.tsx"],"names":[],"mappings":";;;;;AAqCO,SAAS,iBAAA,CAAkB,KAAK,YAAA,EAAuB;AAC5D,EAAA,OAAO,SAAS,EAAE,CAAA;AACpB;AAGO,SAAS,UAAU,KAAA,EAAkC;AAC1D,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,OAAO,KAAA,KAAU,UAAU,OAAO,KAAA;AACxD,EAAA,MAAM,CAAA,GAAI,KAAA;AACV,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,QAAQ,CAAA,IAAK,OAAO,CAAA,CAAE,KAAA,KAAU,QAAA,IAAY,CAAA,CAAE,KAAA,KAAU,IAAA;AACjF;AAQO,SAAS,4BAAA,CAA6B,OAAA,GAMzC,EAAC,EAAe;AAClB,EAAA,OAAO,wBAAA,CAAyB;AAAA,IAC9B,UAAA;AAAA;AAAA;AAAA;AAAA,IAIA,UAAA,EAAY,EAAE,OAAA,EAAS,SAAA,EAAW,OAAO,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,IAAA,EAAK;AAAA;AAAA;AAAA,IAIxE,gBAAgB,CAAC,GAAA,KACf,SAAA,CAAU,GAAG,IACT,aAAA,CAAc,OAAA,EAAS,EAAE,GAAA,EAAK,UAAU,OAAA,CAAQ,QAAA,EAAU,MAAM,OAAA,CAAQ,IAAA,EAAM,CAAA,GAC9E,IAAA;AAAA;AAAA,IAGN,cAAc,CAAC,EAAE,OAAO,QAAA,EAAS,KAC/B,cAAc,MAAA,EAAQ;AAAA,MACpB,YAAA,EAAc,SAAA,CAAU,KAAK,CAAA,GAAI,QAAQ,iBAAA,EAAkB;AAAA,MAC3D,QAAA;AAAA,MACA,UAAU,OAAA,CAAQ,QAAA;AAAA,MAClB,MAAM,OAAA,CAAQ;AAAA,KACf;AAAA,GACJ,CAAA;AACH;AAEA,4BAAA,EAA6B;AAatB,IAAM,uBAAA,GAA0B","file":"rich-input.js","sourcesContent":["/**\n * `@particle-academy/fancy-flow/rich-input` — wires `rich_user_input` to the\n * real CMS.\n *\n * Import this entry once, anywhere in your app, and the node lights up:\n *\n * ```ts\n * import \"@particle-academy/fancy-flow/rich-input\";\n * ```\n *\n * ## Why this is a separate entry\n *\n * The document a rich input step shows IS a fancy-cms page — same `PageDoc`,\n * same renderer, same editor. fancy-flow declares no document schema of its\n * own and never re-implements one; duplicating the model would guarantee the\n * two drift, and a step authored in fancy-flow would stop being a thing\n * fancy-cms could open.\n *\n * But most flows never use a rich input, and a workflow editor should not drag\n * a CMS into every install. So the dependency lives HERE, on an opt-in\n * subpath, instead of in the main entry. `@particle-academy/fancy-cms-ui` and\n * `@particle-academy/react-fancy` are optional peers: required to import this\n * module, irrelevant if you never do.\n */\nimport { createElement } from \"react\";\nimport { CmsPage, Editor, emptyDoc, type PageDoc } from \"@particle-academy/fancy-cms-ui\";\nimport { FauxClient } from \"@particle-academy/react-fancy\";\nimport { registerRichInputAdapter } from \"./registry/rich-input\";\n\nexport type { PageDoc };\n\n/**\n * A blank page for a step that hasn't been authored yet.\n *\n * `Editor` takes a `defaultValue`, not a nullable doc — so a never-authored\n * step needs a real empty document to open into rather than a blank screen.\n */\nexport function emptyRichInputDoc(id = \"rich-input\"): PageDoc {\n return emptyDoc(id);\n}\n\n/** True when a stored value is a fancy-cms page document. */\nexport function isPageDoc(value: unknown): value is PageDoc {\n if (value === null || typeof value !== \"object\") return false;\n const v = value as Partial<PageDoc>;\n return Array.isArray(v.sections) && typeof v.nodes === \"object\" && v.nodes !== null;\n}\n\n/**\n * Register fancy-cms as the document engine for `rich_user_input`.\n *\n * Called automatically on import. Exported so a host can re-register after\n * swapping in a custom element registry, and so the call is testable.\n */\nexport function registerFancyCmsForRichInput(options: {\n /** Custom element registry — pass the SAME one you give `CmsPage` at runtime,\n * or the edit canvas renders your node types as blank placeholders. */\n registry?: Parameters<typeof CmsPage>[0][\"registry\"];\n /** Data context that `{ $bind }` props preview against. */\n data?: Parameters<typeof CmsPage>[0][\"data\"];\n} = {}): () => void {\n return registerRichInputAdapter({\n FauxClient,\n // Render the page at a real desktop width and scale it down, rather than\n // letting it reflow into a card-sized viewport — a preview that reflows\n // shows a layout the person hitting the step will never see.\n frameProps: { variant: \"browser\", width: 1280, scale: \"fit\", dots: true },\n\n // Preview: the CMS's own renderer, so the node card shows exactly what the\n // person hitting this step will see. Styles are scoped by the frame.\n renderDocument: (doc) =>\n isPageDoc(doc)\n ? createElement(CmsPage, { doc, registry: options.registry, data: options.data })\n : null,\n\n // Authoring: the CMS's own WYSIWYG editor, in the config panel.\n renderEditor: ({ value, onChange }) =>\n createElement(Editor, {\n defaultValue: isPageDoc(value) ? value : emptyRichInputDoc(),\n onChange: onChange as (doc: PageDoc) => void,\n registry: options.registry,\n data: options.data,\n }),\n });\n}\n\nregisterFancyCmsForRichInput();\n\n/**\n * @deprecated Renamed to `registerFancyCmsForRichInput`. This alias still works\n * and will not be removed in 0.x.\n *\n * The `use` prefix was wrong: this registers a document engine and returns a\n * dispose function — it is not a React hook and calls none. React reserves\n * `use*` for hooks, and the name made `react-hooks/rules-of-hooks` report the\n * module-scope call on import as a hook called outside a component. A linter\n * being misled by a name is the mild version; a reader assuming hook rules\n * apply, or that it must be called during render, is the real cost.\n */\nexport const useFancyCmsForRichInput = registerFancyCmsForRichInput;\n"]}
@@ -1,4 +1,4 @@
1
- import { F as FlowGraph, E as ExecutorRegistry, R as RunEvent } from './types-CMSrWVYM.cjs';
1
+ import { F as FlowGraph, E as ExecutorRegistry, R as RunEvent } from './types-sOmpCitB.cjs';
2
2
 
3
3
  type RunOptions = {
4
4
  /** Stop the run after this many ms. Default: no timeout. */
@@ -1,4 +1,4 @@
1
- import { F as FlowGraph, E as ExecutorRegistry, R as RunEvent } from './types-CMSrWVYM.js';
1
+ import { F as FlowGraph, E as ExecutorRegistry, R as RunEvent } from './types-sOmpCitB.js';
2
2
 
3
3
  type RunOptions = {
4
4
  /** Stop the run after this many ms. Default: no timeout. */
@@ -1,6 +1,6 @@
1
- import { d as RunResult, R as RunOptions } from '../run-cohort-DitsaYgS.cjs';
2
- export { C as CohortGuard, a as CohortOptions, b as CohortPolicy, c as CohortResult, r as runCohort, e as runFlow } from '../run-cohort-DitsaYgS.cjs';
3
- import { e as NodeRunStatus, F as FlowGraph, E as ExecutorRegistry, a as FlowNode, b as FlowEdge } from '../types-CMSrWVYM.cjs';
1
+ import { d as RunResult, R as RunOptions } from '../run-cohort-Bjv2ZU6g.cjs';
2
+ export { C as CohortGuard, a as CohortOptions, b as CohortPolicy, c as CohortResult, r as runCohort, e as runFlow } from '../run-cohort-Bjv2ZU6g.cjs';
3
+ import { e as NodeRunStatus, F as FlowGraph, E as ExecutorRegistry, b as FlowNode, a as FlowEdge } from '../types-sOmpCitB.cjs';
4
4
  import { NodeChange, EdgeChange, Connection } from '@xyflow/react';
5
5
 
6
6
  type FlowRunFeedEntry = {
@@ -1,6 +1,6 @@
1
- import { d as RunResult, R as RunOptions } from '../run-cohort-CV7EcwGG.js';
2
- export { C as CohortGuard, a as CohortOptions, b as CohortPolicy, c as CohortResult, r as runCohort, e as runFlow } from '../run-cohort-CV7EcwGG.js';
3
- import { e as NodeRunStatus, F as FlowGraph, E as ExecutorRegistry, a as FlowNode, b as FlowEdge } from '../types-CMSrWVYM.js';
1
+ import { d as RunResult, R as RunOptions } from '../run-cohort-BnP6ErHi.js';
2
+ export { C as CohortGuard, a as CohortOptions, b as CohortPolicy, c as CohortResult, r as runCohort, e as runFlow } from '../run-cohort-BnP6ErHi.js';
3
+ import { e as NodeRunStatus, F as FlowGraph, E as ExecutorRegistry, b as FlowNode, a as FlowEdge } from '../types-sOmpCitB.js';
4
4
  import { NodeChange, EdgeChange, Connection } from '@xyflow/react';
5
5
 
6
6
  type FlowRunFeedEntry = {
@@ -1,4 +1,4 @@
1
- import { P as PortDescriptor, F as FlowGraph } from '../types-CMSrWVYM.cjs';
1
+ import { P as PortDescriptor, F as FlowGraph } from '../types-sOmpCitB.cjs';
2
2
  import '@xyflow/react';
3
3
 
4
4
  /** Schema version. Bump on breaking shape changes; add migrations as needed. */
@@ -1,4 +1,4 @@
1
- import { P as PortDescriptor, F as FlowGraph } from '../types-CMSrWVYM.js';
1
+ import { P as PortDescriptor, F as FlowGraph } from '../types-sOmpCitB.js';
2
2
  import '@xyflow/react';
3
3
 
4
4
  /** Schema version. Bump on breaking shape changes; add migrations as needed. */
@@ -1,6 +1,6 @@
1
1
  import { ReactNode, ComponentType } from 'react';
2
2
  import { NodeProps } from '@xyflow/react';
3
- import { P as PortDescriptor, N as NodeExecutor, a as FlowNode } from './types-CMSrWVYM.cjs';
3
+ import { P as PortDescriptor, N as NodeExecutor, b as FlowNode } from './types-sOmpCitB.js';
4
4
 
5
5
  /**
6
6
  * The human-pause contract.
@@ -97,6 +97,237 @@ declare function pauseForHuman(ctx: {
97
97
  abort: (reason?: string) => never;
98
98
  }, awaiting: PauseAwaiting, detail?: unknown): never;
99
99
 
100
+ /**
101
+ * The node package manifest — what a marketplace node declares about itself.
102
+ *
103
+ * A node is not one artifact. It is a kind definition (palette entry, config
104
+ * schema, ports, renderer) plus an executor for EACH runtime the consumer runs.
105
+ * A package shipping only a TS executor is unusable to anyone executing on PHP,
106
+ * and without a manifest that is invisible until a run fails. Requested by the
107
+ * MOIC Suite consumer (fancy-flow#2 §2), who runs the editor in TS and executes
108
+ * in PHP and hit exactly this.
109
+ *
110
+ * The manifest is data, not code: the registry, the CLI, and the MCP all read
111
+ * it without executing anything a package author wrote.
112
+ *
113
+ * ## Why the engine range lives per runtime
114
+ *
115
+ * The first cut of this manifest carried ONE `fancyFlow` range, and it was
116
+ * wrong: the two engines version independently, so a single range cannot say
117
+ * "needs npm >=0.11 AND php >=0.5". A package supporting both runtimes would
118
+ * install cleanly against a host whose OTHER runtime was too old — the 0.9.0
119
+ * failure shape wearing a manifest. The range belongs inside each runtime.
120
+ */
121
+
122
+ /** The current manifest schema version. Bump only on a breaking shape change. */
123
+ declare const NODE_MANIFEST_SCHEMA_VERSION = 1;
124
+ /**
125
+ * A runtime a node can implement.
126
+ *
127
+ * Open rather than a closed union — the PHP and Node twins are what exist
128
+ * today, but the point of a manifest is that a runtime we haven't written can
129
+ * declare itself without a release here.
130
+ */
131
+ type NodeRuntimeId = "ts" | "php" | (string & {});
132
+ /**
133
+ * How one runtime provides this node, and which engine version it needs.
134
+ *
135
+ * `files` are source directories, not a dependency: a node is **vendored**, so
136
+ * `fancy-cli add node` copies them into the project the way it copies a
137
+ * component's source. One node, one source, readable and editable in the app
138
+ * rather than hidden in `node_modules` or `vendor`.
139
+ *
140
+ * This spec covers a runtime's BACKEND only. The surface lives in the
141
+ * manifest's `ui`, because the editor is React on every host — a Laravel
142
+ * project needs the React kind and does not need the TypeScript executor.
143
+ */
144
+ type NodeRuntimeSpec = {
145
+ /** This backend's source directories, relative to the node. */
146
+ files: string[];
147
+ /** Semver range of THIS runtime's engine. Required — see the module note. */
148
+ engine: string;
149
+ };
150
+ /**
151
+ * Whether a capability must be wired for the node to work at all.
152
+ *
153
+ * `required` is checked at AUTHOR time, not run time. The failure that hurts is
154
+ * a node that installs fine, authors fine, and silently no-ops during a run —
155
+ * the same silence as a routing bug. An editor can grey the node and say which
156
+ * capability the host never registered.
157
+ */
158
+ type CapabilityRequirement = "required" | "optional";
159
+ /**
160
+ * Whether a node is safe to run again.
161
+ *
162
+ * Durable runs RETRY. A node that writes needs to say so, or a host has to pick
163
+ * one retry policy for every node and get it wrong somewhere.
164
+ */
165
+ type SideEffects = "none" | "idempotent" | "unsafe-to-replay";
166
+ /**
167
+ * A Fancy suite package this node's source imports.
168
+ *
169
+ * Separate from a plain npm dependency because the suite is polyglot and
170
+ * vendorable: the same capability ships as an npm package, a Composer package,
171
+ * or source you copy in, and which of those a consumer wants depends on the
172
+ * host — so the node names the *package*, and the CLI offers the routes that
173
+ * exist. A bare `dependencies: ["@particle-academy/fancy-screens"]` can only
174
+ * ever offer `npm install`, which is the wrong answer in a Laravel app whose
175
+ * editor is vendored.
176
+ *
177
+ * ## No versions, deliberately
178
+ *
179
+ * Nothing here carries a range, and the validator rejects one. A pinned
180
+ * marketplace node freezes a consumer on an old surface: the suite ships
181
+ * additively and often, and a node that pinned `^0.4` at authoring time would
182
+ * be holding a project back a year later for a constraint nobody revisits.
183
+ * Compatibility is expressed where it can actually be checked — `runtimes[].engine`
184
+ * against the *engine*, which is the contract a node really depends on.
185
+ */
186
+ type FancyDependency = {
187
+ /** Suite slug — what `/packages/<slug>` documents. */
188
+ package: string;
189
+ /** npm name, when the suite ships one. */
190
+ npm?: string;
191
+ /** Composer name, when the suite ships one. */
192
+ composer?: string;
193
+ /**
194
+ * Why the node needs it, in a few words.
195
+ *
196
+ * Printed at install. Without it the CLI can only present a bare name and a
197
+ * command, which reads as an unexplained demand for a second package.
198
+ */
199
+ reason?: string;
200
+ /** Whether the node fails without it, or merely does less. Defaults to required. */
201
+ requirement?: CapabilityRequirement;
202
+ };
203
+ type NodePackageManifest = {
204
+ /** Must equal `NODE_MANIFEST_SCHEMA_VERSION`. */
205
+ schemaVersion: number;
206
+ /** Package name, as installed (`@acme/fancy-flow-salesforce`). */
207
+ name: string;
208
+ /**
209
+ * The canonical kind id this package provides — namespaced, and the string
210
+ * that gets persisted into every document using it.
211
+ */
212
+ kind: string;
213
+ /**
214
+ * Previous ids this kind still answers to.
215
+ *
216
+ * Core renamed `llm_branch` to `llm_router` and kept every old id working.
217
+ * Third-party packages will rename too, and their documents break the same
218
+ * way core's would have — so packages get the same escape hatch, otherwise
219
+ * only first-party nodes can rename safely.
220
+ */
221
+ aliases?: string[];
222
+ /**
223
+ * Version of this node's CONFIG shape, independent of the document schema.
224
+ *
225
+ * A node's config evolves on its own clock. Without a declared version, every
226
+ * executor accretes hand-written read-fallbacks forever (MOIC carries one for
227
+ * `routes[].key` → `routes[].port`), because nothing canonicalises old config
228
+ * the way import canonicalises kind ids.
229
+ */
230
+ configVersion?: number;
231
+ /**
232
+ * The node's SURFACE — its React kind — as source directories.
233
+ *
234
+ * Copied whichever backend the consumer picks, because the editor is React on
235
+ * every host. Kept out of `runtimes` deliberately: fold the two together and
236
+ * a PHP project either loses its palette entry or gains a TypeScript executor
237
+ * it will never run.
238
+ */
239
+ ui?: string[];
240
+ /** Per-runtime backend source and engine ranges. */
241
+ runtimes: Partial<Record<NodeRuntimeId, NodeRuntimeSpec>>;
242
+ /**
243
+ * Host capabilities this node needs, and whether each is mandatory.
244
+ *
245
+ * Declared so the CLI and the editor can say what to wire BEFORE a run,
246
+ * rather than the node silently no-opping or crashing mid-run.
247
+ */
248
+ capabilities?: Record<string, CapabilityRequirement>;
249
+ /** Fancy suite packages this node's source imports. See {@link FancyDependency}. */
250
+ fancyDependencies?: FancyDependency[];
251
+ /**
252
+ * Path to this node's golden fixtures, relative to the package root.
253
+ *
254
+ * REQUIRED. Every runtime the package claims runs these same cases, which is
255
+ * what makes "behaves identically on both runtimes" verified rather than
256
+ * asserted. See `./fixtures`.
257
+ */
258
+ fixtures: string;
259
+ /**
260
+ * Declares the node halts for a person. Mirrors `NodeKindDefinition`.
261
+ *
262
+ * A host-planning fact, not a node internal: a parent that embeds workflows
263
+ * needs to reject a child that can pause, because a paused child wedges the
264
+ * parent — and discovering that at run time means watching a run park.
265
+ */
266
+ pausesForHuman?: PauseAwaiting;
267
+ /** Whether this node is safe to replay. See `SideEffects`. */
268
+ sideEffects?: SideEffects;
269
+ /** One-line summary — what `search_nodes` matches against. */
270
+ description?: string;
271
+ /**
272
+ * Assigned by the registry, never by the author. Present in a manifest being
273
+ * submitted for publication, it is a claim to a trust signal the author does
274
+ * not get to make.
275
+ */
276
+ verified?: boolean;
277
+ };
278
+ type ManifestProblem = {
279
+ level: "error" | "warning";
280
+ field: string;
281
+ message: string;
282
+ };
283
+ type ManifestValidation = {
284
+ /** True when there are no `error`-level problems. Warnings do not block. */
285
+ ok: boolean;
286
+ manifest?: NodePackageManifest;
287
+ problems: ManifestProblem[];
288
+ };
289
+ /**
290
+ * Validate a manifest read from disk or a registry.
291
+ *
292
+ * Returns every problem rather than throwing on the first, because an author
293
+ * fixing a package wants the whole list — a validator that reveals one error
294
+ * per run turns a five-minute fix into five round trips.
295
+ */
296
+ declare function validateNodeManifest(input: unknown): ManifestValidation;
297
+ /**
298
+ * Check a node against the runtimes a host executes on, and their versions.
299
+ *
300
+ * Two failures live here, and both are errors because the node genuinely
301
+ * cannot run: a runtime the package does not implement at all, and a runtime
302
+ * it implements against an engine newer than the host's.
303
+ *
304
+ * `engineVersions` is optional — pass what the host knows. An unchecked range
305
+ * is reported as a warning rather than passed over silently, because "we did
306
+ * not check" and "it is fine" must not look the same.
307
+ */
308
+ declare function checkRuntimeSupport(manifest: Pick<NodePackageManifest, "kind" | "runtimes">, hostRuntimes: readonly string[], engineVersions?: Readonly<Record<string, string>>): ManifestProblem[];
309
+ /**
310
+ * Minimal semver range check — `^x.y.z`, `~x.y.z`, `>=x.y.z`, `x.y.z`, `*`.
311
+ *
312
+ * Deliberately small: this runs in the CLI and in CI, and pulling a semver
313
+ * library into the engine for one comparison is not worth the dependency. It
314
+ * covers the forms a manifest actually uses; anything it cannot parse is
315
+ * treated as unsatisfied rather than silently passed, so an unparseable range
316
+ * fails loudly instead of waving a node through.
317
+ */
318
+ declare function satisfiesRange(version: string, range: string): boolean;
319
+ /**
320
+ * Check that every capability a node needs is wired.
321
+ *
322
+ * A `required` capability that is missing is an ERROR — that is the whole point
323
+ * of the requirement level. It is meant to be surfaced at author time, so an
324
+ * editor can grey the node and name what the host never registered, rather than
325
+ * the node installing cleanly and silently no-opping during a run.
326
+ *
327
+ * An `optional` one is a warning: the node still works, with less.
328
+ */
329
+ declare function checkCapabilities(manifest: Pick<NodePackageManifest, "kind" | "capabilities">, available: Readonly<Record<string, boolean>>): ManifestProblem[];
330
+
100
331
  /** Categories used by the palette for grouping. */
101
332
  type NodeCategory = "trigger" | "logic" | "data" | "ai" | "io" | "human" | "output" | "layout" | "annotation" | "custom";
102
333
  /**
@@ -336,6 +567,24 @@ type NodeKindDefinition<TConfig = Record<string, unknown>, TIn = any, TOut = any
336
567
  * refuse to list a pausing node whose package never says so.
337
568
  */
338
569
  pausesForHuman?: PauseAwaiting;
570
+ /**
571
+ * Whether a node of this kind is safe to run twice.
572
+ *
573
+ * The same three values a marketplace manifest declares (see `SideEffects`),
574
+ * lifted onto the KIND so it is readable at run time. A manifest is install-
575
+ * time data; a durable runner needs this while deciding whether to retry, and
576
+ * cannot go and read a manifest to find out.
577
+ *
578
+ * `fancy-flow-php`'s per-node queue driver keys its retry policy on exactly
579
+ * this: an `unsafe-to-replay` node gets one attempt whatever `tries` says,
580
+ * because a retried `git_pr_open` opens a second pull request. This field is
581
+ * the TypeScript half of that contract — the twins declare the same kind
582
+ * metadata or they are not twins.
583
+ *
584
+ * Declaration only. Nothing here enforces it; a host's runner decides what to
585
+ * do with it, and the TS in-process runner currently does not retry at all.
586
+ */
587
+ sideEffects?: SideEffects;
339
588
  /**
340
589
  * Make nodes of this kind resizable via drag handles (xyflow NodeResizer).
341
590
  * `true` enables it with defaults; pass options to bound it. The resulting
@@ -374,4 +623,4 @@ type NodeKindDefinition<TConfig = Record<string, unknown>, TIn = any, TOut = any
374
623
  reactive?: boolean;
375
624
  };
376
625
 
377
- export { type ConfigField as C, type DocumentConfigField as D, type ExpressionConfigField as E, type JsonConfigField as J, type KeyValueConfigField as K, LEGACY_PAUSE_PREFIXES as L, type NodeKindDefinition as N, type PauseAwaiting as P, type RenderBodyContext as R, type SelectConfigField as S, type TextConfigField as T, type NodeCategory as a, PAUSE_PREFIX as b, type PauseSignal as c, type PortSpec as d, decodePause as e, encodePause as f, type CredentialConfigField as g, type NumberConfigField as h, isPause as i, type RepeaterConfigField as j, type RepeaterRowField as k, type SwitchConfigField as l, type TextareaConfigField as m, pauseForHuman as p };
626
+ export { type CapabilityRequirement as C, type DocumentConfigField as D, type ExpressionConfigField as E, type JsonConfigField as J, type KeyValueConfigField as K, LEGACY_PAUSE_PREFIXES as L, type ManifestProblem as M, NODE_MANIFEST_SCHEMA_VERSION as N, type PauseAwaiting as P, type RenderBodyContext as R, type SideEffects as S, type TextConfigField as T, type ConfigField as a, type ManifestValidation as b, type NodeCategory as c, type NodeKindDefinition as d, type NodePackageManifest as e, type NodeRuntimeId as f, type NodeRuntimeSpec as g, PAUSE_PREFIX as h, type PauseSignal as i, type PortSpec as j, checkCapabilities as k, checkRuntimeSupport as l, decodePause as m, encodePause as n, isPause as o, pauseForHuman as p, type CredentialConfigField as q, type NumberConfigField as r, satisfiesRange as s, type RepeaterConfigField as t, type RepeaterRowField as u, validateNodeManifest as v, type SelectConfigField as w, type SwitchConfigField as x, type TextareaConfigField as y };