@pixerate/editor 0.54.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/agent/index.cjs +6 -6
  2. package/dist/agent/index.cjs.map +1 -1
  3. package/dist/agent/index.js +6 -6
  4. package/dist/animation/index.cjs +1 -1
  5. package/dist/animation/index.cjs.map +1 -1
  6. package/dist/animation/index.d.cts +2 -1
  7. package/dist/animation/index.d.ts +2 -1
  8. package/dist/animation/index.js +1 -1
  9. package/dist/animation-editor/index.cjs +2221 -0
  10. package/dist/animation-editor/index.cjs.map +1 -0
  11. package/dist/animation-editor/index.d.cts +1117 -0
  12. package/dist/animation-editor/index.d.ts +1117 -0
  13. package/dist/animation-editor/index.js +2221 -0
  14. package/dist/animation-editor/index.js.map +1 -0
  15. package/dist/bezier-BfwIbVTl.d.cts +548 -0
  16. package/dist/bezier-DYBvLgUH.d.ts +548 -0
  17. package/dist/canvas/index.cjs +713 -486
  18. package/dist/canvas/index.cjs.map +1 -1
  19. package/dist/canvas/index.d.cts +105 -5
  20. package/dist/canvas/index.d.ts +105 -5
  21. package/dist/canvas/index.js +710 -483
  22. package/dist/canvas/index.js.map +1 -1
  23. package/dist/chart/index.cjs +1 -1
  24. package/dist/chart/index.cjs.map +1 -1
  25. package/dist/chart/index.js +1 -1
  26. package/dist/{chunk-TO22LX34.js → chunk-GTMJ4Y3S.js} +2208 -2920
  27. package/dist/chunk-GTMJ4Y3S.js.map +1 -0
  28. package/dist/chunk-MLREO6J3.cjs +737 -0
  29. package/dist/chunk-MLREO6J3.cjs.map +1 -0
  30. package/dist/chunk-NONDTTH7.js +5979 -0
  31. package/dist/chunk-NONDTTH7.js.map +1 -0
  32. package/dist/{chunk-YNSK4MGT.cjs → chunk-O2UH7LZE.cjs} +1549 -2261
  33. package/dist/chunk-O2UH7LZE.cjs.map +1 -0
  34. package/dist/chunk-VWZB6BCY.js +737 -0
  35. package/dist/chunk-VWZB6BCY.js.map +1 -0
  36. package/dist/chunk-XCXQDPID.cjs +5979 -0
  37. package/dist/chunk-XCXQDPID.cjs.map +1 -0
  38. package/dist/color-E9pwm--w.d.cts +33 -0
  39. package/dist/color-E9pwm--w.d.ts +33 -0
  40. package/dist/commands/index.cjs +2 -2
  41. package/dist/commands/index.cjs.map +1 -1
  42. package/dist/commands/index.js +3 -3
  43. package/dist/diagram/index.cjs +25 -4
  44. package/dist/diagram/index.cjs.map +1 -1
  45. package/dist/diagram/index.d.cts +23 -0
  46. package/dist/diagram/index.d.ts +23 -0
  47. package/dist/diagram/index.js +33 -12
  48. package/dist/diagram/index.js.map +1 -1
  49. package/dist/pathfinder-BZL8aSu0.d.cts +469 -0
  50. package/dist/pathfinder-CNtsbCRK.d.ts +469 -0
  51. package/dist/{player-DSMjdgQ9.d.cts → player-Dg0FT0UI.d.cts} +1 -33
  52. package/dist/{player-DdfVrKWq.d.ts → player-W-5DX3Go.d.ts} +1 -33
  53. package/dist/svg-CLg2zqZA.d.cts +847 -0
  54. package/dist/svg-CPK82TtU.d.ts +847 -0
  55. package/dist/vector/index.cjs +1328 -6752
  56. package/dist/vector/index.cjs.map +1 -1
  57. package/dist/vector/index.d.cts +6 -846
  58. package/dist/vector/index.d.ts +6 -846
  59. package/dist/vector/index.js +645 -6069
  60. package/dist/vector/index.js.map +1 -1
  61. package/package.json +6 -1
  62. package/dist/chunk-TO22LX34.js.map +0 -1
  63. package/dist/chunk-YNSK4MGT.cjs.map +0 -1
  64. package/dist/pathfinder-B9xh_pCZ.d.ts +0 -1013
  65. package/dist/pathfinder-QdG70sr9.d.cts +0 -1013
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/diagram/index.cjs","../../src/diagram/types.ts","../../src/diagram/source/find.ts","../../src/diagram/controller.ts","../../src/diagram/tools.ts","../../src/diagram/draft.ts","../../src/diagram/director.ts"],"names":[],"mappings":"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QAAA;AACH,MAAA;AACoC,QAAA;AACjC,QAAA;AACK,MAAA;AAC4C,QAAA;AAC3B,QAAA;AACuD,QAAA;AAC5D,UAAA;AACiE,UAAA;AACP,UAAA;AAChD,UAAA;AACjC,QAAA;AACM,QAAA;AACR,MAAA;AACe,MAAA;AAGoD,QAAA;AAChB,QAAA;AACjB,QAAA;AAClC,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAGyI;AAC3G,EAAA;AACH,EAAA;AACsC,IAAA;AACrC,IAAA;AACnB,IAAA;AACP,EAAA;AACF;ANyuB4F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/diagram/index.cjs","sourcesContent":[null,"/**\n * Core document model shared by every diagram family.\n *\n * Design goals:\n * - **Lossless**: a document parsed and serialised without edits is\n * byte-identical to its input. Anything the diagram module does not understand is kept\n * verbatim as a passthrough statement.\n * - **Graph-first**: every editable family projects onto the same\n * {@link DiagramGraph} so that one canvas can render all of them.\n * - **Mermaid-compatible metadata**: canvas state is stored in\n * `%% @pixerate …` comments (or frontmatter) that every Mermaid parser ignores.\n *\n * @module\n */\n\n/** Mermaid diagram families the diagram module knows how to detect. */\nexport type DiagramFamily =\n | \"flowchart\"\n | \"state\"\n | \"class\"\n | \"er\"\n | \"requirement\"\n | \"mindmap\"\n | \"sequence\"\n | \"gantt\"\n | \"pie\"\n | \"journey\"\n | \"timeline\"\n | \"gitGraph\"\n | \"c4\"\n | \"quadrant\"\n | \"sankey\"\n | \"xychart\"\n | \"block\"\n | \"architecture\"\n | \"packet\"\n | \"kanban\"\n | \"radar\"\n | \"treemap\"\n | \"info\"\n | \"unknown\";\n\n/**\n * How deeply the diagram module can edit a family.\n *\n * - `editable` — statement-level lossless editing on the canvas. Every\n * family the diagram module ships an adapter for is editable; the canvas model differs\n * per family (graph, tree, timeline, chart data…).\n * - `render-only` — source editing with a live Mermaid preview; no canvas.\n * Used for families without an adapter and for unknown input.\n */\nexport type SupportTier = \"editable\" | \"render-only\";\n\n/** `[x, y]` canvas position in React Flow coordinates. */\nexport type Point = readonly [number, number];\n\n/** Line pattern of an edge. Mirrors Mermaid's `thickness` / `pattern`. */\nexport type EdgePattern = \"solid\" | \"dotted\" | \"thick\" | \"invisible\";\n\n/**\n * Arrow head identifiers, aligned with Mermaid's `arrowTypeStart` /\n * `arrowTypeEnd` values so React Flow markers can be derived uniformly.\n */\nexport type ArrowHead =\n | \"none\"\n | \"arrow_point\"\n | \"arrow_open\"\n | \"arrow_cross\"\n | \"arrow_circle\"\n | \"arrow_barb\"\n | \"extension\"\n | \"composition\"\n | \"aggregation\"\n | \"dependency\"\n | \"lollipop\"\n | \"only_one\"\n | \"zero_or_one\"\n | \"one_or_more\"\n | \"zero_or_more\"\n | \"md_parent\"\n | \"requirement_arrow\"\n | \"requirement_contains\";\n\nexport interface GraphNode {\n /** Stable identifier (the Mermaid id, e.g. `A`). */\n id: string;\n /** Display label (raw Mermaid text, markdown strings included). */\n label: string;\n /**\n * Canonical shape identifier (`rect`, `diam`, `cyl`, `stateStart`,\n * `classBox`, `erBox`…). Mermaid aliases such as `squareRect` or `diamond`\n * are normalised on parse. See `FLOWCHART_SHAPES` for the flowchart set.\n */\n shape: string;\n /** Enclosing group (subgraph / composite state / namespace) id. */\n parentId?: string;\n /** CSS class names applied with `:::`, `class`, or `classDef` defaults. */\n classes: string[];\n /** Inline `style` declarations (`fill:#f9f`, …). */\n styles: string[];\n /** `click` href target. */\n link?: string;\n linkTarget?: string;\n tooltip?: string;\n /** Family-specific payload (class members, ER attributes, state notes…). */\n data?: Record<string, unknown>;\n}\n\nexport interface GraphGroup {\n id: string;\n label: string;\n parentId?: string;\n /** Local layout direction override (`TB`, `LR`, …). */\n direction?: string;\n classes: string[];\n styles: string[];\n data?: Record<string, unknown>;\n}\n\nexport interface GraphEdge {\n /** Mermaid-compatible id (`L_A_B_0` for auto ids, or the user id). */\n id: string;\n source: string;\n target: string;\n label?: string;\n pattern: EdgePattern;\n arrowStart: ArrowHead;\n arrowEnd: ArrowHead;\n /** Minimum rank length requested with extra dashes (`---->`). */\n length: number;\n classes: string[];\n styles: string[];\n animate?: boolean;\n animation?: \"fast\" | \"slow\";\n /** True when the id was written by the user (`A e1@--> B`). */\n userDefinedId: boolean;\n data?: Record<string, unknown>;\n}\n\n/** Normalised, family-independent view of a diagram. */\nexport interface DiagramGraph {\n direction: string;\n nodes: GraphNode[];\n edges: GraphEdge[];\n groups: GraphGroup[];\n /** `classDef` declarations keyed by class name. */\n classDefs: Record<string, string[]>;\n title?: string;\n /**\n * Diagram-level, family-specific settings that are not nodes or edges\n * (pie `showData`, quadrant axis labels, gantt `dateFormat`…). Edited with\n * `setGraphData`; the keys are documented per family in `FamilyUI.diagramFields`.\n */\n data?: Record<string, unknown>;\n}\n\n/** Canvas state that Mermaid itself does not model. Persisted as metadata. */\nexport interface DiagramMeta {\n /** Schema version of the metadata payload. */\n v: 1;\n /** Node / group positions keyed by id. */\n layout?: Record<string, Point>;\n /** Explicit node sizes for resized nodes/groups. */\n sizes?: Record<string, readonly [number, number]>;\n /** Viewport the user last looked at. */\n viewport?: { x: number; y: number; zoom: number };\n /** Free-form extension slot for host applications. */\n ext?: Record<string, unknown>;\n}\n\n/** Where metadata lives in the source. */\nexport type MetaCarrier = \"comment\" | \"frontmatter\";\n\n/**\n * Lines that precede the diagram header: YAML frontmatter, `%%{init}%%`\n * directives, comments and blank lines. Kept verbatim.\n */\nexport interface Preamble {\n /** Raw lines, including the `---` fences of any frontmatter. */\n lines: string[];\n /** Parsed frontmatter when present (title/config/pixerate…). */\n frontmatter?: Record<string, unknown>;\n}\n\n/** A parse problem reported by the diagram parser or by Mermaid. */\nexport interface Diagnostic {\n severity: \"error\" | \"warning\" | \"info\";\n message: string;\n /** 1-based line in the source, when known. */\n line?: number;\n column?: number;\n source: \"pixerate\" | \"mermaid\";\n}\n\n/**\n * The parsed document. Family adapters extend `body` with their own statement\n * list; consumers that only need the graph never look at it.\n */\nexport interface MermaidDocument<TBody = unknown> {\n family: DiagramFamily;\n tier: SupportTier;\n /** Source as given to {@link parseMermaid}. */\n source: string;\n preamble: Preamble;\n /** The header line (`flowchart LR`, `stateDiagram-v2`, …) exactly as written. */\n header: string;\n /** Trailing newline / whitespace after the last statement, kept for fidelity. */\n trailing: string;\n /** End-of-line sequence detected in the source. */\n eol: \"\\n\" | \"\\r\\n\";\n graph: DiagramGraph;\n meta: DiagramMeta;\n metaCarrier: MetaCarrier;\n body: TBody;\n diagnostics: Diagnostic[];\n /** True once any edit has been applied to this document. */\n dirty: boolean;\n}\n\n/** Directions understood by flowchart / state / class / ER. */\nexport const DIRECTIONS = [\"TB\", \"TD\", \"BT\", \"LR\", \"RL\"] as const;\nexport type Direction = (typeof DIRECTIONS)[number];\n\n// ---------------------------------------------------------------------------\n// Edit operations\n// ---------------------------------------------------------------------------\n\nexport type EditOp =\n | { type: \"addNode\"; node: Partial<GraphNode> & { id: string }; position?: Point }\n | { type: \"removeNodes\"; ids: string[] }\n | { type: \"renameNode\"; id: string; label: string }\n | { type: \"setNodeShape\"; id: string; shape: string }\n | { type: \"setNodeClasses\"; id: string; classes: string[] }\n | { type: \"setNodeStyles\"; id: string; styles: string[] }\n | { type: \"setNodeLink\"; id: string; link?: string; target?: string; tooltip?: string }\n | { type: \"setNodeData\"; id: string; data: Record<string, unknown> }\n | { type: \"moveNodes\"; moves: { id: string; position: Point }[] }\n | { type: \"resizeNode\"; id: string; size: readonly [number, number] | undefined }\n | { type: \"addEdge\"; edge: Partial<GraphEdge> & { source: string; target: string } }\n | { type: \"removeEdges\"; ids: string[] }\n | { type: \"setEdgeLabel\"; id: string; label: string }\n /** Family-specific edge payload (sequence activation, message order, gantt dependency type…). */\n | { type: \"setEdgeData\"; id: string; data: Record<string, unknown> }\n | {\n type: \"setEdgeStyle\";\n id: string;\n pattern?: EdgePattern;\n arrowStart?: ArrowHead;\n arrowEnd?: ArrowHead;\n length?: number;\n styles?: string[];\n }\n | { type: \"reconnectEdge\"; id: string; source: string; target: string }\n | { type: \"groupNodes\"; groupId: string; label: string; nodeIds: string[]; parentId?: string }\n | { type: \"ungroup\"; groupId: string }\n | { type: \"renameGroup\"; id: string; label: string }\n | { type: \"setGroupDirection\"; id: string; direction?: string }\n | { type: \"setNodeParent\"; ids: string[]; parentId?: string }\n | { type: \"setDirection\"; direction: string }\n | { type: \"setClassDef\"; name: string; styles: string[] | undefined }\n | { type: \"setTitle\"; title: string | undefined }\n /** Merge diagram-level settings into `graph.data` (see {@link DiagramGraph.data}). */\n | { type: \"setGraphData\"; data: Record<string, unknown> }\n | { type: \"setViewport\"; viewport: DiagramMeta[\"viewport\"] }\n | { type: \"setMetaExt\"; key: string; value: unknown }\n | { type: \"replaceSource\"; source: string };\n\n/** Family adapter contract. See `docs/architecture.md`. */\nexport interface DiagramAdapter<TBody = unknown> {\n family: DiagramFamily;\n tier: SupportTier;\n parse(source: string, context: AdapterParseContext): MermaidDocument<TBody>;\n serialize(doc: MermaidDocument<TBody>): string;\n apply(doc: MermaidDocument<TBody>, op: EditOp): MermaidDocument<TBody>;\n}\n\n/** Pre-split source handed to adapters so they do not repeat the work. */\nexport interface AdapterParseContext {\n source: string;\n eol: \"\\n\" | \"\\r\\n\";\n preamble: Preamble;\n header: string;\n /** Body lines after the header (metadata comments already removed). */\n bodyLines: string[];\n trailing: string;\n meta: DiagramMeta;\n metaCarrier: MetaCarrier;\n family: DiagramFamily;\n}\n","import type { DiagramFamily } from \"../types.js\";\nimport { detectFamilyFromHeader, splitSource } from \"./lines.js\";\n\n/** A Mermaid diagram found in some text by {@link findMermaidSources}. */\nexport interface FoundMermaid {\n /** The diagram's Mermaid text, without any surrounding code fence. */\n source: string;\n /** Its family (never `\"unknown\"`). */\n family: DiagramFamily;\n /** The frontmatter `title`, else the Markdown heading above its code fence. */\n title?: string;\n}\n\n/** Fence info strings that mark a Mermaid block. */\nconst MERMAID_INFO = /^(mermaid|mmd)\\b/i;\n\n/**\n * Finds the Mermaid diagrams in a piece of text: a `.mmd` file, a Markdown\n * document with ```` ```mermaid ```` blocks (GitHub, Notion and most AI\n * chats write them like this), or a diagram pasted with or without its fence.\n *\n * Fenced blocks are used when there are any: those marked `mermaid` / `mmd`,\n * and unmarked ones whose first line is a Mermaid header. Otherwise the whole\n * text is one diagram if it starts with one. Unmarked text must spell the\n * keyword as Mermaid does (`timeline`, not `Timeline`). Text that holds no\n * diagram of a known family returns `[]`.\n */\nexport function findMermaidSources(text: string): FoundMermaid[] {\n const clean = text.replace(/^\\uFEFF/, \"\");\n const blocks = fencedBlocks(clean);\n if (blocks.length) {\n const found: FoundMermaid[] = [];\n for (const block of blocks) {\n if (block.info && !MERMAID_INFO.test(block.info)) continue;\n const item = asDiagram(block.body, block.heading, !block.info);\n if (item) found.push(item);\n }\n if (found.length) return found;\n }\n const whole = asDiagram(clean.trim(), undefined, true);\n return whole ? [whole] : [];\n}\n\n/**\n * `strict` (unmarked text) also wants the keyword's own case, as Mermaid\n * writes it, so prose such as \"Timeline of events\" is not taken for a diagram.\n */\nfunction asDiagram(source: string, heading?: string, strict = false): FoundMermaid | undefined {\n if (!source.trim()) return undefined;\n const split = splitSource(source);\n const family = detectFamilyFromHeader(split.header);\n if (family === \"unknown\") return undefined;\n if (strict && !/^\\s*([a-z]|C4)/.test(split.header)) return undefined;\n const fmTitle = split.preamble.frontmatter?.title;\n const title = typeof fmTitle === \"string\" && fmTitle.trim() ? fmTitle.trim() : heading;\n return { source: source.trimEnd() + \"\\n\", family, ...(title ? { title } : {}) };\n}\n\ninterface FencedBlock {\n info: string;\n body: string;\n /** The last Markdown heading before the fence. */\n heading?: string;\n}\n\n/** CommonMark-style fenced code blocks (``` or ~~~, three or more, indented up to three spaces). */\nfunction fencedBlocks(text: string): FencedBlock[] {\n const lines = text.split(/\\r\\n|\\n/);\n const blocks: FencedBlock[] = [];\n let heading: string | undefined;\n for (let i = 0; i < lines.length; i++) {\n // Patterns kept free of overlapping runs, so long lines cannot make them backtrack.\n const open = /^ {0,3}(`{3,}(?!`)|~{3,}(?!~))([^`]*)$/.exec(lines[i]!);\n if (!open) {\n const h = headingText(lines[i]!);\n if (h) heading = h;\n continue;\n }\n const fence = open[1]!;\n const close = new RegExp(`^ {0,3}${fence[0] === \"`\" ? \"`\" : \"~\"}{${fence.length},}$`);\n const body: string[] = [];\n let j = i + 1;\n while (j < lines.length && !close.test(lines[j]!.trimEnd())) body.push(lines[j++]!);\n blocks.push({ info: open[2]!.trim(), body: body.join(\"\\n\"), heading });\n i = j;\n }\n return blocks;\n}\n\n/** The text of an ATX heading (`## Title ##`), without its markers. */\nfunction headingText(line: string): string | undefined {\n const m = /^ {0,3}#{1,6}([ \\t].*)$/.exec(line);\n if (!m) return undefined;\n let text = m[1]!.trim();\n // A closing run of `#` counts only after a space (or when it is all there is).\n let end = text.length;\n while (end > 0 && text[end - 1] === \"#\") end--;\n if (end === 0 || text[end - 1] === \" \" || text[end - 1] === \"\\t\") text = text.slice(0, end).trim();\n return text || undefined;\n}\n","import { HistoryManager } from '../history/HistoryManager.js';\nimport type { Command } from '../history/types.js';\nimport { applyEdits, parseMermaid } from './document.js';\nimport { FLOWCHART_SHAPES } from './flowchart/shapes.js';\nimport { STATE_SHAPES } from './lines-grammar/state.js';\nimport { DIRECTIONS, type DiagramFamily, type EditOp, type MermaidDocument } from './types.js';\n\n/** Families that can be edited on the canvas; every other family is kept as text. */\nexport const EDITABLE_DIAGRAM_FAMILIES = ['flowchart', 'state', 'class', 'er'] as const;\n\n/** The shapes a node of `family` can take (`setNodeShape`), as canonical names. */\nexport function diagramShapes(family: DiagramFamily): string[] {\n\tif (family === 'flowchart') return FLOWCHART_SHAPES.map((shape) => shape.name);\n\tif (family === 'state') return STATE_SHAPES.map((shape) => shape.name);\n\treturn [];\n}\n\n/** Whether the family has groups (subgraphs, composite states, namespaces). ER diagrams do not. */\nexport function diagramSupportsGroups(family: DiagramFamily): boolean {\n\treturn family === 'flowchart' || family === 'state' || family === 'class';\n}\n\nconst quote = (ids: Iterable<string>) => [...ids].map((id) => `\"${id}\"`).join(', ');\n\n/**\n * Checks an edit against the document before it is applied. Returns a sentence saying\n * what is wrong and how to fix it, or `null` when the edit is valid. Ids are checked\n * against nodes, groups and edges; shapes against {@link diagramShapes}.\n */\nexport function validateDiagramEdit(doc: MermaidDocument, op: EditOp): string | null {\n\tconst { graph, family } = doc;\n\tif (op.type === 'replaceSource') return null;\n\tif (doc.tier !== 'editable')\n\t\treturn `This ${family} diagram can only be edited as Mermaid text; replace the source instead.`;\n\tconst nodes = new Set(graph.nodes.map((n) => n.id));\n\tconst groups = new Set(graph.groups.map((g) => g.id));\n\tconst edges = new Set(graph.edges.map((e) => e.id));\n\tconst anyIds = () => (nodes.size ? `existing node ids are ${quote([...nodes].slice(0, 20))}` : 'the diagram has no nodes yet');\n\tconst node = (id: string, allowGroup = false) =>\n\t\tnodes.has(id) || (allowGroup && groups.has(id)) ? null : `There is no node \"${id}\"; ${anyIds()}.`;\n\tconst group = (id: string | undefined) =>\n\t\tid === undefined || groups.has(id)\n\t\t\t? null\n\t\t\t: `There is no group \"${id}\"; ${groups.size ? `existing groups are ${quote(groups)}` : 'the diagram has no groups'}.`;\n\tconst edge = (id: string) =>\n\t\tedges.has(id)\n\t\t\t? null\n\t\t\t: `There is no edge \"${id}\"; ${edges.size ? `existing edge ids are ${quote([...edges].slice(0, 20))}` : 'the diagram has no edges yet'}.`;\n\tconst shape = (name: string | undefined) => {\n\t\tif (name === undefined) return null;\n\t\tconst shapes = diagramShapes(family);\n\t\tif (!shapes.length) return `Nodes of a ${family} diagram have no shapes to choose from.`;\n\t\treturn shapes.includes(name) ? null : `Unknown shape \"${name}\"; use one of ${quote(shapes)}.`;\n\t};\n\tconst free = (id: string) =>\n\t\t!/^[A-Za-z0-9_][\\w.-]*$/.test(id)\n\t\t\t? `Id \"${id}\" is not a valid Mermaid id; use letters, digits, \"_\", \"-\" or \".\".`\n\t\t\t: nodes.has(id) || groups.has(id)\n\t\t\t\t? `Id \"${id}\" is already used; choose another.`\n\t\t\t\t: null;\n\tconst first = (...problems: (string | null)[]) => problems.find((p) => p) ?? null;\n\n\tswitch (op.type) {\n\t\tcase 'addNode':\n\t\t\treturn first(free(op.node.id), shape(op.node.shape), group(op.node.parentId));\n\t\tcase 'removeNodes':\n\t\t\treturn first(...op.ids.map((id) => node(id, true)));\n\t\tcase 'renameNode':\n\t\t\treturn node(op.id, true);\n\t\tcase 'setNodeShape':\n\t\t\treturn first(node(op.id), shape(op.shape));\n\t\tcase 'setNodeClasses':\n\t\tcase 'setNodeStyles':\n\t\tcase 'setNodeLink':\n\t\tcase 'setNodeData':\n\t\tcase 'resizeNode':\n\t\t\treturn node(op.id, true);\n\t\tcase 'moveNodes':\n\t\t\treturn first(...op.moves.map((m) => node(m.id, true)));\n\t\tcase 'addEdge':\n\t\t\treturn first(node(op.edge.source, true), node(op.edge.target, true));\n\t\tcase 'removeEdges':\n\t\t\treturn first(...op.ids.map(edge));\n\t\tcase 'setEdgeLabel':\n\t\tcase 'setEdgeStyle':\n\t\tcase 'setEdgeData':\n\t\t\treturn edge(op.id);\n\t\tcase 'reconnectEdge':\n\t\t\treturn first(edge(op.id), node(op.source, true), node(op.target, true));\n\t\tcase 'groupNodes':\n\t\t\tif (!diagramSupportsGroups(family)) return `${family} diagrams have no groups.`;\n\t\t\treturn first(free(op.groupId), ...op.nodeIds.map((id) => node(id, true)), group(op.parentId));\n\t\tcase 'ungroup':\n\t\t\treturn group(op.groupId);\n\t\tcase 'renameGroup':\n\t\tcase 'setGroupDirection':\n\t\t\treturn group(op.id);\n\t\tcase 'setNodeParent':\n\t\t\treturn first(...op.ids.map((id) => node(id, true)), group(op.parentId));\n\t\tcase 'setDirection':\n\t\t\treturn (DIRECTIONS as readonly string[]).includes(op.direction)\n\t\t\t\t? null\n\t\t\t\t: `Unknown direction \"${op.direction}\"; use one of ${quote(DIRECTIONS)}.`;\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nexport interface DiagramControllerOptions {\n\t/** Starting Mermaid source (default: an empty `flowchart TD`). */\n\tsource?: string;\n\t/** History to record into, e.g. one shared with the rest of the app (default: a new one). */\n\thistory?: HistoryManager;\n\t/** Refuse edits (read tools still work). */\n\treadOnly?: boolean;\n\t/** Called after the source changes (edit, undo, redo, load). */\n\tonChange?: (source: string, document: MermaidDocument) => void;\n}\n\n/** A snapshot of the controller. The same object is returned until something changes. */\nexport interface DiagramState {\n\tdocument: MermaidDocument;\n\t/** The Mermaid text, always in step with `document`. */\n\tsource: string;\n\tfamily: DiagramFamily;\n\t/** True for flowchart, state, class and ER diagrams that are not read-only. */\n\teditable: boolean;\n\treadOnly: boolean;\n\t/** Selected node, group or edge ids. */\n\tselection: readonly string[];\n\t/** The problem with the last rejected edit, if any. */\n\tissue: string | null;\n\tcanUndo: boolean;\n\tcanRedo: boolean;\n\tundoLabel: string;\n\tredoLabel: string;\n\t/** Bumped on every change. */\n\trevision: number;\n}\n\nexport type DiagramActionResult =\n\t| { ok: true; document: MermaidDocument }\n\t| { ok: false; message: string };\n\n/**\n * Headless state for a Mermaid diagram editor: the parsed document, a selection and\n * undoable statement-level edits. Every `apply` is one undo step and only rewrites the\n * statements it touches, so untouched text (comments, directives, formatting) survives.\n * Canvas positions go into the `%% @pixerate {…}` comment. Drags merge into one undo\n * step between `beginGesture` and `endGesture`. `getState()` returns the same object\n * until something changes, so it works with `useSyncExternalStore` and Svelte runes.\n */\nexport class DiagramController {\n\treadonly history: HistoryManager;\n\tprivate doc: MermaidDocument;\n\tprivate selection: string[] = [];\n\tprivate issue: string | null = null;\n\tprivate readOnly: boolean;\n\tprivate revision = 0;\n\tprivate applying = false;\n\tprivate gestureKey: string | null = null;\n\tprivate gestureCount = 0;\n\tprivate readonly onChange?: DiagramControllerOptions['onChange'];\n\tprivate readonly listeners = new Set<(state: DiagramState) => void>();\n\tprivate unsubscribeHistory?: () => void;\n\tprivate state!: DiagramState;\n\n\tconstructor(options: DiagramControllerOptions = {}) {\n\t\tthis.history = options.history ?? new HistoryManager();\n\t\tthis.onChange = options.onChange;\n\t\tthis.readOnly = options.readOnly ?? false;\n\t\tthis.doc = parseMermaid(options.source ?? 'flowchart TD\\n');\n\t\tthis.state = this.computeState();\n\t\tthis.connect();\n\t}\n\n\tprivate connect() {\n\t\tthis.unsubscribeHistory?.();\n\t\tthis.unsubscribeHistory = this.history.subscribe((h) => {\n\t\t\tconst s = this.state;\n\t\t\tif (this.applying || (s.canUndo === h.canUndo && s.canRedo === h.canRedo && s.undoLabel === h.undoName && s.redoLabel === h.redoName)) return;\n\t\t\tthis.notify(false);\n\t\t});\n\t}\n\n\t/** Stops listening to the history (subscribing again reconnects). */\n\tdestroy() {\n\t\tthis.unsubscribeHistory?.();\n\t\tthis.unsubscribeHistory = undefined;\n\t}\n\n\t/** The current snapshot (stable until the next change). */\n\tgetState = (): DiagramState => this.state;\n\n\t/** Calls `listener` after every change. Returns an unsubscribe function. */\n\tsubscribe = (listener: (state: DiagramState) => void): (() => void) => {\n\t\tif (!this.unsubscribeHistory) this.connect();\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t};\n\n\t/** The current document (immutable; edit through the controller). */\n\tgetDocument(): MermaidDocument {\n\t\treturn this.doc;\n\t}\n\n\tgetSource(): string {\n\t\treturn this.doc.source;\n\t}\n\n\tprivate computeState(): DiagramState {\n\t\tconst h = this.history.getState();\n\t\treturn {\n\t\t\tdocument: this.doc,\n\t\t\tsource: this.doc.source,\n\t\t\tfamily: this.doc.family,\n\t\t\teditable: this.doc.tier === 'editable' && !this.readOnly,\n\t\t\treadOnly: this.readOnly,\n\t\t\tselection: this.selection,\n\t\t\tissue: this.issue,\n\t\t\tcanUndo: h.canUndo,\n\t\t\tcanRedo: h.canRedo,\n\t\t\tundoLabel: h.undoName,\n\t\t\tredoLabel: h.redoName,\n\t\t\trevision: this.revision\n\t\t};\n\t}\n\n\tprivate notify(sourceChanged: boolean) {\n\t\tthis.revision++;\n\t\tthis.state = this.computeState();\n\t\tif (sourceChanged) this.onChange?.(this.doc.source, this.doc);\n\t\tconst state = this.state;\n\t\tthis.listeners.forEach((listener) => {\n\t\t\ttry {\n\t\t\t\tlistener(state);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[DiagramController] Error in state listener:', error);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate setDocument(doc: MermaidDocument) {\n\t\tthis.applying = true;\n\t\ttry {\n\t\t\tthis.doc = doc;\n\t\t\tconst ids = new Set([...doc.graph.nodes, ...doc.graph.groups, ...doc.graph.edges].map((x) => x.id));\n\t\t\tthis.selection = this.selection.filter((id) => ids.has(id));\n\t\t} finally {\n\t\t\tthis.applying = false;\n\t\t}\n\t\tthis.notify(true);\n\t}\n\n\tprivate commit(label: string, next: MermaidDocument) {\n\t\tconst before = this.doc;\n\t\tthis.issue = null;\n\t\tif (next.source === before.source) {\n\t\t\tthis.notify(false);\n\t\t\treturn;\n\t\t}\n\t\tconst command: Command = {\n\t\t\tname: label,\n\t\t\ttag: 'diagram',\n\t\t\texecute: () => this.setDocument(next),\n\t\t\tundo: () => this.setDocument(before),\n\t\t\tredo: () => this.setDocument(next),\n\t\t\tbyteSize: (before.source.length + next.source.length) * 2\n\t\t};\n\t\tif (this.gestureKey !== null) this.history.executeMerged(command, { key: `diagram.gesture:${this.gestureKey}:${this.gestureCount}` });\n\t\telse this.history.execute(command);\n\t}\n\n\tprivate reject(message: string): DiagramActionResult {\n\t\tthis.issue = message;\n\t\tthis.notify(false);\n\t\treturn { ok: false, message };\n\t}\n\n\t/**\n\t * Applies edits as one undo step. All-or-nothing: every edit is checked against the\n\t * document as it stands after the edits before it, and nothing changes when one fails.\n\t */\n\tapply(ops: EditOp | EditOp[], label = 'Edit diagram'): DiagramActionResult {\n\t\tconst list = Array.isArray(ops) ? ops : [ops];\n\t\tif (this.readOnly) return this.reject('The diagram is read-only.');\n\t\tlet doc = this.doc;\n\t\tfor (const op of list) {\n\t\t\tconst problem = validateDiagramEdit(doc, op);\n\t\t\tif (problem) return this.reject(problem);\n\t\t\tdoc = applyEdits(doc, [op]);\n\t\t}\n\t\tthis.commit(label, doc);\n\t\treturn { ok: true, document: this.doc };\n\t}\n\n\t/**\n\t * Replaces the whole source. `clearHistory` drops the undo stack (opening a file);\n\t * otherwise the replacement is one undo step.\n\t */\n\tload(source: string, options: { clearHistory?: boolean } = {}): DiagramActionResult {\n\t\tconst next = parseMermaid(source, { metaCarrier: this.doc.metaCarrier });\n\t\tif (options.clearHistory) {\n\t\t\tthis.history.clear();\n\t\t\tthis.issue = null;\n\t\t\tthis.selection = [];\n\t\t\tthis.setDocument(next);\n\t\t\treturn { ok: true, document: next };\n\t\t}\n\t\tif (this.readOnly) return this.reject('The diagram is read-only.');\n\t\tthis.commit('Replace diagram', next);\n\t\treturn { ok: true, document: this.doc };\n\t}\n\n\tsetReadOnly(readOnly: boolean) {\n\t\tif (this.readOnly === readOnly) return;\n\t\tthis.readOnly = readOnly;\n\t\tthis.notify(false);\n\t}\n\n\t/** Selects node, group or edge ids (unknown ids are dropped). */\n\tselect(ids: readonly string[]) {\n\t\tconst known = new Set([...this.doc.graph.nodes, ...this.doc.graph.groups, ...this.doc.graph.edges].map((x) => x.id));\n\t\tconst next = ids.filter((id) => known.has(id));\n\t\tif (next.length === this.selection.length && next.every((id, i) => id === this.selection[i])) return;\n\t\tthis.selection = next;\n\t\tthis.notify(false);\n\t}\n\n\t/** Edits until `endGesture` (e.g. the moves of one drag) fold into one undo step. */\n\tbeginGesture(key: string) {\n\t\tthis.gestureKey = key;\n\t\tthis.gestureCount++;\n\t}\n\n\tendGesture() {\n\t\tthis.gestureKey = null;\n\t}\n\n\tundo(): boolean {\n\t\treturn this.history.undo();\n\t}\n\n\tredo(): boolean {\n\t\treturn this.history.redo();\n\t}\n\n\t/** A node id not used by any node or group: `prefix` followed by the next free number. */\n\tnextId(prefix = 'N'): string {\n\t\tconst used = new Set([...this.doc.graph.nodes, ...this.doc.graph.groups].map((x) => x.id));\n\t\tlet i = 1;\n\t\twhile (used.has(`${prefix}${i}`)) i++;\n\t\treturn `${prefix}${i}`;\n\t}\n}\n","import type { AgentTool, AgentToolProvider } from '../tools/registry.js';\nimport type { JsonSchema } from '../tools/schema.js';\nimport type { PresenceActorInfo } from '../animation/presence.js';\nimport { diagramShapes, diagramSupportsGroups, type DiagramController } from './controller.js';\nimport type { DiagramDirector } from './director.js';\nimport { detectFamily } from './document.js';\nimport { findMermaidSources } from './source/find.js';\nimport { DIRECTIONS, type EditOp, type MermaidDocument, type Point } from './types.js';\n\nexport interface DiagramToolProviderOptions {\n\t/** Provider id (unique per registry). Default `diagram`. */\n\tid?: string;\n\t/** Always prefix tool names with `<namespace>__`. */\n\tnamespace?: string;\n\t/**\n\t * The diagram cannot be edited: write tools are hidden and `get_diagram` says why. A\n\t * string is the reason (\"The report is locked\"). The controller's own `readOnly` counts too.\n\t */\n\treadOnly?: boolean | string | (() => boolean | string);\n\t/**\n\t * Where the canvas shows each node now (absolute top-left), e.g. `() => canvas.layout.positions`.\n\t * When given, a move pins every node that has no stored position yet, so moving one\n\t * node of an auto-laid diagram does not shift the others.\n\t */\n\tgetPositions?: () => Readonly<Record<string, Point>>;\n\t/**\n\t * Acts out every write with a visible cursor (see `DiagramDirector`): it moves to the node,\n\t * clicks, drags, connects and types, and a whole-diagram load is drawn statement by\n\t * statement. Each call is still one undo step. Without it, edits apply at once.\n\t */\n\tdirector?: DiagramDirector;\n\t/** Who the cursor belongs to (default `{ id: 'agent', name: 'Agent' }`). */\n\tactor?: PresenceActorInfo;\n}\n\nconst READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false } as const;\nconst WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false } as const;\nconst WRITE_IDEMPOTENT = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false } as const;\nconst DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false } as const;\n\nconst ID = { type: 'string', minLength: 1, description: 'A node id from get_diagram (e.g. \"A\").' } as const;\nconst IDS = (what: string) => ({ type: 'array', items: { type: 'string', minLength: 1 }, minItems: 1, description: what }) as const;\nconst EDGE_ID = { type: 'string', minLength: 1, description: 'An edge id from get_diagram (e.g. \"L_A_B_0\").' } as const;\nconst LABEL = (what: string) => ({ type: 'string', description: what }) as const;\nconst object = (properties: Record<string, JsonSchema>, required: string[] = []) => ({\n\ttype: 'object' as const,\n\tproperties,\n\t...(required.length ? { required } : {}),\n\tadditionalProperties: false\n});\n\n/** What an agent needs to know about a document: ids, labels, shapes, structure. */\nexport function describeDiagram(doc: MermaidDocument) {\n\tconst { graph } = doc;\n\treturn {\n\t\tfamily: doc.family,\n\t\teditable: doc.tier === 'editable',\n\t\tdirection: graph.direction,\n\t\t...(graph.title ? { title: graph.title } : {}),\n\t\tnodes: graph.nodes.map((n) => ({\n\t\t\tid: n.id,\n\t\t\tlabel: n.label,\n\t\t\tshape: n.shape,\n\t\t\t...(n.parentId ? { parentId: n.parentId } : {}),\n\t\t\t...(n.classes.length ? { classes: n.classes } : {}),\n\t\t\t...(n.styles.length ? { styles: n.styles } : {}),\n\t\t\t...(n.data?.synthetic ? { synthetic: true } : {})\n\t\t})),\n\t\tedges: graph.edges.map((e) => ({\n\t\t\tid: e.id,\n\t\t\tsource: e.source,\n\t\t\ttarget: e.target,\n\t\t\t...(e.label ? { label: e.label } : {}),\n\t\t\tpattern: e.pattern,\n\t\t\tarrowEnd: e.arrowEnd\n\t\t})),\n\t\tgroups: graph.groups.map((g) => ({\n\t\t\tid: g.id,\n\t\t\tlabel: g.label,\n\t\t\t...(g.parentId ? { parentId: g.parentId } : {}),\n\t\t\t...(g.direction ? { direction: g.direction } : {})\n\t\t})),\n\t\tshapes: diagramShapes(doc.family),\n\t\tpositions: Object.fromEntries(Object.entries(doc.meta.layout ?? {}).map(([id, [x, y]]) => [id, { x: Math.round(x), y: Math.round(y) }])),\n\t\t...(doc.diagnostics.length ? { diagnostics: doc.diagnostics.map((d) => ({ severity: d.severity, message: d.message, ...(d.line ? { line: d.line } : {}) })) } : {})\n\t};\n}\n\n/**\n * Agent tools for a Mermaid diagram (`DiagramController`), for a `ToolRegistry`: one\n * tool per operation with strict schemas. Reads (`get_diagram`, `export_diagram_mermaid`,\n * `find_mermaid_diagrams`) are always there; writes are hidden while the diagram is\n * read-only. Every write is checked against the current diagram first (unknown ids,\n * taken ids, shapes the family does not have), applies as one undo step, and returns\n * what it created or changed. Untouched Mermaid statements are kept byte for byte.\n */\nexport function createDiagramToolProvider(controller: DiagramController, options: DiagramToolProviderOptions = {}): AgentToolProvider {\n\tconst readOnlyReason = (): string | null => {\n\t\tconst r = typeof options.readOnly === 'function' ? options.readOnly() : options.readOnly;\n\t\tif (r) return typeof r === 'string' ? r : 'This diagram is read-only.';\n\t\treturn controller.getState().readOnly ? 'This diagram is read-only.' : null;\n\t};\n\tconst doc = () => controller.getDocument();\n\tconst actor = options.actor ?? { id: 'agent', name: 'Agent' };\n\tconst apply = async (ops: EditOp | EditOp[], label: string) => {\n\t\tconst result = options.director ? await options.director.perform(actor, Array.isArray(ops) ? ops : [ops], label) : controller.apply(ops, label);\n\t\tif (!result.ok) throw new Error(result.message);\n\t\treturn result.document;\n\t};\n\tconst edgeIds = () => new Set(doc().graph.edges.map((e) => e.id));\n\tconst newEdges = (before: Set<string>, after: MermaidDocument) =>\n\t\tafter.graph.edges.filter((e) => !before.has(e.id)).map((e) => ({ id: e.id, source: e.source, target: e.target, ...(e.label ? { label: e.label } : {}) }));\n\tconst nodeOut = (d: MermaidDocument, id: string) => {\n\t\tconst n = d.graph.nodes.find((x) => x.id === id) ?? d.graph.groups.find((x) => x.id === id);\n\t\treturn n ? { id: n.id, label: n.label, ...('shape' in n ? { shape: n.shape } : {}), ...(n.parentId ? { parentId: n.parentId } : {}) } : { id };\n\t};\n\t/** Moves plus the current positions of nodes not stored yet (see `getPositions`). */\n\tconst pinned = (moves: Array<{ id: string; position: Point }>) => {\n\t\tconst shown = options.getPositions?.();\n\t\tif (!shown) return moves;\n\t\tconst stored = doc().meta.layout ?? {};\n\t\tconst taken = new Set(moves.map((m) => m.id));\n\t\treturn [...moves, ...Object.entries(shown).filter(([id]) => !taken.has(id) && !stored[id]).map(([id, position]) => ({ id, position }))];\n\t};\n\tconst requireEditable = () => {\n\t\tif (doc().tier !== 'editable')\n\t\t\tthrow new Error(`This ${doc().family} diagram can only be changed as Mermaid text; use load_diagram_mermaid.`);\n\t};\n\n\tconst getDiagram: AgentTool = {\n\t\tname: 'get_diagram',\n\t\ttitle: 'Read the diagram',\n\t\tdescription:\n\t\t\t'Returns the diagram: its Mermaid family (flowchart, state, class, er; other families are text only), direction, nodes (id, label, shape, parentId), edges (id, source, target, label), groups (subgraphs / composite states), the shapes nodes can take, stored canvas positions, any parse diagnostics and the selection. Use the ids it returns with the write tools.',\n\t\tinputSchema: object({ includeSource: { type: 'boolean', description: 'Also return the Mermaid text (default false).' } }),\n\t\tannotations: { title: 'Read the diagram', ...READ },\n\t\thandler: async ({ includeSource = false }) => {\n\t\t\tconst reason = readOnlyReason();\n\t\t\treturn {\n\t\t\t\t...describeDiagram(doc()),\n\t\t\t\tselection: controller.getState().selection,\n\t\t\t\t...(includeSource ? { source: doc().source } : {}),\n\t\t\t\t...(reason ? { readOnly: reason } : {})\n\t\t\t};\n\t\t}\n\t};\n\n\tconst exportMermaid: AgentTool = {\n\t\tname: 'export_diagram_mermaid',\n\t\ttitle: 'Export the diagram as Mermaid',\n\t\tdescription:\n\t\t\t'Returns the diagram as Mermaid text. Canvas positions are kept in a \"%% @pixerate {…}\" comment that Mermaid ignores; pass withLayout false to leave it out.',\n\t\tinputSchema: object({ withLayout: { type: 'boolean', description: 'Keep the %% @pixerate layout comment (default true).' } }),\n\t\tannotations: { title: 'Export the diagram as Mermaid', ...READ },\n\t\thandler: async ({ withLayout = true }) => {\n\t\t\tconst source = doc().source;\n\t\t\treturn { family: doc().family, source: withLayout ? source : source.replace(/^[ \\t]*%%\\s*@(?:pixerate|ursula)\\s+\\{.*\\}[ \\t]*(\\r?\\n|$)/gm, '') };\n\t\t}\n\t};\n\n\tconst findDiagrams: AgentTool = {\n\t\tname: 'find_mermaid_diagrams',\n\t\ttitle: 'Find Mermaid diagrams in text',\n\t\tdescription:\n\t\t\t'Finds the Mermaid diagrams in a piece of text (Markdown with ```mermaid blocks, a chat reply, or a bare diagram) without changing anything. Returns each diagram\\'s source, family and title; pass a source to load_diagram_mermaid to open it.',\n\t\tinputSchema: object({ text: { type: 'string', description: 'The text to search.' } }, ['text']),\n\t\tannotations: { title: 'Find Mermaid diagrams in text', ...READ },\n\t\thandler: ({ text }) => ({ diagrams: findMermaidSources(text) })\n\t};\n\n\tconst loadMermaid: AgentTool = {\n\t\tname: 'load_diagram_mermaid',\n\t\ttitle: 'Replace the diagram with Mermaid',\n\t\tdescription:\n\t\t\t'Replaces the whole diagram with Mermaid text (one undo step). Flowchart, state, class and ER diagrams become editable on the canvas; other families are kept as text. Returns the family, node and edge counts and any parse diagnostics. Prefer the per-operation tools for small changes: they keep the rest of the text as written.',\n\t\tinputSchema: object({ source: { type: 'string', minLength: 1, description: 'Mermaid text, starting with a header such as \"flowchart LR\".' } }, ['source']),\n\t\tannotations: { title: 'Replace the diagram with Mermaid', ...DESTRUCTIVE },\n\t\thandler: async ({ source }) => {\n\t\t\t// A fenced block (```mermaid … ```) is unwrapped; a bare diagram is taken as written.\n\t\t\tconst fenced = /^\\s*(`{3,}|~{3,})/m.test(source) ? findMermaidSources(source) : [];\n\t\t\tif (fenced.length > 1) throw new Error(`The text holds ${fenced.length} diagrams; pass one (find_mermaid_diagrams lists them).`);\n\t\t\tconst text = fenced[0]?.source ?? source;\n\t\t\tif (detectFamily(text) === 'unknown')\n\t\t\t\tthrow new Error('The text has no Mermaid header (e.g. \"flowchart LR\", \"stateDiagram-v2\", \"classDiagram\", \"erDiagram\"); nothing was changed.');\n\t\t\tconst result = options.director ? await options.director.playSource(actor, text) : controller.load(text);\n\t\t\tif (!result.ok) throw new Error(result.message);\n\t\t\tconst d = result.document;\n\t\t\treturn {\n\t\t\t\tfamily: d.family,\n\t\t\t\teditable: d.tier === 'editable',\n\t\t\t\tnodes: d.graph.nodes.length,\n\t\t\t\tedges: d.graph.edges.length,\n\t\t\t\tgroups: d.graph.groups.length,\n\t\t\t\t...(d.diagnostics.length ? { diagnostics: d.diagnostics.map((x) => x.message) } : {})\n\t\t\t};\n\t\t}\n\t};\n\n\tconst addNode: AgentTool = {\n\t\tname: 'add_diagram_node',\n\t\ttitle: 'Add a node',\n\t\tdescription:\n\t\t\t'Adds a node to the diagram, optionally inside a group and connected from an existing node. Without a position the canvas places it beside its neighbours. Returns the node and any edge created.',\n\t\tinputSchema: object(\n\t\t\t{\n\t\t\t\tlabel: LABEL('Text shown in the node.'),\n\t\t\t\tid: { type: 'string', pattern: '^[A-Za-z0-9_][\\\\w.-]*$', description: 'Mermaid id (default: the next free \"N<number>\").' },\n\t\t\t\tshape: { type: 'string', description: 'One of the shapes get_diagram lists (default \"rect\"). Flowchart and state diagrams only.' },\n\t\t\t\tparentId: { type: 'string', description: 'Group (subgraph) to add the node to.' },\n\t\t\t\tfrom: { type: 'string', description: 'An existing node to connect to the new node.' },\n\t\t\t\tedgeLabel: LABEL('Label of the edge from \"from\" (needs \"from\").'),\n\t\t\t\tx: { type: 'number', description: 'Left edge in canvas px (needs y).' },\n\t\t\t\ty: { type: 'number', description: 'Top edge in canvas px (needs x).' }\n\t\t\t},\n\t\t\t['label']\n\t\t),\n\t\tannotations: { title: 'Add a node', ...WRITE },\n\t\thandler: async ({ label, id, shape, parentId, from, edgeLabel, x, y }) => {\n\t\t\trequireEditable();\n\t\t\tif ((x === undefined) !== (y === undefined)) throw new Error('Give both x and y, or neither to place the node automatically.');\n\t\t\tif (edgeLabel !== undefined && from === undefined) throw new Error('edgeLabel needs \"from\", the node the edge starts at.');\n\t\t\tconst nodeId = id ?? controller.nextId();\n\t\t\tconst before = edgeIds();\n\t\t\tconst ops: EditOp[] = [\n\t\t\t\t{ type: 'addNode', node: { id: nodeId, label, ...(shape ? { shape } : {}), ...(parentId ? { parentId } : {}) }, ...(x !== undefined ? { position: [x, y] as const } : {}) }\n\t\t\t];\n\t\t\tif (from !== undefined) ops.push({ type: 'addEdge', edge: { source: from, target: nodeId, ...(edgeLabel ? { label: edgeLabel } : {}) } });\n\t\t\tconst d = await apply(ops, 'Add node');\n\t\t\tconst edges = newEdges(before, d);\n\t\t\treturn { node: nodeOut(d, nodeId), ...(edges.length ? { edge: edges[0] } : {}) };\n\t\t}\n\t};\n\n\tconst removeElements: AgentTool = {\n\t\tname: 'remove_diagram_nodes',\n\t\ttitle: 'Remove nodes or groups',\n\t\tdescription:\n\t\t\t'Deletes nodes with every edge that touches them. A group id removes the group but keeps its contents. Returns the removed ids and the ids of the edges that went with them.',\n\t\tinputSchema: object({ ids: IDS('Node or group ids from get_diagram.') }, ['ids']),\n\t\tannotations: { title: 'Remove nodes or groups', ...DESTRUCTIVE },\n\t\thandler: async ({ ids }) => {\n\t\t\trequireEditable();\n\t\t\tconst before = doc().graph.edges.map((e) => e.id);\n\t\t\tconst d = await apply({ type: 'removeNodes', ids }, ids.length === 1 ? 'Delete node' : 'Delete nodes');\n\t\t\tconst after = new Set(d.graph.edges.map((e) => e.id));\n\t\t\treturn { removed: ids, removedEdges: before.filter((id) => !after.has(id)) };\n\t\t}\n\t};\n\n\tconst renameNode: AgentTool = {\n\t\tname: 'rename_diagram_node',\n\t\ttitle: 'Rename a node or group',\n\t\tdescription: 'Changes the label of a node or group. Returns the node with its new label.',\n\t\tinputSchema: object({ id: { ...ID, description: 'A node or group id from get_diagram.' }, label: LABEL('The new label.') }, ['id', 'label']),\n\t\tannotations: { title: 'Rename a node or group', ...WRITE_IDEMPOTENT },\n\t\thandler: async ({ id, label }) => {\n\t\t\trequireEditable();\n\t\t\tconst isGroup = doc().graph.groups.some((g) => g.id === id);\n\t\t\tconst d = await apply(isGroup ? { type: 'renameGroup', id, label } : { type: 'renameNode', id, label }, 'Rename');\n\t\t\treturn { node: nodeOut(d, id) };\n\t\t}\n\t};\n\n\tconst setShape: AgentTool = {\n\t\tname: 'set_diagram_node_shape',\n\t\ttitle: 'Change a node\\'s shape',\n\t\tdescription: 'Changes a node\\'s shape (flowchart and state diagrams). Use one of the shapes get_diagram lists. Returns the node.',\n\t\tinputSchema: object({ id: ID, shape: { type: 'string', minLength: 1, description: 'Shape name, e.g. \"rect\", \"rounded\", \"diam\", \"cyl\", \"circle\".' } }, ['id', 'shape']),\n\t\tannotations: { title: 'Change a node\\'s shape', ...WRITE_IDEMPOTENT },\n\t\thandler: async ({ id, shape }) => {\n\t\t\trequireEditable();\n\t\t\treturn { node: nodeOut(await apply({ type: 'setNodeShape', id, shape }, 'Change shape'), id) };\n\t\t}\n\t};\n\n\tconst setStyle: AgentTool = {\n\t\tname: 'set_diagram_node_style',\n\t\ttitle: 'Style a node',\n\t\tdescription:\n\t\t\t'Replaces a node\\'s inline Mermaid styles (written as a `style` statement), e.g. [\"fill:#fde68a\", \"stroke:#b45309\", \"stroke-width:2px\"]. An empty list removes them. Returns the node with its styles.',\n\t\tinputSchema: object(\n\t\t\t{ id: ID, styles: { type: 'array', items: { type: 'string', pattern: '^[\\\\w-]+:[^,;]+$' }, description: 'CSS-like \"property:value\" pairs.' } },\n\t\t\t['id', 'styles']\n\t\t),\n\t\tannotations: { title: 'Style a node', ...WRITE_IDEMPOTENT },\n\t\thandler: async ({ id, styles }) => {\n\t\t\trequireEditable();\n\t\t\tconst d = await apply({ type: 'setNodeStyles', id, styles }, 'Style node');\n\t\t\treturn { id, styles: d.graph.nodes.find((n) => n.id === id)?.styles ?? [] };\n\t\t}\n\t};\n\n\tconst connect: AgentTool = {\n\t\tname: 'connect_diagram_nodes',\n\t\ttitle: 'Connect two nodes',\n\t\tdescription: 'Draws an edge from one existing node to another, with an optional label. Returns the new edge with its id.',\n\t\tinputSchema: object({ source: { ...ID, description: 'Node the edge starts at.' }, target: { ...ID, description: 'Node the edge ends at.' }, label: LABEL('Edge label (optional).') }, ['source', 'target']),\n\t\tannotations: { title: 'Connect two nodes', ...WRITE },\n\t\thandler: async ({ source, target, label }) => {\n\t\t\trequireEditable();\n\t\t\tconst before = edgeIds();\n\t\t\tconst d = await apply({ type: 'addEdge', edge: { source, target, ...(label ? { label } : {}) } }, 'Connect');\n\t\t\treturn { edge: newEdges(before, d)[0] ?? null };\n\t\t}\n\t};\n\n\tconst removeEdges: AgentTool = {\n\t\tname: 'remove_diagram_edges',\n\t\ttitle: 'Remove edges',\n\t\tdescription: 'Deletes edges by id; the nodes stay. Returns the removed ids.',\n\t\tinputSchema: object({ ids: IDS('Edge ids from get_diagram.') }, ['ids']),\n\t\tannotations: { title: 'Remove edges', ...DESTRUCTIVE },\n\t\thandler: async ({ ids }) => {\n\t\t\trequireEditable();\n\t\t\tawait apply({ type: 'removeEdges', ids }, ids.length === 1 ? 'Delete edge' : 'Delete edges');\n\t\t\treturn { removed: ids };\n\t\t}\n\t};\n\n\tconst labelEdge: AgentTool = {\n\t\tname: 'set_diagram_edge_label',\n\t\ttitle: 'Label an edge',\n\t\tdescription: 'Sets an edge\\'s label; an empty string removes it. Returns the edge.',\n\t\tinputSchema: object({ id: EDGE_ID, label: LABEL('The new label (\"\" removes it).') }, ['id', 'label']),\n\t\tannotations: { title: 'Label an edge', ...WRITE_IDEMPOTENT },\n\t\thandler: async ({ id, label }) => {\n\t\t\trequireEditable();\n\t\t\tconst d = await apply({ type: 'setEdgeLabel', id, label }, 'Label edge');\n\t\t\tconst e = d.graph.edges.find((x) => x.id === id);\n\t\t\treturn { edge: e ? { id: e.id, source: e.source, target: e.target, label: e.label ?? '' } : { id, label } };\n\t\t}\n\t};\n\n\tconst reconnect: AgentTool = {\n\t\tname: 'reconnect_diagram_edge',\n\t\ttitle: 'Reconnect an edge',\n\t\tdescription: 'Moves an edge to new endpoints, keeping its label and style. Returns the edge (its id may change with its endpoints).',\n\t\tinputSchema: object({ id: EDGE_ID, source: { ...ID, description: 'New start node.' }, target: { ...ID, description: 'New end node.' } }, ['id', 'source', 'target']),\n\t\tannotations: { title: 'Reconnect an edge', ...WRITE },\n\t\thandler: async ({ id, source, target }) => {\n\t\t\trequireEditable();\n\t\t\tconst index = doc().graph.edges.findIndex((e) => e.id === id);\n\t\t\tconst d = await apply({ type: 'reconnectEdge', id, source, target }, 'Reconnect edge');\n\t\t\tconst e = d.graph.edges.find((x) => x.id === id) ?? d.graph.edges.find((x, i) => i === index && x.source === source && x.target === target) ?? d.graph.edges.find((x) => x.source === source && x.target === target);\n\t\t\treturn { edge: e ? { id: e.id, source: e.source, target: e.target, ...(e.label ? { label: e.label } : {}) } : null };\n\t\t}\n\t};\n\n\tconst setDirection: AgentTool = {\n\t\tname: 'set_diagram_direction',\n\t\ttitle: 'Set the layout direction',\n\t\tdescription: 'Sets the direction the diagram flows in: TB / TD (top to bottom), BT, LR (left to right) or RL. Returns the direction.',\n\t\tinputSchema: object({ direction: { type: 'string', enum: [...DIRECTIONS], description: 'TB, TD, BT, LR or RL.' } }, ['direction']),\n\t\tannotations: { title: 'Set the layout direction', ...WRITE_IDEMPOTENT },\n\t\thandler: async ({ direction }) => {\n\t\t\trequireEditable();\n\t\t\treturn { direction: (await apply({ type: 'setDirection', direction }, 'Change direction')).graph.direction };\n\t\t}\n\t};\n\n\tconst group: AgentTool = {\n\t\tname: 'group_diagram_nodes',\n\t\ttitle: 'Group nodes',\n\t\tdescription: 'Puts nodes into a new group (a flowchart subgraph, composite state or class namespace). Returns the group id.',\n\t\tinputSchema: object(\n\t\t\t{\n\t\t\t\tnodeIds: IDS('Node ids to put in the group.'),\n\t\t\t\tlabel: LABEL('Group title.'),\n\t\t\t\tid: { type: 'string', pattern: '^[A-Za-z0-9_][\\\\w.-]*$', description: 'Group id (default: the next free \"G<number>\").' }\n\t\t\t},\n\t\t\t['nodeIds', 'label']\n\t\t),\n\t\tannotations: { title: 'Group nodes', ...WRITE },\n\t\thandler: async ({ nodeIds, label, id }) => {\n\t\t\trequireEditable();\n\t\t\tif (!diagramSupportsGroups(doc().family)) throw new Error(`${doc().family} diagrams have no groups.`);\n\t\t\tconst groupId = id ?? controller.nextId('G');\n\t\t\tawait apply({ type: 'groupNodes', groupId, label, nodeIds }, 'Group');\n\t\t\treturn { group: { id: groupId, label, nodeIds } };\n\t\t}\n\t};\n\n\tconst ungroup: AgentTool = {\n\t\tname: 'ungroup_diagram_group',\n\t\ttitle: 'Ungroup',\n\t\tdescription: 'Removes a group and keeps its nodes (they move to the group\\'s parent). Returns the ids that were in it.',\n\t\tinputSchema: object({ id: { type: 'string', minLength: 1, description: 'A group id from get_diagram.' } }, ['id']),\n\t\tannotations: { title: 'Ungroup', ...DESTRUCTIVE },\n\t\thandler: async ({ id }) => {\n\t\t\trequireEditable();\n\t\t\tconst members = [...doc().graph.nodes, ...doc().graph.groups].filter((x) => x.parentId === id).map((x) => x.id);\n\t\t\tawait apply({ type: 'ungroup', groupId: id }, 'Ungroup');\n\t\t\treturn { ungrouped: id, members };\n\t\t}\n\t};\n\n\tconst move: AgentTool = {\n\t\tname: 'move_diagram_nodes',\n\t\ttitle: 'Move nodes',\n\t\tdescription:\n\t\t\t'Moves nodes or groups on the canvas to absolute positions (top-left, canvas px). Positions are stored in the %% @pixerate comment; the Mermaid statements do not change. Returns the new positions.',\n\t\tinputSchema: object(\n\t\t\t{\n\t\t\t\tmoves: {\n\t\t\t\t\ttype: 'array',\n\t\t\t\t\tminItems: 1,\n\t\t\t\t\tdescription: 'One entry per node.',\n\t\t\t\t\titems: object({ id: { ...ID, description: 'Node or group id.' }, x: { type: 'number', description: 'Left edge in px.' }, y: { type: 'number', description: 'Top edge in px.' } }, ['id', 'x', 'y'])\n\t\t\t\t}\n\t\t\t},\n\t\t\t['moves']\n\t\t),\n\t\tannotations: { title: 'Move nodes', ...WRITE_IDEMPOTENT },\n\t\thandler: async ({ moves }) => {\n\t\t\tconst list = moves as Array<{ id: string; x: number; y: number }>;\n\t\t\trequireEditable();\n\t\t\tawait apply({ type: 'moveNodes', moves: pinned(list.map((m) => ({ id: m.id, position: [m.x, m.y] as const }))) }, 'Move');\n\t\t\treturn { moved: moves };\n\t\t}\n\t};\n\n\tconst undo: AgentTool = {\n\t\tname: 'undo_diagram',\n\t\ttitle: 'Undo a diagram edit',\n\t\tdescription: 'Undoes the last diagram edit. Returns whether something was undone and its label.',\n\t\tinputSchema: object({}),\n\t\tannotations: { title: 'Undo a diagram edit', ...WRITE },\n\t\thandler: async () => {\n\t\t\tconst label = controller.getState().undoLabel;\n\t\t\tconst undone = controller.undo();\n\t\t\treturn { undone, ...(undone ? { label } : {}) };\n\t\t}\n\t};\n\n\tconst redo: AgentTool = {\n\t\tname: 'redo_diagram',\n\t\ttitle: 'Redo a diagram edit',\n\t\tdescription: 'Redoes the last undone diagram edit. Returns whether something was redone and its label.',\n\t\tinputSchema: object({}),\n\t\tannotations: { title: 'Redo a diagram edit', ...WRITE },\n\t\thandler: async () => {\n\t\t\tconst label = controller.getState().redoLabel;\n\t\t\tconst redone = controller.redo();\n\t\t\treturn { redone, ...(redone ? { label } : {}) };\n\t\t}\n\t};\n\n\tconst reads = [getDiagram, exportMermaid, findDiagrams];\n\tconst writes = [loadMermaid, addNode, removeElements, renameNode, setShape, setStyle, connect, removeEdges, labelEdge, reconnect, setDirection, group, ungroup, move, undo, redo];\n\n\treturn {\n\t\tid: options.id ?? 'diagram',\n\t\tnamespace: options.namespace,\n\t\ttools: () => (readOnlyReason() ? reads : [...reads, ...writes]),\n\t\tsubscribe: (notify) => {\n\t\t\tlet last = readOnlyReason();\n\t\t\treturn controller.subscribe(() => {\n\t\t\t\tconst now = readOnlyReason();\n\t\t\t\tif (now !== last) {\n\t\t\t\t\tlast = now;\n\t\t\t\t\tnotify();\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n}\n","import { parseMermaid } from \"./document.js\";\nimport type { DiagramGraph } from \"./types.js\";\n\n/** What a frame shows, for telling frames apart and finding what each one adds. */\nfunction signature(graph: DiagramGraph): string {\n return JSON.stringify([\n graph.title ?? \"\",\n graph.nodes.map((n) => [n.id, n.label, n.parentId ?? \"\", n.data ?? null]),\n graph.edges.map((e) => [e.source, e.target, e.label ?? \"\"]),\n graph.groups.map((g) => [g.id, g.label, g.parentId ?? \"\"]),\n graph.data ?? null,\n ]);\n}\n\nconst isMeta = (line: string) => /^\\s*%%\\s*@(?:pixerate|ursula)\\b/.test(line);\n\n/** Body lines (after the header), without the layout comment and trailing blanks. */\nfunction bodyLines(lines: string[], headerIndex: number): string[] {\n const body = lines.slice(headerIndex + 1).filter((l) => !isMeta(l));\n while (body.length && !body[body.length - 1]!.trim()) body.pop();\n return body;\n}\n\nfunction headerIndexOf(lines: string[], header: string): number {\n const wanted = header.trim();\n return wanted ? lines.findIndex((l) => l.trim() === wanted) : -1;\n}\n\nexport interface DraftOptions {\n /** Most frames to play; longer drafts are sampled evenly (default 40). */\n maxFrames?: number;\n}\n\n/**\n * Splits writing `target` over `current` into frames that grow the diagram\n * statement by statement, so a whole-diagram write can be played as the\n * diagram being drawn. Each frame is a valid prefix of `target` (it parses\n * as the same family without diagnostics) that shows something new; the last\n * frame is `target` itself.\n *\n * Returns null when `target` does not extend `current` (some existing line\n * changes), when the family changes, or when there is nothing to grow: the\n * write is then committed in one step.\n */\nexport function draftFrames(\n current: string,\n target: string,\n options: DraftOptions = {},\n): string[] | null {\n const maxFrames = Math.max(2, options.maxFrames ?? 40);\n const now = parseMermaid(current);\n const goal = parseMermaid(target);\n if (goal.family !== now.family || goal.diagnostics.length) return null;\n\n const eol = goal.eol;\n const lines = target.split(/\\r?\\n/);\n const at = headerIndexOf(lines, goal.header);\n if (at < 0) return null;\n const currentLines = current.split(/\\r?\\n/);\n const currentAt = headerIndexOf(currentLines, now.header);\n if (currentAt < 0) return null;\n\n // The target must keep every current body line, in order, as its start.\n const have = bodyLines(currentLines, currentAt);\n const body = lines\n .map((line, index) => ({ line, index }))\n .slice(at + 1)\n .filter(({ line }) => !isMeta(line));\n for (let i = 0; i < have.length; i++) {\n if (body[i]?.line !== have[i]) return null;\n }\n\n const frames: string[] = [];\n let last = signature(now.graph);\n for (let i = have.length; i < body.length; i++) {\n const end = body[i]!.index;\n const candidate = lines.slice(0, end + 1).join(eol) + eol;\n const doc = parseMermaid(candidate);\n if (doc.family !== goal.family || doc.diagnostics.length) continue;\n const sig = signature(doc.graph);\n if (sig === last) continue;\n frames.push(candidate);\n last = sig;\n }\n if (!frames.length) return null;\n // The last frame is the target exactly (its trailing lines and metadata included).\n if (last === signature(goal.graph)) frames[frames.length - 1] = target;\n else frames.push(target);\n\n if (frames.length <= maxFrames) return frames;\n const sampled: string[] = [];\n for (let k = 1; k <= maxFrames; k++) {\n sampled.push(frames[Math.round((k * frames.length) / maxFrames) - 1]!);\n }\n return sampled;\n}\n\n/** Ids of nodes and groups in `after` that are new or changed since `before`. */\nexport function changedItems(before: DiagramGraph, after: DiagramGraph): string[] {\n const old = new Map<string, string>();\n for (const n of before.nodes) old.set(n.id, JSON.stringify([n.label, n.parentId, n.data]));\n for (const g of before.groups) old.set(g.id, JSON.stringify([g.label, g.parentId]));\n const out: string[] = [];\n for (const n of after.nodes)\n if (old.get(n.id) !== JSON.stringify([n.label, n.parentId, n.data])) out.push(n.id);\n for (const g of after.groups)\n if (old.get(g.id) !== JSON.stringify([g.label, g.parentId])) out.push(g.id);\n return out;\n}\n","// Acting out diagram edits follows Ursula's remote control (src/remote/commands.ts `planCommand`,\n// src/remote/draft.ts): a pure planner turns each edit into cursor steps on the canvas, and a\n// whole-diagram write is drawn statement by statement. Shared pieces live in animation/presence.\nimport {\n\tPerformanceQueue,\n\tPresenceStore,\n\trealPresenceClock,\n\ttweenPresenceCursor,\n\ttype PresenceActorInfo,\n\ttype PresenceClock\n} from '../animation/presence.js';\nimport type { DiagramController } from './controller.js';\nimport { validateDiagramEdit } from './controller.js';\nimport { applyEdits, parseMermaid } from './document.js';\nimport { changedItems, draftFrames } from './draft.js';\nimport type { EditOp, MermaidDocument, Point } from './types.js';\n\n/** Where things are drawn on the canvas (absolute top-left and size), e.g. `mermaidToCanvas(...).layout`. */\nexport interface DiagramPlacement {\n\tpositions: Readonly<Record<string, Point>>;\n\tsizes: Readonly<Record<string, { width: number; height: number }>>;\n}\n\nexport type DiagramPerformanceStep =\n\t| { type: 'move'; to: { x: number; y: number }; ms: number }\n\t| { type: 'say'; text: string; ms: number }\n\t| { type: 'type'; text: string; ms: number }\n\t| { type: 'press' }\n\t| { type: 'release' }\n\t/** Drag a node (and the cursor) so its top-left ends at `to`; the node follows live. */\n\t| { type: 'drag'; id: string; to: { x: number; y: number }; position: Point; ms: number }\n\t| { type: 'commit' };\n\nconst center = (place: DiagramPlacement, id: string) => {\n\tconst p = place.positions[id];\n\tif (!p) return null;\n\tconst s = place.sizes[id] ?? { width: 160, height: 56 };\n\treturn { x: p[0] + s.width / 2, y: p[1] + s.height / 2 };\n};\n\n/** Bottom-right of everything drawn, where a node without a better place appears. */\nfunction freeSpot(place: DiagramPlacement): { x: number; y: number } {\n\tlet maxX = 0;\n\tlet maxY = 0;\n\tlet any = false;\n\tfor (const [id, [x, y]] of Object.entries(place.positions)) {\n\t\tconst s = place.sizes[id] ?? { width: 160, height: 56 };\n\t\tmaxX = Math.max(maxX, x + s.width);\n\t\tmaxY = Math.max(maxY, y + s.height);\n\t\tany = true;\n\t}\n\treturn any ? { x: maxX + 60, y: maxY / 2 } : { x: 80, y: 40 };\n}\n\nconst NEW_NODE = { width: 160, height: 56 };\n\n/**\n * Where a new node's centre goes beside `from`: one rank further along the diagram's\n * direction, then stepped sideways until it overlaps nothing drawn.\n */\nfunction besideFree(place: DiagramPlacement, from: { x: number; y: number }, direction: string): { x: number; y: number } {\n\tconst d = direction.toUpperCase();\n\tconst along = d === 'LR' ? { x: 1, y: 0 } : d === 'RL' ? { x: -1, y: 0 } : d === 'BT' ? { x: 0, y: -1 } : { x: 0, y: 1 };\n\tconst side = { x: along.y, y: along.x };\n\tconst rects = Object.entries(place.positions).map(([id, [x, y]]) => ({ x, y, ...(place.sizes[id] ?? NEW_NODE) }));\n\tconst free = (c: { x: number; y: number }) =>\n\t\t!rects.some((r) => c.x - NEW_NODE.width / 2 - 20 < r.x + r.width && c.x + NEW_NODE.width / 2 + 20 > r.x && c.y - NEW_NODE.height / 2 - 20 < r.y + r.height && c.y + NEW_NODE.height / 2 + 20 > r.y);\n\tconst base = { x: from.x + along.x * 240, y: from.y + along.y * 140 };\n\tfor (let i = 0; i < 24; i++) {\n\t\tconst k = i === 0 ? 0 : (i % 2 ? 1 : -1) * Math.ceil(i / 2);\n\t\tconst c = { x: base.x + side.x * k * 200, y: base.y + side.y * k * 90 };\n\t\tif (free(c)) return c;\n\t}\n\treturn base;\n}\n\n/**\n * Plans how an agent acts out one edit on the canvas: where the cursor goes, what it\n * clicks, drags or types, and when the edit commits. Pure: the same document, placement\n * and edit give the same steps. `next` is the edit that follows in the same call (a new\n * node is placed beside the node an edge will connect it from).\n */\nexport function planDiagramEdit(doc: MermaidDocument, place: DiagramPlacement, op: EditOp, speed = 1, next?: EditOp): DiagramPerformanceStep[] {\n\tconst ms = (n: number) => Math.round(n / Math.max(0.1, speed));\n\tconst at = (id: string) => center(place, id);\n\tconst click = (to: { x: number; y: number } | null): DiagramPerformanceStep[] =>\n\t\tto ? [{ type: 'move', to, ms: ms(380) }, { type: 'press' }, { type: 'release' }] : [];\n\tconst edgeMid = (id: string) => {\n\t\tconst e = doc.graph.edges.find((x) => x.id === id);\n\t\tconst a = e && at(e.source);\n\t\tconst b = e && at(e.target);\n\t\treturn a && b ? { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 } : null;\n\t};\n\tswitch (op.type) {\n\t\tcase 'addNode': {\n\t\t\tconst from = next?.type === 'addEdge' && next.edge.target === op.node.id ? at(next.edge.source) : null;\n\t\t\tconst to = op.position\n\t\t\t\t? { x: op.position[0] + 80, y: op.position[1] + 28 }\n\t\t\t\t: from\n\t\t\t\t\t? besideFree(place, from, doc.graph.direction)\n\t\t\t\t\t: freeSpot(place);\n\t\t\tconst label = op.node.label ?? op.node.id;\n\t\t\treturn [...click(to), { type: 'type', text: label, ms: ms(Math.min(900, 60 * label.length + 200)) }, { type: 'commit' }];\n\t\t}\n\t\tcase 'addEdge': {\n\t\t\tconst a = at(op.edge.source);\n\t\t\tconst b = at(op.edge.target) ?? (a ? { x: a.x + 260, y: a.y } : null);\n\t\t\tif (!a || !b) return [{ type: 'commit' }];\n\t\t\treturn [\n\t\t\t\t{ type: 'move', to: a, ms: ms(380) },\n\t\t\t\t{ type: 'press' },\n\t\t\t\t{ type: 'move', to: b, ms: ms(520) },\n\t\t\t\t{ type: 'release' },\n\t\t\t\t...(op.edge.label ? [{ type: 'type' as const, text: op.edge.label, ms: ms(500) }] : []),\n\t\t\t\t{ type: 'commit' }\n\t\t\t];\n\t\t}\n\t\tcase 'moveNodes': {\n\t\t\tconst steps: DiagramPerformanceStep[] = [];\n\t\t\tfor (const move of op.moves) {\n\t\t\t\tconst from = at(move.id);\n\t\t\t\tconst size = place.sizes[move.id] ?? { width: 160, height: 56 };\n\t\t\t\tif (!from) continue;\n\t\t\t\tsteps.push(\n\t\t\t\t\t{ type: 'move', to: from, ms: ms(380) },\n\t\t\t\t\t{ type: 'press' },\n\t\t\t\t\t{ type: 'drag', id: move.id, to: { x: move.position[0] + size.width / 2, y: move.position[1] + size.height / 2 }, position: move.position, ms: ms(520) },\n\t\t\t\t\t{ type: 'release' }\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn [...steps, { type: 'commit' }];\n\t\t}\n\t\tcase 'renameNode':\n\t\tcase 'renameGroup':\n\t\t\treturn [...click(at(op.id)), { type: 'press' }, { type: 'release' }, { type: 'type', text: op.label, ms: ms(Math.min(900, 60 * op.label.length + 200)) }, { type: 'commit' }];\n\t\tcase 'removeNodes':\n\t\t\treturn [...click(at(op.ids[0]!)), { type: 'say', text: 'Delete', ms: ms(300) }, { type: 'commit' }];\n\t\tcase 'removeEdges':\n\t\t\treturn [...click(edgeMid(op.ids[0]!)), { type: 'say', text: 'Delete', ms: ms(300) }, { type: 'commit' }];\n\t\tcase 'setEdgeLabel':\n\t\t\treturn [...click(edgeMid(op.id)), { type: 'type', text: op.label, ms: ms(500) }, { type: 'commit' }];\n\t\tcase 'reconnectEdge': {\n\t\t\tconst a = at(op.source);\n\t\t\tconst b = at(op.target);\n\t\t\treturn [...click(edgeMid(op.id)), ...(a && b ? [{ type: 'move' as const, to: a, ms: ms(300) }, { type: 'press' as const }, { type: 'move' as const, to: b, ms: ms(500) }, { type: 'release' as const }] : []), { type: 'commit' }];\n\t\t}\n\t\tcase 'setNodeShape':\n\t\tcase 'setNodeStyles':\n\t\tcase 'setNodeClasses':\n\t\tcase 'setNodeLink':\n\t\tcase 'setNodeData':\n\t\tcase 'resizeNode':\n\t\t\treturn [...click(at(op.id)), { type: 'commit' }];\n\t\tcase 'groupNodes': {\n\t\t\tconst points = op.nodeIds.map(at).filter((p): p is { x: number; y: number } => !!p);\n\t\t\tif (!points.length) return [{ type: 'commit' }];\n\t\t\tconst min = { x: Math.min(...points.map((p) => p.x)) - 90, y: Math.min(...points.map((p) => p.y)) - 40 };\n\t\t\tconst max = { x: Math.max(...points.map((p) => p.x)) + 90, y: Math.max(...points.map((p) => p.y)) + 40 };\n\t\t\treturn [{ type: 'move', to: min, ms: ms(380) }, { type: 'press' }, { type: 'move', to: max, ms: ms(520) }, { type: 'release' }, { type: 'type', text: op.label, ms: ms(500) }, { type: 'commit' }];\n\t\t}\n\t\tdefault:\n\t\t\treturn [{ type: 'commit' }];\n\t}\n}\n\nexport interface DiagramDirectorOptions {\n\t/** Where nodes are drawn now, e.g. `() => mermaidToCanvas(controller.getDocument()).layout`. */\n\tplacement: () => DiagramPlacement;\n\tclock?: PresenceClock;\n\t/** 1 = natural speed; 2 = twice as fast. */\n\tspeed?: number;\n\t/** Hide each actor's cursor this long after its last call (ms, default 1500; 0 keeps it). */\n\tlinger?: number;\n\t/** Most frames when a whole-diagram write is drawn statement by statement (default 40). */\n\tmaxDraftFrames?: number;\n}\n\nexport type DiagramPerformanceResult = { ok: true; document: MermaidDocument } | { ok: false; message: string };\n\n/**\n * Acts out agent edits on a Mermaid diagram: each actor's cursor moves to the node or edge,\n * clicks, drags, connects and types, the edit lands, and everything one call changes is one\n * undo step, while the user keeps editing. Calls queue per actor. A batch is checked against\n * the diagram before the cursor moves; if the diagram changed underneath and an edit no\n * longer applies, the call's earlier edits are rolled back and the call fails with a sentence.\n * A whole-diagram write (`playSource`) is drawn statement by statement.\n */\nexport class DiagramDirector {\n\treadonly presence: PresenceStore;\n\tprivate readonly clock: PresenceClock;\n\tprivate readonly queue: PerformanceQueue;\n\n\tconstructor(\n\t\tprivate readonly controller: DiagramController,\n\t\tpresence: PresenceStore,\n\t\tprivate readonly options: DiagramDirectorOptions\n\t) {\n\t\tthis.presence = presence;\n\t\tthis.clock = options.clock ?? realPresenceClock;\n\t\tthis.queue = new PerformanceQueue(presence, {\n\t\t\tclock: this.clock,\n\t\t\tlinger: options.linger,\n\t\t\tbeginGesture: (key) => controller.beginGesture(key),\n\t\t\tendGesture: () => controller.endGesture()\n\t\t});\n\t}\n\n\t/** Acts out `ops` in order for `actor` as one undo step. */\n\tperform(actor: PresenceActorInfo, ops: readonly EditOp[], label = 'Edit diagram'): Promise<DiagramPerformanceResult> {\n\t\t// All-or-nothing: the whole batch must apply before the cursor moves.\n\t\tlet check = this.controller.getDocument();\n\t\tif (this.controller.getState().readOnly) return Promise.resolve({ ok: false, message: 'The diagram is read-only.' });\n\t\tfor (const op of ops) {\n\t\t\tconst problem = validateDiagramEdit(check, op);\n\t\t\tif (problem) return Promise.resolve({ ok: false, message: problem });\n\t\t\tcheck = applyEdits(check, [op]);\n\t\t}\n\t\treturn this.queue.run(actor, async () => {\n\t\t\tconst mark = this.controller.history.mark();\n\t\t\tconst fail = (message: string): DiagramPerformanceResult => {\n\t\t\t\tthis.controller.history.revertTo(mark);\n\t\t\t\treturn { ok: false, message };\n\t\t\t};\n\t\t\t// Placing or dragging stores positions; pin the rest of an auto-laid diagram first so\n\t\t\t// it does not shift around them (see GOTCHAS.md).\n\t\t\tif (ops.some((op) => op.type === 'addNode' || op.type === 'moveNodes')) {\n\t\t\t\tconst problem = this.pin(label);\n\t\t\t\tif (problem) return fail(problem);\n\t\t\t}\n\t\t\tfor (let i = 0; i < ops.length; i++) {\n\t\t\t\tconst op = ops[i]!;\n\t\t\t\tconst steps = planDiagramEdit(this.controller.getDocument(), this.options.placement(), op, this.options.speed, ops[i + 1]);\n\t\t\t\tfor (const step of steps) {\n\t\t\t\t\tconst problem = await this.act(actor.id, step, op, label);\n\t\t\t\t\tif (problem) return fail(problem);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { ok: true, document: this.controller.getDocument() };\n\t\t});\n\t}\n\n\t/**\n\t * Replaces the diagram with `source`, drawing it statement by statement when it extends\n\t * what is there (Ursula's draft playback): each frame lands, the cursor points at what\n\t * appeared, and the whole write is one undo step. Otherwise it lands at once.\n\t */\n\tplaySource(actor: PresenceActorInfo, source: string, label = 'Replace diagram'): Promise<DiagramPerformanceResult> {\n\t\tif (this.controller.getState().readOnly) return Promise.resolve({ ok: false, message: 'The diagram is read-only.' });\n\t\treturn this.queue.run(actor, async () => {\n\t\t\tconst frames = draftFrames(this.controller.getSource(), source, { maxFrames: this.options.maxDraftFrames }) ?? [source];\n\t\t\tconst pause = Math.round(260 / Math.max(0.1, this.options.speed ?? 1));\n\t\t\tfor (const frame of frames) {\n\t\t\t\tconst before = this.controller.getDocument().graph;\n\t\t\t\tconst result = this.controller.apply({ type: 'replaceSource', source: frame }, label);\n\t\t\t\tif (!result.ok) return result;\n\t\t\t\tconst fresh = changedItems(before, result.document.graph);\n\t\t\t\tconst target = fresh.length ? center(this.options.placement(), fresh[fresh.length - 1]!) : null;\n\t\t\t\tconst actorNow = this.presence.get(actor.id);\n\t\t\t\tif (target && actorNow) await tweenPresenceCursor(this.presence, this.clock, actor.id, actorNow, target, pause);\n\t\t\t\telse await this.clock.wait(pause);\n\t\t\t}\n\t\t\treturn { ok: true, document: this.controller.getDocument() };\n\t\t});\n\t}\n\n\t/** Stores the drawn position of every node and group that has none yet. */\n\tprivate pin(label: string): string | null {\n\t\tconst doc = this.controller.getDocument();\n\t\tconst stored = doc.meta.layout ?? {};\n\t\tconst known = new Set([...doc.graph.nodes, ...doc.graph.groups].map((x) => x.id));\n\t\tconst moves = Object.entries(this.options.placement().positions)\n\t\t\t.filter(([id]) => known.has(id) && !stored[id])\n\t\t\t.map(([id, position]) => ({ id, position }));\n\t\tif (!moves.length) return null;\n\t\tconst result = this.controller.apply({ type: 'moveNodes', moves }, label);\n\t\treturn result.ok ? null : result.message;\n\t}\n\n\t/** Runs one step; returns a problem sentence when the edit can no longer be applied. */\n\tprivate async act(id: string, step: DiagramPerformanceStep, op: EditOp, label: string): Promise<string | null> {\n\t\tconst where = () => {\n\t\t\tconst a = this.presence.get(id);\n\t\t\treturn a ? { x: a.x, y: a.y } : { x: 0, y: 0 };\n\t\t};\n\t\tswitch (step.type) {\n\t\t\tcase 'say':\n\t\t\t\tthis.presence.set({ id, message: step.text });\n\t\t\t\tawait this.clock.wait(step.ms);\n\t\t\t\treturn null;\n\t\t\tcase 'type': {\n\t\t\t\t// The note grows as if typed.\n\t\t\t\tconst chars = [...step.text];\n\t\t\t\tconst each = step.ms / Math.max(1, chars.length);\n\t\t\t\tfor (let i = 1; i <= chars.length; i++) {\n\t\t\t\t\tthis.presence.set({ id, message: chars.slice(0, i).join('') });\n\t\t\t\t\tawait this.clock.wait(each);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tcase 'move':\n\t\t\t\tawait tweenPresenceCursor(this.presence, this.clock, id, where(), step.to, step.ms);\n\t\t\t\treturn null;\n\t\t\tcase 'press':\n\t\t\t\tthis.presence.set({ id, pressed: true });\n\t\t\t\treturn null;\n\t\t\tcase 'release':\n\t\t\t\tthis.presence.set({ id, pressed: false });\n\t\t\t\treturn null;\n\t\t\tcase 'drag': {\n\t\t\t\tconst start = this.options.placement().positions[step.id];\n\t\t\t\tlet problem: string | null = null;\n\t\t\t\tawait tweenPresenceCursor(this.presence, this.clock, id, where(), step.to, step.ms, (e) => {\n\t\t\t\t\tif (problem || !start) return;\n\t\t\t\t\tconst position: Point = [start[0] + (step.position[0] - start[0]) * e, start[1] + (step.position[1] - start[1]) * e];\n\t\t\t\t\tconst result = this.controller.apply({ type: 'moveNodes', moves: [{ id: step.id, position }] }, label);\n\t\t\t\t\tif (!result.ok) problem = result.message;\n\t\t\t\t});\n\t\t\t\treturn problem;\n\t\t\t}\n\t\t\tcase 'commit': {\n\t\t\t\t// A new node lands where the cursor clicked.\n\t\t\t\tconst placed: EditOp =\n\t\t\t\t\top.type === 'addNode' && !op.position ? { ...op, position: [Math.round(where().x - 80), Math.round(where().y - 28)] } : op;\n\t\t\t\tconst result = this.controller.apply(placed, label);\n\t\t\t\treturn result.ok ? null : result.message;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** Reads a document for planning without a controller (tests, previews). */\nexport function planDiagramEdits(source: string, place: DiagramPlacement, ops: readonly EditOp[], speed = 1): DiagramPerformanceStep[][] {\n\tlet doc = parseMermaid(source);\n\treturn ops.map((op, i) => {\n\t\tconst steps = planDiagramEdit(doc, place, op, speed, ops[i + 1]);\n\t\tdoc = applyEdits(doc, [op]);\n\t\treturn steps;\n\t});\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/diagram/index.cjs","../../src/diagram/types.ts","../../src/diagram/source/find.ts","../../src/diagram/controller.ts","../../src/diagram/tools.ts","../../src/diagram/draft.ts","../../src/diagram/director.ts"],"names":[],"mappings":"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AAA;AAC9C,IAAA;AACmB,IAAA;AACb,MAAA;AACwC,QAAA;AACf,QAAA;AACtB,QAAA;AACK,MAAA;AAEe,QAAA;AACoB,QAAA;AACP,QAAA;AACsB,UAAA;AACnC,UAAA;AAC3B,QAAA;AACO,QAAA;AACR,MAAA;AACK,MAAA;AAC8E,QAAA;AAC3E,QAAA;AACH,MAAA;AACmC,QAAA;AAChC,QAAA;AACH,MAAA;AACoC,QAAA;AACjC,QAAA;AACK,MAAA;AAC4C,QAAA;AAC3B,QAAA;AACuD,QAAA;AAC5D,UAAA;AACiE,UAAA;AACP,UAAA;AAChD,UAAA;AACjC,QAAA;AACM,QAAA;AACR,MAAA;AACe,MAAA;AAGoD,QAAA;AAChB,QAAA;AACjB,QAAA;AAClC,MAAA;AACD,IAAA;AACD,EAAA;AACD;AAGyI;AAC3G,EAAA;AACH,EAAA;AACsC,IAAA;AACrC,IAAA;AACnB,IAAA;AACP,EAAA;AACF;AN8vB4F;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/diagram/index.cjs","sourcesContent":[null,"/**\n * Core document model shared by every diagram family.\n *\n * Design goals:\n * - **Lossless**: a document parsed and serialised without edits is\n * byte-identical to its input. Anything the diagram module does not understand is kept\n * verbatim as a passthrough statement.\n * - **Graph-first**: every editable family projects onto the same\n * {@link DiagramGraph} so that one canvas can render all of them.\n * - **Mermaid-compatible metadata**: canvas state is stored in\n * `%% @pixerate …` comments (or frontmatter) that every Mermaid parser ignores.\n *\n * @module\n */\n\n/** Mermaid diagram families the diagram module knows how to detect. */\nexport type DiagramFamily =\n | \"flowchart\"\n | \"state\"\n | \"class\"\n | \"er\"\n | \"requirement\"\n | \"mindmap\"\n | \"sequence\"\n | \"gantt\"\n | \"pie\"\n | \"journey\"\n | \"timeline\"\n | \"gitGraph\"\n | \"c4\"\n | \"quadrant\"\n | \"sankey\"\n | \"xychart\"\n | \"block\"\n | \"architecture\"\n | \"packet\"\n | \"kanban\"\n | \"radar\"\n | \"treemap\"\n | \"info\"\n | \"unknown\";\n\n/**\n * How deeply the diagram module can edit a family.\n *\n * - `editable` — statement-level lossless editing on the canvas. Every\n * family the diagram module ships an adapter for is editable; the canvas model differs\n * per family (graph, tree, timeline, chart data…).\n * - `render-only` — source editing with a live Mermaid preview; no canvas.\n * Used for families without an adapter and for unknown input.\n */\nexport type SupportTier = \"editable\" | \"render-only\";\n\n/** `[x, y]` canvas position in React Flow coordinates. */\nexport type Point = readonly [number, number];\n\n/** Line pattern of an edge. Mirrors Mermaid's `thickness` / `pattern`. */\nexport type EdgePattern = \"solid\" | \"dotted\" | \"thick\" | \"invisible\";\n\n/**\n * Arrow head identifiers, aligned with Mermaid's `arrowTypeStart` /\n * `arrowTypeEnd` values so React Flow markers can be derived uniformly.\n */\nexport type ArrowHead =\n | \"none\"\n | \"arrow_point\"\n | \"arrow_open\"\n | \"arrow_cross\"\n | \"arrow_circle\"\n | \"arrow_barb\"\n | \"extension\"\n | \"composition\"\n | \"aggregation\"\n | \"dependency\"\n | \"lollipop\"\n | \"only_one\"\n | \"zero_or_one\"\n | \"one_or_more\"\n | \"zero_or_more\"\n | \"md_parent\"\n | \"requirement_arrow\"\n | \"requirement_contains\";\n\nexport interface GraphNode {\n /** Stable identifier (the Mermaid id, e.g. `A`). */\n id: string;\n /** Display label (raw Mermaid text, markdown strings included). */\n label: string;\n /**\n * Canonical shape identifier (`rect`, `diam`, `cyl`, `stateStart`,\n * `classBox`, `erBox`…). Mermaid aliases such as `squareRect` or `diamond`\n * are normalised on parse. See `FLOWCHART_SHAPES` for the flowchart set.\n */\n shape: string;\n /** Enclosing group (subgraph / composite state / namespace) id. */\n parentId?: string;\n /** CSS class names applied with `:::`, `class`, or `classDef` defaults. */\n classes: string[];\n /** Inline `style` declarations (`fill:#f9f`, …). */\n styles: string[];\n /** `click` href target. */\n link?: string;\n linkTarget?: string;\n tooltip?: string;\n /** Family-specific payload (class members, ER attributes, state notes…). */\n data?: Record<string, unknown>;\n}\n\nexport interface GraphGroup {\n id: string;\n label: string;\n parentId?: string;\n /** Local layout direction override (`TB`, `LR`, …). */\n direction?: string;\n classes: string[];\n styles: string[];\n data?: Record<string, unknown>;\n}\n\nexport interface GraphEdge {\n /** Mermaid-compatible id (`L_A_B_0` for auto ids, or the user id). */\n id: string;\n source: string;\n target: string;\n label?: string;\n pattern: EdgePattern;\n arrowStart: ArrowHead;\n arrowEnd: ArrowHead;\n /** Minimum rank length requested with extra dashes (`---->`). */\n length: number;\n classes: string[];\n styles: string[];\n animate?: boolean;\n animation?: \"fast\" | \"slow\";\n /** True when the id was written by the user (`A e1@--> B`). */\n userDefinedId: boolean;\n data?: Record<string, unknown>;\n}\n\n/** Normalised, family-independent view of a diagram. */\nexport interface DiagramGraph {\n direction: string;\n nodes: GraphNode[];\n edges: GraphEdge[];\n groups: GraphGroup[];\n /** `classDef` declarations keyed by class name. */\n classDefs: Record<string, string[]>;\n title?: string;\n /**\n * Diagram-level, family-specific settings that are not nodes or edges\n * (pie `showData`, quadrant axis labels, gantt `dateFormat`…). Edited with\n * `setGraphData`; the keys are documented per family in `FamilyUI.diagramFields`.\n */\n data?: Record<string, unknown>;\n}\n\n/** Canvas state that Mermaid itself does not model. Persisted as metadata. */\nexport interface DiagramMeta {\n /** Schema version of the metadata payload. */\n v: 1;\n /** Node / group positions keyed by id. */\n layout?: Record<string, Point>;\n /** Explicit node sizes for resized nodes/groups. */\n sizes?: Record<string, readonly [number, number]>;\n /** Viewport the user last looked at. */\n viewport?: { x: number; y: number; zoom: number };\n /** Free-form extension slot for host applications. */\n ext?: Record<string, unknown>;\n}\n\n/** Where metadata lives in the source. */\nexport type MetaCarrier = \"comment\" | \"frontmatter\";\n\n/**\n * Lines that precede the diagram header: YAML frontmatter, `%%{init}%%`\n * directives, comments and blank lines. Kept verbatim.\n */\nexport interface Preamble {\n /** Raw lines, including the `---` fences of any frontmatter. */\n lines: string[];\n /** Parsed frontmatter when present (title/config/pixerate…). */\n frontmatter?: Record<string, unknown>;\n}\n\n/** A parse problem reported by the diagram parser or by Mermaid. */\nexport interface Diagnostic {\n severity: \"error\" | \"warning\" | \"info\";\n message: string;\n /** 1-based line in the source, when known. */\n line?: number;\n column?: number;\n source: \"pixerate\" | \"mermaid\";\n}\n\n/**\n * The parsed document. Family adapters extend `body` with their own statement\n * list; consumers that only need the graph never look at it.\n */\nexport interface MermaidDocument<TBody = unknown> {\n family: DiagramFamily;\n tier: SupportTier;\n /** Source as given to {@link parseMermaid}. */\n source: string;\n preamble: Preamble;\n /** The header line (`flowchart LR`, `stateDiagram-v2`, …) exactly as written. */\n header: string;\n /** Trailing newline / whitespace after the last statement, kept for fidelity. */\n trailing: string;\n /** End-of-line sequence detected in the source. */\n eol: \"\\n\" | \"\\r\\n\";\n graph: DiagramGraph;\n meta: DiagramMeta;\n metaCarrier: MetaCarrier;\n body: TBody;\n diagnostics: Diagnostic[];\n /** True once any edit has been applied to this document. */\n dirty: boolean;\n}\n\n/** Directions understood by flowchart / state / class / ER. */\nexport const DIRECTIONS = [\"TB\", \"TD\", \"BT\", \"LR\", \"RL\"] as const;\nexport type Direction = (typeof DIRECTIONS)[number];\n\n// ---------------------------------------------------------------------------\n// Edit operations\n// ---------------------------------------------------------------------------\n\nexport type EditOp =\n | { type: \"addNode\"; node: Partial<GraphNode> & { id: string }; position?: Point }\n | { type: \"removeNodes\"; ids: string[] }\n | { type: \"renameNode\"; id: string; label: string }\n | { type: \"setNodeShape\"; id: string; shape: string }\n | { type: \"setNodeClasses\"; id: string; classes: string[] }\n | { type: \"setNodeStyles\"; id: string; styles: string[] }\n | { type: \"setNodeLink\"; id: string; link?: string; target?: string; tooltip?: string }\n | { type: \"setNodeData\"; id: string; data: Record<string, unknown> }\n | { type: \"moveNodes\"; moves: { id: string; position: Point }[] }\n | { type: \"resizeNode\"; id: string; size: readonly [number, number] | undefined }\n | { type: \"addEdge\"; edge: Partial<GraphEdge> & { source: string; target: string } }\n | { type: \"removeEdges\"; ids: string[] }\n | { type: \"setEdgeLabel\"; id: string; label: string }\n /** Family-specific edge payload (sequence activation, message order, gantt dependency type…). */\n | { type: \"setEdgeData\"; id: string; data: Record<string, unknown> }\n | {\n type: \"setEdgeStyle\";\n id: string;\n pattern?: EdgePattern;\n arrowStart?: ArrowHead;\n arrowEnd?: ArrowHead;\n length?: number;\n styles?: string[];\n }\n | { type: \"reconnectEdge\"; id: string; source: string; target: string }\n | { type: \"groupNodes\"; groupId: string; label: string; nodeIds: string[]; parentId?: string }\n | { type: \"ungroup\"; groupId: string }\n | { type: \"renameGroup\"; id: string; label: string }\n | { type: \"setGroupDirection\"; id: string; direction?: string }\n | { type: \"setNodeParent\"; ids: string[]; parentId?: string }\n | { type: \"setDirection\"; direction: string }\n | { type: \"setClassDef\"; name: string; styles: string[] | undefined }\n | { type: \"setTitle\"; title: string | undefined }\n /** Merge diagram-level settings into `graph.data` (see {@link DiagramGraph.data}). */\n | { type: \"setGraphData\"; data: Record<string, unknown> }\n | { type: \"setViewport\"; viewport: DiagramMeta[\"viewport\"] }\n | { type: \"setMetaExt\"; key: string; value: unknown }\n | { type: \"replaceSource\"; source: string };\n\n/** Family adapter contract. See `docs/architecture.md`. */\nexport interface DiagramAdapter<TBody = unknown> {\n family: DiagramFamily;\n tier: SupportTier;\n parse(source: string, context: AdapterParseContext): MermaidDocument<TBody>;\n serialize(doc: MermaidDocument<TBody>): string;\n apply(doc: MermaidDocument<TBody>, op: EditOp): MermaidDocument<TBody>;\n}\n\n/** Pre-split source handed to adapters so they do not repeat the work. */\nexport interface AdapterParseContext {\n source: string;\n eol: \"\\n\" | \"\\r\\n\";\n preamble: Preamble;\n header: string;\n /** Body lines after the header (metadata comments already removed). */\n bodyLines: string[];\n trailing: string;\n meta: DiagramMeta;\n metaCarrier: MetaCarrier;\n family: DiagramFamily;\n}\n","import type { DiagramFamily } from \"../types.js\";\nimport { detectFamilyFromHeader, splitSource } from \"./lines.js\";\n\n/** A Mermaid diagram found in some text by {@link findMermaidSources}. */\nexport interface FoundMermaid {\n /** The diagram's Mermaid text, without any surrounding code fence. */\n source: string;\n /** Its family (never `\"unknown\"`). */\n family: DiagramFamily;\n /** The frontmatter `title`, else the Markdown heading above its code fence. */\n title?: string;\n}\n\n/** Fence info strings that mark a Mermaid block. */\nconst MERMAID_INFO = /^(mermaid|mmd)\\b/i;\n\n/**\n * Finds the Mermaid diagrams in a piece of text: a `.mmd` file, a Markdown\n * document with ```` ```mermaid ```` blocks (GitHub, Notion and most AI\n * chats write them like this), or a diagram pasted with or without its fence.\n *\n * Fenced blocks are used when there are any: those marked `mermaid` / `mmd`,\n * and unmarked ones whose first line is a Mermaid header. Otherwise the whole\n * text is one diagram if it starts with one. Unmarked text must spell the\n * keyword as Mermaid does (`timeline`, not `Timeline`). Text that holds no\n * diagram of a known family returns `[]`.\n */\nexport function findMermaidSources(text: string): FoundMermaid[] {\n const clean = text.replace(/^\\uFEFF/, \"\");\n const blocks = fencedBlocks(clean);\n if (blocks.length) {\n const found: FoundMermaid[] = [];\n for (const block of blocks) {\n if (block.info && !MERMAID_INFO.test(block.info)) continue;\n const item = asDiagram(block.body, block.heading, !block.info);\n if (item) found.push(item);\n }\n if (found.length) return found;\n }\n const whole = asDiagram(clean.trim(), undefined, true);\n return whole ? [whole] : [];\n}\n\n/**\n * `strict` (unmarked text) also wants the keyword's own case, as Mermaid\n * writes it, so prose such as \"Timeline of events\" is not taken for a diagram.\n */\nfunction asDiagram(source: string, heading?: string, strict = false): FoundMermaid | undefined {\n if (!source.trim()) return undefined;\n const split = splitSource(source);\n const family = detectFamilyFromHeader(split.header);\n if (family === \"unknown\") return undefined;\n if (strict && !/^\\s*([a-z]|C4)/.test(split.header)) return undefined;\n const fmTitle = split.preamble.frontmatter?.title;\n const title = typeof fmTitle === \"string\" && fmTitle.trim() ? fmTitle.trim() : heading;\n return { source: source.trimEnd() + \"\\n\", family, ...(title ? { title } : {}) };\n}\n\ninterface FencedBlock {\n info: string;\n body: string;\n /** The last Markdown heading before the fence. */\n heading?: string;\n}\n\n/** CommonMark-style fenced code blocks (``` or ~~~, three or more, indented up to three spaces). */\nfunction fencedBlocks(text: string): FencedBlock[] {\n const lines = text.split(/\\r\\n|\\n/);\n const blocks: FencedBlock[] = [];\n let heading: string | undefined;\n for (let i = 0; i < lines.length; i++) {\n // Patterns kept free of overlapping runs, so long lines cannot make them backtrack.\n const open = /^ {0,3}(`{3,}(?!`)|~{3,}(?!~))([^`]*)$/.exec(lines[i]!);\n if (!open) {\n const h = headingText(lines[i]!);\n if (h) heading = h;\n continue;\n }\n const fence = open[1]!;\n const close = new RegExp(`^ {0,3}${fence[0] === \"`\" ? \"`\" : \"~\"}{${fence.length},}$`);\n const body: string[] = [];\n let j = i + 1;\n while (j < lines.length && !close.test(lines[j]!.trimEnd())) body.push(lines[j++]!);\n blocks.push({ info: open[2]!.trim(), body: body.join(\"\\n\"), heading });\n i = j;\n }\n return blocks;\n}\n\n/** The text of an ATX heading (`## Title ##`), without its markers. */\nfunction headingText(line: string): string | undefined {\n const m = /^ {0,3}#{1,6}([ \\t].*)$/.exec(line);\n if (!m) return undefined;\n let text = m[1]!.trim();\n // A closing run of `#` counts only after a space (or when it is all there is).\n let end = text.length;\n while (end > 0 && text[end - 1] === \"#\") end--;\n if (end === 0 || text[end - 1] === \" \" || text[end - 1] === \"\\t\") text = text.slice(0, end).trim();\n return text || undefined;\n}\n","import { HistoryManager } from '../history/HistoryManager.js';\nimport type { Command } from '../history/types.js';\nimport { applyEdits, parseMermaid } from './document.js';\nimport { FLOWCHART_SHAPES } from './flowchart/shapes.js';\nimport { STATE_SHAPES } from './lines-grammar/state.js';\nimport { DIRECTIONS, type DiagramFamily, type EditOp, type MermaidDocument } from './types.js';\n\n/** Families that can be edited on the canvas; every other family is kept as text. */\nexport const EDITABLE_DIAGRAM_FAMILIES = ['flowchart', 'state', 'class', 'er'] as const;\n\n/** The shapes a node of `family` can take (`setNodeShape`), as canonical names. */\nexport function diagramShapes(family: DiagramFamily): string[] {\n\tif (family === 'flowchart') return FLOWCHART_SHAPES.map((shape) => shape.name);\n\tif (family === 'state') return STATE_SHAPES.map((shape) => shape.name);\n\treturn [];\n}\n\n/** Whether the family has groups (subgraphs, composite states, namespaces). ER diagrams do not. */\nexport function diagramSupportsGroups(family: DiagramFamily): boolean {\n\treturn family === 'flowchart' || family === 'state' || family === 'class';\n}\n\nconst quote = (ids: Iterable<string>) => [...ids].map((id) => `\"${id}\"`).join(', ');\n\n/**\n * Checks an edit against the document before it is applied. Returns a sentence saying\n * what is wrong and how to fix it, or `null` when the edit is valid. Ids are checked\n * against nodes, groups and edges; shapes against {@link diagramShapes}.\n */\nexport function validateDiagramEdit(doc: MermaidDocument, op: EditOp): string | null {\n\tconst { graph, family } = doc;\n\tif (op.type === 'replaceSource') return null;\n\tif (doc.tier !== 'editable')\n\t\treturn `This ${family} diagram can only be edited as Mermaid text; replace the source instead.`;\n\tconst nodes = new Set(graph.nodes.map((n) => n.id));\n\tconst groups = new Set(graph.groups.map((g) => g.id));\n\tconst edges = new Set(graph.edges.map((e) => e.id));\n\tconst anyIds = () => (nodes.size ? `existing node ids are ${quote([...nodes].slice(0, 20))}` : 'the diagram has no nodes yet');\n\tconst node = (id: string, allowGroup = false) =>\n\t\tnodes.has(id) || (allowGroup && groups.has(id)) ? null : `There is no node \"${id}\"; ${anyIds()}.`;\n\tconst group = (id: string | undefined) =>\n\t\tid === undefined || groups.has(id)\n\t\t\t? null\n\t\t\t: `There is no group \"${id}\"; ${groups.size ? `existing groups are ${quote(groups)}` : 'the diagram has no groups'}.`;\n\tconst edge = (id: string) =>\n\t\tedges.has(id)\n\t\t\t? null\n\t\t\t: `There is no edge \"${id}\"; ${edges.size ? `existing edge ids are ${quote([...edges].slice(0, 20))}` : 'the diagram has no edges yet'}.`;\n\tconst shape = (name: string | undefined) => {\n\t\tif (name === undefined) return null;\n\t\tconst shapes = diagramShapes(family);\n\t\tif (!shapes.length) return `Nodes of a ${family} diagram have no shapes to choose from.`;\n\t\treturn shapes.includes(name) ? null : `Unknown shape \"${name}\"; use one of ${quote(shapes)}.`;\n\t};\n\tconst free = (id: string) =>\n\t\t!/^[A-Za-z0-9_][\\w.-]*$/.test(id)\n\t\t\t? `Id \"${id}\" is not a valid Mermaid id; use letters, digits, \"_\", \"-\" or \".\".`\n\t\t\t: nodes.has(id) || groups.has(id)\n\t\t\t\t? `Id \"${id}\" is already used; choose another.`\n\t\t\t\t: null;\n\tconst first = (...problems: (string | null)[]) => problems.find((p) => p) ?? null;\n\n\tswitch (op.type) {\n\t\tcase 'addNode':\n\t\t\treturn first(free(op.node.id), shape(op.node.shape), group(op.node.parentId));\n\t\tcase 'removeNodes':\n\t\t\treturn first(...op.ids.map((id) => node(id, true)));\n\t\tcase 'renameNode':\n\t\t\treturn node(op.id, true);\n\t\tcase 'setNodeShape':\n\t\t\treturn first(node(op.id), shape(op.shape));\n\t\tcase 'setNodeClasses':\n\t\tcase 'setNodeStyles':\n\t\tcase 'setNodeLink':\n\t\tcase 'setNodeData':\n\t\tcase 'resizeNode':\n\t\t\treturn node(op.id, true);\n\t\tcase 'moveNodes':\n\t\t\treturn first(...op.moves.map((m) => node(m.id, true)));\n\t\tcase 'addEdge':\n\t\t\treturn first(node(op.edge.source, true), node(op.edge.target, true));\n\t\tcase 'removeEdges':\n\t\t\treturn first(...op.ids.map(edge));\n\t\tcase 'setEdgeLabel':\n\t\tcase 'setEdgeStyle':\n\t\tcase 'setEdgeData':\n\t\t\treturn edge(op.id);\n\t\tcase 'reconnectEdge':\n\t\t\treturn first(edge(op.id), node(op.source, true), node(op.target, true));\n\t\tcase 'groupNodes':\n\t\t\tif (!diagramSupportsGroups(family)) return `${family} diagrams have no groups.`;\n\t\t\treturn first(free(op.groupId), ...op.nodeIds.map((id) => node(id, true)), group(op.parentId));\n\t\tcase 'ungroup':\n\t\t\treturn group(op.groupId);\n\t\tcase 'renameGroup':\n\t\tcase 'setGroupDirection':\n\t\t\treturn group(op.id);\n\t\tcase 'setNodeParent':\n\t\t\treturn first(...op.ids.map((id) => node(id, true)), group(op.parentId));\n\t\tcase 'setDirection':\n\t\t\treturn (DIRECTIONS as readonly string[]).includes(op.direction)\n\t\t\t\t? null\n\t\t\t\t: `Unknown direction \"${op.direction}\"; use one of ${quote(DIRECTIONS)}.`;\n\t\tdefault:\n\t\t\treturn null;\n\t}\n}\n\nexport interface DiagramControllerOptions {\n\t/** Starting Mermaid source (default: an empty `flowchart TD`). */\n\tsource?: string;\n\t/** History to record into, e.g. one shared with the rest of the app (default: a new one). */\n\thistory?: HistoryManager;\n\t/** Refuse edits (read tools still work). */\n\treadOnly?: boolean;\n\t/** Called after the source changes (edit, undo, redo, load). */\n\tonChange?: (source: string, document: MermaidDocument) => void;\n}\n\n/** A snapshot of the controller. The same object is returned until something changes. */\nexport interface DiagramState {\n\tdocument: MermaidDocument;\n\t/** The Mermaid text, always in step with `document`. */\n\tsource: string;\n\tfamily: DiagramFamily;\n\t/** True for flowchart, state, class and ER diagrams that are not read-only. */\n\teditable: boolean;\n\treadOnly: boolean;\n\t/** Selected node, group or edge ids. */\n\tselection: readonly string[];\n\t/** The problem with the last rejected edit, if any. */\n\tissue: string | null;\n\tcanUndo: boolean;\n\tcanRedo: boolean;\n\tundoLabel: string;\n\tredoLabel: string;\n\t/** Bumped on every change. */\n\trevision: number;\n}\n\nexport type DiagramActionResult =\n\t| { ok: true; document: MermaidDocument }\n\t| { ok: false; message: string };\n\n/**\n * Headless state for a Mermaid diagram editor: the parsed document, a selection and\n * undoable statement-level edits. Every `apply` is one undo step and only rewrites the\n * statements it touches, so untouched text (comments, directives, formatting) survives.\n * Canvas positions go into the `%% @pixerate {…}` comment. Drags merge into one undo\n * step between `beginGesture` and `endGesture`. `getState()` returns the same object\n * until something changes, so it works with `useSyncExternalStore` and Svelte runes.\n */\nexport class DiagramController {\n\treadonly history: HistoryManager;\n\tprivate doc: MermaidDocument;\n\tprivate selection: string[] = [];\n\tprivate issue: string | null = null;\n\tprivate readOnly: boolean;\n\tprivate revision = 0;\n\tprivate applying = false;\n\tprivate gestureKey: string | null = null;\n\tprivate gestureCount = 0;\n\tprivate readonly onChange?: DiagramControllerOptions['onChange'];\n\tprivate readonly listeners = new Set<(state: DiagramState) => void>();\n\tprivate unsubscribeHistory?: () => void;\n\tprivate state!: DiagramState;\n\n\tconstructor(options: DiagramControllerOptions = {}) {\n\t\tthis.history = options.history ?? new HistoryManager();\n\t\tthis.onChange = options.onChange;\n\t\tthis.readOnly = options.readOnly ?? false;\n\t\tthis.doc = parseMermaid(options.source ?? 'flowchart TD\\n');\n\t\tthis.state = this.computeState();\n\t\tthis.connect();\n\t}\n\n\tprivate connect() {\n\t\tthis.unsubscribeHistory?.();\n\t\tthis.unsubscribeHistory = this.history.subscribe((h) => {\n\t\t\tconst s = this.state;\n\t\t\tif (this.applying || (s.canUndo === h.canUndo && s.canRedo === h.canRedo && s.undoLabel === h.undoName && s.redoLabel === h.redoName)) return;\n\t\t\tthis.notify(false);\n\t\t});\n\t}\n\n\t/** Stops listening to the history (subscribing again reconnects). */\n\tdestroy() {\n\t\tthis.unsubscribeHistory?.();\n\t\tthis.unsubscribeHistory = undefined;\n\t}\n\n\t/** The current snapshot (stable until the next change). */\n\tgetState = (): DiagramState => this.state;\n\n\t/** Calls `listener` after every change. Returns an unsubscribe function. */\n\tsubscribe = (listener: (state: DiagramState) => void): (() => void) => {\n\t\tif (!this.unsubscribeHistory) this.connect();\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t};\n\n\t/** The current document (immutable; edit through the controller). */\n\tgetDocument(): MermaidDocument {\n\t\treturn this.doc;\n\t}\n\n\tgetSource(): string {\n\t\treturn this.doc.source;\n\t}\n\n\tprivate computeState(): DiagramState {\n\t\tconst h = this.history.getState();\n\t\treturn {\n\t\t\tdocument: this.doc,\n\t\t\tsource: this.doc.source,\n\t\t\tfamily: this.doc.family,\n\t\t\teditable: this.doc.tier === 'editable' && !this.readOnly,\n\t\t\treadOnly: this.readOnly,\n\t\t\tselection: this.selection,\n\t\t\tissue: this.issue,\n\t\t\tcanUndo: h.canUndo,\n\t\t\tcanRedo: h.canRedo,\n\t\t\tundoLabel: h.undoName,\n\t\t\tredoLabel: h.redoName,\n\t\t\trevision: this.revision\n\t\t};\n\t}\n\n\tprivate notify(sourceChanged: boolean) {\n\t\tthis.revision++;\n\t\tthis.state = this.computeState();\n\t\tif (sourceChanged) this.onChange?.(this.doc.source, this.doc);\n\t\tconst state = this.state;\n\t\tthis.listeners.forEach((listener) => {\n\t\t\ttry {\n\t\t\t\tlistener(state);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[DiagramController] Error in state listener:', error);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate setDocument(doc: MermaidDocument) {\n\t\tthis.applying = true;\n\t\ttry {\n\t\t\tthis.doc = doc;\n\t\t\tconst ids = new Set([...doc.graph.nodes, ...doc.graph.groups, ...doc.graph.edges].map((x) => x.id));\n\t\t\tthis.selection = this.selection.filter((id) => ids.has(id));\n\t\t} finally {\n\t\t\tthis.applying = false;\n\t\t}\n\t\tthis.notify(true);\n\t}\n\n\tprivate commit(label: string, next: MermaidDocument) {\n\t\tconst before = this.doc;\n\t\tthis.issue = null;\n\t\tif (next.source === before.source) {\n\t\t\tthis.notify(false);\n\t\t\treturn;\n\t\t}\n\t\tconst command: Command = {\n\t\t\tname: label,\n\t\t\ttag: 'diagram',\n\t\t\texecute: () => this.setDocument(next),\n\t\t\tundo: () => this.setDocument(before),\n\t\t\tredo: () => this.setDocument(next),\n\t\t\tbyteSize: (before.source.length + next.source.length) * 2\n\t\t};\n\t\tif (this.gestureKey !== null) this.history.executeMerged(command, { key: `diagram.gesture:${this.gestureKey}:${this.gestureCount}` });\n\t\telse this.history.execute(command);\n\t}\n\n\tprivate reject(message: string): DiagramActionResult {\n\t\tthis.issue = message;\n\t\tthis.notify(false);\n\t\treturn { ok: false, message };\n\t}\n\n\t/**\n\t * Applies edits as one undo step. All-or-nothing: every edit is checked against the\n\t * document as it stands after the edits before it, and nothing changes when one fails.\n\t */\n\tapply(ops: EditOp | EditOp[], label = 'Edit diagram'): DiagramActionResult {\n\t\tconst list = Array.isArray(ops) ? ops : [ops];\n\t\tif (this.readOnly) return this.reject('The diagram is read-only.');\n\t\tlet doc = this.doc;\n\t\tfor (const op of list) {\n\t\t\tconst problem = validateDiagramEdit(doc, op);\n\t\t\tif (problem) return this.reject(problem);\n\t\t\tdoc = applyEdits(doc, [op]);\n\t\t}\n\t\tthis.commit(label, doc);\n\t\treturn { ok: true, document: this.doc };\n\t}\n\n\t/**\n\t * Replaces the whole source. `clearHistory` drops the undo stack (opening a file);\n\t * otherwise the replacement is one undo step.\n\t */\n\tload(source: string, options: { clearHistory?: boolean } = {}): DiagramActionResult {\n\t\tconst next = parseMermaid(source, { metaCarrier: this.doc.metaCarrier });\n\t\tif (options.clearHistory) {\n\t\t\tthis.history.clear();\n\t\t\tthis.issue = null;\n\t\t\tthis.selection = [];\n\t\t\tthis.setDocument(next);\n\t\t\treturn { ok: true, document: next };\n\t\t}\n\t\tif (this.readOnly) return this.reject('The diagram is read-only.');\n\t\tthis.commit('Replace diagram', next);\n\t\treturn { ok: true, document: this.doc };\n\t}\n\n\tsetReadOnly(readOnly: boolean) {\n\t\tif (this.readOnly === readOnly) return;\n\t\tthis.readOnly = readOnly;\n\t\tthis.notify(false);\n\t}\n\n\t/** Selects node, group or edge ids (unknown ids are dropped). */\n\tselect(ids: readonly string[]) {\n\t\tconst known = new Set([...this.doc.graph.nodes, ...this.doc.graph.groups, ...this.doc.graph.edges].map((x) => x.id));\n\t\tconst next = ids.filter((id) => known.has(id));\n\t\tif (next.length === this.selection.length && next.every((id, i) => id === this.selection[i])) return;\n\t\tthis.selection = next;\n\t\tthis.notify(false);\n\t}\n\n\t/** Edits until `endGesture` (e.g. the moves of one drag) fold into one undo step. */\n\tbeginGesture(key: string) {\n\t\tthis.gestureKey = key;\n\t\tthis.gestureCount++;\n\t}\n\n\tendGesture() {\n\t\tthis.gestureKey = null;\n\t}\n\n\tundo(): boolean {\n\t\treturn this.history.undo();\n\t}\n\n\tredo(): boolean {\n\t\treturn this.history.redo();\n\t}\n\n\t/** A node id not used by any node or group: `prefix` followed by the next free number. */\n\tnextId(prefix = 'N'): string {\n\t\tconst used = new Set([...this.doc.graph.nodes, ...this.doc.graph.groups].map((x) => x.id));\n\t\tlet i = 1;\n\t\twhile (used.has(`${prefix}${i}`)) i++;\n\t\treturn `${prefix}${i}`;\n\t}\n}\n","import type { AgentTool, AgentToolProvider } from '../tools/registry.js';\nimport type { JsonSchema } from '../tools/schema.js';\nimport type { PresenceActorInfo } from '../animation/presence.js';\nimport { diagramShapes, diagramSupportsGroups, type DiagramController } from './controller.js';\nimport type { DiagramDirector } from './director.js';\nimport { detectFamily } from './document.js';\nimport { findMermaidSources } from './source/find.js';\nimport { DIRECTIONS, type EditOp, type MermaidDocument, type Point } from './types.js';\n\nexport interface DiagramToolProviderOptions {\n\t/** Provider id (unique per registry). Default `diagram`. */\n\tid?: string;\n\t/** Always prefix tool names with `<namespace>__`. */\n\tnamespace?: string;\n\t/**\n\t * The diagram cannot be edited: write tools are hidden and `get_diagram` says why. A\n\t * string is the reason (\"The report is locked\"). The controller's own `readOnly` counts too.\n\t */\n\treadOnly?: boolean | string | (() => boolean | string);\n\t/**\n\t * Where the canvas shows each node now (absolute top-left), e.g. `() => canvas.layout.positions`.\n\t * When given, a move pins every node that has no stored position yet, so moving one\n\t * node of an auto-laid diagram does not shift the others.\n\t */\n\tgetPositions?: () => Readonly<Record<string, Point>>;\n\t/**\n\t * Draws the diagram as standalone SVG (e.g. `(o) => mermaidToSvg(controller.getDocument(), o)`\n\t * from `@pixerate/editor/canvas`). Enables `render_diagram`.\n\t */\n\trenderSvg?: (options: { maxWidth?: number; maxHeight?: number }) => { svg: string; width: number; height: number };\n\t/** Turns that SVG into PNG (e.g. `rasterizeSvg`), so `render_diagram` can return an image. */\n\trasterize?: (svg: string, size: { width: number; height: number }) => Promise<{ data: string; mimeType: string }> | { data: string; mimeType: string };\n\t/**\n\t * Acts out every write with a visible cursor (see `DiagramDirector`): it moves to the node,\n\t * clicks, drags, connects and types, and a whole-diagram load is drawn statement by\n\t * statement. Each call is still one undo step. Without it, edits apply at once.\n\t */\n\tdirector?: DiagramDirector;\n\t/** Who the cursor belongs to (default `{ id: 'agent', name: 'Agent' }`). */\n\tactor?: PresenceActorInfo;\n}\n\nconst READ = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false } as const;\nconst WRITE = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false } as const;\nconst WRITE_IDEMPOTENT = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false } as const;\nconst DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false } as const;\n\nconst ID = { type: 'string', minLength: 1, description: 'A node id from get_diagram (e.g. \"A\").' } as const;\nconst IDS = (what: string) => ({ type: 'array', items: { type: 'string', minLength: 1 }, minItems: 1, description: what }) as const;\nconst EDGE_ID = { type: 'string', minLength: 1, description: 'An edge id from get_diagram (e.g. \"L_A_B_0\").' } as const;\nconst LABEL = (what: string) => ({ type: 'string', description: what }) as const;\nconst object = (properties: Record<string, JsonSchema>, required: string[] = []) => ({\n\ttype: 'object' as const,\n\tproperties,\n\t...(required.length ? { required } : {}),\n\tadditionalProperties: false\n});\n\n/** What an agent needs to know about a document: ids, labels, shapes, structure. */\nexport function describeDiagram(doc: MermaidDocument) {\n\tconst { graph } = doc;\n\treturn {\n\t\tfamily: doc.family,\n\t\teditable: doc.tier === 'editable',\n\t\tdirection: graph.direction,\n\t\t...(graph.title ? { title: graph.title } : {}),\n\t\tnodes: graph.nodes.map((n) => ({\n\t\t\tid: n.id,\n\t\t\tlabel: n.label,\n\t\t\tshape: n.shape,\n\t\t\t...(n.parentId ? { parentId: n.parentId } : {}),\n\t\t\t...(n.classes.length ? { classes: n.classes } : {}),\n\t\t\t...(n.styles.length ? { styles: n.styles } : {}),\n\t\t\t...(n.data?.synthetic ? { synthetic: true } : {})\n\t\t})),\n\t\tedges: graph.edges.map((e) => ({\n\t\t\tid: e.id,\n\t\t\tsource: e.source,\n\t\t\ttarget: e.target,\n\t\t\t...(e.label ? { label: e.label } : {}),\n\t\t\tpattern: e.pattern,\n\t\t\tarrowEnd: e.arrowEnd\n\t\t})),\n\t\tgroups: graph.groups.map((g) => ({\n\t\t\tid: g.id,\n\t\t\tlabel: g.label,\n\t\t\t...(g.parentId ? { parentId: g.parentId } : {}),\n\t\t\t...(g.direction ? { direction: g.direction } : {})\n\t\t})),\n\t\tshapes: diagramShapes(doc.family),\n\t\tpositions: Object.fromEntries(Object.entries(doc.meta.layout ?? {}).map(([id, [x, y]]) => [id, { x: Math.round(x), y: Math.round(y) }])),\n\t\t...(doc.diagnostics.length ? { diagnostics: doc.diagnostics.map((d) => ({ severity: d.severity, message: d.message, ...(d.line ? { line: d.line } : {}) })) } : {})\n\t};\n}\n\n/**\n * Agent tools for a Mermaid diagram (`DiagramController`), for a `ToolRegistry`: one\n * tool per operation with strict schemas. Reads (`get_diagram`, `export_diagram_mermaid`,\n * `find_mermaid_diagrams`) are always there; writes are hidden while the diagram is\n * read-only. Every write is checked against the current diagram first (unknown ids,\n * taken ids, shapes the family does not have), applies as one undo step, and returns\n * what it created or changed. Untouched Mermaid statements are kept byte for byte.\n */\nexport function createDiagramToolProvider(controller: DiagramController, options: DiagramToolProviderOptions = {}): AgentToolProvider {\n\tconst readOnlyReason = (): string | null => {\n\t\tconst r = typeof options.readOnly === 'function' ? options.readOnly() : options.readOnly;\n\t\tif (r) return typeof r === 'string' ? r : 'This diagram is read-only.';\n\t\treturn controller.getState().readOnly ? 'This diagram is read-only.' : null;\n\t};\n\tconst doc = () => controller.getDocument();\n\tconst actor = options.actor ?? { id: 'agent', name: 'Agent' };\n\tconst apply = async (ops: EditOp | EditOp[], label: string) => {\n\t\tconst result = options.director ? await options.director.perform(actor, Array.isArray(ops) ? ops : [ops], label) : controller.apply(ops, label);\n\t\tif (!result.ok) throw new Error(result.message);\n\t\treturn result.document;\n\t};\n\tconst edgeIds = () => new Set(doc().graph.edges.map((e) => e.id));\n\tconst newEdges = (before: Set<string>, after: MermaidDocument) =>\n\t\tafter.graph.edges.filter((e) => !before.has(e.id)).map((e) => ({ id: e.id, source: e.source, target: e.target, ...(e.label ? { label: e.label } : {}) }));\n\tconst nodeOut = (d: MermaidDocument, id: string) => {\n\t\tconst n = d.graph.nodes.find((x) => x.id === id) ?? d.graph.groups.find((x) => x.id === id);\n\t\treturn n ? { id: n.id, label: n.label, ...('shape' in n ? { shape: n.shape } : {}), ...(n.parentId ? { parentId: n.parentId } : {}) } : { id };\n\t};\n\t/** Moves plus the current positions of nodes not stored yet (see `getPositions`). */\n\tconst pinned = (moves: Array<{ id: string; position: Point }>) => {\n\t\tconst shown = options.getPositions?.();\n\t\tif (!shown) return moves;\n\t\tconst stored = doc().meta.layout ?? {};\n\t\tconst taken = new Set(moves.map((m) => m.id));\n\t\treturn [...moves, ...Object.entries(shown).filter(([id]) => !taken.has(id) && !stored[id]).map(([id, position]) => ({ id, position }))];\n\t};\n\tconst requireEditable = () => {\n\t\tif (doc().tier !== 'editable')\n\t\t\tthrow new Error(`This ${doc().family} diagram can only be changed as Mermaid text; use load_diagram_mermaid.`);\n\t};\n\n\tconst getDiagram: AgentTool = {\n\t\tname: 'get_diagram',\n\t\ttitle: 'Read the diagram',\n\t\tdescription:\n\t\t\t'Returns the diagram: its Mermaid family (flowchart, state, class, er; other families are text only), direction, nodes (id, label, shape, parentId), edges (id, source, target, label), groups (subgraphs / composite states), the shapes nodes can take, stored canvas positions, any parse diagnostics and the selection. Use the ids it returns with the write tools.',\n\t\tinputSchema: object({ includeSource: { type: 'boolean', description: 'Also return the Mermaid text (default false).' } }),\n\t\tannotations: { title: 'Read the diagram', ...READ },\n\t\thandler: async ({ includeSource = false }) => {\n\t\t\tconst reason = readOnlyReason();\n\t\t\treturn {\n\t\t\t\t...describeDiagram(doc()),\n\t\t\t\tselection: controller.getState().selection,\n\t\t\t\t...(includeSource ? { source: doc().source } : {}),\n\t\t\t\t...(reason ? { readOnly: reason } : {})\n\t\t\t};\n\t\t}\n\t};\n\n\tconst exportMermaid: AgentTool = {\n\t\tname: 'export_diagram_mermaid',\n\t\ttitle: 'Export the diagram as Mermaid',\n\t\tdescription:\n\t\t\t'Returns the diagram as Mermaid text. Canvas positions are kept in a \"%% @pixerate {…}\" comment that Mermaid ignores; pass withLayout false to leave it out.',\n\t\tinputSchema: object({ withLayout: { type: 'boolean', description: 'Keep the %% @pixerate layout comment (default true).' } }),\n\t\tannotations: { title: 'Export the diagram as Mermaid', ...READ },\n\t\thandler: async ({ withLayout = true }) => {\n\t\t\tconst source = doc().source;\n\t\t\treturn { family: doc().family, source: withLayout ? source : source.replace(/^[ \\t]*%%\\s*@(?:pixerate|ursula)\\s+\\{.*\\}[ \\t]*(\\r?\\n|$)/gm, '') };\n\t\t}\n\t};\n\n\tconst findDiagrams: AgentTool = {\n\t\tname: 'find_mermaid_diagrams',\n\t\ttitle: 'Find Mermaid diagrams in text',\n\t\tdescription:\n\t\t\t'Finds the Mermaid diagrams in a piece of text (Markdown with ```mermaid blocks, a chat reply, or a bare diagram) without changing anything. Returns each diagram\\'s source, family and title; pass a source to load_diagram_mermaid to open it.',\n\t\tinputSchema: object({ text: { type: 'string', description: 'The text to search.' } }, ['text']),\n\t\tannotations: { title: 'Find Mermaid diagrams in text', ...READ },\n\t\thandler: ({ text }) => ({ diagrams: findMermaidSources(text) })\n\t};\n\n\tconst loadMermaid: AgentTool = {\n\t\tname: 'load_diagram_mermaid',\n\t\ttitle: 'Replace the diagram with Mermaid',\n\t\tdescription:\n\t\t\t'Replaces the whole diagram with Mermaid text (one undo step). Flowchart, state, class and ER diagrams become editable on the canvas; other families are kept as text. Returns the family, node and edge counts and any parse diagnostics. Prefer the per-operation tools for small changes: they keep the rest of the text as written.',\n\t\tinputSchema: object({ source: { type: 'string', minLength: 1, description: 'Mermaid text, starting with a header such as \"flowchart LR\".' } }, ['source']),\n\t\tannotations: { title: 'Replace the diagram with Mermaid', ...DESTRUCTIVE },\n\t\thandler: async ({ source }) => {\n\t\t\t// A fenced block (```mermaid … ```) is unwrapped; a bare diagram is taken as written.\n\t\t\tconst fenced = /^\\s*(`{3,}|~{3,})/m.test(source) ? findMermaidSources(source) : [];\n\t\t\tif (fenced.length > 1) throw new Error(`The text holds ${fenced.length} diagrams; pass one (find_mermaid_diagrams lists them).`);\n\t\t\tconst text = fenced[0]?.source ?? source;\n\t\t\tif (detectFamily(text) === 'unknown')\n\t\t\t\tthrow new Error('The text has no Mermaid header (e.g. \"flowchart LR\", \"stateDiagram-v2\", \"classDiagram\", \"erDiagram\"); nothing was changed.');\n\t\t\tconst result = options.director ? await options.director.playSource(actor, text) : controller.load(text);\n\t\t\tif (!result.ok) throw new Error(result.message);\n\t\t\tconst d = result.document;\n\t\t\treturn {\n\t\t\t\tfamily: d.family,\n\t\t\t\teditable: d.tier === 'editable',\n\t\t\t\tnodes: d.graph.nodes.length,\n\t\t\t\tedges: d.graph.edges.length,\n\t\t\t\tgroups: d.graph.groups.length,\n\t\t\t\t...(d.diagnostics.length ? { diagnostics: d.diagnostics.map((x) => x.message) } : {})\n\t\t\t};\n\t\t}\n\t};\n\n\tconst addNode: AgentTool = {\n\t\tname: 'add_diagram_node',\n\t\ttitle: 'Add a node',\n\t\tdescription:\n\t\t\t'Adds a node to the diagram, optionally inside a group and connected from an existing node. Without a position the canvas places it beside its neighbours. Returns the node and any edge created.',\n\t\tinputSchema: object(\n\t\t\t{\n\t\t\t\tlabel: LABEL('Text shown in the node.'),\n\t\t\t\tid: { type: 'string', pattern: '^[A-Za-z0-9_][\\\\w.-]*$', description: 'Mermaid id (default: the next free \"N<number>\").' },\n\t\t\t\tshape: { type: 'string', description: 'One of the shapes get_diagram lists (default \"rect\"). Flowchart and state diagrams only.' },\n\t\t\t\tparentId: { type: 'string', description: 'Group (subgraph) to add the node to.' },\n\t\t\t\tfrom: { type: 'string', description: 'An existing node to connect to the new node.' },\n\t\t\t\tedgeLabel: LABEL('Label of the edge from \"from\" (needs \"from\").'),\n\t\t\t\tx: { type: 'number', description: 'Left edge in canvas px (needs y).' },\n\t\t\t\ty: { type: 'number', description: 'Top edge in canvas px (needs x).' }\n\t\t\t},\n\t\t\t['label']\n\t\t),\n\t\tannotations: { title: 'Add a node', ...WRITE },\n\t\thandler: async ({ label, id, shape, parentId, from, edgeLabel, x, y }) => {\n\t\t\trequireEditable();\n\t\t\tif ((x === undefined) !== (y === undefined)) throw new Error('Give both x and y, or neither to place the node automatically.');\n\t\t\tif (edgeLabel !== undefined && from === undefined) throw new Error('edgeLabel needs \"from\", the node the edge starts at.');\n\t\t\tconst nodeId = id ?? controller.nextId();\n\t\t\tconst before = edgeIds();\n\t\t\tconst ops: EditOp[] = [\n\t\t\t\t{ type: 'addNode', node: { id: nodeId, label, ...(shape ? { shape } : {}), ...(parentId ? { parentId } : {}) }, ...(x !== undefined ? { position: [x, y] as const } : {}) }\n\t\t\t];\n\t\t\tif (from !== undefined) ops.push({ type: 'addEdge', edge: { source: from, target: nodeId, ...(edgeLabel ? { label: edgeLabel } : {}) } });\n\t\t\tconst d = await apply(ops, 'Add node');\n\t\t\tconst edges = newEdges(before, d);\n\t\t\treturn { node: nodeOut(d, nodeId), ...(edges.length ? { edge: edges[0] } : {}) };\n\t\t}\n\t};\n\n\tconst removeElements: AgentTool = {\n\t\tname: 'remove_diagram_nodes',\n\t\ttitle: 'Remove nodes or groups',\n\t\tdescription:\n\t\t\t'Deletes nodes with every edge that touches them. A group id removes the group but keeps its contents. Returns the removed ids and the ids of the edges that went with them.',\n\t\tinputSchema: object({ ids: IDS('Node or group ids from get_diagram.') }, ['ids']),\n\t\tannotations: { title: 'Remove nodes or groups', ...DESTRUCTIVE },\n\t\thandler: async ({ ids }) => {\n\t\t\trequireEditable();\n\t\t\tconst before = doc().graph.edges.map((e) => e.id);\n\t\t\tconst d = await apply({ type: 'removeNodes', ids }, ids.length === 1 ? 'Delete node' : 'Delete nodes');\n\t\t\tconst after = new Set(d.graph.edges.map((e) => e.id));\n\t\t\treturn { removed: ids, removedEdges: before.filter((id) => !after.has(id)) };\n\t\t}\n\t};\n\n\tconst renameNode: AgentTool = {\n\t\tname: 'rename_diagram_node',\n\t\ttitle: 'Rename a node or group',\n\t\tdescription: 'Changes the label of a node or group. Returns the node with its new label.',\n\t\tinputSchema: object({ id: { ...ID, description: 'A node or group id from get_diagram.' }, label: LABEL('The new label.') }, ['id', 'label']),\n\t\tannotations: { title: 'Rename a node or group', ...WRITE_IDEMPOTENT },\n\t\thandler: async ({ id, label }) => {\n\t\t\trequireEditable();\n\t\t\tconst isGroup = doc().graph.groups.some((g) => g.id === id);\n\t\t\tconst d = await apply(isGroup ? { type: 'renameGroup', id, label } : { type: 'renameNode', id, label }, 'Rename');\n\t\t\treturn { node: nodeOut(d, id) };\n\t\t}\n\t};\n\n\tconst setShape: AgentTool = {\n\t\tname: 'set_diagram_node_shape',\n\t\ttitle: 'Change a node\\'s shape',\n\t\tdescription: 'Changes a node\\'s shape (flowchart and state diagrams). Use one of the shapes get_diagram lists. Returns the node.',\n\t\tinputSchema: object({ id: ID, shape: { type: 'string', minLength: 1, description: 'Shape name, e.g. \"rect\", \"rounded\", \"diam\", \"cyl\", \"circle\".' } }, ['id', 'shape']),\n\t\tannotations: { title: 'Change a node\\'s shape', ...WRITE_IDEMPOTENT },\n\t\thandler: async ({ id, shape }) => {\n\t\t\trequireEditable();\n\t\t\treturn { node: nodeOut(await apply({ type: 'setNodeShape', id, shape }, 'Change shape'), id) };\n\t\t}\n\t};\n\n\tconst setStyle: AgentTool = {\n\t\tname: 'set_diagram_node_style',\n\t\ttitle: 'Style a node',\n\t\tdescription:\n\t\t\t'Replaces a node\\'s inline Mermaid styles (written as a `style` statement), e.g. [\"fill:#fde68a\", \"stroke:#b45309\", \"stroke-width:2px\"]. An empty list removes them. Returns the node with its styles.',\n\t\tinputSchema: object(\n\t\t\t{ id: ID, styles: { type: 'array', items: { type: 'string', pattern: '^[\\\\w-]+:[^,;]+$' }, description: 'CSS-like \"property:value\" pairs.' } },\n\t\t\t['id', 'styles']\n\t\t),\n\t\tannotations: { title: 'Style a node', ...WRITE_IDEMPOTENT },\n\t\thandler: async ({ id, styles }) => {\n\t\t\trequireEditable();\n\t\t\tconst d = await apply({ type: 'setNodeStyles', id, styles }, 'Style node');\n\t\t\treturn { id, styles: d.graph.nodes.find((n) => n.id === id)?.styles ?? [] };\n\t\t}\n\t};\n\n\tconst connect: AgentTool = {\n\t\tname: 'connect_diagram_nodes',\n\t\ttitle: 'Connect two nodes',\n\t\tdescription: 'Draws an edge from one existing node to another, with an optional label. Returns the new edge with its id.',\n\t\tinputSchema: object({ source: { ...ID, description: 'Node the edge starts at.' }, target: { ...ID, description: 'Node the edge ends at.' }, label: LABEL('Edge label (optional).') }, ['source', 'target']),\n\t\tannotations: { title: 'Connect two nodes', ...WRITE },\n\t\thandler: async ({ source, target, label }) => {\n\t\t\trequireEditable();\n\t\t\tconst before = edgeIds();\n\t\t\tconst d = await apply({ type: 'addEdge', edge: { source, target, ...(label ? { label } : {}) } }, 'Connect');\n\t\t\treturn { edge: newEdges(before, d)[0] ?? null };\n\t\t}\n\t};\n\n\tconst removeEdges: AgentTool = {\n\t\tname: 'remove_diagram_edges',\n\t\ttitle: 'Remove edges',\n\t\tdescription: 'Deletes edges by id; the nodes stay. Returns the removed ids.',\n\t\tinputSchema: object({ ids: IDS('Edge ids from get_diagram.') }, ['ids']),\n\t\tannotations: { title: 'Remove edges', ...DESTRUCTIVE },\n\t\thandler: async ({ ids }) => {\n\t\t\trequireEditable();\n\t\t\tawait apply({ type: 'removeEdges', ids }, ids.length === 1 ? 'Delete edge' : 'Delete edges');\n\t\t\treturn { removed: ids };\n\t\t}\n\t};\n\n\tconst labelEdge: AgentTool = {\n\t\tname: 'set_diagram_edge_label',\n\t\ttitle: 'Label an edge',\n\t\tdescription: 'Sets an edge\\'s label; an empty string removes it. Returns the edge.',\n\t\tinputSchema: object({ id: EDGE_ID, label: LABEL('The new label (\"\" removes it).') }, ['id', 'label']),\n\t\tannotations: { title: 'Label an edge', ...WRITE_IDEMPOTENT },\n\t\thandler: async ({ id, label }) => {\n\t\t\trequireEditable();\n\t\t\tconst d = await apply({ type: 'setEdgeLabel', id, label }, 'Label edge');\n\t\t\tconst e = d.graph.edges.find((x) => x.id === id);\n\t\t\treturn { edge: e ? { id: e.id, source: e.source, target: e.target, label: e.label ?? '' } : { id, label } };\n\t\t}\n\t};\n\n\tconst reconnect: AgentTool = {\n\t\tname: 'reconnect_diagram_edge',\n\t\ttitle: 'Reconnect an edge',\n\t\tdescription: 'Moves an edge to new endpoints, keeping its label and style. Returns the edge (its id may change with its endpoints).',\n\t\tinputSchema: object({ id: EDGE_ID, source: { ...ID, description: 'New start node.' }, target: { ...ID, description: 'New end node.' } }, ['id', 'source', 'target']),\n\t\tannotations: { title: 'Reconnect an edge', ...WRITE },\n\t\thandler: async ({ id, source, target }) => {\n\t\t\trequireEditable();\n\t\t\tconst index = doc().graph.edges.findIndex((e) => e.id === id);\n\t\t\tconst d = await apply({ type: 'reconnectEdge', id, source, target }, 'Reconnect edge');\n\t\t\tconst e = d.graph.edges.find((x) => x.id === id) ?? d.graph.edges.find((x, i) => i === index && x.source === source && x.target === target) ?? d.graph.edges.find((x) => x.source === source && x.target === target);\n\t\t\treturn { edge: e ? { id: e.id, source: e.source, target: e.target, ...(e.label ? { label: e.label } : {}) } : null };\n\t\t}\n\t};\n\n\tconst setDirection: AgentTool = {\n\t\tname: 'set_diagram_direction',\n\t\ttitle: 'Set the layout direction',\n\t\tdescription: 'Sets the direction the diagram flows in: TB / TD (top to bottom), BT, LR (left to right) or RL. Returns the direction.',\n\t\tinputSchema: object({ direction: { type: 'string', enum: [...DIRECTIONS], description: 'TB, TD, BT, LR or RL.' } }, ['direction']),\n\t\tannotations: { title: 'Set the layout direction', ...WRITE_IDEMPOTENT },\n\t\thandler: async ({ direction }) => {\n\t\t\trequireEditable();\n\t\t\treturn { direction: (await apply({ type: 'setDirection', direction }, 'Change direction')).graph.direction };\n\t\t}\n\t};\n\n\tconst group: AgentTool = {\n\t\tname: 'group_diagram_nodes',\n\t\ttitle: 'Group nodes',\n\t\tdescription: 'Puts nodes into a new group (a flowchart subgraph, composite state or class namespace). Returns the group id.',\n\t\tinputSchema: object(\n\t\t\t{\n\t\t\t\tnodeIds: IDS('Node ids to put in the group.'),\n\t\t\t\tlabel: LABEL('Group title.'),\n\t\t\t\tid: { type: 'string', pattern: '^[A-Za-z0-9_][\\\\w.-]*$', description: 'Group id (default: the next free \"G<number>\").' }\n\t\t\t},\n\t\t\t['nodeIds', 'label']\n\t\t),\n\t\tannotations: { title: 'Group nodes', ...WRITE },\n\t\thandler: async ({ nodeIds, label, id }) => {\n\t\t\trequireEditable();\n\t\t\tif (!diagramSupportsGroups(doc().family)) throw new Error(`${doc().family} diagrams have no groups.`);\n\t\t\tconst groupId = id ?? controller.nextId('G');\n\t\t\tawait apply({ type: 'groupNodes', groupId, label, nodeIds }, 'Group');\n\t\t\treturn { group: { id: groupId, label, nodeIds } };\n\t\t}\n\t};\n\n\tconst ungroup: AgentTool = {\n\t\tname: 'ungroup_diagram_group',\n\t\ttitle: 'Ungroup',\n\t\tdescription: 'Removes a group and keeps its nodes (they move to the group\\'s parent). Returns the ids that were in it.',\n\t\tinputSchema: object({ id: { type: 'string', minLength: 1, description: 'A group id from get_diagram.' } }, ['id']),\n\t\tannotations: { title: 'Ungroup', ...DESTRUCTIVE },\n\t\thandler: async ({ id }) => {\n\t\t\trequireEditable();\n\t\t\tconst members = [...doc().graph.nodes, ...doc().graph.groups].filter((x) => x.parentId === id).map((x) => x.id);\n\t\t\tawait apply({ type: 'ungroup', groupId: id }, 'Ungroup');\n\t\t\treturn { ungrouped: id, members };\n\t\t}\n\t};\n\n\tconst move: AgentTool = {\n\t\tname: 'move_diagram_nodes',\n\t\ttitle: 'Move nodes',\n\t\tdescription:\n\t\t\t'Moves nodes or groups on the canvas to absolute positions (top-left, canvas px). Positions are stored in the %% @pixerate comment; the Mermaid statements do not change. Returns the new positions.',\n\t\tinputSchema: object(\n\t\t\t{\n\t\t\t\tmoves: {\n\t\t\t\t\ttype: 'array',\n\t\t\t\t\tminItems: 1,\n\t\t\t\t\tdescription: 'One entry per node.',\n\t\t\t\t\titems: object({ id: { ...ID, description: 'Node or group id.' }, x: { type: 'number', description: 'Left edge in px.' }, y: { type: 'number', description: 'Top edge in px.' } }, ['id', 'x', 'y'])\n\t\t\t\t}\n\t\t\t},\n\t\t\t['moves']\n\t\t),\n\t\tannotations: { title: 'Move nodes', ...WRITE_IDEMPOTENT },\n\t\thandler: async ({ moves }) => {\n\t\t\tconst list = moves as Array<{ id: string; x: number; y: number }>;\n\t\t\trequireEditable();\n\t\t\tawait apply({ type: 'moveNodes', moves: pinned(list.map((m) => ({ id: m.id, position: [m.x, m.y] as const }))) }, 'Move');\n\t\t\treturn { moved: moves };\n\t\t}\n\t};\n\n\tconst undo: AgentTool = {\n\t\tname: 'undo_diagram',\n\t\ttitle: 'Undo a diagram edit',\n\t\tdescription: 'Undoes the last diagram edit. Returns whether something was undone and its label.',\n\t\tinputSchema: object({}),\n\t\tannotations: { title: 'Undo a diagram edit', ...WRITE },\n\t\thandler: async () => {\n\t\t\tconst label = controller.getState().undoLabel;\n\t\t\tconst undone = controller.undo();\n\t\t\treturn { undone, ...(undone ? { label } : {}) };\n\t\t}\n\t};\n\n\tconst redo: AgentTool = {\n\t\tname: 'redo_diagram',\n\t\ttitle: 'Redo a diagram edit',\n\t\tdescription: 'Redoes the last undone diagram edit. Returns whether something was redone and its label.',\n\t\tinputSchema: object({}),\n\t\tannotations: { title: 'Redo a diagram edit', ...WRITE },\n\t\thandler: async () => {\n\t\t\tconst label = controller.getState().redoLabel;\n\t\t\tconst redone = controller.redo();\n\t\t\treturn { redone, ...(redone ? { label } : {}) };\n\t\t}\n\t};\n\n\tconst renderDiagram: AgentTool = {\n\t\tname: 'render_diagram',\n\t\ttitle: 'Render the diagram',\n\t\tdescription:\n\t\t\t'Draws the diagram without changing it, to check it visually: standalone SVG text by default, or a PNG image (format \"png\") when the host can rasterize. Positions match the canvas.',\n\t\tinputSchema: object({\n\t\t\tformat: { type: 'string', enum: ['svg', 'png'], description: 'svg (default) or png (only when the host supports it).' },\n\t\t\tmaxWidth: { type: 'number', minimum: 16, description: 'Fit within this width in px (default: natural size).' },\n\t\t\tmaxHeight: { type: 'number', minimum: 16, description: 'Fit within this height in px (default: natural size).' }\n\t\t}),\n\t\tannotations: { title: 'Render the diagram', ...READ },\n\t\thandler: async ({ format = 'svg', maxWidth, maxHeight }) => {\n\t\t\tif (doc().tier !== 'editable') throw new Error(`A ${doc().family} diagram is kept as Mermaid text and cannot be drawn here; use export_diagram_mermaid.`);\n\t\t\tconst drawn = options.renderSvg!({ maxWidth, maxHeight });\n\t\t\tif (format === 'png') {\n\t\t\t\tif (!options.rasterize) throw new Error('This host cannot make PNG images; call render_diagram with format \"svg\".');\n\t\t\t\tconst image = await options.rasterize(drawn.svg, { width: drawn.width, height: drawn.height });\n\t\t\t\treturn { content: [{ type: 'image', data: image.data, mimeType: image.mimeType }] };\n\t\t\t}\n\t\t\treturn { format: 'svg', width: drawn.width, height: drawn.height, svg: drawn.svg };\n\t\t}\n\t};\n\n\tconst reads = [getDiagram, exportMermaid, findDiagrams, ...(options.renderSvg ? [renderDiagram] : [])];\n\tconst writes = [loadMermaid, addNode, removeElements, renameNode, setShape, setStyle, connect, removeEdges, labelEdge, reconnect, setDirection, group, ungroup, move, undo, redo];\n\n\treturn {\n\t\tid: options.id ?? 'diagram',\n\t\tnamespace: options.namespace,\n\t\ttools: () => (readOnlyReason() ? reads : [...reads, ...writes]),\n\t\tsubscribe: (notify) => {\n\t\t\tlet last = readOnlyReason();\n\t\t\treturn controller.subscribe(() => {\n\t\t\t\tconst now = readOnlyReason();\n\t\t\t\tif (now !== last) {\n\t\t\t\t\tlast = now;\n\t\t\t\t\tnotify();\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t};\n}\n","import { parseMermaid } from \"./document.js\";\nimport type { DiagramGraph } from \"./types.js\";\n\n/** What a frame shows, for telling frames apart and finding what each one adds. */\nfunction signature(graph: DiagramGraph): string {\n return JSON.stringify([\n graph.title ?? \"\",\n graph.nodes.map((n) => [n.id, n.label, n.parentId ?? \"\", n.data ?? null]),\n graph.edges.map((e) => [e.source, e.target, e.label ?? \"\"]),\n graph.groups.map((g) => [g.id, g.label, g.parentId ?? \"\"]),\n graph.data ?? null,\n ]);\n}\n\nconst isMeta = (line: string) => /^\\s*%%\\s*@(?:pixerate|ursula)\\b/.test(line);\n\n/** Body lines (after the header), without the layout comment and trailing blanks. */\nfunction bodyLines(lines: string[], headerIndex: number): string[] {\n const body = lines.slice(headerIndex + 1).filter((l) => !isMeta(l));\n while (body.length && !body[body.length - 1]!.trim()) body.pop();\n return body;\n}\n\nfunction headerIndexOf(lines: string[], header: string): number {\n const wanted = header.trim();\n return wanted ? lines.findIndex((l) => l.trim() === wanted) : -1;\n}\n\nexport interface DraftOptions {\n /** Most frames to play; longer drafts are sampled evenly (default 40). */\n maxFrames?: number;\n}\n\n/**\n * Splits writing `target` over `current` into frames that grow the diagram\n * statement by statement, so a whole-diagram write can be played as the\n * diagram being drawn. Each frame is a valid prefix of `target` (it parses\n * as the same family without diagnostics) that shows something new; the last\n * frame is `target` itself.\n *\n * Returns null when `target` does not extend `current` (some existing line\n * changes), when the family changes, or when there is nothing to grow: the\n * write is then committed in one step.\n */\nexport function draftFrames(\n current: string,\n target: string,\n options: DraftOptions = {},\n): string[] | null {\n const maxFrames = Math.max(2, options.maxFrames ?? 40);\n const now = parseMermaid(current);\n const goal = parseMermaid(target);\n if (goal.family !== now.family || goal.diagnostics.length) return null;\n\n const eol = goal.eol;\n const lines = target.split(/\\r?\\n/);\n const at = headerIndexOf(lines, goal.header);\n if (at < 0) return null;\n const currentLines = current.split(/\\r?\\n/);\n const currentAt = headerIndexOf(currentLines, now.header);\n if (currentAt < 0) return null;\n\n // The target must keep every current body line, in order, as its start.\n const have = bodyLines(currentLines, currentAt);\n const body = lines\n .map((line, index) => ({ line, index }))\n .slice(at + 1)\n .filter(({ line }) => !isMeta(line));\n for (let i = 0; i < have.length; i++) {\n if (body[i]?.line !== have[i]) return null;\n }\n\n const frames: string[] = [];\n let last = signature(now.graph);\n for (let i = have.length; i < body.length; i++) {\n const end = body[i]!.index;\n const candidate = lines.slice(0, end + 1).join(eol) + eol;\n const doc = parseMermaid(candidate);\n if (doc.family !== goal.family || doc.diagnostics.length) continue;\n const sig = signature(doc.graph);\n if (sig === last) continue;\n frames.push(candidate);\n last = sig;\n }\n if (!frames.length) return null;\n // The last frame is the target exactly (its trailing lines and metadata included).\n if (last === signature(goal.graph)) frames[frames.length - 1] = target;\n else frames.push(target);\n\n if (frames.length <= maxFrames) return frames;\n const sampled: string[] = [];\n for (let k = 1; k <= maxFrames; k++) {\n sampled.push(frames[Math.round((k * frames.length) / maxFrames) - 1]!);\n }\n return sampled;\n}\n\n/** Ids of nodes and groups in `after` that are new or changed since `before`. */\nexport function changedItems(before: DiagramGraph, after: DiagramGraph): string[] {\n const old = new Map<string, string>();\n for (const n of before.nodes) old.set(n.id, JSON.stringify([n.label, n.parentId, n.data]));\n for (const g of before.groups) old.set(g.id, JSON.stringify([g.label, g.parentId]));\n const out: string[] = [];\n for (const n of after.nodes)\n if (old.get(n.id) !== JSON.stringify([n.label, n.parentId, n.data])) out.push(n.id);\n for (const g of after.groups)\n if (old.get(g.id) !== JSON.stringify([g.label, g.parentId])) out.push(g.id);\n return out;\n}\n","// Acting out diagram edits follows Ursula's remote control (src/remote/commands.ts `planCommand`,\n// src/remote/draft.ts): a pure planner turns each edit into cursor steps on the canvas, and a\n// whole-diagram write is drawn statement by statement. Shared pieces live in animation/presence.\nimport {\n\tPerformanceQueue,\n\tPresenceStore,\n\trealPresenceClock,\n\ttweenPresenceCursor,\n\ttype PresenceActorInfo,\n\ttype PresenceClock\n} from '../animation/presence.js';\nimport type { DiagramController } from './controller.js';\nimport { validateDiagramEdit } from './controller.js';\nimport { applyEdits, parseMermaid } from './document.js';\nimport { changedItems, draftFrames } from './draft.js';\nimport type { EditOp, MermaidDocument, Point } from './types.js';\n\n/** Where things are drawn on the canvas (absolute top-left and size), e.g. `mermaidToCanvas(...).layout`. */\nexport interface DiagramPlacement {\n\tpositions: Readonly<Record<string, Point>>;\n\tsizes: Readonly<Record<string, { width: number; height: number }>>;\n}\n\nexport type DiagramPerformanceStep =\n\t| { type: 'move'; to: { x: number; y: number }; ms: number }\n\t| { type: 'say'; text: string; ms: number }\n\t| { type: 'type'; text: string; ms: number }\n\t| { type: 'press' }\n\t| { type: 'release' }\n\t/** Drag a node (and the cursor) so its top-left ends at `to`; the node follows live. */\n\t| { type: 'drag'; id: string; to: { x: number; y: number }; position: Point; ms: number }\n\t| { type: 'commit' };\n\nconst center = (place: DiagramPlacement, id: string) => {\n\tconst p = place.positions[id];\n\tif (!p) return null;\n\tconst s = place.sizes[id] ?? { width: 160, height: 56 };\n\treturn { x: p[0] + s.width / 2, y: p[1] + s.height / 2 };\n};\n\n/** Bottom-right of everything drawn, where a node without a better place appears. */\nfunction freeSpot(place: DiagramPlacement): { x: number; y: number } {\n\tlet maxX = 0;\n\tlet maxY = 0;\n\tlet any = false;\n\tfor (const [id, [x, y]] of Object.entries(place.positions)) {\n\t\tconst s = place.sizes[id] ?? { width: 160, height: 56 };\n\t\tmaxX = Math.max(maxX, x + s.width);\n\t\tmaxY = Math.max(maxY, y + s.height);\n\t\tany = true;\n\t}\n\treturn any ? { x: maxX + 60, y: maxY / 2 } : { x: 80, y: 40 };\n}\n\nconst NEW_NODE = { width: 160, height: 56 };\n\n/**\n * Where a new node's centre goes beside `from`: one rank further along the diagram's\n * direction, then stepped sideways until it overlaps nothing drawn.\n */\nfunction besideFree(place: DiagramPlacement, from: { x: number; y: number }, direction: string): { x: number; y: number } {\n\tconst d = direction.toUpperCase();\n\tconst along = d === 'LR' ? { x: 1, y: 0 } : d === 'RL' ? { x: -1, y: 0 } : d === 'BT' ? { x: 0, y: -1 } : { x: 0, y: 1 };\n\tconst side = { x: along.y, y: along.x };\n\tconst rects = Object.entries(place.positions).map(([id, [x, y]]) => ({ x, y, ...(place.sizes[id] ?? NEW_NODE) }));\n\tconst free = (c: { x: number; y: number }) =>\n\t\t!rects.some((r) => c.x - NEW_NODE.width / 2 - 20 < r.x + r.width && c.x + NEW_NODE.width / 2 + 20 > r.x && c.y - NEW_NODE.height / 2 - 20 < r.y + r.height && c.y + NEW_NODE.height / 2 + 20 > r.y);\n\tconst base = { x: from.x + along.x * 240, y: from.y + along.y * 140 };\n\tfor (let i = 0; i < 24; i++) {\n\t\tconst k = i === 0 ? 0 : (i % 2 ? 1 : -1) * Math.ceil(i / 2);\n\t\tconst c = { x: base.x + side.x * k * 200, y: base.y + side.y * k * 90 };\n\t\tif (free(c)) return c;\n\t}\n\treturn base;\n}\n\n/**\n * Plans how an agent acts out one edit on the canvas: where the cursor goes, what it\n * clicks, drags or types, and when the edit commits. Pure: the same document, placement\n * and edit give the same steps. `next` is the edit that follows in the same call (a new\n * node is placed beside the node an edge will connect it from).\n */\nexport function planDiagramEdit(doc: MermaidDocument, place: DiagramPlacement, op: EditOp, speed = 1, next?: EditOp): DiagramPerformanceStep[] {\n\tconst ms = (n: number) => Math.round(n / Math.max(0.1, speed));\n\tconst at = (id: string) => center(place, id);\n\tconst click = (to: { x: number; y: number } | null): DiagramPerformanceStep[] =>\n\t\tto ? [{ type: 'move', to, ms: ms(380) }, { type: 'press' }, { type: 'release' }] : [];\n\tconst edgeMid = (id: string) => {\n\t\tconst e = doc.graph.edges.find((x) => x.id === id);\n\t\tconst a = e && at(e.source);\n\t\tconst b = e && at(e.target);\n\t\treturn a && b ? { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 } : null;\n\t};\n\tswitch (op.type) {\n\t\tcase 'addNode': {\n\t\t\tconst from = next?.type === 'addEdge' && next.edge.target === op.node.id ? at(next.edge.source) : null;\n\t\t\tconst to = op.position\n\t\t\t\t? { x: op.position[0] + 80, y: op.position[1] + 28 }\n\t\t\t\t: from\n\t\t\t\t\t? besideFree(place, from, doc.graph.direction)\n\t\t\t\t\t: freeSpot(place);\n\t\t\tconst label = op.node.label ?? op.node.id;\n\t\t\treturn [...click(to), { type: 'type', text: label, ms: ms(Math.min(900, 60 * label.length + 200)) }, { type: 'commit' }];\n\t\t}\n\t\tcase 'addEdge': {\n\t\t\tconst a = at(op.edge.source);\n\t\t\tconst b = at(op.edge.target) ?? (a ? { x: a.x + 260, y: a.y } : null);\n\t\t\tif (!a || !b) return [{ type: 'commit' }];\n\t\t\treturn [\n\t\t\t\t{ type: 'move', to: a, ms: ms(380) },\n\t\t\t\t{ type: 'press' },\n\t\t\t\t{ type: 'move', to: b, ms: ms(520) },\n\t\t\t\t{ type: 'release' },\n\t\t\t\t...(op.edge.label ? [{ type: 'type' as const, text: op.edge.label, ms: ms(500) }] : []),\n\t\t\t\t{ type: 'commit' }\n\t\t\t];\n\t\t}\n\t\tcase 'moveNodes': {\n\t\t\tconst steps: DiagramPerformanceStep[] = [];\n\t\t\tfor (const move of op.moves) {\n\t\t\t\tconst from = at(move.id);\n\t\t\t\tconst size = place.sizes[move.id] ?? { width: 160, height: 56 };\n\t\t\t\tif (!from) continue;\n\t\t\t\tsteps.push(\n\t\t\t\t\t{ type: 'move', to: from, ms: ms(380) },\n\t\t\t\t\t{ type: 'press' },\n\t\t\t\t\t{ type: 'drag', id: move.id, to: { x: move.position[0] + size.width / 2, y: move.position[1] + size.height / 2 }, position: move.position, ms: ms(520) },\n\t\t\t\t\t{ type: 'release' }\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn [...steps, { type: 'commit' }];\n\t\t}\n\t\tcase 'renameNode':\n\t\tcase 'renameGroup':\n\t\t\treturn [...click(at(op.id)), { type: 'press' }, { type: 'release' }, { type: 'type', text: op.label, ms: ms(Math.min(900, 60 * op.label.length + 200)) }, { type: 'commit' }];\n\t\tcase 'removeNodes':\n\t\t\treturn [...click(at(op.ids[0]!)), { type: 'say', text: 'Delete', ms: ms(300) }, { type: 'commit' }];\n\t\tcase 'removeEdges':\n\t\t\treturn [...click(edgeMid(op.ids[0]!)), { type: 'say', text: 'Delete', ms: ms(300) }, { type: 'commit' }];\n\t\tcase 'setEdgeLabel':\n\t\t\treturn [...click(edgeMid(op.id)), { type: 'type', text: op.label, ms: ms(500) }, { type: 'commit' }];\n\t\tcase 'reconnectEdge': {\n\t\t\tconst a = at(op.source);\n\t\t\tconst b = at(op.target);\n\t\t\treturn [...click(edgeMid(op.id)), ...(a && b ? [{ type: 'move' as const, to: a, ms: ms(300) }, { type: 'press' as const }, { type: 'move' as const, to: b, ms: ms(500) }, { type: 'release' as const }] : []), { type: 'commit' }];\n\t\t}\n\t\tcase 'setNodeShape':\n\t\tcase 'setNodeStyles':\n\t\tcase 'setNodeClasses':\n\t\tcase 'setNodeLink':\n\t\tcase 'setNodeData':\n\t\tcase 'resizeNode':\n\t\t\treturn [...click(at(op.id)), { type: 'commit' }];\n\t\tcase 'groupNodes': {\n\t\t\tconst points = op.nodeIds.map(at).filter((p): p is { x: number; y: number } => !!p);\n\t\t\tif (!points.length) return [{ type: 'commit' }];\n\t\t\tconst min = { x: Math.min(...points.map((p) => p.x)) - 90, y: Math.min(...points.map((p) => p.y)) - 40 };\n\t\t\tconst max = { x: Math.max(...points.map((p) => p.x)) + 90, y: Math.max(...points.map((p) => p.y)) + 40 };\n\t\t\treturn [{ type: 'move', to: min, ms: ms(380) }, { type: 'press' }, { type: 'move', to: max, ms: ms(520) }, { type: 'release' }, { type: 'type', text: op.label, ms: ms(500) }, { type: 'commit' }];\n\t\t}\n\t\tdefault:\n\t\t\treturn [{ type: 'commit' }];\n\t}\n}\n\nexport interface DiagramDirectorOptions {\n\t/** Where nodes are drawn now, e.g. `() => mermaidToCanvas(controller.getDocument()).layout`. */\n\tplacement: () => DiagramPlacement;\n\tclock?: PresenceClock;\n\t/** 1 = natural speed; 2 = twice as fast. */\n\tspeed?: number;\n\t/** Hide each actor's cursor this long after its last call (ms, default 1500; 0 keeps it). */\n\tlinger?: number;\n\t/** Most frames when a whole-diagram write is drawn statement by statement (default 40). */\n\tmaxDraftFrames?: number;\n}\n\nexport type DiagramPerformanceResult = { ok: true; document: MermaidDocument } | { ok: false; message: string };\n\n/**\n * Acts out agent edits on a Mermaid diagram: each actor's cursor moves to the node or edge,\n * clicks, drags, connects and types, the edit lands, and everything one call changes is one\n * undo step, while the user keeps editing. Calls queue per actor. A batch is checked against\n * the diagram before the cursor moves; if the diagram changed underneath and an edit no\n * longer applies, the call's earlier edits are rolled back and the call fails with a sentence.\n * A whole-diagram write (`playSource`) is drawn statement by statement.\n */\nexport class DiagramDirector {\n\treadonly presence: PresenceStore;\n\tprivate readonly clock: PresenceClock;\n\tprivate readonly queue: PerformanceQueue;\n\n\tconstructor(\n\t\tprivate readonly controller: DiagramController,\n\t\tpresence: PresenceStore,\n\t\tprivate readonly options: DiagramDirectorOptions\n\t) {\n\t\tthis.presence = presence;\n\t\tthis.clock = options.clock ?? realPresenceClock;\n\t\tthis.queue = new PerformanceQueue(presence, {\n\t\t\tclock: this.clock,\n\t\t\tlinger: options.linger,\n\t\t\tbeginGesture: (key) => controller.beginGesture(key),\n\t\t\tendGesture: () => controller.endGesture()\n\t\t});\n\t}\n\n\t/** Acts out `ops` in order for `actor` as one undo step. */\n\tperform(actor: PresenceActorInfo, ops: readonly EditOp[], label = 'Edit diagram'): Promise<DiagramPerformanceResult> {\n\t\t// All-or-nothing: the whole batch must apply before the cursor moves.\n\t\tlet check = this.controller.getDocument();\n\t\tif (this.controller.getState().readOnly) return Promise.resolve({ ok: false, message: 'The diagram is read-only.' });\n\t\tfor (const op of ops) {\n\t\t\tconst problem = validateDiagramEdit(check, op);\n\t\t\tif (problem) return Promise.resolve({ ok: false, message: problem });\n\t\t\tcheck = applyEdits(check, [op]);\n\t\t}\n\t\treturn this.queue.run(actor, async () => {\n\t\t\tconst mark = this.controller.history.mark();\n\t\t\tconst fail = (message: string): DiagramPerformanceResult => {\n\t\t\t\tthis.controller.history.revertTo(mark);\n\t\t\t\treturn { ok: false, message };\n\t\t\t};\n\t\t\t// Placing or dragging stores positions; pin the rest of an auto-laid diagram first so\n\t\t\t// it does not shift around them (see GOTCHAS.md).\n\t\t\tif (ops.some((op) => op.type === 'addNode' || op.type === 'moveNodes')) {\n\t\t\t\tconst problem = this.pin(label);\n\t\t\t\tif (problem) return fail(problem);\n\t\t\t}\n\t\t\tfor (let i = 0; i < ops.length; i++) {\n\t\t\t\tconst op = ops[i]!;\n\t\t\t\tconst steps = planDiagramEdit(this.controller.getDocument(), this.options.placement(), op, this.options.speed, ops[i + 1]);\n\t\t\t\tfor (const step of steps) {\n\t\t\t\t\tconst problem = await this.act(actor.id, step, op, label);\n\t\t\t\t\tif (problem) return fail(problem);\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn { ok: true, document: this.controller.getDocument() };\n\t\t});\n\t}\n\n\t/**\n\t * Replaces the diagram with `source`, drawing it statement by statement when it extends\n\t * what is there (Ursula's draft playback): each frame lands, the cursor points at what\n\t * appeared, and the whole write is one undo step. Otherwise it lands at once.\n\t */\n\tplaySource(actor: PresenceActorInfo, source: string, label = 'Replace diagram'): Promise<DiagramPerformanceResult> {\n\t\tif (this.controller.getState().readOnly) return Promise.resolve({ ok: false, message: 'The diagram is read-only.' });\n\t\treturn this.queue.run(actor, async () => {\n\t\t\tconst frames = draftFrames(this.controller.getSource(), source, { maxFrames: this.options.maxDraftFrames }) ?? [source];\n\t\t\tconst pause = Math.round(260 / Math.max(0.1, this.options.speed ?? 1));\n\t\t\tfor (const frame of frames) {\n\t\t\t\tconst before = this.controller.getDocument().graph;\n\t\t\t\tconst result = this.controller.apply({ type: 'replaceSource', source: frame }, label);\n\t\t\t\tif (!result.ok) return result;\n\t\t\t\tconst fresh = changedItems(before, result.document.graph);\n\t\t\t\tconst target = fresh.length ? center(this.options.placement(), fresh[fresh.length - 1]!) : null;\n\t\t\t\tconst actorNow = this.presence.get(actor.id);\n\t\t\t\tif (target && actorNow) await tweenPresenceCursor(this.presence, this.clock, actor.id, actorNow, target, pause);\n\t\t\t\telse await this.clock.wait(pause);\n\t\t\t}\n\t\t\treturn { ok: true, document: this.controller.getDocument() };\n\t\t});\n\t}\n\n\t/** Stores the drawn position of every node and group that has none yet. */\n\tprivate pin(label: string): string | null {\n\t\tconst doc = this.controller.getDocument();\n\t\tconst stored = doc.meta.layout ?? {};\n\t\tconst known = new Set([...doc.graph.nodes, ...doc.graph.groups].map((x) => x.id));\n\t\tconst moves = Object.entries(this.options.placement().positions)\n\t\t\t.filter(([id]) => known.has(id) && !stored[id])\n\t\t\t.map(([id, position]) => ({ id, position }));\n\t\tif (!moves.length) return null;\n\t\tconst result = this.controller.apply({ type: 'moveNodes', moves }, label);\n\t\treturn result.ok ? null : result.message;\n\t}\n\n\t/** Runs one step; returns a problem sentence when the edit can no longer be applied. */\n\tprivate async act(id: string, step: DiagramPerformanceStep, op: EditOp, label: string): Promise<string | null> {\n\t\tconst where = () => {\n\t\t\tconst a = this.presence.get(id);\n\t\t\treturn a ? { x: a.x, y: a.y } : { x: 0, y: 0 };\n\t\t};\n\t\tswitch (step.type) {\n\t\t\tcase 'say':\n\t\t\t\tthis.presence.set({ id, message: step.text });\n\t\t\t\tawait this.clock.wait(step.ms);\n\t\t\t\treturn null;\n\t\t\tcase 'type': {\n\t\t\t\t// The note grows as if typed.\n\t\t\t\tconst chars = [...step.text];\n\t\t\t\tconst each = step.ms / Math.max(1, chars.length);\n\t\t\t\tfor (let i = 1; i <= chars.length; i++) {\n\t\t\t\t\tthis.presence.set({ id, message: chars.slice(0, i).join('') });\n\t\t\t\t\tawait this.clock.wait(each);\n\t\t\t\t}\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\tcase 'move':\n\t\t\t\tawait tweenPresenceCursor(this.presence, this.clock, id, where(), step.to, step.ms);\n\t\t\t\treturn null;\n\t\t\tcase 'press':\n\t\t\t\tthis.presence.set({ id, pressed: true });\n\t\t\t\treturn null;\n\t\t\tcase 'release':\n\t\t\t\tthis.presence.set({ id, pressed: false });\n\t\t\t\treturn null;\n\t\t\tcase 'drag': {\n\t\t\t\tconst start = this.options.placement().positions[step.id];\n\t\t\t\tlet problem: string | null = null;\n\t\t\t\tawait tweenPresenceCursor(this.presence, this.clock, id, where(), step.to, step.ms, (e) => {\n\t\t\t\t\tif (problem || !start) return;\n\t\t\t\t\tconst position: Point = [start[0] + (step.position[0] - start[0]) * e, start[1] + (step.position[1] - start[1]) * e];\n\t\t\t\t\tconst result = this.controller.apply({ type: 'moveNodes', moves: [{ id: step.id, position }] }, label);\n\t\t\t\t\tif (!result.ok) problem = result.message;\n\t\t\t\t});\n\t\t\t\treturn problem;\n\t\t\t}\n\t\t\tcase 'commit': {\n\t\t\t\t// A new node lands where the cursor clicked.\n\t\t\t\tconst placed: EditOp =\n\t\t\t\t\top.type === 'addNode' && !op.position ? { ...op, position: [Math.round(where().x - 80), Math.round(where().y - 28)] } : op;\n\t\t\t\tconst result = this.controller.apply(placed, label);\n\t\t\t\treturn result.ok ? null : result.message;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/** Reads a document for planning without a controller (tests, previews). */\nexport function planDiagramEdits(source: string, place: DiagramPlacement, ops: readonly EditOp[], speed = 1): DiagramPerformanceStep[][] {\n\tlet doc = parseMermaid(source);\n\treturn ops.map((op, i) => {\n\t\tconst steps = planDiagramEdit(doc, place, op, speed, ops[i + 1]);\n\t\tdoc = applyEdits(doc, [op]);\n\t\treturn steps;\n\t});\n}\n"]}
@@ -704,6 +704,29 @@ interface DiagramToolProviderOptions {
704
704
  * node of an auto-laid diagram does not shift the others.
705
705
  */
706
706
  getPositions?: () => Readonly<Record<string, Point>>;
707
+ /**
708
+ * Draws the diagram as standalone SVG (e.g. `(o) => mermaidToSvg(controller.getDocument(), o)`
709
+ * from `@pixerate/editor/canvas`). Enables `render_diagram`.
710
+ */
711
+ renderSvg?: (options: {
712
+ maxWidth?: number;
713
+ maxHeight?: number;
714
+ }) => {
715
+ svg: string;
716
+ width: number;
717
+ height: number;
718
+ };
719
+ /** Turns that SVG into PNG (e.g. `rasterizeSvg`), so `render_diagram` can return an image. */
720
+ rasterize?: (svg: string, size: {
721
+ width: number;
722
+ height: number;
723
+ }) => Promise<{
724
+ data: string;
725
+ mimeType: string;
726
+ }> | {
727
+ data: string;
728
+ mimeType: string;
729
+ };
707
730
  /**
708
731
  * Acts out every write with a visible cursor (see `DiagramDirector`): it moves to the node,
709
732
  * clicks, drags, connects and types, and a whole-diagram load is drawn statement by
@@ -704,6 +704,29 @@ interface DiagramToolProviderOptions {
704
704
  * node of an auto-laid diagram does not shift the others.
705
705
  */
706
706
  getPositions?: () => Readonly<Record<string, Point>>;
707
+ /**
708
+ * Draws the diagram as standalone SVG (e.g. `(o) => mermaidToSvg(controller.getDocument(), o)`
709
+ * from `@pixerate/editor/canvas`). Enables `render_diagram`.
710
+ */
711
+ renderSvg?: (options: {
712
+ maxWidth?: number;
713
+ maxHeight?: number;
714
+ }) => {
715
+ svg: string;
716
+ width: number;
717
+ height: number;
718
+ };
719
+ /** Turns that SVG into PNG (e.g. `rasterizeSvg`), so `render_diagram` can return an image. */
720
+ rasterize?: (svg: string, size: {
721
+ width: number;
722
+ height: number;
723
+ }) => Promise<{
724
+ data: string;
725
+ mimeType: string;
726
+ }> | {
727
+ data: string;
728
+ mimeType: string;
729
+ };
707
730
  /**
708
731
  * Acts out every write with a visible cursor (see `DiagramDirector`): it moves to the node,
709
732
  * clicks, drags, connects and types, and a whole-diagram load is drawn statement by
@@ -1,14 +1,3 @@
1
- import {
2
- HistoryManager
3
- } from "../chunk-QVZZGT4X.js";
4
- import {
5
- PRESENCE_COLORS,
6
- PerformanceQueue,
7
- PresenceStore,
8
- createInstantPresenceClock,
9
- realPresenceClock,
10
- tweenPresenceCursor
11
- } from "../chunk-B6DSIEXA.js";
12
1
  import {
13
2
  CLASS_RELATIONS,
14
3
  ER_CARDINALITIES,
@@ -52,6 +41,17 @@ import {
52
41
  stateGrammar,
53
42
  supportTierOf
54
43
  } from "../chunk-WWJNC7SX.js";
44
+ import {
45
+ PRESENCE_COLORS,
46
+ PerformanceQueue,
47
+ PresenceStore,
48
+ createInstantPresenceClock,
49
+ realPresenceClock,
50
+ tweenPresenceCursor
51
+ } from "../chunk-B6DSIEXA.js";
52
+ import {
53
+ HistoryManager
54
+ } from "../chunk-QVZZGT4X.js";
55
55
 
56
56
  // src/diagram/types.ts
57
57
  var DIRECTIONS = ["TB", "TD", "BT", "LR", "RL"];
@@ -743,7 +743,28 @@ function createDiagramToolProvider(controller, options = {}) {
743
743
  return { redone, ...redone ? { label } : {} };
744
744
  }
745
745
  };
746
- const reads = [getDiagram, exportMermaid, findDiagrams];
746
+ const renderDiagram = {
747
+ name: "render_diagram",
748
+ title: "Render the diagram",
749
+ description: 'Draws the diagram without changing it, to check it visually: standalone SVG text by default, or a PNG image (format "png") when the host can rasterize. Positions match the canvas.',
750
+ inputSchema: object({
751
+ format: { type: "string", enum: ["svg", "png"], description: "svg (default) or png (only when the host supports it)." },
752
+ maxWidth: { type: "number", minimum: 16, description: "Fit within this width in px (default: natural size)." },
753
+ maxHeight: { type: "number", minimum: 16, description: "Fit within this height in px (default: natural size)." }
754
+ }),
755
+ annotations: { title: "Render the diagram", ...READ },
756
+ handler: async ({ format = "svg", maxWidth, maxHeight }) => {
757
+ if (doc().tier !== "editable") throw new Error(`A ${doc().family} diagram is kept as Mermaid text and cannot be drawn here; use export_diagram_mermaid.`);
758
+ const drawn = options.renderSvg({ maxWidth, maxHeight });
759
+ if (format === "png") {
760
+ if (!options.rasterize) throw new Error('This host cannot make PNG images; call render_diagram with format "svg".');
761
+ const image = await options.rasterize(drawn.svg, { width: drawn.width, height: drawn.height });
762
+ return { content: [{ type: "image", data: image.data, mimeType: image.mimeType }] };
763
+ }
764
+ return { format: "svg", width: drawn.width, height: drawn.height, svg: drawn.svg };
765
+ }
766
+ };
767
+ const reads = [getDiagram, exportMermaid, findDiagrams, ...options.renderSvg ? [renderDiagram] : []];
747
768
  const writes = [loadMermaid, addNode, removeElements, renameNode, setShape, setStyle, connect, removeEdges, labelEdge, reconnect, setDirection, group, ungroup, move, undo, redo];
748
769
  return {
749
770
  id: options.id ?? "diagram",