@pipelex/mthds-ui 0.29.1 → 0.30.1

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 (33) hide show
  1. package/README.md +14 -8
  2. package/dist/{chunk-ZTXSDTVF.js → chunk-3BXB6HSF.js} +1304 -31
  3. package/dist/chunk-3BXB6HSF.js.map +1 -0
  4. package/dist/{chunk-Y46574OW.js → chunk-GAOBB3UT.js} +5 -2
  5. package/dist/chunk-GAOBB3UT.js.map +1 -0
  6. package/dist/chunk-KB2DIEVF.js +1 -0
  7. package/dist/{chunk-3I3ZPYMW.js → chunk-PJTYQURQ.js} +33 -1
  8. package/dist/chunk-PJTYQURQ.js.map +1 -0
  9. package/dist/form/react/index.d.ts +1 -1
  10. package/dist/form/react/index.js +2 -2
  11. package/dist/graph/index.d.ts +336 -9
  12. package/dist/graph/index.js +98 -4
  13. package/dist/graph/react/index.d.ts +26 -2
  14. package/dist/graph/react/index.js +975 -371
  15. package/dist/graph/react/index.js.map +1 -1
  16. package/dist/graph/react/styles/simple/SimpleStyle.css +434 -0
  17. package/dist/graph/react/viewer/GraphToolbar.css +114 -0
  18. package/dist/index.d.ts +2 -2
  19. package/dist/index.js +98 -4
  20. package/dist/standalone/graph-standalone.html +714 -165
  21. package/dist/standalone/graph-viewer.css +549 -0
  22. package/dist/standalone/graph-viewer.js +164 -164
  23. package/dist/static-graph/index.d.ts +1 -1
  24. package/dist/static-graph/index.js +1 -1
  25. package/dist/{types-BaFWy56P.d.ts → types-BNqjYF_x.d.ts} +152 -1
  26. package/package.json +3 -2
  27. package/dist/chunk-3I3ZPYMW.js.map +0 -1
  28. package/dist/chunk-F6TZIJ7A.js +0 -1
  29. package/dist/chunk-Y46574OW.js.map +0 -1
  30. package/dist/chunk-ZTXSDTVF.js.map +0 -1
  31. package/dist/graph/react/index.css +0 -749
  32. package/dist/graph/react/index.css.map +0 -1
  33. /package/dist/{chunk-F6TZIJ7A.js.map → chunk-KB2DIEVF.js.map} +0 -0
@@ -1,4 +1,4 @@
1
- import { C as ConceptInfo, P as PipeBlueprintUnion, a as PresenceMarker, b as GraphSpec, V as ValidationIssue } from '../types-BaFWy56P.js';
1
+ import { C as ConceptInfo, P as PipeBlueprintUnion, a as PresenceMarker, b as GraphSpec, V as ValidationIssue } from '../types-BNqjYF_x.js';
2
2
  export { P as ParsedPipeRef, m as makePipeRef, p as parsePipeRef } from '../pipeRefs-BQJO5nh5.js';
3
3
 
4
4
  type DiagnosticSeverity = "error" | "warning";
@@ -7,7 +7,7 @@ import {
7
7
  KNOWN_PIPE_TYPES,
8
8
  isBindingStepSpec,
9
9
  isPluralMultiplicity
10
- } from "../chunk-3I3ZPYMW.js";
10
+ } from "../chunk-PJTYQURQ.js";
11
11
  import {
12
12
  __spreadProps,
13
13
  __spreadValues
@@ -32,6 +32,16 @@ interface GraphSpecMeta {
32
32
  declare const NODE_TYPE_PIPE_CARD: "pipeCard";
33
33
  declare const NODE_TYPE_STUFF: "default";
34
34
  declare const NODE_TYPE_CONTROLLER: "controllerGroup";
35
+ declare const NODE_TYPE_SIMPLE_STEP: "simpleStep";
36
+ declare const NODE_TYPE_SIMPLE_TERMINAL: "simpleTerminal";
37
+ declare const NODE_TYPE_SIMPLE_DECISION: "simpleDecision";
38
+ declare const NODE_TYPE_SIMPLE_FRAME: "simpleFrame";
39
+ /**
40
+ * The edge type that draws an edge along the route the layout computed for it
41
+ * (`GraphEdge.route`), around the nodes in its way, rather than as a free curve
42
+ * between its two ends. Used by the styles that lay out with `routeEdges`.
43
+ */
44
+ declare const EDGE_TYPE_ROUTED: "routed";
35
45
  declare const STUFF_ID_PREFIX = "stuff_";
36
46
  declare function stuffNodeId(digest: string): string;
37
47
  declare function isStuffNodeId(id: string): boolean;
@@ -628,6 +638,20 @@ type ToolbarOrientation = "horizontal" | "vertical";
628
638
  */
629
639
  declare function toolbarOrientation(position: ToolbarPosition): ToolbarOrientation;
630
640
  type ToolbarSide = "left" | "center" | "right";
641
+ /**
642
+ * How a graph is drawn: which nodes exist, what they say, what shapes they take
643
+ * and how they are spaced. Orthogonal to the theme, the direction and the spec's
644
+ * mode. `detailed` is the author's drawing (every pipe as a card with its slots,
645
+ * every value as a node) and the default; `simple` is a flowchart of the
646
+ * method's steps in the words its author declared, for a reader who designs or
647
+ * reviews the method without reading its code. The registry of what each style
648
+ * is called and supports is `GRAPH_STYLES` in `styles/graphStyles.ts`.
649
+ */
650
+ declare const GRAPH_STYLE: {
651
+ readonly DETAILED: "detailed";
652
+ readonly SIMPLE: "simple";
653
+ };
654
+ type GraphStyleId = (typeof GRAPH_STYLE)[keyof typeof GRAPH_STYLE];
631
655
  /**
632
656
  * Derive which edge the anchor hugs. The built-in `DetailPanel` overlays the
633
657
  * right edge, so only right-side anchors (`*-right`) need to dodge it — the
@@ -721,7 +745,17 @@ interface GraphConfig {
721
745
  * are reactive and persistence is the host's responsibility.
722
746
  */
723
747
  toolbarPosition?: ToolbarPosition;
748
+ /**
749
+ * The style the graph is drawn in (default `detailed`). The `graphStyle` prop
750
+ * on `GraphViewer` takes precedence over this; both are reactive, and a value
751
+ * that names no registered style falls back to the default rather than
752
+ * throwing, since it may come from an untyped source such as an editor
753
+ * setting. Persistence is the host's responsibility.
754
+ */
755
+ graphStyle?: GraphStyleId;
756
+ /** Space between sibling nodes, for the styles that keep the host's spacing (`detailed`). */
724
757
  nodesep?: number;
758
+ /** Space between ranks, for the styles that keep the host's spacing (`detailed`). */
725
759
  ranksep?: number;
726
760
  edgeType?: EdgeType;
727
761
  initialZoom?: number | null;
@@ -770,6 +804,64 @@ interface PipeCardPayload {
770
804
  /** Badge click handler (opens the validation panel), stamped alongside `validation`. */
771
805
  onValidationBadgeClick?: () => void;
772
806
  }
807
+ /**
808
+ * What a step of the simple style is, in a reader's terms rather than a pipe
809
+ * class's: each category has a plain word and an icon. `steps`, `parallel`,
810
+ * `decision` and `repeat` name a folded controller, drawn as one step, and
811
+ * `pick` a binding step, drawn only when it failed.
812
+ */
813
+ declare const STEP_CATEGORY: {
814
+ readonly AI: "ai";
815
+ readonly EXTRACT: "extract";
816
+ readonly IMAGE: "image";
817
+ readonly SEARCH: "search";
818
+ readonly CODE: "code";
819
+ readonly TEMPLATE: "template";
820
+ readonly DOCUMENT: "document";
821
+ readonly JUDGE: "judge";
822
+ readonly PLANNED: "planned";
823
+ readonly STEPS: "steps";
824
+ readonly PARALLEL: "parallel";
825
+ readonly DECISION: "decision";
826
+ readonly REPEAT: "repeat";
827
+ readonly PICK: "pick";
828
+ };
829
+ type StepCategory = (typeof STEP_CATEGORY)[keyof typeof STEP_CATEGORY];
830
+ /** What one node of the simple style says, discriminated on `kind`. All text is plain, never an identifier. */
831
+ type SimpleNodePayload = {
832
+ kind: "step";
833
+ /** The pipe's authored description, or its humanized code when it has none. */
834
+ title: string;
835
+ category: StepCategory;
836
+ status: PipeStatus;
837
+ graphMode?: GraphSpecMode;
838
+ /** "For each page" when the step runs once per item of a list. */
839
+ forEach?: string;
840
+ /** For a folded controller: how many steps it holds. */
841
+ innerStepCount?: number;
842
+ /** For a folded controller: unfolds it. */
843
+ onExpand?: (options?: FoldToggleOptions) => void;
844
+ } | {
845
+ kind: "input" | "output";
846
+ title: string;
847
+ /** The concept's plain name, when it says something the title does not. */
848
+ subtitle?: string;
849
+ /** Whether the value is a list, drawn as a stack. */
850
+ isList: boolean;
851
+ } | {
852
+ kind: "decision";
853
+ title: string;
854
+ status: PipeStatus;
855
+ graphMode?: GraphSpecMode;
856
+ } | {
857
+ kind: "frame";
858
+ /** "For each record". */
859
+ title: string;
860
+ status: PipeStatus;
861
+ graphMode?: GraphSpecMode;
862
+ /** Folds the frame into one step. */
863
+ onFold?: (options?: FoldToggleOptions) => void;
864
+ };
773
865
  type StuffRole = "input" | "output" | "combined";
774
866
  interface GraphNodeData extends Record<string, unknown> {
775
867
  labelDescriptor?: LabelDescriptor;
@@ -791,6 +883,17 @@ interface GraphNodeData extends Record<string, unknown> {
791
883
  validation?: NodeValidationSummary;
792
884
  /** Badge click handler (opens the validation panel), stamped alongside `validation`. */
793
885
  onValidationBadgeClick?: () => void;
886
+ /** What a node of the simple style says (see `SimpleNodePayload`); absent in the detailed style. */
887
+ simple?: SimpleNodePayload;
888
+ /**
889
+ * A size the node's producer fixed for layout, used instead of the estimate
890
+ * `estimateNodeDimensions` makes from the node's content. The simple style
891
+ * fixes the size of each of its nodes, because it knows its own text metrics.
892
+ */
893
+ layoutSize?: {
894
+ width: number;
895
+ height: number;
896
+ };
794
897
  }
795
898
  interface GraphNode {
796
899
  id: string;
@@ -825,6 +928,37 @@ interface GraphEdge {
825
928
  };
826
929
  _batchEdge?: boolean;
827
930
  _crossGroup?: boolean;
931
+ /**
932
+ * The size the edge's label is drawn at, background included, for a layout
933
+ * that reserves room for labels (`LayoutConfig.routeEdges`). Like a node's
934
+ * `layoutSize`, it is fixed by the style that knows its own text metrics.
935
+ */
936
+ labelSize?: {
937
+ width: number;
938
+ height: number;
939
+ };
940
+ /** The route the layout computed for the edge, when it was asked to (`LayoutConfig.routeEdges`). */
941
+ route?: EdgeRoute;
942
+ }
943
+ /** A point in flow coordinates. */
944
+ interface GraphPoint {
945
+ x: number;
946
+ y: number;
947
+ }
948
+ /**
949
+ * An edge's route as the layout computed it, in absolute flow coordinates:
950
+ * from the source's port to the target's, through every bend, and the box the
951
+ * layout placed its label in.
952
+ */
953
+ interface EdgeRoute {
954
+ points: GraphPoint[];
955
+ /** The label's box: its top-left corner and its size. Absent for an edge without a label. */
956
+ label?: {
957
+ x: number;
958
+ y: number;
959
+ width: number;
960
+ height: number;
961
+ };
828
962
  }
829
963
  interface GraphData {
830
964
  nodes: GraphNode[];
@@ -833,6 +967,23 @@ interface GraphData {
833
967
  interface LayoutConfig {
834
968
  nodesep?: number;
835
969
  ranksep?: number;
970
+ /**
971
+ * Route every edge around the nodes in its way, reserve room for the labels
972
+ * of the edges that carry a `labelSize`, and return each route on its edge
973
+ * (`GraphEdge.route`). Off by default: the detailed style draws its edges as
974
+ * free curves between their ends.
975
+ */
976
+ routeEdges?: boolean;
977
+ /**
978
+ * The room a group keeps around its children (a controller group, or a
979
+ * style's frame), before the deeper groups' scaling. Defaults to
980
+ * `CONTROLLER_PADDING_X`, `CONTROLLER_PADDING_TOP` and `CONTROLLER_PADDING_BOTTOM`.
981
+ */
982
+ groupPadding?: {
983
+ x: number;
984
+ top: number;
985
+ bottom: number;
986
+ };
836
987
  }
837
988
  declare const CONTROLLER_PADDING_X = 40;
838
989
  declare const CONTROLLER_PADDING_TOP = 48;
@@ -841,4 +992,4 @@ declare const ARROW_CLOSED_MARKER = "arrowclosed";
841
992
  declare function nodeWidth(n: GraphNode): number;
842
993
  declare function nodeHeight(n: GraphNode): number;
843
994
 
844
- export { type GraphSpecNodeTiming as $, ARROW_CLOSED_MARKER as A, BINDING_STEP_TYPE as B, type ConceptInfo as C, type DataflowAnalysis as D, CONTROLLER_PADDING_TOP as E, type FoldMode as F, type GraphTheme as G, CONTROLLER_PADDING_X as H, EDGE_TYPE as I, type EdgeType as J, FOLD_MODE as K, type LabelDescriptor as L, type FieldResolution as M, type NodeValidationSummary as N, GRAPH_DIRECTION as O, type PipeBlueprintUnion as P, GRAPH_SPEC_MODE as Q, GRAPH_THEME as R, GRAPH_THEME_MODE as S, type ToolbarPosition as T, type GraphSpecEdge as U, type ValidationIssue as V, type GraphSpecEdgeKind as W, type GraphSpecMeta as X, type GraphSpecModelUsage as Y, type GraphSpecNodeError as Z, type GraphSpecNodeIo as _, type PresenceMarker as a, type GraphSpecNodeUsage as a0, type GraphSpecUsage as a1, type JudgmentQuestionSpec as a2, KNOWN_PIPE_TYPES as a3, NODE_TYPE_CONTROLLER as a4, NODE_TYPE_PIPE_CARD as a5, NODE_TYPE_STUFF as a6, type NodeKind as a7, type PipeBatchBlueprint as a8, type PipeBlueprintBase as a9, type ToolbarSide as aA, VALIDATION_STATE as aB, graphSpecMode as aC, isBindingNode as aD, isBindingStepSpec as aE, isDryGraphSpec as aF, isPluralMultiplicity as aG, isStaticGraphSpec as aH, isStuffNodeId as aI, multiplicitySuffix as aJ, nodeHeight as aK, nodeWidth as aL, stuffDigestFromId as aM, stuffNodeId as aN, toolbarOrientation as aO, toolbarSide as aP, type PipeComposeBlueprint as aa, type PipeComposeConstructBlueprint as ab, type PipeComposeConstructField as ac, type PipeConditionBlueprint as ad, type PipeDocGenBlueprint as ae, type PipeExtractBlueprint as af, type PipeFuncBlueprint as ag, type PipeImgGenBlueprint as ah, type PipeJudgeBlueprint as ai, type PipeLLMBlueprint as aj, type PipeParallelBlueprint as ak, type PipeSearchBlueprint as al, type PipeSequenceBlueprint as am, type PipeSignatureBlueprint as an, type PipeStructureBlueprint as ao, type PipeType as ap, STUFF_ID_PREFIX as aq, type SequenceStepSpec as ar, type StuffMultiplicity as as, type StuffRegistryEntry as at, type StuffRole as au, type StuffSpecInfo as av, type SubPipeSpec as aw, TOOLBAR_POSITION as ax, type TemplateBlueprint as ay, type ToolbarOrientation as az, type GraphSpec as b, type GraphNodeData as c, type GraphEdge as d, type GraphNode as e, type GraphConfig as f, type GraphDirection as g, type GraphThemeMode as h, type PipeStatus as i, type GraphSpecNodeIoItem as j, type ValidationState as k, type PipeControllerType as l, type FoldToggleOptions as m, type NodePipeType as n, type GraphSpecMode as o, type GraphSpecNode as p, type PipeOperatorType as q, type CardNode as r, type PipeCallNode as s, type GraphData as t, type PipeCardPayload as u, type LayoutConfig as v, type BindingNode as w, type BindingStepSpec as x, type BindingStepType as y, CONTROLLER_PADDING_BOTTOM as z };
995
+ export { type GraphSpecEdge as $, ARROW_CLOSED_MARKER as A, BINDING_STEP_TYPE as B, type ConceptInfo as C, type DataflowAnalysis as D, type EdgeRoute as E, type FoldMode as F, type GraphTheme as G, type BindingStepType as H, CONTROLLER_PADDING_BOTTOM as I, CONTROLLER_PADDING_TOP as J, CONTROLLER_PADDING_X as K, type LabelDescriptor as L, EDGE_TYPE as M, type NodeValidationSummary as N, EDGE_TYPE_ROUTED as O, type PipeBlueprintUnion as P, type EdgeType as Q, FOLD_MODE as R, type StepCategory as S, type ToolbarPosition as T, type FieldResolution as U, type ValidationIssue as V, GRAPH_DIRECTION as W, GRAPH_SPEC_MODE as X, GRAPH_STYLE as Y, GRAPH_THEME as Z, GRAPH_THEME_MODE as _, type PresenceMarker as a, toolbarSide as a$, type GraphSpecEdgeKind as a0, type GraphSpecMeta as a1, type GraphSpecModelUsage as a2, type GraphSpecNodeError as a3, type GraphSpecNodeIo as a4, type GraphSpecNodeTiming as a5, type GraphSpecNodeUsage as a6, type GraphSpecUsage as a7, type JudgmentQuestionSpec as a8, KNOWN_PIPE_TYPES as a9, STEP_CATEGORY as aA, STUFF_ID_PREFIX as aB, type SequenceStepSpec as aC, type SimpleNodePayload as aD, type StuffMultiplicity as aE, type StuffRegistryEntry as aF, type StuffRole as aG, type StuffSpecInfo as aH, type SubPipeSpec as aI, TOOLBAR_POSITION as aJ, type TemplateBlueprint as aK, type ToolbarOrientation as aL, type ToolbarSide as aM, VALIDATION_STATE as aN, graphSpecMode as aO, isBindingNode as aP, isBindingStepSpec as aQ, isDryGraphSpec as aR, isPluralMultiplicity as aS, isStaticGraphSpec as aT, isStuffNodeId as aU, multiplicitySuffix as aV, nodeHeight as aW, nodeWidth as aX, stuffDigestFromId as aY, stuffNodeId as aZ, toolbarOrientation as a_, NODE_TYPE_CONTROLLER as aa, NODE_TYPE_PIPE_CARD as ab, NODE_TYPE_SIMPLE_DECISION as ac, NODE_TYPE_SIMPLE_FRAME as ad, NODE_TYPE_SIMPLE_STEP as ae, NODE_TYPE_SIMPLE_TERMINAL as af, NODE_TYPE_STUFF as ag, type NodeKind as ah, type PipeBatchBlueprint as ai, type PipeBlueprintBase as aj, type PipeComposeBlueprint as ak, type PipeComposeConstructBlueprint as al, type PipeComposeConstructField as am, type PipeConditionBlueprint as an, type PipeDocGenBlueprint as ao, type PipeExtractBlueprint as ap, type PipeFuncBlueprint as aq, type PipeImgGenBlueprint as ar, type PipeJudgeBlueprint as as, type PipeLLMBlueprint as at, type PipeParallelBlueprint as au, type PipeSearchBlueprint as av, type PipeSequenceBlueprint as aw, type PipeSignatureBlueprint as ax, type PipeStructureBlueprint as ay, type PipeType as az, type GraphSpec as b, type GraphNodeData as c, type GraphEdge as d, type GraphNode as e, type GraphConfig as f, type GraphDirection as g, type GraphThemeMode as h, type GraphStyleId as i, type PipeStatus as j, type GraphSpecNodeIoItem as k, type ValidationState as l, type PipeControllerType as m, type FoldToggleOptions as n, type NodePipeType as o, type GraphSpecMode as p, type GraphSpecNode as q, type PipeOperatorType as r, type CardNode as s, type PipeCallNode as t, type GraphData as u, type PipeCardPayload as v, type LayoutConfig as w, type GraphPoint as x, type BindingNode as y, type BindingStepSpec as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipelex/mthds-ui",
3
- "version": "0.29.1",
3
+ "version": "0.30.1",
4
4
  "description": "Shared graph rendering logic for MTHDS method visualization",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,6 +35,7 @@
35
35
  "./graph/react/graph-core.css": "./dist/graph/react/graph-core.css",
36
36
  "./graph/react/detail/DetailPanel.css": "./dist/graph/react/detail/DetailPanel.css",
37
37
  "./graph/react/viewer/GraphToolbar.css": "./dist/graph/react/viewer/GraphToolbar.css",
38
+ "./graph/react/styles/simple/SimpleStyle.css": "./dist/graph/react/styles/simple/SimpleStyle.css",
38
39
  "./form": {
39
40
  "import": "./dist/form/index.js",
40
41
  "types": "./dist/form/index.d.ts"
@@ -78,7 +79,7 @@
78
79
  "build-storybook": "storybook build"
79
80
  },
80
81
  "dependencies": {
81
- "@pipelex/mthds-form": "^0.14.0",
82
+ "@pipelex/mthds-form": "^0.14.1",
82
83
  "@xyflow/react": "^12",
83
84
  "elkjs": "^0.11.1",
84
85
  "smol-toml": "^1.7.0"
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/graph/types.ts"],"sourcesContent":["// ─── Pipe type taxonomy ─────────────────────────────────────────────────────\n// Operators perform work; controllers orchestrate other pipes.\n\nexport type PipeOperatorType =\n | \"PipeLLM\"\n | \"PipeExtract\"\n | \"PipeCompose\"\n | \"PipeImgGen\"\n | \"PipeSearch\"\n | \"PipeFunc\"\n | \"PipeStructure\" // turns Text into a structured concept via an LLM\n | \"PipeJudge\" // asks a judging model one closed question, answered as a verdict native\n | \"PipeDocGen\" // lays its inputs out as a stored document file, calling no AI model\n | \"PipeSignature\"; // contract-only stub — emitted under `--allow-signatures`\n\nexport type PipeControllerType = \"PipeSequence\" | \"PipeParallel\" | \"PipeCondition\" | \"PipeBatch\";\n\nexport type PipeType = PipeOperatorType | PipeControllerType;\n\n// Presence map over the full `PipeType` union — typed as `Record<PipeType, true>`\n// so adding a pipe class to the union without listing it here is a compile\n// error. `KNOWN_PIPE_TYPES` derives its keys from this map, keeping the runtime\n// check the validator performs in sync with the type.\nconst PIPE_TYPE_PRESENCE: Record<PipeType, true> = {\n PipeLLM: true,\n PipeExtract: true,\n PipeCompose: true,\n PipeImgGen: true,\n PipeSearch: true,\n PipeFunc: true,\n PipeStructure: true,\n PipeJudge: true,\n PipeDocGen: true,\n PipeSignature: true,\n PipeSequence: true,\n PipeParallel: true,\n PipeCondition: true,\n PipeBatch: true,\n};\n\n/** Every pipe class name pipelex emits as `pipe_type`. Used by validateGraphSpec. */\nexport const KNOWN_PIPE_TYPES: ReadonlySet<string> = new Set(Object.keys(PIPE_TYPE_PRESENCE));\n\n/**\n * The `pipe_type` of a binding node. A PipeSequence's binding step\n * (`{ from = \"invoice.total\", result = \"total_amount\" }`) runs no pipe, so the\n * class it carries is not a pipe class: pipelex writes `BindingStep` beside the\n * `binding` node kind, and the node's `pipe_code` is the step's `from` path.\n */\nexport const BINDING_STEP_TYPE = \"BindingStep\" as const;\n\nexport type BindingStepType = typeof BINDING_STEP_TYPE;\n\n/** What a GraphSpec node's `pipe_type` names: a pipe class, or the binding step that runs none. */\nexport type NodePipeType = PipeType | BindingStepType;\n\nexport type PipeStatus = \"succeeded\" | \"failed\" | \"running\" | \"scheduled\" | \"skipped\" | \"canceled\";\n\n// ─── GraphSpec mode contract ────────────────────────────────────────────────\n\nexport const GRAPH_SPEC_MODE = {\n DRY: \"dry\",\n LIVE: \"live\",\n STATIC: \"static\",\n} as const;\n\nexport type GraphSpecMode = (typeof GRAPH_SPEC_MODE)[keyof typeof GRAPH_SPEC_MODE];\n\nexport interface GraphSpecMeta {\n format: \"mthds\";\n /**\n * Missing mode is a legacy runtime graph. Static UI behavior must key only on\n * `mode === \"static\"` and never infer it from absent runtime fields.\n */\n mode?: GraphSpecMode;\n [key: string]: unknown;\n}\n\n// ─── Node type constants ────────────────────────────────────────────────────\n// Used by graphBuilders and consumed by ReactFlow custom node registration.\n\nexport const NODE_TYPE_PIPE_CARD = \"pipeCard\" as const;\nexport const NODE_TYPE_STUFF = \"default\" as const;\nexport const NODE_TYPE_CONTROLLER = \"controllerGroup\" as const;\n\n// ─── Stuff node ID helpers ──────────────────────────────────────────────────\n// Stuff (data) nodes use a \"stuff_<digest>\" convention throughout the graph.\n\nexport const STUFF_ID_PREFIX = \"stuff_\";\n\nexport function stuffNodeId(digest: string): string {\n return STUFF_ID_PREFIX + digest;\n}\n\nexport function isStuffNodeId(id: string): boolean {\n return id.startsWith(STUFF_ID_PREFIX);\n}\n\nexport function stuffDigestFromId(id: string): string {\n return id.slice(STUFF_ID_PREFIX.length);\n}\n\n// ─── Stuff multiplicity ─────────────────────────────────────────────────────\n// How many values a stuff holds, in the encoding the pipe registry already uses\n// for `StuffSpecInfo.multiplicity`: `true` for a variable-length list (`Code[]`),\n// an integer for a fixed count (`Code[N]`), `null`/`false`/absent for a single\n// value. A count of exactly one is single throughout the MTHDS standard —\n// `Code[1]` is a way of writing `Code`, never a one-item list — so it reads as\n// single here too.\n\nexport type StuffMultiplicity = number | boolean | null;\n\n/** Whether a multiplicity declares a list: variable-length, or a fixed count of at least two. */\nexport function isPluralMultiplicity(multiplicity: StuffMultiplicity | undefined): boolean {\n return multiplicity === true || (Number.isInteger(multiplicity) && (multiplicity as number) > 1);\n}\n\n/** The authored marker for a multiplicity — `\"[]\"`, `\"[N]\"`, or `\"\"` for a single value. */\nexport function multiplicitySuffix(multiplicity: StuffMultiplicity | undefined): string {\n if (!isPluralMultiplicity(multiplicity)) return \"\";\n return multiplicity === true ? \"[]\" : `[${multiplicity as number}]`;\n}\n\n// ─── GraphSpec types (from pipelex-agent --view output) ─────────────────────\n\nexport interface GraphSpecNodeIoItem {\n name: string;\n digest?: string;\n /** The bare concept code or ref, never carrying a multiplicity marker — that is `multiplicity`. */\n concept?: string;\n /**\n * How many values the stuff holds (see `StuffMultiplicity`). Emitted only on a\n * plural stuff; absent means single. The static builder writes it, and a\n * runtime graph carries it once its producer emits it, so a renderer must read\n * absence as single rather than as unknown.\n */\n multiplicity?: StuffMultiplicity;\n content_type?: string;\n preview?: string;\n size?: number;\n data?: unknown;\n data_text?: string;\n data_html?: string;\n extra?: Record<string, unknown>;\n}\n\nexport interface GraphSpecNodeIo {\n inputs: GraphSpecNodeIoItem[];\n outputs: GraphSpecNodeIoItem[];\n}\n\nexport type NodeKind =\n | \"pipe_call\"\n | \"controller\"\n | \"operator\"\n | \"input\"\n | \"output\"\n | \"artifact\"\n | \"error\"\n // A PipeSequence binding step: it runs no pipe, and produces the stuff it binds from a path in working memory.\n | \"binding\";\n\nexport interface GraphSpecNodeTiming {\n started_at: string;\n ended_at: string;\n duration: number;\n}\n\nexport interface GraphSpecNodeError {\n error_type: string;\n message: string;\n stack?: string;\n}\n\n/**\n * Inference usage attributed to one graph node — the mirror of pipelex's\n * `NodeUsageSpec`. Read the invariants before rendering any of these numbers;\n * getting them wrong means showing a wrong dollar figure, which is worse than\n * showing none.\n *\n * 1. `node.usage === undefined` is ALL-OR-NOTHING across a spec: it means the\n * run reported no usage at all (collection off, or zero inference calls).\n * As soon as one call was reported, EVERY node carries a usage object —\n * zeroed for controllers, PipeFunc, PipeCompose, skipped and lifted pipes.\n * So it never means \"this particular node was not measured\".\n * 2. `cost === null` ⟺ `rated_inference_calls === 0`. \"Made no call\" and \"made\n * only unrated calls\" both land here; `inference_calls` tells them apart.\n * Never render `null` as `$0.00` — a dry run is entirely unrated.\n * 3. `inference_calls > rated_inference_calls > 0` means `cost` is a LOWER\n * BOUND, not a total. It must be marked (a leading \"≥\").\n * 4. `total_tokens` is input_joined + output. Do NOT sum\n * `nb_tokens_by_category`: `input_cached` is a subset of `input`, not\n * additive, so summing double-counts.\n *\n * The `subtree_*` half covers the node plus every descendant, rolled up by\n * pipelex so no consumer re-derives it. Controllers have only a subtree.\n */\n/**\n * What one inference model actually did for a node — the graph's only record of\n * the model that RAN.\n *\n * Everything else in a GraphSpec that names a model records what was *asked for*:\n * `execution_data.resolved_model` is the handle the pipe resolved to (often still\n * an alias like `@default-premium`, since aliases resolve at inference time), and\n * the blueprint holds the authored choice (`$writing-factual`). Only this survives\n * alias resolution, deck defaults, and any fallback or retry.\n *\n * `cost` follows the same rule as the node's: `null` iff no call to this model was\n * priced.\n */\nexport interface GraphSpecModelUsage {\n inference_model_name: string;\n inference_model_id: string;\n /**\n * Kind of inference: `\"llm\"`, `\"img_gen\"`, `\"extract\"`, `\"search\"`. Open set.\n *\n * The discriminator to check before displaying token counts: everything except\n * `\"llm\"` is billed PER REQUEST, and pipelex encodes that price by putting\n * `1_000_000` in each token category (rates are per-million), so those \"tokens\"\n * are a scaled request counter rather than a measurement.\n */\n model_type: string;\n inference_calls: number;\n rated_inference_calls: number;\n cost: number | null;\n}\n\nexport interface GraphSpecNodeUsage {\n inference_calls: number;\n rated_inference_calls: number;\n nb_tokens_by_category: Record<string, number>;\n total_tokens: number;\n cost: number | null;\n /** Components of `cost`; `input` is the joined input cost (non-cached + cached). */\n cost_input: number | null;\n cost_output: number | null;\n /**\n * The models that actually ran, most-used first. A list because one node\n * routinely uses more than one — a PipeLLM's text pass and its object pass\n * resolve separately — so `by_model[0]` is the dominant model, not \"the\" model.\n */\n by_model: GraphSpecModelUsage[];\n subtree_inference_calls: number;\n subtree_rated_inference_calls: number;\n subtree_nb_tokens_by_category: Record<string, number>;\n subtree_total_tokens: number;\n subtree_cost: number | null;\n subtree_cost_input: number | null;\n subtree_cost_output: number | null;\n /** Every model this node's whole branch used. */\n subtree_by_model: GraphSpecModelUsage[];\n}\n\n/**\n * Run-level usage for a whole spec — the mirror of pipelex's `GraphUsageSpec`.\n *\n * `total` covers every call the run reported, attributed or not. `unattributed`\n * is the part that named no live node; on a healthy run it is zeroed, and a\n * non-zero one is worth surfacing rather than hiding. Neither has a subtree\n * distinct from itself, so their `subtree_*` fields repeat their own.\n */\nexport interface GraphSpecUsage {\n total: GraphSpecNodeUsage;\n unattributed: GraphSpecNodeUsage;\n}\n\nexport interface GraphSpecNode {\n id: string;\n kind: NodeKind;\n pipe_code?: string;\n pipe_type: NodePipeType;\n description?: string;\n domain_code?: string;\n status: PipeStatus;\n timing?: GraphSpecNodeTiming;\n io: GraphSpecNodeIo;\n error?: GraphSpecNodeError;\n tags?: Record<string, string>;\n metrics?: Record<string, number>;\n usage?: GraphSpecNodeUsage;\n execution_data?: Record<string, unknown>;\n}\n\n/**\n * A pipe-call node: a controller or an operator. Narrowed from `GraphSpecNode`\n * for code paths that only ever render pipe-call nodes, where `pipe_code` is\n * guaranteed (validateGraphSpec enforces this).\n */\nexport type PipeCallNode = GraphSpecNode & {\n kind: \"controller\" | \"operator\";\n pipe_code: string;\n pipe_type: PipeType;\n};\n\n/**\n * A binding node: a PipeSequence's binding step, which runs no pipe. Its\n * `pipe_code` is the step's `from` path (`invoice.total`), its one input the\n * root the path starts from, and its one output the stuff it binds, under the\n * step's `result`. A binding that bound nothing, because the path held nothing,\n * has no output; one whose root was absent has no input; a failed one may have\n * neither. The graph draws it all the same. It is never a controller: nothing\n * is contained in it.\n */\nexport type BindingNode = GraphSpecNode & {\n kind: \"binding\";\n pipe_code: string;\n pipe_type: BindingStepType;\n};\n\n/** A node the graph draws as a card: a pipe-call node or a binding node. */\nexport type CardNode = PipeCallNode | BindingNode;\n\n/** Whether a node is a binding step's node. */\nexport function isBindingNode(node: Pick<GraphSpecNode, \"kind\">): boolean {\n return node.kind === \"binding\";\n}\n\nexport type GraphSpecEdgeKind =\n | \"contains\"\n | \"data\"\n | \"control\"\n | \"selected_outcome\"\n | \"batch_item\"\n | \"batch_aggregate\"\n | \"parallel_combine\";\n\nexport interface GraphSpecEdge {\n id: string;\n source: string;\n target: string;\n kind: GraphSpecEdgeKind;\n label?: string;\n source_stuff_digest?: string;\n target_stuff_digest?: string;\n meta?: Record<string, unknown>;\n}\n\n// ─── Concept and Pipe registry types ───────────────────────────────────────\n// Serialized from Python Concept and PipeAbstract instances via model_dump().\n//\n// Every field pipelex writes as `T | null` is declared `field?: T | null` here,\n// optional as well as nullable. Unlike `usage`, the registries cross the boundary\n// unvalidated, and a host that drops `null` values from the JSON it relays (ChatGPT\n// does, on the way to an MCP App view) delivers such a field with the key absent.\n// The optional marker is what makes the compiler refuse a strict `!== null` test\n// followed by a dereference.\n\n/**\n * A concept as the registry serializes it. A field that can be `null` is also\n * declared optional, because a host that drops `null` values delivers it absent:\n * test it with `!= null`, optional chaining or truthiness.\n */\nexport interface ConceptInfo {\n code: string;\n domain_code: string;\n description: string;\n structure_class_name: string;\n refines?: string | null;\n json_schema?: Record<string, unknown>;\n}\n\n/**\n * Presence of a value in a declared slot, mirroring the runtime's\n * `PresenceMarker`: `plain` (always present), `optional` (the `?` suffix — the\n * slot may legitimately hold no value), `force` (the `!` suffix — a use-site\n * assertion that the value must be present). The runtime defaults the field to\n * `plain`, so an absent `presence` reads as plain.\n */\nexport type PresenceMarker = \"plain\" | \"optional\" | \"force\";\n\nexport interface StuffSpecInfo {\n concept: ConceptInfo;\n multiplicity?: StuffMultiplicity;\n presence?: PresenceMarker;\n}\n\n// ─── Template blueprint (shared by LLM prompts, Search, Compose, ImgGen) ───\n\nexport interface TemplateBlueprint {\n template: string;\n templating_style?: string | null;\n category: string;\n extra_context?: Record<string, unknown> | null;\n}\n\n// ─── Sub-pipe (used by Sequence, Parallel, Batch) ──────────────────────\n\nexport interface SubPipeSpec {\n pipe_code: string;\n output_name?: string | null;\n output_multiplicity?: string | number | boolean | null;\n batch_params?: { input_list_stuff_name: string; input_item_stuff_name: string } | null;\n}\n\n/**\n * A PipeSequence binding step, as the runtime holds it and the registry\n * serializes it: the value at `from_path` stored under `output_name`. A step\n * whose `batch_over` is a dotted path is held as such a binding, under a\n * private `_bound_…` name and with `is_dotted_batch_over` set, followed by the\n * same pipe step batching over that name.\n */\nexport interface BindingStepSpec {\n from_path: string;\n output_name: string;\n is_dotted_batch_over?: boolean;\n}\n\n/** One step of a PipeSequence: a pipe step, or a binding step. */\nexport type SequenceStepSpec = SubPipeSpec | BindingStepSpec;\n\n/** Whether a sequence step is a binding step, which carries a `from_path` and runs no pipe. */\nexport function isBindingStepSpec(step: SequenceStepSpec): step is BindingStepSpec {\n return \"from_path\" in step;\n}\n\n// ─── PipeAbstract base (common to all pipe types) ──────────────────────\n\nexport interface PipeBlueprintBase {\n type: PipeType;\n pipe_category: \"PipeOperator\" | \"PipeController\";\n code: string;\n domain_code: string;\n description: string;\n inputs: Record<string, StuffSpecInfo>;\n output: StuffSpecInfo;\n}\n\n// ─── Operator blueprints ───────────────────────────────────────────────\n\nexport interface PipeLLMBlueprint extends PipeBlueprintBase {\n type: \"PipeLLM\";\n llm_prompt_spec: {\n system_prompt_blueprint?: TemplateBlueprint | null;\n prompt_blueprint?: TemplateBlueprint | null;\n user_image_references?: unknown[] | null;\n user_document_references?: unknown[] | null;\n system_image_references?: unknown[] | null;\n system_document_references?: unknown[] | null;\n };\n llm_choices?: { for_text?: string | null; for_object?: string | null } | null;\n structuring_method?: string | null;\n output_multiplicity?: string | number | null;\n}\n\nexport interface PipeImgGenBlueprint extends PipeBlueprintBase {\n type: \"PipeImgGen\";\n img_gen_prompt_blueprint: {\n prompt_blueprint?: TemplateBlueprint | null;\n negative_prompt_blueprint?: TemplateBlueprint | null;\n image_references?: unknown[] | null;\n };\n img_gen_choice?: string | null;\n aspect_ratio?: string | null;\n is_raw?: boolean | null;\n seed?: number | string | null;\n background?: string | null;\n output_format?: string | null;\n output_multiplicity: number;\n}\n\n/**\n * A single field in a PipeCompose construct blueprint. Mirrors the\n * `ConstructFieldBlueprint` Pydantic model in pipelex. Exactly one of\n * `fixed_value` / `from_path` / `template` / `nested` is populated, matching\n * the `method` discriminator.\n *\n * - `fixed` → `fixed_value` holds a literal (string, number, bool, list)\n * - `from_var` → `from_path` holds a dotted path into working memory,\n * optionally with a `list_to_dict_keyed_by` modifier\n * - `template` → `template` holds a Jinja2 template string (per-field)\n * - `nested` → `nested` holds a recursive construct blueprint for building\n * nested structured content\n */\nexport interface PipeComposeConstructField {\n method: \"from_var\" | \"fixed\" | \"template\" | \"nested\";\n fixed_value?: unknown;\n from_path?: string | null;\n template?: string | null;\n nested?: PipeComposeConstructBlueprint | null;\n list_to_dict_keyed_by?: string | null;\n}\n\n/**\n * A PipeCompose construct blueprint, parsed from `[pipe.X.construct]` in MTHDS.\n * Mirrors the `ConstructBlueprint` Pydantic model in pipelex.\n */\nexport interface PipeComposeConstructBlueprint {\n fields: Record<string, PipeComposeConstructField>;\n}\n\n/**\n * Per-field record of how each field was built at runtime, emitted by\n * `PipeCompose._run_construct_mode` in pipelex via `execution_data.fields`.\n *\n * - `method` → which composition method was used (mirrors `ConstructFieldMethod`)\n * - `rendered` → present only for `template` fields, holds the Jinja2 output\n *\n * Nested fields record only their method; their sub-fields are not surfaced.\n */\nexport interface FieldResolution {\n method: \"from_var\" | \"fixed\" | \"template\" | \"nested\";\n rendered?: string;\n}\n\nexport interface PipeComposeBlueprint extends PipeBlueprintBase {\n type: \"PipeCompose\";\n /** Legacy monolithic template. Null when construct_blueprint is used instead. */\n template?: string | null;\n templating_style?: string | null;\n category: string;\n extra_context?: Record<string, unknown> | null;\n /** Field-level construct form (e.g. `[pipe.X.construct]` in MTHDS). */\n construct_blueprint?: PipeComposeConstructBlueprint | null;\n}\n\nexport interface PipeExtractBlueprint extends PipeBlueprintBase {\n type: \"PipeExtract\";\n extract_choice?: string | null;\n should_caption_images: boolean;\n max_page_images?: number | null;\n should_include_page_views: boolean;\n page_views_dpi?: number | null;\n render_js?: boolean | null;\n include_raw_html?: boolean | null;\n image_stuff_name?: string | null;\n document_stuff_name?: string | null;\n}\n\nexport interface PipeSearchBlueprint extends PipeBlueprintBase {\n type: \"PipeSearch\";\n search_choice?: string | null;\n prompt_blueprint: TemplateBlueprint;\n include_images_override?: boolean | null;\n max_results_override?: number | null;\n from_date?: string | null;\n to_date?: string | null;\n include_domains?: string[] | null;\n exclude_domains?: string[] | null;\n is_structured_output: boolean;\n}\n\nexport interface PipeFuncBlueprint extends PipeBlueprintBase {\n type: \"PipeFunc\";\n}\n\n/**\n * A PipeStructure: an LLM-backed operator that turns a single Text-compatible\n * input into a structured concept. Serialized from the runtime `PipeStructure`,\n * so the registry entry carries the runtime fields below alongside the base.\n */\nexport interface PipeStructureBlueprint extends PipeBlueprintBase {\n type: \"PipeStructure\";\n /**\n * LLM used to structure the text. A string model handle, an inline LLM\n * setting object, or null (→ resolved from the model deck's `for_object`\n * default at run time).\n */\n llm_choice?: string | Record<string, unknown> | null;\n /** The single Text-compatible input variable the structuring reads. */\n text_input_name: string;\n /** `true` → let the LLM decide the count, a number → fixed count, null → single object. */\n output_multiplicity?: boolean | number | null;\n}\n\n/**\n * The question a PipeJudge asks, as the runtime serializes it. Its `kind`\n * decides the verdict native the judge answers with: `yes_no` → `YesNo`,\n * `choice` → `Choice`, `rating` → `Rating`. `instructions` is the authored\n * question, still a template.\n */\nexport interface JudgmentQuestionSpec {\n kind: \"yes_no\" | \"choice\" | \"rating\";\n instructions: string;\n yes_criterion?: string | null;\n no_criterion?: string | null;\n options?: Record<string, string | null> | null;\n levels?: string[] | null;\n}\n\n/**\n * A PipeJudge: asks a judging model one closed question about its inputs and\n * stores the verdict native it answers with. Serialized from the runtime\n * `PipeJudge`.\n */\nexport interface PipeJudgeBlueprint extends PipeBlueprintBase {\n type: \"PipeJudge\";\n /** The judging model, a handle or an inline setting, or null for the deck's default. */\n judgment_choice?: string | Record<string, unknown> | null;\n judgment_question: JudgmentQuestionSpec;\n /** The probability a yes/no verdict must reach to read as yes, when the pipe declares one. */\n threshold?: number | null;\n}\n\n/**\n * A PipeDocGen: lays its inputs out as a stored document file, in the declared\n * format, and calls no AI model. Serialized from the runtime `PipeDocGen`.\n */\nexport interface PipeDocGenBlueprint extends PipeBlueprintBase {\n type: \"PipeDocGen\";\n /** `pdf`, `html`, `xlsx` or `docx`. */\n doc_gen_format: string;\n /** The engine the step names, or null for the deck's default. */\n doc_gen_choice?: string | Record<string, unknown> | null;\n /** The HTML template of a pdf step, inline or read from its template file. */\n template?: string | null;\n /** The template file as the method names it. */\n template_file?: string | null;\n /** The file name the document is stored under, a template. */\n filename?: string | null;\n}\n\n/**\n * A contract-only pipe: declares inputs + output but has no implementation.\n * Emitted under `--allow-signatures` so an in-progress bundle still validates\n * (dry-run mocks the declared output) before every referenced pipe is built.\n */\nexport interface PipeSignatureBlueprint extends Omit<PipeBlueprintBase, \"pipe_category\"> {\n type: \"PipeSignature\";\n /**\n * Signatures sit outside the executable taxonomy, so pipelex serializes\n * `pipe_category: null` (present, not omitted) — unlike operator/controller\n * blueprints which carry \"PipeOperator\" / \"PipeController\". Optional all the\n * same, since a host that drops `null` values delivers it absent.\n */\n pipe_category?: null;\n /** Intended downstream pipe type once implemented — an optional hint. */\n signature_for?: PipeType | null;\n}\n\n// ─── Controller blueprints ─────────────────────────────────────────────\n\nexport interface PipeSequenceBlueprint extends PipeBlueprintBase {\n type: \"PipeSequence\";\n /** Pipe steps and binding steps, in order — a dotted `batch_over` held as a binding then a batch. */\n sequential_sub_pipes: SequenceStepSpec[];\n}\n\nexport interface PipeParallelBlueprint extends PipeBlueprintBase {\n type: \"PipeParallel\";\n parallel_sub_pipes: SubPipeSpec[];\n add_each_output: boolean;\n /**\n * Removed from the language in pipelex 0.41 (a parallel always combines now),\n * so 0.41 registry dumps omit the key entirely. Optional rather than deleted\n * because the static builder still honors it as a legacy authoring key when\n * naming the combined stuff, which L-260929-1e09e0 removes.\n */\n combined_output?: string | null;\n}\n\nexport interface PipeConditionBlueprint extends PipeBlueprintBase {\n type: \"PipeCondition\";\n expression: string;\n outcome_map: Record<string, string>;\n default_outcome: string;\n add_alias_from_expression_to?: string | null;\n}\n\nexport interface PipeBatchBlueprint extends PipeBlueprintBase {\n type: \"PipeBatch\";\n branch_pipe_code: string;\n batch_params: { input_list_stuff_name: string; input_item_stuff_name: string };\n}\n\n/**\n * A pipe as the registry serializes it, discriminated on `type`. A field that can\n * be `null` is also declared optional, because a host that drops `null` values\n * delivers it absent: test it with `!= null`, optional chaining or truthiness.\n */\nexport type PipeBlueprintUnion =\n | PipeLLMBlueprint\n | PipeImgGenBlueprint\n | PipeComposeBlueprint\n | PipeExtractBlueprint\n | PipeSearchBlueprint\n | PipeFuncBlueprint\n | PipeStructureBlueprint\n | PipeJudgeBlueprint\n | PipeDocGenBlueprint\n | PipeSignatureBlueprint\n | PipeSequenceBlueprint\n | PipeParallelBlueprint\n | PipeConditionBlueprint\n | PipeBatchBlueprint;\n\n// ─── GraphSpec top-level ───────────────────────────────────────────────────\n\nexport interface GraphSpec {\n graph_id?: string;\n created_at?: string;\n pipeline_ref?: { domain?: string; main_pipe?: string; entrypoint?: string };\n nodes: GraphSpecNode[];\n edges: GraphSpecEdge[];\n usage?: GraphSpecUsage;\n meta?: GraphSpecMeta;\n pipe_registry?: Record<string, PipeBlueprintUnion>;\n concept_registry?: Record<string, ConceptInfo>;\n}\n\nexport function graphSpecMode(\n spec: Pick<GraphSpec, \"meta\"> | null | undefined,\n): GraphSpecMode | undefined {\n return spec?.meta?.mode;\n}\n\nexport function isStaticGraphSpec(spec: Pick<GraphSpec, \"meta\"> | null | undefined): boolean {\n return graphSpecMode(spec) === GRAPH_SPEC_MODE.STATIC;\n}\n\nexport function isDryGraphSpec(spec: Pick<GraphSpec, \"meta\"> | null | undefined): boolean {\n return graphSpecMode(spec) === GRAPH_SPEC_MODE.DRY;\n}\n\n// ─── Dataflow analysis result ───────────────────────────────────────────────\n\n/** One stuff as the dataflow analysis registers it, from the first io item naming its digest. */\nexport interface StuffRegistryEntry {\n name: string;\n concept?: string;\n multiplicity?: StuffMultiplicity;\n contentType?: string;\n}\n\nexport interface DataflowAnalysis {\n readonly stuffRegistry: Readonly<Record<string, StuffRegistryEntry>>;\n /**\n * Every operator that writes each stuff. Usually one; a condition's output has one per\n * outcome, because every outcome writes the condition's one slot and only one of them runs.\n */\n readonly stuffProducers: Readonly<Record<string, readonly string[]>>;\n readonly stuffConsumers: Readonly<Record<string, readonly string[]>>;\n readonly controllerNodeIds: ReadonlySet<string>;\n readonly childNodeIds: ReadonlySet<string>;\n readonly containmentTree: Readonly<Record<string, readonly string[]>>;\n}\n\n// ─── Graph configuration ────────────────────────────────────────────────────\n\nexport const GRAPH_DIRECTION = {\n TB: \"TB\",\n BT: \"BT\",\n LR: \"LR\",\n RL: \"RL\",\n} as const;\n\nexport type GraphDirection = (typeof GRAPH_DIRECTION)[keyof typeof GRAPH_DIRECTION];\n\nexport const EDGE_TYPE = {\n /** Bezier curve — ReactFlow v12 renamed this type from \"bezier\" to \"default\". */\n DEFAULT: \"default\",\n STEP: \"step\",\n STRAIGHT: \"straight\",\n SMOOTH_STEP: \"smoothstep\",\n} as const;\n\nexport type EdgeType = (typeof EDGE_TYPE)[keyof typeof EDGE_TYPE];\n\nexport const FOLD_MODE = {\n /** Every pipe controller folded into a single pipe card. */\n FOLDED: \"folded\",\n /** Every pipe controller expanded as a group wrapper. */\n EXPANDED: \"expanded\",\n /** Renderer decides — reserved for future heuristics; currently behaves like EXPANDED. */\n AUTO: \"auto\",\n} as const;\n\nexport type FoldMode = (typeof FOLD_MODE)[keyof typeof FOLD_MODE];\n\n/**\n * The *resolved* binary theme — the value that actually drives the palette and\n * the container class. `getPaletteForTheme` takes this. `system` is never a\n * resolved theme; it resolves to one of these via the environment.\n */\nexport const GRAPH_THEME = {\n DARK: \"dark\",\n LIGHT: \"light\",\n} as const;\n\nexport type GraphTheme = (typeof GRAPH_THEME)[keyof typeof GRAPH_THEME];\n\n/**\n * The user's theme *selection* — what the toolbar cycles, what gets persisted\n * and reported. `system` follows the host environment (browser `prefers-color-scheme`\n * or an injected `systemTheme`) and resolves to a binary `GraphTheme` at render\n * time. The `dark`/`light` overlap with `GraphTheme` is intentional: a resolved\n * theme is also a valid mode.\n */\nexport const GRAPH_THEME_MODE = {\n DARK: \"dark\",\n LIGHT: \"light\",\n SYSTEM: \"system\",\n} as const;\n\nexport type GraphThemeMode = (typeof GRAPH_THEME_MODE)[keyof typeof GRAPH_THEME_MODE];\n\n/**\n * Anchor for the built-in floating toolbar. The values match ReactFlow's\n * `PanelPosition` union exactly, so they pass straight to `<Panel position=…>`\n * (the React layer asserts that subset relationship at compile time). Orientation\n * is *derived* from the position, never configured independently — see\n * `toolbarOrientation`: only the two edge-center anchors are vertical, everything\n * else (corners + top/bottom-center) is a horizontal bar.\n *\n * `types.ts` stays React-free, so `PanelPosition` is intentionally NOT imported\n * here — the compatibility assertion lives in `GraphToolbar.tsx`.\n */\nexport const TOOLBAR_POSITION = {\n TOP_LEFT: \"top-left\",\n TOP_CENTER: \"top-center\",\n TOP_RIGHT: \"top-right\",\n BOTTOM_LEFT: \"bottom-left\",\n BOTTOM_CENTER: \"bottom-center\",\n BOTTOM_RIGHT: \"bottom-right\",\n CENTER_LEFT: \"center-left\",\n CENTER_RIGHT: \"center-right\",\n} as const;\n\nexport type ToolbarPosition = (typeof TOOLBAR_POSITION)[keyof typeof TOOLBAR_POSITION];\n\nexport type ToolbarOrientation = \"horizontal\" | \"vertical\";\n\n/**\n * Derive the bar orientation from its anchor. \"Corners are horizontal, edges\n * decide the rest\": only `center-left` / `center-right` produce a vertical bar;\n * every other position (the four corners plus `top-center` / `bottom-center`) is\n * horizontal. Pure + React-free so it is unit-testable without rendering.\n */\nexport function toolbarOrientation(position: ToolbarPosition): ToolbarOrientation {\n return position === TOOLBAR_POSITION.CENTER_LEFT || position === TOOLBAR_POSITION.CENTER_RIGHT\n ? \"vertical\"\n : \"horizontal\";\n}\n\nexport type ToolbarSide = \"left\" | \"center\" | \"right\";\n\n/**\n * Derive which edge the anchor hugs. The built-in `DetailPanel` overlays the\n * right edge, so only right-side anchors (`*-right`) need to dodge it — the\n * toolbar reads this to decide whether to shift left while the panel is open.\n * Pure + React-free so it is unit-testable without rendering, and exhaustively\n * switched so adding a `TOOLBAR_POSITION` value without a side fails to compile\n * — exactly like {@link toolbarOrientation}.\n */\nexport function toolbarSide(position: ToolbarPosition): ToolbarSide {\n switch (position) {\n case TOOLBAR_POSITION.TOP_LEFT:\n case TOOLBAR_POSITION.CENTER_LEFT:\n case TOOLBAR_POSITION.BOTTOM_LEFT:\n return \"left\";\n case TOOLBAR_POSITION.TOP_RIGHT:\n case TOOLBAR_POSITION.CENTER_RIGHT:\n case TOOLBAR_POSITION.BOTTOM_RIGHT:\n return \"right\";\n case TOOLBAR_POSITION.TOP_CENTER:\n case TOOLBAR_POSITION.BOTTOM_CENTER:\n return \"center\";\n default: {\n const _exhaustive: never = position;\n return _exhaustive;\n }\n }\n}\n\n// ─── Validation status (toolbar widget) ──────────────────────────────────────\n\n/**\n * State of the toolbar's validation widget. The widget itself is opt-in: it\n * renders only when the host passes a `validationState` to `GraphViewer` —\n * `undefined` (the default) keeps it hidden entirely. The states describe the\n * host's validation lifecycle, not the static analyzer's: `validating` while a\n * verdict is being produced, `valid`/`invalid` once one exists, and `error`\n * when no verdict could be produced at all (validator unavailable, timeout…).\n * `unvalidated` is for a host with no validator at all, such as a page drawing\n * a method from its source: no verdict exists or is coming, and the issues are\n * what reading the source turned up.\n */\nexport const VALIDATION_STATE = {\n VALIDATING: \"validating\",\n VALID: \"valid\",\n INVALID: \"invalid\",\n ERROR: \"error\",\n UNVALIDATED: \"unvalidated\",\n} as const;\n\nexport type ValidationState = (typeof VALIDATION_STATE)[keyof typeof VALIDATION_STATE];\n\n/**\n * One issue row in the validation panel. Mostly presentation: the host decides\n * which issues to show for each state (its validator's errors, the static\n * analyzer's diagnostics, or a mix) and handles navigation on row click. The\n * only fields the viewer interprets are the optional targeting fields\n * (`pipeRef` / `nodeId`), which drive node decorations on the graph.\n */\nexport interface ValidationIssue {\n severity: \"error\" | \"warning\";\n message: string;\n /** Short locator chip, e.g. `pipe.analyze_candidate` or a TOML path. */\n context?: string;\n /** Owning-file basename, when the issue lives in a specific file. */\n file?: string;\n /** Human-readable suggested fix, when the validator derived one. */\n suggestedFix?: string;\n /** Which analyzer produced the issue: the host's validator or the static parser. */\n origin?: \"validator\" | \"static\";\n /**\n * Fully-qualified pipe ref (`domain_code.pipe_code`, see `makePipeRef`) this\n * issue targets — decorates every rendered node invoking that pipe (a pipe\n * can be invoked from several places in the graph). Always qualified, never a\n * bare code: two domains may declare the same pipe code, and a bare match\n * would ring every same-code node. An emitter that cannot qualify must leave\n * the issue untargeted (panel-only) rather than decorate by guess.\n */\n pipeRef?: string;\n /**\n * Precise invocation this issue targets — a GraphSpec node id (e.g.\n * `demo.main_flow/step_2`). Takes precedence over `pipeRef` when both are\n * set. Issues with neither targeting field stay panel-only.\n */\n nodeId?: string;\n}\n\n/**\n * Aggregated validation decoration for one rendered node — derived from the\n * targeted `ValidationIssue`s by `buildValidationDecorations` (worst severity\n * wins, folded controllers roll up their hidden descendants' issues). Rendered\n * as a severity ring + count badge on the node, with `lines` as the tooltip.\n */\nexport interface NodeValidationSummary {\n /** Worst severity among the node's issues (`error` wins over `warning`). */\n severity: \"error\" | \"warning\";\n /** Number of issue hits on the node (a multi-instance pipe counts per invocation). */\n count: number;\n /** Tooltip lines: each issue's message, followed by a `Fix: …` line when present. */\n lines: string[];\n}\n\nexport interface GraphConfig {\n direction?: GraphDirection;\n showControllers?: boolean;\n foldMode?: FoldMode;\n /**\n * Theme *mode*: `dark | light | system`. `system` follows the host environment.\n * Defaults to `system` (see `DEFAULT_GRAPH_CONFIG`). Pass `dark`/`light` to pin\n * a fixed appearance.\n */\n theme?: GraphThemeMode;\n /**\n * Anchor for the built-in floating toolbar (default `top-right`). Orientation\n * is derived from the position — see {@link toolbarOrientation}. The\n * `toolbarPosition` *prop* on `GraphViewer` takes precedence over this; both\n * are reactive and persistence is the host's responsibility.\n */\n toolbarPosition?: ToolbarPosition;\n nodesep?: number;\n ranksep?: number;\n edgeType?: EdgeType;\n initialZoom?: number | null;\n panToTop?: boolean;\n paletteColors?: Record<string, string>;\n}\n\n// ─── Label descriptors ──────────────────────────────────────────────────────\n// Plain objects, no React dependency. GraphViewer maps these to React elements.\n\nexport type LabelDescriptor =\n | { kind: \"pipe\"; label: string; isFailed: boolean }\n | { kind: \"stuff\"; label: string; concept: string };\n\n// ─── Fold toggle options ────────────────────────────────────────────────────\n// Passed by UI click handlers so the orchestrator can decide whether the\n// toggle should propagate to \"cousin\" controllers (other instances of the\n// same pipe) or affect only the clicked one.\n\nexport interface FoldToggleOptions {\n /**\n * When `true`, the toggle applies only to the clicked controller — its\n * cousins (other controller nodes sharing the same `pipe_code`) are left\n * untouched. Wired to the alt/option modifier key in the click handlers.\n */\n soloMode?: boolean;\n}\n\n// ─── Pipe card payload ──────────────────────────────────────────────────────\n// Built by graphBuilders, consumed by PipeCardNode in the React layer.\n\nexport interface PipeCardPayload {\n pipeCode: string;\n pipeType: NodePipeType;\n description?: string;\n status: PipeStatus;\n graphMode?: GraphSpecMode;\n inputs: { name: string; concept: string }[];\n outputs: { name: string; concept: string }[];\n /** Authored/static annotations and runtime tags carried by the GraphSpec node. */\n tags?: Record<string, string>;\n /** Layout direction — injected by the layout engine */\n direction?: \"LR\" | \"TB\";\n /** When set, the card renders an unfold button that invokes this callback. */\n onExpand?: (options?: FoldToggleOptions) => void;\n /** Validation decoration (severity ring + count badge), stamped by GraphViewer. */\n validation?: NodeValidationSummary;\n /** Badge click handler (opens the validation panel), stamped alongside `validation`. */\n onValidationBadgeClick?: () => void;\n}\n\n// ─── Graph node data ────────────────────────────────────────────────────────\n// Extends Record<string, unknown> for ReactFlow's Node<T> generic parameter.\n\nexport type StuffRole = \"input\" | \"output\" | \"combined\";\n\nexport interface GraphNodeData extends Record<string, unknown> {\n labelDescriptor?: LabelDescriptor;\n label?: unknown;\n nodeData?: GraphSpecNode;\n isPipe: boolean;\n isStuff: boolean;\n isController?: boolean;\n labelText: string;\n pipeCode?: string;\n pipeType?: NodePipeType;\n graphMode?: GraphSpecMode;\n pipeCardData?: PipeCardPayload;\n /** For stuff nodes: \"input\" (no producer), \"output\" (no consumer), or undefined (intermediate). */\n stuffRole?: StuffRole;\n /** For stuff nodes: the digest used to build the node ID. */\n stuffDigest?: string;\n /** Validation decoration (severity ring + count badge), stamped by GraphViewer. */\n validation?: NodeValidationSummary;\n /** Badge click handler (opens the validation panel), stamped alongside `validation`. */\n onValidationBadgeClick?: () => void;\n}\n\n// ─── Graph node / edge / data ───────────────────────────────────────────────\n\nexport interface GraphNode {\n id: string;\n type: string;\n data: GraphNodeData;\n position: { x: number; y: number };\n style?: Record<string, string | number>;\n sourcePosition?: \"top\" | \"bottom\" | \"left\" | \"right\";\n targetPosition?: \"top\" | \"bottom\" | \"left\" | \"right\";\n parentId?: string;\n extent?: \"parent\";\n selected?: boolean;\n}\n\nexport interface GraphEdge {\n id: string;\n source: string;\n target: string;\n type: string;\n animated?: boolean;\n label?: string;\n labelStyle?: Record<string, string | number>;\n labelBgStyle?: Record<string, string | number>;\n labelBgPadding?: [number, number];\n labelBgBorderRadius?: number;\n style?: Record<string, string | number>;\n markerEnd?: { type: string; color: string };\n _batchEdge?: boolean;\n _crossGroup?: boolean;\n}\n\nexport interface GraphData {\n nodes: GraphNode[];\n edges: GraphEdge[];\n}\n\n// ─── Layout ─────────────────────────────────────────────────────────────────\n\nexport interface LayoutConfig {\n nodesep?: number;\n ranksep?: number;\n}\n\n// Controller padding constants (shared between layout and controller modules)\nexport const CONTROLLER_PADDING_X = 40;\nexport const CONTROLLER_PADDING_TOP = 48;\nexport const CONTROLLER_PADDING_BOTTOM = 20;\n\n// Default marker type string (avoids ReactFlow dependency in pure modules)\nexport const ARROW_CLOSED_MARKER = \"arrowclosed\";\n\n// ─── Node dimension helpers ─────────────────────────────────────────────────\n// Extract dimensions from style. Used by buildControllerNodes.\n// NOT used by getLayoutedElements, which estimates dimensions before styles exist.\n\nexport function nodeWidth(n: GraphNode): number {\n const raw = n.style?.width;\n if (raw == null) return 200;\n const w = typeof raw === \"number\" ? raw : parseFloat(raw);\n return isNaN(w) || w <= 0 ? 200 : w;\n}\n\nexport function nodeHeight(n: GraphNode): number {\n const raw = n.style?.height;\n if (raw != null) {\n const h = typeof raw === \"number\" ? raw : parseFloat(raw);\n if (!isNaN(h) && h > 0) return h;\n }\n return n.data?.isStuff ? 60 : 70;\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- //# sourceMappingURL=chunk-F6TZIJ7A.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/graph/graphConfig.ts"],"sourcesContent":["import type { GraphConfig, GraphTheme, ToolbarPosition } from \"./types\";\nimport { EDGE_TYPE, FOLD_MODE, GRAPH_THEME, GRAPH_THEME_MODE, TOOLBAR_POSITION } from \"./types\";\n\n/**\n * Semantic design tokens consumed by every component CSS file.\n *\n * Component CSS **must never** hardcode raw hex/rgba values. Reference these\n * tokens via `var(--token-name)`. A new theme is just a new set of values.\n * A new component picks up theming for free as long as it only uses tokens.\n *\n * Categories:\n * surface-* → backgrounds (page, panels, pills, glass overlays)\n * border-* → borders, dividers\n * text-* → foreground text\n * shadow-* → drop shadows\n * focus-ring → focus outline\n * color-* → domain-semantic colors (pipe accents, edges, status); kept\n * separate because they're meaningful in both themes (a pipe is\n * still red), only the *value* shifts for contrast.\n */\nconst FONT_TOKENS: Record<string, string> = {\n \"--font-sans\": '\"Inter\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif',\n \"--font-mono\": '\"JetBrains Mono\", \"Monaco\", \"Menlo\", monospace',\n};\n\nexport const DARK_PALETTE_COLORS: Record<string, string> = {\n ...FONT_TOKENS,\n\n // Surfaces\n \"--surface-page\": \"#0a0a0a\",\n \"--surface-panel\": \"#111118\",\n \"--surface-overlay\": \"rgba(17, 17, 24, 0.8)\",\n \"--surface-overlay-hover\": \"rgba(30, 30, 40, 0.9)\",\n \"--surface-overlay-disabled\": \"rgba(17, 17, 24, 0.6)\",\n \"--surface-elevated\": \"rgba(255, 255, 255, 0.06)\",\n \"--surface-elevated-hover\": \"rgba(255, 255, 255, 0.1)\",\n \"--surface-sunken\": \"rgba(255, 255, 255, 0.03)\",\n \"--surface-pill\": \"rgba(255, 255, 255, 0.06)\",\n \"--surface-pill-border\": \"rgba(255, 255, 255, 0.08)\",\n\n // Borders\n \"--border-subtle\": \"rgba(255, 255, 255, 0.06)\",\n \"--border-default\": \"rgba(255, 255, 255, 0.1)\",\n \"--border-strong\": \"rgba(255, 255, 255, 0.18)\",\n \"--border-dashed\": \"rgba(255, 255, 255, 0.15)\",\n\n // Text\n \"--text-primary\": \"#f8fafc\",\n \"--text-default\": \"#e2e8f0\",\n \"--text-secondary\": \"#cbd5e1\",\n \"--text-muted\": \"#94a3b8\",\n \"--text-dim\": \"#64748b\",\n \"--text-on-accent\": \"#0e0e0e\",\n\n // Effects\n \"--shadow-sm\": \"0 2px 8px rgba(0, 0, 0, 0.4)\",\n \"--shadow-md\": \"0 4px 16px rgba(0, 0, 0, 0.6)\",\n \"--shadow-lg\": \"0 8px 24px rgba(0, 0, 0, 0.5)\",\n \"--focus-ring\": \"rgba(59, 130, 246, 0.6)\",\n\n // Domain-semantic colors (graph nodes/edges)\n \"--color-pipe\": \"#ff6b6b\",\n \"--color-pipe-bg\": \"rgba(224,108,117,0.18)\",\n \"--color-pipe-text\": \"#ffffff\",\n \"--color-stuff\": \"#4ECDC4\",\n \"--color-stuff-bg\": \"rgba(78,205,196,0.12)\",\n \"--color-stuff-border\": \"#9ddcfd\",\n \"--color-stuff-text\": \"#98FB98\",\n \"--color-stuff-text-dim\": \"#9ddcfd\",\n \"--color-edge\": \"#FFFACD\",\n \"--color-batch-item\": \"#bd93f9\",\n \"--color-batch-aggregate\": \"#50fa7b\",\n \"--color-parallel-combine\": \"#d6a4ff\",\n \"--color-success\": \"#50FA7B\",\n \"--color-success-bg\": \"rgba(80,250,123,0.15)\",\n \"--color-error\": \"#FF5555\",\n \"--color-error-bg\": \"rgba(255,85,85,0.15)\",\n \"--color-error-border\": \"rgba(255,85,85,0.2)\",\n \"--color-accent\": \"#8BE9FD\",\n \"--color-accent-strong\": \"#3b82f6\",\n \"--color-warning\": \"#FFB86C\",\n\n // Controller-group palette (tinted borders/backgrounds per controller type)\n \"--ctrl-sequence-border\": \"rgba(148, 163, 184, 0.25)\",\n \"--ctrl-sequence-bg\": \"rgba(148, 163, 184, 0.03)\",\n \"--ctrl-sequence-text\": \"#94a3b8\",\n \"--ctrl-parallel-border\": \"rgba(139, 233, 253, 0.35)\",\n \"--ctrl-parallel-bg\": \"rgba(139, 233, 253, 0.03)\",\n \"--ctrl-parallel-text\": \"#8be9fd\",\n \"--ctrl-condition-border\": \"rgba(251, 191, 36, 0.35)\",\n \"--ctrl-condition-bg\": \"rgba(251, 191, 36, 0.03)\",\n \"--ctrl-condition-text\": \"#fbbf24\",\n \"--ctrl-batch-border\": \"rgba(167, 139, 250, 0.35)\",\n \"--ctrl-batch-bg\": \"rgba(167, 139, 250, 0.03)\",\n \"--ctrl-batch-text\": \"#a78bfa\",\n \"--ctrl-folded-bg\": \"rgba(148, 163, 184, 0.25)\",\n \"--ctrl-folded-border\": \"rgba(148, 163, 184, 0.4)\",\n\n // Legacy aliases — kept so existing inline styles in graph builders keep\n // working until they're migrated. New code should use the semantic tokens.\n \"--color-bg\": \"#0a0a0a\",\n \"--color-bg-dots\": \"rgba(255, 255, 255, 0.35)\",\n \"--color-text-muted\": \"#94a3b8\",\n \"--color-controller-text\": \"#94a3b8\",\n};\n\nexport const LIGHT_PALETTE_COLORS: Record<string, string> = {\n ...FONT_TOKENS,\n\n // Surfaces — chart stays a soft off-white, panels slightly cooler\n \"--surface-page\": \"#f8fafc\",\n \"--surface-panel\": \"#ffffff\",\n \"--surface-overlay\": \"rgba(255, 255, 255, 0.92)\",\n \"--surface-overlay-hover\": \"rgba(241, 245, 249, 0.96)\",\n \"--surface-overlay-disabled\": \"rgba(255, 255, 255, 0.7)\",\n \"--surface-elevated\": \"rgba(15, 23, 42, 0.05)\",\n \"--surface-elevated-hover\": \"rgba(15, 23, 42, 0.09)\",\n \"--surface-sunken\": \"rgba(15, 23, 42, 0.03)\",\n \"--surface-pill\": \"rgba(15, 23, 42, 0.05)\",\n \"--surface-pill-border\": \"rgba(15, 23, 42, 0.1)\",\n\n // Borders\n \"--border-subtle\": \"rgba(15, 23, 42, 0.08)\",\n \"--border-default\": \"rgba(15, 23, 42, 0.12)\",\n \"--border-strong\": \"rgba(15, 23, 42, 0.22)\",\n \"--border-dashed\": \"rgba(15, 23, 42, 0.18)\",\n\n // Text\n \"--text-primary\": \"#020617\",\n \"--text-default\": \"#0f172a\",\n \"--text-secondary\": \"#1e293b\",\n \"--text-muted\": \"#475569\",\n \"--text-dim\": \"#64748b\",\n \"--text-on-accent\": \"#ffffff\",\n\n // Effects\n \"--shadow-sm\": \"0 2px 8px rgba(15, 23, 42, 0.08)\",\n \"--shadow-md\": \"0 4px 16px rgba(15, 23, 42, 0.14)\",\n \"--shadow-lg\": \"0 8px 24px rgba(15, 23, 42, 0.15)\",\n \"--focus-ring\": \"rgba(2, 132, 199, 0.5)\",\n\n // Domain-semantic colors — darker for contrast on light backgrounds\n \"--color-pipe\": \"#dc2626\",\n \"--color-pipe-bg\": \"rgba(220, 38, 38, 0.1)\",\n \"--color-pipe-text\": \"#ffffff\",\n \"--color-stuff\": \"#0891b2\",\n \"--color-stuff-bg\": \"rgba(8, 145, 178, 0.08)\",\n \"--color-stuff-border\": \"#0e7490\",\n \"--color-stuff-text\": \"#0f172a\",\n \"--color-stuff-text-dim\": \"#475569\",\n \"--color-edge\": \"#64748b\",\n \"--color-batch-item\": \"#7c3aed\",\n \"--color-batch-aggregate\": \"#15803d\",\n \"--color-parallel-combine\": \"#6d28d9\",\n \"--color-success\": \"#15803d\",\n \"--color-success-bg\": \"rgba(21, 128, 61, 0.12)\",\n \"--color-error\": \"#dc2626\",\n \"--color-error-bg\": \"rgba(220, 38, 38, 0.1)\",\n \"--color-error-border\": \"rgba(220, 38, 38, 0.25)\",\n \"--color-accent\": \"#0284c7\",\n \"--color-accent-strong\": \"#0284c7\",\n \"--color-warning\": \"#d97706\",\n\n // Controller-group palette\n \"--ctrl-sequence-border\": \"rgba(71, 85, 105, 0.3)\",\n \"--ctrl-sequence-bg\": \"rgba(71, 85, 105, 0.04)\",\n \"--ctrl-sequence-text\": \"#475569\",\n \"--ctrl-parallel-border\": \"rgba(8, 145, 178, 0.4)\",\n \"--ctrl-parallel-bg\": \"rgba(8, 145, 178, 0.05)\",\n \"--ctrl-parallel-text\": \"#0e7490\",\n \"--ctrl-condition-border\": \"rgba(217, 119, 6, 0.4)\",\n \"--ctrl-condition-bg\": \"rgba(217, 119, 6, 0.05)\",\n \"--ctrl-condition-text\": \"#b45309\",\n \"--ctrl-batch-border\": \"rgba(124, 58, 237, 0.4)\",\n \"--ctrl-batch-bg\": \"rgba(124, 58, 237, 0.05)\",\n \"--ctrl-batch-text\": \"#6d28d9\",\n \"--ctrl-folded-bg\": \"rgba(71, 85, 105, 0.18)\",\n \"--ctrl-folded-border\": \"rgba(71, 85, 105, 0.35)\",\n\n // Legacy aliases\n \"--color-bg\": \"#f8fafc\",\n \"--color-bg-dots\": \"rgba(15, 23, 42, 0.18)\",\n \"--color-text-muted\": \"#475569\",\n \"--color-controller-text\": \"#475569\",\n};\n\nexport function getPaletteForTheme(theme: GraphTheme): Record<string, string> {\n return theme === GRAPH_THEME.LIGHT ? LIGHT_PALETTE_COLORS : DARK_PALETTE_COLORS;\n}\n\n// `toolbarPosition` is narrowed to required: the default always sets it, so\n// `resolveToolbarPosition` can terminate its fallback chain here without a\n// redundant literal floor.\nexport const DEFAULT_GRAPH_CONFIG: GraphConfig & { toolbarPosition: ToolbarPosition } = {\n direction: \"LR\",\n showControllers: false,\n foldMode: FOLD_MODE.EXPANDED,\n // `system` follows the host environment (OS/browser, or editor theme) out of\n // the box — the least-surprising default for an embedded component. Hosts\n // that want a fixed appearance pass `theme: \"dark\"` / `\"light\"` explicitly.\n theme: GRAPH_THEME_MODE.SYSTEM,\n // Backward-compatible default: the toolbar stays pinned top-right unless the\n // host overrides it via `config.toolbarPosition` or the `toolbarPosition` prop.\n toolbarPosition: TOOLBAR_POSITION.TOP_RIGHT,\n nodesep: 50,\n ranksep: 100,\n edgeType: EDGE_TYPE.DEFAULT,\n initialZoom: null,\n panToTop: true,\n // `paletteColors` intentionally omitted from the default. The viewer derives\n // the palette from the active `theme`. Consumers only set `paletteColors` to\n // override specific tokens (sparse merge on top of the theme palette).\n};\n\nexport function getPaletteColors(): Record<string, string> {\n return DARK_PALETTE_COLORS;\n}\n"],"mappings":"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","names":[]}