@pipelex/mthds-ui 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -102,12 +102,34 @@ result = px.validate_bundle("my-method.mthds", view=True)
102
102
  graphspec = result.graphspec # dict ready for JSON serialization
103
103
  ```
104
104
 
105
+ ### Static GraphSpec from `.mthds`
106
+
107
+ For authored-structure previews that should not run Pipelex, use the pure
108
+ TypeScript static builder:
109
+
110
+ ```ts
111
+ import { buildStaticGraphSpecFromToml } from "@pipelex/mthds-ui/static-graph";
112
+
113
+ const { spec, diagnostics } = buildStaticGraphSpecFromToml(tomlText);
114
+ ```
115
+
116
+ The returned `spec` has `meta: { format: "mthds", mode: "static" }` and can be
117
+ passed directly to `GraphViewer`. Static graphs hide runtime status/timing
118
+ chrome and are best-effort: diagnostics report incomplete or unresolved authored
119
+ content without preventing rendering.
120
+
121
+ See [docs/static-graph.md](./docs/static-graph.md) for the full contract.
122
+
105
123
  ### Structure
106
124
 
107
125
  ```typescript
108
126
  interface GraphSpec {
109
127
  nodes: GraphSpecNode[];
110
128
  edges: GraphSpecEdge[];
129
+ meta: {
130
+ format: "mthds";
131
+ mode?: "dry" | "live" | "static";
132
+ };
111
133
  }
112
134
 
113
135
  interface GraphSpecNode {
@@ -0,0 +1,163 @@
1
+ // src/graph/types.ts
2
+ var PIPE_TYPE_PRESENCE = {
3
+ PipeLLM: true,
4
+ PipeExtract: true,
5
+ PipeCompose: true,
6
+ PipeImgGen: true,
7
+ PipeSearch: true,
8
+ PipeFunc: true,
9
+ PipeStructure: true,
10
+ PipeSignature: true,
11
+ PipeSequence: true,
12
+ PipeParallel: true,
13
+ PipeCondition: true,
14
+ PipeBatch: true
15
+ };
16
+ var KNOWN_PIPE_TYPES = new Set(Object.keys(PIPE_TYPE_PRESENCE));
17
+ var GRAPH_SPEC_MODE = {
18
+ DRY: "dry",
19
+ LIVE: "live",
20
+ STATIC: "static"
21
+ };
22
+ var NODE_TYPE_PIPE_CARD = "pipeCard";
23
+ var NODE_TYPE_STUFF = "default";
24
+ var NODE_TYPE_CONTROLLER = "controllerGroup";
25
+ var STUFF_ID_PREFIX = "stuff_";
26
+ function stuffNodeId(digest) {
27
+ return STUFF_ID_PREFIX + digest;
28
+ }
29
+ function isStuffNodeId(id) {
30
+ return id.startsWith(STUFF_ID_PREFIX);
31
+ }
32
+ function stuffDigestFromId(id) {
33
+ return id.slice(STUFF_ID_PREFIX.length);
34
+ }
35
+ function graphSpecMode(spec) {
36
+ var _a;
37
+ return (_a = spec == null ? void 0 : spec.meta) == null ? void 0 : _a.mode;
38
+ }
39
+ function isStaticGraphSpec(spec) {
40
+ return graphSpecMode(spec) === GRAPH_SPEC_MODE.STATIC;
41
+ }
42
+ function isDryGraphSpec(spec) {
43
+ return graphSpecMode(spec) === GRAPH_SPEC_MODE.DRY;
44
+ }
45
+ var GRAPH_DIRECTION = {
46
+ TB: "TB",
47
+ BT: "BT",
48
+ LR: "LR",
49
+ RL: "RL"
50
+ };
51
+ var EDGE_TYPE = {
52
+ /** Bezier curve — ReactFlow v12 renamed this type from "bezier" to "default". */
53
+ DEFAULT: "default",
54
+ STEP: "step",
55
+ STRAIGHT: "straight",
56
+ SMOOTH_STEP: "smoothstep"
57
+ };
58
+ var FOLD_MODE = {
59
+ /** Every pipe controller folded into a single pipe card. */
60
+ FOLDED: "folded",
61
+ /** Every pipe controller expanded as a group wrapper. */
62
+ EXPANDED: "expanded",
63
+ /** Renderer decides — reserved for future heuristics; currently behaves like EXPANDED. */
64
+ AUTO: "auto"
65
+ };
66
+ var GRAPH_THEME = {
67
+ DARK: "dark",
68
+ LIGHT: "light"
69
+ };
70
+ var GRAPH_THEME_MODE = {
71
+ DARK: "dark",
72
+ LIGHT: "light",
73
+ SYSTEM: "system"
74
+ };
75
+ var TOOLBAR_POSITION = {
76
+ TOP_LEFT: "top-left",
77
+ TOP_CENTER: "top-center",
78
+ TOP_RIGHT: "top-right",
79
+ BOTTOM_LEFT: "bottom-left",
80
+ BOTTOM_CENTER: "bottom-center",
81
+ BOTTOM_RIGHT: "bottom-right",
82
+ CENTER_LEFT: "center-left",
83
+ CENTER_RIGHT: "center-right"
84
+ };
85
+ function toolbarOrientation(position) {
86
+ return position === TOOLBAR_POSITION.CENTER_LEFT || position === TOOLBAR_POSITION.CENTER_RIGHT ? "vertical" : "horizontal";
87
+ }
88
+ function toolbarSide(position) {
89
+ switch (position) {
90
+ case TOOLBAR_POSITION.TOP_LEFT:
91
+ case TOOLBAR_POSITION.CENTER_LEFT:
92
+ case TOOLBAR_POSITION.BOTTOM_LEFT:
93
+ return "left";
94
+ case TOOLBAR_POSITION.TOP_RIGHT:
95
+ case TOOLBAR_POSITION.CENTER_RIGHT:
96
+ case TOOLBAR_POSITION.BOTTOM_RIGHT:
97
+ return "right";
98
+ case TOOLBAR_POSITION.TOP_CENTER:
99
+ case TOOLBAR_POSITION.BOTTOM_CENTER:
100
+ return "center";
101
+ default: {
102
+ const _exhaustive = position;
103
+ return _exhaustive;
104
+ }
105
+ }
106
+ }
107
+ var VALIDATION_STATE = {
108
+ VALIDATING: "validating",
109
+ VALID: "valid",
110
+ INVALID: "invalid",
111
+ ERROR: "error"
112
+ };
113
+ var CONTROLLER_PADDING_X = 40;
114
+ var CONTROLLER_PADDING_TOP = 48;
115
+ var CONTROLLER_PADDING_BOTTOM = 20;
116
+ var ARROW_CLOSED_MARKER = "arrowclosed";
117
+ function nodeWidth(n) {
118
+ var _a;
119
+ const raw = (_a = n.style) == null ? void 0 : _a.width;
120
+ if (raw == null) return 200;
121
+ const w = typeof raw === "number" ? raw : parseFloat(raw);
122
+ return isNaN(w) || w <= 0 ? 200 : w;
123
+ }
124
+ function nodeHeight(n) {
125
+ var _a, _b;
126
+ const raw = (_a = n.style) == null ? void 0 : _a.height;
127
+ if (raw != null) {
128
+ const h = typeof raw === "number" ? raw : parseFloat(raw);
129
+ if (!isNaN(h) && h > 0) return h;
130
+ }
131
+ return ((_b = n.data) == null ? void 0 : _b.isStuff) ? 60 : 70;
132
+ }
133
+
134
+ export {
135
+ KNOWN_PIPE_TYPES,
136
+ GRAPH_SPEC_MODE,
137
+ NODE_TYPE_PIPE_CARD,
138
+ NODE_TYPE_STUFF,
139
+ NODE_TYPE_CONTROLLER,
140
+ STUFF_ID_PREFIX,
141
+ stuffNodeId,
142
+ isStuffNodeId,
143
+ stuffDigestFromId,
144
+ graphSpecMode,
145
+ isStaticGraphSpec,
146
+ isDryGraphSpec,
147
+ GRAPH_DIRECTION,
148
+ EDGE_TYPE,
149
+ FOLD_MODE,
150
+ GRAPH_THEME,
151
+ GRAPH_THEME_MODE,
152
+ TOOLBAR_POSITION,
153
+ toolbarOrientation,
154
+ toolbarSide,
155
+ VALIDATION_STATE,
156
+ CONTROLLER_PADDING_X,
157
+ CONTROLLER_PADDING_TOP,
158
+ CONTROLLER_PADDING_BOTTOM,
159
+ ARROW_CLOSED_MARKER,
160
+ nodeWidth,
161
+ nodeHeight
162
+ };
163
+ //# sourceMappingURL=chunk-GS2KODND.js.map
@@ -0,0 +1 @@
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 | \"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 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\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// ─── GraphSpec types (from pipelex-agent --view output) ─────────────────────\n\nexport interface GraphSpecNodeIoItem {\n name: string;\n digest?: string;\n concept?: string;\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\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\nexport interface GraphSpecNode {\n id: string;\n kind: NodeKind;\n pipe_code?: string;\n pipe_type: PipeType;\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 execution_data?: Record<string, unknown>;\n}\n\n/**\n * A pipe-call node — the only node kind a real pipelex run emits. Narrowed\n * from `GraphSpecNode` for code paths that only ever render pipe-call nodes,\n * where `pipe_code` is guaranteed (validateGraphSpec enforces this).\n */\nexport type PipeCallNode = GraphSpecNode & {\n kind: \"controller\" | \"operator\";\n pipe_code: string;\n pipe_type: PipeType;\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\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\nexport interface StuffSpecInfo {\n concept: ConceptInfo;\n multiplicity: number | boolean | null;\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// ─── 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 templating_style: string | null;\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 * 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\".\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 sequential_sub_pipes: SubPipeSpec[];\n}\n\nexport interface PipeParallelBlueprint extends PipeBlueprintBase {\n type: \"PipeParallel\";\n parallel_sub_pipes: SubPipeSpec[];\n add_each_output: boolean;\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\nexport type PipeBlueprintUnion =\n | PipeLLMBlueprint\n | PipeImgGenBlueprint\n | PipeComposeBlueprint\n | PipeExtractBlueprint\n | PipeSearchBlueprint\n | PipeFuncBlueprint\n | PipeStructureBlueprint\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 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\nexport interface DataflowAnalysis {\n readonly stuffRegistry: Readonly<\n Record<string, { name: string; concept?: string; contentType?: string }>\n >;\n readonly stuffProducers: Readonly<Record<string, 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 */\nexport const VALIDATION_STATE = {\n VALIDATING: \"validating\",\n VALID: \"valid\",\n INVALID: \"invalid\",\n ERROR: \"error\",\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 * (`pipeCode` / `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 * Pipe code this issue targets — decorates every rendered node invoking that\n * pipe (a pipe can be invoked from several places in the graph).\n */\n pipeCode?: string;\n /**\n * Precise invocation this issue targets — a GraphSpec node id (e.g.\n * `demo.main_flow/step_2`). Takes precedence over `pipeCode` 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: PipeType;\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?: PipeType;\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":[]}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chunk-LJQS5ZNA.js.map
@@ -1,121 +1,30 @@
1
+ import {
2
+ ARROW_CLOSED_MARKER,
3
+ CONTROLLER_PADDING_BOTTOM,
4
+ CONTROLLER_PADDING_TOP,
5
+ CONTROLLER_PADDING_X,
6
+ EDGE_TYPE,
7
+ FOLD_MODE,
8
+ GRAPH_THEME,
9
+ GRAPH_THEME_MODE,
10
+ KNOWN_PIPE_TYPES,
11
+ NODE_TYPE_CONTROLLER,
12
+ NODE_TYPE_PIPE_CARD,
13
+ NODE_TYPE_STUFF,
14
+ TOOLBAR_POSITION,
15
+ graphSpecMode,
16
+ isStuffNodeId,
17
+ nodeHeight,
18
+ nodeWidth,
19
+ stuffDigestFromId,
20
+ stuffNodeId
21
+ } from "./chunk-GS2KODND.js";
1
22
  import {
2
23
  __objRest,
3
24
  __spreadProps,
4
25
  __spreadValues
5
26
  } from "./chunk-2NMEKWO5.js";
6
27
 
7
- // src/graph/types.ts
8
- var PIPE_TYPE_PRESENCE = {
9
- PipeLLM: true,
10
- PipeExtract: true,
11
- PipeCompose: true,
12
- PipeImgGen: true,
13
- PipeSearch: true,
14
- PipeFunc: true,
15
- PipeStructure: true,
16
- PipeSignature: true,
17
- PipeSequence: true,
18
- PipeParallel: true,
19
- PipeCondition: true,
20
- PipeBatch: true
21
- };
22
- var KNOWN_PIPE_TYPES = new Set(Object.keys(PIPE_TYPE_PRESENCE));
23
- var NODE_TYPE_PIPE_CARD = "pipeCard";
24
- var NODE_TYPE_STUFF = "default";
25
- var NODE_TYPE_CONTROLLER = "controllerGroup";
26
- var STUFF_ID_PREFIX = "stuff_";
27
- function stuffNodeId(digest) {
28
- return STUFF_ID_PREFIX + digest;
29
- }
30
- function isStuffNodeId(id) {
31
- return id.startsWith(STUFF_ID_PREFIX);
32
- }
33
- function stuffDigestFromId(id) {
34
- return id.slice(STUFF_ID_PREFIX.length);
35
- }
36
- var GRAPH_DIRECTION = {
37
- TB: "TB",
38
- BT: "BT",
39
- LR: "LR",
40
- RL: "RL"
41
- };
42
- var EDGE_TYPE = {
43
- /** Bezier curve — ReactFlow v12 renamed this type from "bezier" to "default". */
44
- DEFAULT: "default",
45
- STEP: "step",
46
- STRAIGHT: "straight",
47
- SMOOTH_STEP: "smoothstep"
48
- };
49
- var FOLD_MODE = {
50
- /** Every pipe controller folded into a single pipe card. */
51
- FOLDED: "folded",
52
- /** Every pipe controller expanded as a group wrapper. */
53
- EXPANDED: "expanded",
54
- /** Renderer decides — reserved for future heuristics; currently behaves like EXPANDED. */
55
- AUTO: "auto"
56
- };
57
- var GRAPH_THEME = {
58
- DARK: "dark",
59
- LIGHT: "light"
60
- };
61
- var GRAPH_THEME_MODE = {
62
- DARK: "dark",
63
- LIGHT: "light",
64
- SYSTEM: "system"
65
- };
66
- var TOOLBAR_POSITION = {
67
- TOP_LEFT: "top-left",
68
- TOP_CENTER: "top-center",
69
- TOP_RIGHT: "top-right",
70
- BOTTOM_LEFT: "bottom-left",
71
- BOTTOM_CENTER: "bottom-center",
72
- BOTTOM_RIGHT: "bottom-right",
73
- CENTER_LEFT: "center-left",
74
- CENTER_RIGHT: "center-right"
75
- };
76
- function toolbarOrientation(position) {
77
- return position === TOOLBAR_POSITION.CENTER_LEFT || position === TOOLBAR_POSITION.CENTER_RIGHT ? "vertical" : "horizontal";
78
- }
79
- function toolbarSide(position) {
80
- switch (position) {
81
- case TOOLBAR_POSITION.TOP_LEFT:
82
- case TOOLBAR_POSITION.CENTER_LEFT:
83
- case TOOLBAR_POSITION.BOTTOM_LEFT:
84
- return "left";
85
- case TOOLBAR_POSITION.TOP_RIGHT:
86
- case TOOLBAR_POSITION.CENTER_RIGHT:
87
- case TOOLBAR_POSITION.BOTTOM_RIGHT:
88
- return "right";
89
- case TOOLBAR_POSITION.TOP_CENTER:
90
- case TOOLBAR_POSITION.BOTTOM_CENTER:
91
- return "center";
92
- default: {
93
- const _exhaustive = position;
94
- return _exhaustive;
95
- }
96
- }
97
- }
98
- var CONTROLLER_PADDING_X = 40;
99
- var CONTROLLER_PADDING_TOP = 48;
100
- var CONTROLLER_PADDING_BOTTOM = 20;
101
- var ARROW_CLOSED_MARKER = "arrowclosed";
102
- function nodeWidth(n) {
103
- var _a;
104
- const raw = (_a = n.style) == null ? void 0 : _a.width;
105
- if (raw == null) return 200;
106
- const w = typeof raw === "number" ? raw : parseFloat(raw);
107
- return isNaN(w) || w <= 0 ? 200 : w;
108
- }
109
- function nodeHeight(n) {
110
- var _a, _b;
111
- const raw = (_a = n.style) == null ? void 0 : _a.height;
112
- if (raw != null) {
113
- const h = typeof raw === "number" ? raw : parseFloat(raw);
114
- if (!isNaN(h) && h > 0) return h;
115
- }
116
- return ((_b = n.data) == null ? void 0 : _b.isStuff) ? 60 : 70;
117
- }
118
-
119
28
  // src/graph/validateGraphSpec.ts
120
29
  var GraphSpecValidationError = class extends Error {
121
30
  constructor(path, detail) {
@@ -133,6 +42,7 @@ var PIPE_STATUSES = /* @__PURE__ */ new Set([
133
42
  "skipped",
134
43
  "canceled"
135
44
  ]);
45
+ var GRAPH_SPEC_MODES = /* @__PURE__ */ new Set(["dry", "live", "static"]);
136
46
  var EDGE_KINDS = /* @__PURE__ */ new Set([
137
47
  "contains",
138
48
  "data",
@@ -247,6 +157,12 @@ function validateGraphSpec(raw) {
247
157
  `expected "mthds" (this does not look like pipelex GraphSpec JSON), got ${JSON.stringify(raw.meta.format)}`
248
158
  );
249
159
  }
160
+ if (raw.meta.mode !== void 0 && (typeof raw.meta.mode !== "string" || !GRAPH_SPEC_MODES.has(raw.meta.mode))) {
161
+ fail(
162
+ "meta.mode",
163
+ `expected one of ${[...GRAPH_SPEC_MODES].join(", ")} when present, got ${JSON.stringify(raw.meta.mode)}`
164
+ );
165
+ }
250
166
  if (raw.pipe_registry !== void 0 && !isPlainObject(raw.pipe_registry)) {
251
167
  fail("pipe_registry", `expected an object when present, got ${describe(raw.pipe_registry)}`);
252
168
  }
@@ -424,8 +340,8 @@ function resolveConceptRef(spec, codeOrRef) {
424
340
  }
425
341
 
426
342
  // src/graph/pipeCardPayload.ts
427
- function buildPipeCardPayload(node) {
428
- return {
343
+ function buildPipeCardPayload(node, graphMode) {
344
+ const payload = {
429
345
  pipeCode: node.pipe_code,
430
346
  pipeType: node.pipe_type,
431
347
  description: node.description,
@@ -439,6 +355,9 @@ function buildPipeCardPayload(node) {
439
355
  return { name: o.name, concept: (_a = o.concept) != null ? _a : "" };
440
356
  })
441
357
  };
358
+ if (graphMode !== void 0) payload.graphMode = graphMode;
359
+ if (node.tags !== void 0) payload.tags = node.tags;
360
+ return payload;
442
361
  }
443
362
 
444
363
  // src/graph/graphBuilders.ts
@@ -449,6 +368,13 @@ function buildDataflowGraph(graphspec, analysis, edgeType) {
449
368
  var _a;
450
369
  const nodes = [];
451
370
  const edges = [];
371
+ const graphMode = graphSpecMode(graphspec);
372
+ const combinedStuffDigests = /* @__PURE__ */ new Set();
373
+ for (const edge of graphspec.edges) {
374
+ if (edge.kind === "parallel_combine" && edge.target_stuff_digest) {
375
+ combinedStuffDigests.add(edge.target_stuff_digest);
376
+ }
377
+ }
452
378
  const participatingPipes = /* @__PURE__ */ new Set();
453
379
  for (const producer of Object.values(analysis.stuffProducers)) {
454
380
  participatingPipes.add(producer);
@@ -463,7 +389,7 @@ function buildDataflowGraph(graphspec, analysis, edgeType) {
463
389
  const pipeNode = asPipeCallNode(node, `nodes[${node.id}]`);
464
390
  const isFailed = pipeNode.status === "failed";
465
391
  const label = pipeNode.pipe_code;
466
- const pipeCardData = buildPipeCardPayload(pipeNode);
392
+ const pipeCardData = buildPipeCardPayload(pipeNode, graphMode);
467
393
  nodes.push({
468
394
  id: pipeNode.id,
469
395
  type: NODE_TYPE_PIPE_CARD,
@@ -475,6 +401,7 @@ function buildDataflowGraph(graphspec, analysis, edgeType) {
475
401
  labelText: label,
476
402
  pipeCode: pipeCardData.pipeCode,
477
403
  pipeType: pipeNode.pipe_type,
404
+ graphMode,
478
405
  pipeCardData
479
406
  },
480
407
  position: { x: 0, y: 0 }
@@ -486,10 +413,11 @@ function buildDataflowGraph(graphspec, analysis, edgeType) {
486
413
  const concept = stuffInfo.concept || "";
487
414
  const textWidth = Math.max(label.length, concept.length) * STUFF_CHAR_WIDTH_PX + STUFF_LABEL_PADDING;
488
415
  const stuffWidth = Math.max(MIN_STUFF_WIDTH, textWidth);
489
- const isInput = !analysis.stuffProducers[digest];
490
- const isOutput = !isInput && !((_a = analysis.stuffConsumers[digest]) == null ? void 0 : _a.length);
491
- const stuffRole = isInput ? "input" : isOutput ? "output" : void 0;
492
- const borderColor = isInput ? "var(--color-stuff-input-border, #50FA7B)" : isOutput ? "var(--color-stuff-output-border, #a78bfa)" : "var(--color-stuff-border)";
416
+ const isCombined = combinedStuffDigests.has(digest);
417
+ const isInput = !analysis.stuffProducers[digest] && !isCombined;
418
+ const isOutput = !isInput && !isCombined && !((_a = analysis.stuffConsumers[digest]) == null ? void 0 : _a.length);
419
+ const stuffRole = isCombined ? "combined" : isInput ? "input" : isOutput ? "output" : void 0;
420
+ const borderColor = isInput ? "var(--color-stuff-input-border, #50FA7B)" : isOutput ? "var(--color-stuff-output-border, #a78bfa)" : isCombined ? "var(--color-parallel-combine)" : "var(--color-stuff-border)";
493
421
  nodes.push({
494
422
  id: stuffId,
495
423
  type: NODE_TYPE_STUFF,
@@ -678,6 +606,7 @@ function applyFolds(graphData, analysis, graphspec, foldedSet, onToggleFold) {
678
606
  return { nodes: graphData.nodes, edges: graphData.edges, analysis };
679
607
  }
680
608
  const childToCtrl = buildChildToControllerMap(graphspec, analysis);
609
+ const graphMode = graphSpecMode(graphspec);
681
610
  const outputDeclarers = /* @__PURE__ */ new Map();
682
611
  for (const controllerId of foldedSet) {
683
612
  const ctrlNode = findSpecNode(graphspec, controllerId);
@@ -724,7 +653,7 @@ function applyFolds(graphData, analysis, graphspec, foldedSet, onToggleFold) {
724
653
  if (outermostFoldedAncestor(folded, childToCtrl, foldedSet)) continue;
725
654
  const specNode = findSpecNode(graphspec, folded);
726
655
  if (!specNode) continue;
727
- const payload = buildPipeCardPayload(asPipeCallNode(specNode, folded));
656
+ const payload = buildPipeCardPayload(asPipeCallNode(specNode, folded), graphMode);
728
657
  if (onToggleFold) {
729
658
  payload.onExpand = (options) => onToggleFold(folded, options);
730
659
  }
@@ -744,6 +673,7 @@ function applyFolds(graphData, analysis, graphspec, foldedSet, onToggleFold) {
744
673
  labelText: payload.pipeCode,
745
674
  pipeCode: payload.pipeCode,
746
675
  pipeType: specNode.pipe_type,
676
+ graphMode,
747
677
  pipeCardData: payload
748
678
  },
749
679
  position: { x: 0, y: 0 }
@@ -843,6 +773,62 @@ function applyFolds(graphData, analysis, graphspec, foldedSet, onToggleFold) {
843
773
  return { nodes: visibleNodes, edges: rewrittenEdges, analysis: updatedAnalysis };
844
774
  }
845
775
 
776
+ // src/graph/graphValidation.ts
777
+ function issueTargetIds(issue, graphspec) {
778
+ if (issue.nodeId) return [issue.nodeId];
779
+ if (issue.pipeCode) {
780
+ return graphspec.nodes.filter((n) => n.pipe_code === issue.pipeCode).map((n) => n.id);
781
+ }
782
+ return [];
783
+ }
784
+ function buildValidationDecorations(issues, graphspec, childToCtrl, foldedSet) {
785
+ var _a;
786
+ const decorations = /* @__PURE__ */ new Map();
787
+ if (!issues || issues.length === 0 || !graphspec) return decorations;
788
+ for (const issue of issues) {
789
+ for (const targetId of issueTargetIds(issue, graphspec)) {
790
+ const visibleId = (_a = outermostFoldedAncestor(targetId, childToCtrl, foldedSet)) != null ? _a : targetId;
791
+ const existing = decorations.get(visibleId);
792
+ const lines = [issue.message, ...issue.suggestedFix ? [`Fix: ${issue.suggestedFix}`] : []];
793
+ if (existing) {
794
+ if (issue.severity === "error") existing.severity = "error";
795
+ existing.count += 1;
796
+ existing.lines.push(...lines);
797
+ } else {
798
+ decorations.set(visibleId, { severity: issue.severity, count: 1, lines });
799
+ }
800
+ }
801
+ }
802
+ return decorations;
803
+ }
804
+ function resolveIssueTargetNodeId(issue, graphspec, childToCtrl, foldedSet, renderedNodeIds) {
805
+ var _a;
806
+ if (!graphspec) return null;
807
+ for (const targetId of issueTargetIds(issue, graphspec)) {
808
+ const visibleId = (_a = outermostFoldedAncestor(targetId, childToCtrl, foldedSet)) != null ? _a : targetId;
809
+ if (renderedNodeIds.has(visibleId)) return visibleId;
810
+ }
811
+ return null;
812
+ }
813
+ function applyValidationDecorations(nodes, decorations, onBadgeClick) {
814
+ return nodes.map((node) => {
815
+ var _a;
816
+ const decoration = decorations.get(node.id);
817
+ if (!decoration && !node.data.validation && !((_a = node.data.pipeCardData) == null ? void 0 : _a.validation)) return node;
818
+ const badgeClick = decoration ? onBadgeClick : void 0;
819
+ return __spreadProps(__spreadValues({}, node), {
820
+ data: __spreadProps(__spreadValues({}, node.data), {
821
+ validation: decoration,
822
+ onValidationBadgeClick: badgeClick,
823
+ pipeCardData: node.data.pipeCardData ? __spreadProps(__spreadValues({}, node.data.pipeCardData), {
824
+ validation: decoration,
825
+ onValidationBadgeClick: badgeClick
826
+ }) : node.data.pipeCardData
827
+ })
828
+ });
829
+ });
830
+ }
831
+
846
832
  // src/graph/elkGraphBuilder.ts
847
833
  function elkDirection(direction) {
848
834
  switch (direction) {
@@ -1266,6 +1252,7 @@ function getDescendants(controllerId, containmentTree, controllerNodeIds) {
1266
1252
  function buildControllerNodes(graphspec, analysis, layoutedNodes, controllerPositions) {
1267
1253
  var _a;
1268
1254
  const nodeById = {};
1255
+ const graphMode = graphSpecMode(graphspec);
1269
1256
  for (const n of layoutedNodes) {
1270
1257
  nodeById[n.id] = n;
1271
1258
  }
@@ -1362,7 +1349,8 @@ function buildControllerNodes(graphspec, analysis, layoutedNodes, controllerPosi
1362
1349
  isPipe: false,
1363
1350
  isStuff: false,
1364
1351
  pipeCode,
1365
- labelText: pipeCode
1352
+ labelText: pipeCode,
1353
+ graphMode
1366
1354
  },
1367
1355
  position: { x: groupX, y: groupY },
1368
1356
  style: {
@@ -1676,28 +1664,6 @@ function getPaletteColors() {
1676
1664
  }
1677
1665
 
1678
1666
  export {
1679
- KNOWN_PIPE_TYPES,
1680
- NODE_TYPE_PIPE_CARD,
1681
- NODE_TYPE_STUFF,
1682
- NODE_TYPE_CONTROLLER,
1683
- STUFF_ID_PREFIX,
1684
- stuffNodeId,
1685
- isStuffNodeId,
1686
- stuffDigestFromId,
1687
- GRAPH_DIRECTION,
1688
- EDGE_TYPE,
1689
- FOLD_MODE,
1690
- GRAPH_THEME,
1691
- GRAPH_THEME_MODE,
1692
- TOOLBAR_POSITION,
1693
- toolbarOrientation,
1694
- toolbarSide,
1695
- CONTROLLER_PADDING_X,
1696
- CONTROLLER_PADDING_TOP,
1697
- CONTROLLER_PADDING_BOTTOM,
1698
- ARROW_CLOSED_MARKER,
1699
- nodeWidth,
1700
- nodeHeight,
1701
1667
  GraphSpecValidationError,
1702
1668
  validateGraphSpec,
1703
1669
  asPipeCallNode,
@@ -1713,6 +1679,9 @@ export {
1713
1679
  buildContainmentChain,
1714
1680
  outermostFoldedAncestor,
1715
1681
  applyFolds,
1682
+ buildValidationDecorations,
1683
+ resolveIssueTargetNodeId,
1684
+ applyValidationDecorations,
1716
1685
  inputPortId,
1717
1686
  outputPortId,
1718
1687
  estimateNodeDimensions,
@@ -1728,4 +1697,4 @@ export {
1728
1697
  DEFAULT_GRAPH_CONFIG,
1729
1698
  getPaletteColors
1730
1699
  };
1731
- //# sourceMappingURL=chunk-R34J4GFM.js.map
1700
+ //# sourceMappingURL=chunk-V4FKGLKA.js.map