# CodePic — Free Online Hand-Drawn Diagram Tool > Browser-based diagramming for flowcharts, system design, ER diagrams, wireframes, mind maps, and 80+ templates. Supports AI-driven creation and editing via MCP or JSON. ## Key Pages ### Product - [Home](https://codepic.cc/) — landing page and overview - [Editor](https://codepic.cc/editor/new) — open a blank diagram in the browser editor (no signup required) - [Templates Gallery](https://codepic.cc/templates) — browse 70+ free diagram templates by category - [Dashboard](https://codepic.cc/dashboard) — manage saved documents (signed-in users) ### Dedicated Tools - [Flowchart Maker](https://codepic.cc/tools/flowchart-maker) — flowchart-focused editor entry - [ER Diagram Maker](https://codepic.cc/tools/er-diagram-maker) — database schema design - [Wireframe Tool](https://codepic.cc/tools/wireframe-tool) — UI wireframing - [SWOT Analysis Maker](https://codepic.cc/tools/swot-analysis-maker) — SWOT matrices - [JSON Import](https://codepic.cc/tools/json-import) — paste JSON to generate a diagram - [MCP Integration](https://codepic.cc/tools/mcp) — connect AI clients via Model Context Protocol ### Content - [Blog](https://codepic.cc/blog) — articles on diagramming, AI workflows, and use cases ### API & Discovery - [MCP Endpoint](https://codepic.cc/api/mcp/mcp) — Streamable HTTP transport for MCP clients - [MCP Discovery](https://codepic.cc/.well-known/mcp.json) — MCP server discovery metadata - [Sitemap](https://codepic.cc/sitemap.xml) — complete list of every template and content page ## MCP Integration (Recommended) CodePic provides an MCP (Model Context Protocol) server. If your AI client supports MCP, this is the preferred way to create and edit diagrams — no JSON generation or manual import needed. - **MCP Endpoint**: `https://codepic.cc/api/mcp/mcp` (Streamable HTTP) - **Authentication**: Bearer token with API key (prefix `cpk_`), obtained from https://codepic.cc/dashboard/api-keys - **Discovery**: `https://codepic.cc/.well-known/mcp.json` ### Available MCP Tools | Tool | Auth required | Description | |------|--------------|-------------| | `list_templates` | No | List available diagram templates, optionally filtered by category (flowchart, planning, design, collaboration, engineering) | | `create_from_template` | Yes | Create a diagram from a template slug. Returns an edit URL. | | `create_diagram` | Yes | Create a custom diagram with nodes and edges. Supports `parentId` for container nesting. Returns an edit URL. | | `get_diagram` | Yes | Fetch the current state of a diagram — returns a compact element summary with IDs, types, positions, and parent-child relationships. Use this before `update_diagram` when you need to add children to existing containers or make targeted edits. | | `update_diagram` | Yes | Update an existing diagram — rename, replace content, or add/remove nodes and edges. Supports `parentId` for nesting new nodes into existing containers (use `get_diagram` first to retrieve parent IDs). | ### MCP Client Configuration For Cursor (`~/.cursor/mcp.json`): ```json { "mcpServers": { "codepic": { "url": "https://codepic.cc/api/mcp/mcp", "headers": { "Authorization": "Bearer cpk_your_api_key_here" } } } } ``` For Claude Desktop (`claude_desktop_config.json`): ```json { "mcpServers": { "codepic": { "type": "streamableHttp", "url": "https://codepic.cc/api/mcp/mcp", "headers": { "Authorization": "Bearer cpk_your_api_key_here" } } } } ``` ### Container Nesting via MCP (IMPORTANT) Use `parentId` to establish parent-child relationships between nodes. This enables: - Moving a container drags all its children with it - Users can drag additional elements into the container in the editor **Rules:** - Any node type can be a container — `frame`, `rect`, `rounded-rect`, `package`, `swimlane` are most common - The parent node automatically becomes `containable: true` (no need to set it manually) - All coordinates are absolute canvas coordinates — child coordinates are auto-converted to parent-relative internally - **Always use nesting** when 2+ nodes logically belong to the same module, component, or region **Example — a service box with two child nodes:** ``` // Container { id: "svc", type: "rounded-rect", text: "Auth Service", x: 100, y: 100, width: 300, height: 200 } // Children (absolute coords, auto-converted to relative) { id: "db", type: "cylinder", text: "User DB", x: 120, y: 160, width: 120, height: 80, parentId: "svc" } { id: "api", type: "rect", text: "REST API", x: 260, y: 160, width: 120, height: 80, parentId: "svc" } ``` ### MCP Layout Best Practices The MCP server auto-computes connector anchors, points, and routing based on node positions. For best results: **Node coordinates must reflect the intended flow direction:** - For left-to-right flows: place nodes with increasing `x` values per stage. E.g. Stage 1 at x=100, Stage 2 at x=500, Stage 3 at x=900. - For top-to-bottom flows: place nodes with increasing `y` values per step. E.g. Step 1 at y=100, Step 2 at y=260, Step 3 at y=420. - The server picks connector anchor direction (left/right/top/bottom) by comparing the center points of source and target nodes, so correct spatial positioning is essential. **Container sizing:** - A container (frame) must be large enough to contain all its children with padding. Minimum: children bounding box + 40px padding on each side + 40px top for the frame label. - Example: 3 children each 160×80, laid out horizontally with 40px gaps → frame width = 40 + 160 + 40 + 160 + 40 + 160 + 40 = 640, frame height = 40 (label) + 40 + 80 + 40 = 200. **Spacing between nodes:** - Minimum 40px gap between sibling nodes - Minimum 80px gap between container frames to leave room for connectors - **When edges have labels**: increase spacing to at least 160px between connected nodes (horizontal: `width + 160`, vertical: `height + 120`) so the 120×28px label does not overlap any shape **Reducing connector crossings:** - Keep the main flow in one direction (left-to-right or top-to-bottom). All primary edges should follow this direction. - For feedback/reverse edges (e.g. writing back a counter, retry loops), use `strokeDash: "dashed"` and a lighter `strokeColor` (e.g. `#94a3b8`) so they are visually distinct from the main flow. Route them along the bottom or outer edge of the diagram to avoid crossing main-flow connectors. - **A reverse edge must leave a different side than the side the main flow enters.** If `review → decision` enters the decision's left side, the decision's "No" branch must NOT also leave from the left (their segments would overlap and merge into one line). Send the reverse edge out the **top** `[0.5, 0]` and route it around the outside. See "Avoiding path conflicts" rule 5 below for the full example. - Avoid more than 2 edges leaving the same side of a single node. If a decision node has 3+ branches, arrange the targets so edges exit from different sides (e.g. right for the main path, bottom for the secondary path). - When a data-write operation (like `success += N`) is part of a node's behavior, describe it in the node's text instead of creating a separate reverse edge. This eliminates a connector entirely. - **Stagger target positions**: when multiple edges leave the same node toward similar destinations, offset the target nodes by 20–40px vertically (or horizontally for top-to-bottom flows) so the right-angle elbow routes naturally separate into distinct paths instead of overlapping in the same corridor. - **Cross-level edges route outside**: an edge that skips over intermediate nodes should exit from the top or bottom side and route around the outside of the main flow area, not cut through the middle where it would cross other edges. - **Edges must not pass through unrelated nodes**: if a straight-line connector between two nodes would visually cross another node's bounding box, reposition the target node (or use an intermediate waypoint) so the connector routes around the obstacle — typically above or below. This is especially important when large nodes like `code` blocks sit between the source and target. - The MCP server automatically spreads anchor ratios when multiple edges share the same node+side, but this works best when target nodes are not all at the same y (or x) coordinate. Spread out target positions for cleaner results. ### Usage guide: https://codepic.cc/tools/mcp --- ## AI Generation Strategy: Quick Whiteboard First CodePic / X-Frame is positioned as a quick whiteboard tool. When generating diagrams, prioritize readability, editability, and a lightweight canvas over packing in many specialized shapes at once. **Default shape hierarchy:** - Core shapes: `rect`, `ellipse`, `diamond`, `text`, `note`, `connector`, `line`, `image`, and `frame`. Prefer these for ordinary flowcharts, sketches, annotations, and structure diagrams. - Common semantic shapes: `rounded-rect`, `parallelogram`, `document`, `cylinder`, `cloud`, and `callout`. Use them when the meaning is obvious, such as `cylinder` for databases, `parallelogram` for input/output, and `cloud` for external networks. - Specialized shapes: `server`, `load-balancer`, `firewall`, `container`, `msg-queue`, `router`, `lambda`, `cache`, `pod`, `code`, tables, and form controls. Use these only when the user explicitly asks for system architecture, cloud architecture, wireframes, data tables, or the corresponding specialized semantics. **Canvas complexity rules:** - Generate one page by default. Do not create multiple pages unless the user explicitly asks for multiple pages, sectioned exports, or several independent diagrams. - Keep each diagram to 5–12 primary nodes at first. When more detail is needed, prefer supporting text, container grouping, and a few annotations instead of stacking more shapes. - Editability matters more for ordinary users: avoid deeply nested structures, tiny elements, dense connectors, too many colors, and too many specialized symbols. - Use containers only when a group of elements truly needs to move together or express a module boundary. Do not overuse `frame` for decoration. - For reusable planning templates, use multiple scenes when the topic naturally has several common scenarios. Put the most representative default scenario first for thumbnails, then add 1–3 additional real-world variants users can copy, delete, or adapt. Do not make every variant a shallow duplicate with only the title changed. - In multi-scene templates, the first scene is only special for thumbnail cropping. Every later scene must meet the same usability bar: complete structure, concrete replaceable examples, and enough context for a user to adapt it without guessing. - Do not make templates from labels alone. Main cards, nodes, table cells, and time slots should contain replaceable example content that shows what users are supposed to enter. Labels such as Morning, Goal, Metric, Owner, or Status can be small helpers, but the primary text should be a concrete example activity, decision, task, metric, location, or note. - When replacing placeholders with examples, preserve useful structural labels. Time periods, roles, priorities, owners, and statuses should remain as small labels, pills, row headers, or column headers when they help users understand the template. - For timeline / roadmap diagrams, do not draw only a line with a few dots. Prefer editable milestone cards, and keep date, title, description, owner, or status fields for each key point; project timelines can include phase bands, while company history or event planning timelines can be arranged as separate scenes. Multi-scene template thumbnails should crop only the first representative scene. - For SWOT diagrams, do not output an empty four-quadrant grid. Each quadrant should include a guiding question and 2–4 editable factor cards; general templates can add separate scenes for business strategy, personal career planning, marketing campaigns, and include actionable next steps at the bottom. - For database schema / ERD templates, do not scatter tables on the canvas without context. Prefer a clear business title, domain grouping, relationship legend, and extension notes; keep enough spacing between tables, clearly mark primary and foreign keys, and route relationship lines horizontally or vertically where possible so they do not cross through tables. **Default connector rules:** - Use `connector` for relationships between shapes by default; do not replace it with an independent `line`. This keeps relationships attached when users move shapes. - When adding text to a connector line (for example Yes / No, offers, teaches, enrolls, records grade), prefer a `line-label` bound to the connector instead of placing independent text near the line. - **Connector shape priority, from highest to lowest: ① horizontal/vertical straight lines → ② elbow lines with 90° orthogonal bends → ③ diagonal lines / curves.** When laying out shapes and choosing anchors, first place related shapes so the connector can be horizontal or vertical (the endpoints share the same x or y). Use a straight line when possible; use an elbow line only when endpoints are offset and cannot be aligned. Avoid diagonal lines and curves except for the special cases below. Do not default every connector to elbow routing just for convenience — adjacent left/right or top/bottom sections should use clean horizontal or vertical lines. - Choose `connector.lineType` by geometry so generated lines feel natural and hand-drawn instead of awkward: use `straight` when the two endpoints defined by `sourceAnchor` / `targetAnchor` are aligned (same x or same y, meaning the line itself is a horizontal or vertical orthogonal line). Use `elbow` when the endpoints have different x and y values and a straight segment would become diagonal; the engine will route a 90° orthogonal path to avoid awkward diagonal flowchart lines. Use `curve` only for the scenario described in the next rule. - Use `curve` only when the user explicitly wants natural branching, brainstorming, or soft relationship lines. - When generating connector endpoints bound to shapes, always set the corresponding `sourceId` / `targetId` and `sourceAnchor` / `targetAnchor`; always set `arrowStart` and `arrowEnd` explicitly instead of relying on implicit defaults. - If connected nodes are children of a `frame`, `swimlane`, `package`, or another container, prefer keeping the `connector` itself as a top-level element (`parentId: null`) with absolute canvas coordinates in `points`, `x`, `y`, `width`, and `height`. The connector should still reference the child nodes via `sourceId` / `targetId`. Do not place the connector as a child of the same frame unless every point is intentionally in that parent-local coordinate system and you have verified it in the editor. On load, the editor recomputes connector endpoints from the target shapes in canvas coordinates; a nested connector can then be rendered as if those canvas points were local points, causing lines to fly away from the intended nodes. - If one endpoint of a connector is not bound to a shape, set the corresponding `sourceId` or `targetId` to `null`, and store the canvas coordinate in `data.freeSourcePoint` or `data.freeTargetPoint`. - Do not generate new `line` elements to express relationships or arrows; even static straight segments should prefer `connector` + `lineType: "straight"`. Use `pencil` for freehand drawing and temporary marks. ## JSON Import (Alternative) If MCP is not available, you can generate a JSON document and tell the user to import it via Command+K → "Import from JSON" in the CodePic editor. ## How to Generate a Diagram Generate a JSON document matching the schema below, then tell the user to import it via ⌘K → "Import from JSON" in the CodePic editor. ## Document Structure ```json { "$schema": "codepic/beta", "$version": "0.1.0-beta", "document": { "id": "", "name": "" }, "pages": [ { "id": "", "name": "Page 1", "elementIds": [""], "backgroundColor": "#f8f9fa" } ], "elements": [ /* flat array of all elements */ ] } ``` **Key rules:** - `elementIds` in each page lists only **top-level** element IDs (no children) - Every element with a `parentId` must have its ID in the parent's `childrenIds`, and vice versa - Connector `sourceId`/`targetId` must reference existing element IDs ## Parent-Child Nesting (JSON) **When to nest:** any time 2+ elements logically belong together and should move as a unit — a service box with its internal components, a card with its content, a swim lane with its steps, a labeled group. **Required setup (all three must be present):** 1. Parent: `"containable": true` — enables drop-in and group-move behavior 2. Parent: `"childrenIds": ["child-a", "child-b"]` — lists direct children 3. Each child: `"parentId": "parent-id"` — back-reference to the parent 4. Child coordinates are **relative to the parent's top-left corner** (not absolute canvas coords) 5. Top-level elements only go in `page.elementIds`; children are excluded ```json [ { "id": "svc-auth", "type": "rounded-rect", "x": 100, "y": 100, "width": 320, "height": 180, "text": "Auth Service", "containable": true, "childrenIds": ["svc-db", "svc-api"], "constraints": [], "tags": [], "data": {} }, { "id": "svc-db", "type": "cylinder", "x": 20, "y": 60, "width": 100, "height": 80, "text": "User DB", "parentId": "svc-auth", "childrenIds": [], "constraints": [], "tags": [], "data": {} }, { "id": "svc-api", "type": "rect", "x": 160, "y": 60, "width": 120, "height": 80, "text": "REST API", "parentId": "svc-auth", "childrenIds": [], "constraints": [], "tags": [], "data": {} } ] ``` > **Note:** In the MCP API, setting `parentId` is enough — the server sets `containable` and `childrenIds` automatically. In raw JSON, you must set all three manually. ## Element Types | type | typical use | default size | |------|-------------|--------------| | mindmap-node | mind map node (root or child); tree layout is auto-computed, connectors are auto-drawn — do NOT emit connector/line elements between mindmap nodes | 120×40 | | rect | process step, card | 160×80 | | ellipse | start/end node | 140×100 | | cylinder | database / data store / cache | 120×80 | | hexagon | service / component / microservice (C4, hexagonal architecture) | 140×100 | | note | annotation / comment / remark (UML note, folded top-right corner) | 160×100 | | actor | person / user / external system (UML use case, C4 Person) | 60×100 | | delay | delay / wait step (rect with right semicircle) | 140×70 | | manual-input | manual data entry (trapezoid, top-left higher) | 160×70 | | stored-data | stored data / tape (drum shape, both ends curved) | 160×70 | | fork-bar | UML fork/join synchronization bar (thin filled rect) | 120×8 | | terminator | start / end node (capsule / stadium shape) | 160×60 | | component | UML component (rect with two left-side socket tabs) | 160×100 | | end-node | UML terminal state (outer ring + inner filled circle) | 40×40 | | cloud | cloud / network / internet / external system boundary | 160×100 | | predefined-process | predefined process / subroutine (rect with inner vertical lines) | 160×80 | | cross | cross / plus sign / marker | 80×80 | | off-page-connector | off-page connector / page reference (downward pentagon) | 120×80 | | rounded-rect | modern card / rounded rectangle (rect alias, corners 12px) | 160×80 | | start-node | UML start node (solid dark circle, ellipse alias) | 40×40 | | package | UML package / module container (frame alias) | 200×160 | | swimlane | process lane / role boundary (frame alias, wide) | 600×200 | | queue | message queue / buffer (parallelogram alias) | 160×60 | | circle | alias for ellipse | 140×100 | | diamond | decision/branch | 120×80 | | parallelogram | input/output | 160×60 | | document | document/report | 140×80 | | text | label, annotation | 120×30 | | textarea | multiline text field (wireframe / forms) | 240×120 | | link | hyperlink text element | 120×28 | | icon | icon graphic (Lucide icon set) | 48×48 | | connector | arrow between shapes | — | | line | standalone line | — | | line-label | text label attached to a line or connector | 120×28 | | frame | container/group — **use for any UI panel, card, section, or page that holds child elements** | 200×160 | | table | data table | 400×200 | | checkbox | UI checkbox (wireframe) | 24×24 | | radio | UI radio button (wireframe) | 20×20 | | slider | UI range slider (wireframe) | 160×20 | | progress | Progress bar showing completion (read-only) | 400×12 | | switch | UI toggle switch (wireframe) | 48×26 | | input | UI single-line text input (wireframe / forms) | 200×36 | | select | UI dropdown select (wireframe / forms) | 200×36 | | image | embedded image / photo | 200×150 | | button | UI button (wireframe, default style) | 120×40 | | button-primary | UI primary action button (blue fill) | 120×40 | | button-secondary | UI secondary button (blue outline) | 120×40 | | button-destructive | UI destructive/danger button (red fill) | 120×40 | | button-ghost | UI ghost/text button (no border) | 120×40 | | triangle | triangle shape (direction: up/down/left/right) | 120×100 | | callout | speech bubble / callout / annotation bubble | 160×120 | | server | server node (system architecture) | 200×120 | | load-balancer | load balancer node (system architecture) | 160×100 | | firewall | firewall node (system architecture) | 160×100 | | container | Docker / OCI container node (system architecture) | 200×140 | | msg-queue | message queue node — SQS, RabbitMQ, Kafka (system architecture) | 180×100 | | router | network router node (system architecture) | 120×120 | | lambda | serverless function node — AWS Lambda (system architecture) | 100×120 | | cache | cache node — Redis, Memcached (system architecture) | 120×140 | | pod | Kubernetes pod node (hexagon, system architecture) | 100×100 | | code | code block with syntax highlighting (20+ languages, dark/light theme) | 240×66 | | line-chart | data-driven line/trend chart (axes, grid, draggable data points) | 320×200 | ### Frame Title (optional) `frame` elements support an optional top-level `title` field — a short single-line label rendered as a gray pill sticker floating 4px above the frame. It is **separate from `text`** (text is the frame's internal content, title is an external label): ```json { "id": "input-frame", "type": "frame", "title": "1. Input Assembly", "text": "", "x": 100, "y": 100, "width": 400, "height": 300, ... } ``` - Add a title when the frame represents a **named section, phase, or logical block** in a larger diagram (e.g. "Input Assembly", "Reasoning Pipeline", "Section 1"). It helps the reader scan the diagram structure at a glance. - Skip the title (omit the field or set to `""`) when the frame is just a visual grouping without a semantic label — e.g. an unnamed card, a wireframe panel, or a container whose meaning is obvious from its content. - Title bar always renders at fixed 24px height + 14px bold text, does NOT scale with the frame — small frames don't get microscopic titles. - Title width auto-fits its content (text width + padding), capped by the frame width. Overflow is truncated with `…`. - Only `frame` (and its aliases `package`, `swimlane`) supports `title`. Other element types ignore this field. ## Required Fields for Every Element ```json { "id": "unique-string", "type": "rect", "name": "human-readable label", "description": "", "x": 100, "y": 100, "width": 160, "height": 80, "rotation": 0, "parentId": null, "childrenIds": [], "strokeColor": "#000000", "strokeWidth": 2, "strokeDash": "solid", "fillColor": "#ffffff", "fillStyle": "solid", "opacity": 1, "roughness": 1, "seed": 42, "corners": [0, 0, 0, 0], // [topLeft, topRight, bottomRight, bottomLeft] — applies to ALL shape types. Use to round corners for a more modern look: rect/frame/input/select/button suggest [8,8,8,8]–[16,16,16,16]; rough style (roughness≥1) pairs well with small corners (4–8); smooth style (roughness:0) pairs well with larger corners (12–20). Other shapes (diamond, hexagon, cylinder, etc.) can also use corners for subtle rounding if desired. "text": "Label", "fontSize": 16, "fontFamily": "Inter, sans-serif", // see Font Guidelines below "fontColor": "#333333", "fontWeight": "bold", "fontStyle": "normal", "textAlign": "center", "verticalAlign": "center", "lineHeight": 1.2, "wordWrap": true, "containable": false, "containableFilter": "", "connectable": true, "textEditable": true, "sizable": "free", "movable": "free", "locked": false, "visible": true, "selectable": true, "constraints": [], "tags": [], "data": {} } ``` **Behavior field notes:** - `selectable` (default `true`) — when `false`, pointer hit-tests skip this element and pass through to its parent. Use for internal decorative children of composite shells (e.g. the content-area of a browser/mobile frame) so the whole shell is dragged as one unit. Leave `true` for everything the user should be able to click. **Use a different `seed` integer for each element** (e.g., 1, 2, 3…) so hand-drawn textures vary. ## Constraints The `constraints` array on an element declares declarative behaviors that are automatically applied when the element or its parent changes. **Rule: every element's example JSON must include a `constraints` field — either with the required constraints or an explicit `[]`.** ### `fill-parent-width` The element's `width` always syncs to its parent's width. Use when a child needs to fill the parent horizontally but controls its own height. ### `fill-parent-height` The element's `height` always syncs to its parent's height. Use when a child needs to fill the parent vertically but controls its own width. Use both together when the child should fully fill the parent in both dimensions (e.g. a `text` inside a `table-cell`). Do NOT use on: elements with `sizable: "ratio"` (image, icon), `connector`, `line`, or children already managed by `align-children`. Example — `text` inside a `table-cell` (fills cell fully so `textAlign`/`verticalAlign` works correctly): ```json { "type": "text", "x": 0, "y": 0, "width": 120, "height": 40, "text": "Cell content", "textAlign": "center", "verticalAlign": "center", "parentId": "", "constraints": [{ "id": "fill-parent-width" }, { "id": "fill-parent-height" }], "tags": [], "data": {} } ``` ### `align-children` The parent element automatically arranges its children in a row or column. Children are repositioned whenever the parent resizes or its `childrenIds` change. Parameters: - `orient`: `"vertical"` | `"horizontal"` - `align`: `"fill"` — children also stretch to fill the parent's cross-axis width/height Example — a frame whose children stack vertically and fill its width: ```json { "type": "frame", "constraints": [{ "id": "align-children", "orient": "vertical", "align": "fill" }], "tags": [], "data": {} } ``` Elements that need no constraints must still write `"constraints": []` explicitly. **`strokeDash`** applies to every element type — `"solid"` (default) | `"dashed"` | `"dotted"`. Use it on any shape (rect, ellipse, connector, etc.) to control the stroke pattern. ## Connector / Line Fields ```json { "type": "connector", "sourceId": "rect-1", "targetId": "ellipse-1", "sourceAnchor": [0.5, 1], "targetAnchor": [0.5, 0], "points": [], "lineType": "straight", "strokeDash": "solid", "arrowStart": "none", "arrowEnd": "arrow", "fillColor": "transparent", "fillStyle": "none", "connectable": false, "sizable": "none", "x": 0, "y": 0, "width": 0, "height": 0, "constraints": [], "tags": [], "data": {} } ``` **`lineType`** — `"straight"` (connector default, direct point-to-point line) | `"elbow"` (90° orthogonal routing) | `"curve"` (curved line) All `connector` elements support line styling fields such as `strokeColor`, `strokeWidth`, `strokeDash`, `roughness`, `opacity`, `arrowStart`, and `arrowEnd`. The legacy `line` element can still appear in older documents, but AI should not generate new `line` elements for diagram relationships. **Free endpoints:** when a connector endpoint is not attached to a shape, set the corresponding id to `null` and store the endpoint canvas coordinate in `data.freeSourcePoint` or `data.freeTargetPoint`. ```json { "type": "connector", "sourceId": "step-a", "targetId": null, "sourceAnchor": [1, 0.5], "points": [[260, 140], [420, 140]], "lineType": "straight", "arrowStart": "none", "arrowEnd": "arrow", "fillColor": "transparent", "fillStyle": "none", "connectable": false, "sizable": "none", "x": 260, "y": 140, "width": 160, "height": 1, "constraints": [], "tags": [], "data": { "freeTargetPoint": [420, 140] } } ``` **`strokeDash`** — universal style field (see Required Fields); listed here for completeness: `"solid"` (default) | `"dashed"` | `"dotted"` **`arrowStart` / `arrowEnd`** — controls arrowheads at each end: - `"none"` — no arrowhead - `"arrow"` — filled solid triangle - `"open"` — open V-shaped arrowhead Defaults: `line` has no arrows on either end; `connector` defaults to `arrowEnd: "arrow"` (→). Arrow usage rules: - For directional relationships, explicitly set `arrowStart: "none"` and `arrowEnd: "arrow"` (or `"open"` for a lighter visual style). Do not rely on implicit defaults in generated JSON. - Use arrows for process steps, sequence order, data flow, API calls, dependency direction, state transitions, branch/merge direction, and cause/effect flows. - Use `arrowEnd: "open"` when the arrow should indicate direction without visually dominating the diagram (e.g. timelines, journey trends, lightweight branch lines). - Use `arrowEnd: "none"` only for non-directional structure lines, decorative separators, grouping guides, mind-map style associations, table/grid lines, and static reference lines. - If a `connector` has a meaningful `sourceId` → `targetId`, the arrow should usually point to `targetId`. If no direction is intended, set both `arrowStart` and `arrowEnd` to `"none"` explicitly. Use `connector` instead of `line` whenever the edge should follow one or both shapes when users move them. A `connector` must have valid `sourceId` and `targetId` values that reference existing connectable shapes. Use `line` only for standalone decorative strokes, static separators, or freehand marks that do not need to stay attached to shapes. **Anchor format** `[x_ratio, y_ratio]` — ratios from 0 to 1 relative to element bounds: - `[0.5, 0]` = top center - `[0.5, 1]` = bottom center - `[0, 0.5]` = left center - `[1, 0.5]` = right center ## Line Label Fields A `line-label` is a floating text element anchored to a point on a `line` or `connector`. It moves with the line when the line is repositioned. ```json { "type": "line-label", "text": "label text", "x": 200, "y": 150, "width": 120, "height": 28, "roughness": 0, "fillColor": "transparent", "fillStyle": "none", "strokeColor": "transparent", "strokeWidth": 0, "fontSize": 14, "fontWeight": "normal", "textEditable": true, "connectable": false, "sizable": "free", "constraints": [], "tags": [], "data": { "lineId": "", "segmentIndex": 0, "segmentT": 0.5, "offsetX": 0, "offsetY": -20 } } ``` **`data.lineId`** — ID of the `line` or `connector` this label is attached to (required) **`data.segmentIndex`** — index of the line segment the label is anchored to (0 = first segment, between `points[0]` and `points[1]`) **`data.segmentT`** — position within that segment from 0.0 (segment start) to 1.0 (segment end); `0.5` = segment midpoint **`data.offsetX` / `data.offsetY`** — pixel offset from the anchor point on the line; default `(0, -20)` places the label slightly above the segment midpoint **IMPORTANT:** The label's `x`/`y` are **not** recomputed on initial load — they are only updated when the parent line/connector moves. **Always pre-compute `x`/`y` using the formula below.** Setting them to `0` will place all labels at the canvas origin until the line is moved. Pre-computation formula (required): ``` W = label width (default 120), H = label height (default 28) a = pts[segmentIndex] b = pts[segmentIndex + 1] ax = a[0] + segmentT * (b[0] - a[0]) ay = a[1] + segmentT * (b[1] - a[1]) x = ax + offsetX - W / 2 y = ay + offsetY - H / 2 ``` For a 2-point straight connector, the only segment is index 0, so `{segmentIndex: 0, segmentT: 0.5}` puts the label at the midpoint — equivalent to the old global `t: 0.5`. For multi-segment routes (elbow connectors), choose the segment you want the label to sit on; the label then stays on that **logical segment** even if the user later drags other segments around. For labels on **vertical** connectors: use `offsetX: 10, offsetY: 0` (label appears to the right of the line, vertically centered). For **horizontal** connectors: use `offsetX: 0, offsetY: -20` (label appears above the line). Default `offsetY: -20` is only suitable for horizontal lines — on vertical lines it pushes the label toward the source shape and can overlap it. ## Connector & Line Usage Guide ### Line type selection Only three line types exist: | Use case | lineType | Notes | |----------|----------|-------| | Endpoints are collinear (share the same x or the same y → the straight line is already horizontal/vertical) | `straight` | Single straight segment between the two endpoints. Use this **only when the direct line is already orthogonal** (e.g. stacked nodes in the same column, side-by-side nodes in the same row). | | Endpoints are offset on both axes (a direct line would be diagonal) — and any 90° routing case (flowcharts, feedback loops, cross-level connections) | `elbow` | Auto-routes a right-angle path from `sourceAnchor` to `targetAnchor`; supports segment dragging. **Default for any pair of misaligned endpoints** so the result matches a hand-drawn orthogonal line instead of an awkward diagonal. | | Bezier curve | `curve` | Smooth curved path; use for relationship-style mind maps, brainstorm radials, or organic flows where a gentle curve is the visual intent. | The old values `"orthogonal"` / `"horizontal"` / `"vertical"` / `"freehand"` are **deprecated**. Generated JSON must use `"elbow"` / `"straight"` / `"curve"` only. **Heuristic: is the straight line already orthogonal?** Look at the direct line between the two anchor points. If it is already horizontal or vertical (endpoints share the same x or same y), use `straight`. If it would be diagonal (endpoints differ on both axes), default to `elbow` so the engine produces a clean 90° orthogonal route. The goal is for generated lines to look like something a person would draw by hand — diagonal lines cutting across a diagram look out of place. Never add intermediate `points` to a `straight` connector to simulate a bend. **Exception: prefer straight lines when unobstructed.** The heuristic above is the default rule, but if a diagonal path does not pass through any other shape (no obstruction), prefer `straight` as well — a clean straight line looks more natural and closer to a hand-drawn whiteboard feel than an unnecessary elbow. Reserve `elbow` for cases where: (1) the straight line would cross another node, (2) multiple edges share the same anchor side causing visual overlap, or (3) feedback/loopback paths need to route around the main flow. How to check: draw the line between the two anchor points and test whether it intersects the bounding box of any intermediate element. **Anchor direction determines routing shape — pick the anchor that matches the visual relationship:** | Elements are... | sourceAnchor | targetAnchor | |-----------------|-------------|-------------| | Side by side (source left of target) | `[1, 0.5]` right | `[0, 0.5]` left | | Stacked (source above target) | `[0.5, 1]` bottom | `[0.5, 0]` top | | Source above-right of target | `[0.5, 1]` or `[0, 0.5]` | nearest edge | **Never add intermediate `points` to simulate a bend on an `elbow` connector** — the engine fully derives the path from `sourceAnchor`/`targetAnchor` (and the shapes they sit on). The recommended `points` for an elbow connector in generated JSON is `[[sourceX, sourceY], [targetX, targetY]]` (the two anchor positions); any intermediate points you write will be overwritten on the next sync. ### Always connect to shapes (sourceId / targetId) Set `sourceId` and `targetId` on every connector so lines follow shapes when they are moved. Also set `sourceAnchor` and `targetAnchor`. If a visual relationship connects two diagram elements, prefer fixing the connector so it references valid connectable shapes rather than replacing it with a standalone `line`. ### Container children and connector coordinates When a diagram uses frames as scene panels, modules, swimlanes, cards, or other movable groups, children inside the frame use parent-local coordinates. Connector routing, however, is recomputed from the connected shapes in absolute canvas coordinates when the document loads. Recommended pattern for templates: - Keep logical nodes inside the frame when they should move with the frame. - Keep connectors between those nodes at the top level (`parentId: null`). - Compute connector endpoints in absolute canvas coordinates: `frame.x + child.x + child.width * anchorX`, `frame.y + child.y + child.height * anchorY`. - Still set `sourceId`, `targetId`, `sourceAnchor`, `targetAnchor`, `arrowStart`, and `arrowEnd`. Avoid this pattern: - Node A is a child of a frame. - Node B is a child of the same frame. - The connector A→B is also a child of that frame, while its `points` are absolute canvas coordinates. That mixed coordinate model can render correctly in raw data checks but fail visually in the editor because the connector points are converted through the parent transform a second time. Before shipping a template with framed sections, open it in the editor and verify that every connector is visually attached to the intended nodes. ``` Anchor values [x_ratio, y_ratio]: [0.5, 0] = top center (exits/enters upward) [0.5, 1] = bottom center (exits/enters downward) [0, 0.5] = left center (exits/enters leftward) [1, 0.5] = right center (exits/enters rightward) ``` Minimal connector example (elbow, bottom→top): ```json { "type": "connector", "points": [[310, 204], [310, 260]], "lineType": "elbow", "sourceId": "step-a", "targetId": "step-b", "sourceAnchor": [0.5, 1], "targetAnchor": [0.5, 0], "arrowEnd": "arrow", "arrowStart": "none", "roughness": 0, "fillColor": "transparent", "fillStyle": "none", "connectable": false, "sizable": "none", "constraints": [], "tags": [], "data": {} } ``` ### Elbow connectors (right-angle routing) `lineType: "elbow"` is not the default line type. Use it for flowcharts, system diagrams, or cross-level relationships that need 90° orthogonal turns. The engine automatically computes the path from `sourceAnchor` / `targetAnchor` and shape geometry, so you do not need to hand-write bend points; when shapes move, ConstraintEngine reroutes the connector automatically. What you provide: - `sourceId` + `sourceAnchor` (which edge of the source shape the line leaves from) - `targetId` + `targetAnchor` (which edge of the target shape the line enters) - `points`: minimally `[[sourceX, sourceY], [targetX, targetY]]` — i.e. the two anchor positions in canvas coordinates. The engine will replace this with the full 3- to 5-point right-angle path. What you must NOT provide for elbow connectors: - Hand-written intermediate bend points in the `points` array (they will be overwritten). **Controlling elbow bend points with `data.segments`:** when the engine's auto-routing causes multiple elbow connectors to overlap (their vertical or horizontal segments land on the same coordinate and visually merge), you CAN pre-set `data.segments` to force different fold positions. Each segment describes one leg of the orthogonal path: ``` data.segments: [{ id: "s0", axis: "h"|"v", coord: }, ...] ``` - `axis: "h"` — horizontal segment at the given y coordinate - `axis: "v"` — vertical segment at the given x coordinate - For a typical right→left Z-shape: `[{h, sourceY}, {v, bendX}, {h, targetY}]` - For bottom→top routing: `[{v, sourceX}, {h, bendY}, {v, targetX}]` - `data.segments` is the authoritative manual route. When an attached or free endpoint moves, the editor preserves these segments and re-anchors the first and last legs to the new endpoint. If the saved axes or geometry are no longer compatible with the new port directions, the router automatically replaces them with a valid default topology. Clear `data.segments` only when the route should be explicitly reset; endpoint movement alone is not a reset. **How to detect overlap:** when two elbow connectors share the same source or target side, their auto-computed paths often put the fold segment at the midpoint x (or y) between the two shapes. If both connectors have the middle segment at the same x, their vertical/horizontal strokes collapse into one line. Check: do any two elbow connectors in the same gap have matching fold coordinates? If yes, stagger them by ≥20px. **How to fix:** assign each overlapping elbow a different fold coordinate. For fan-out patterns (one source → many targets), spread the vertical segments across the gap — the connector to the highest target gets the fold closest to the source, the lowest target gets the fold closest to the destination. Path-shape rules of thumb (helpful for choosing the right anchors, not for writing points): - **down → up** or **right → left** (anchors face each other across the gap): the engine produces a 3-segment Z-shape with the middle segment centered between the two anchors. - **down → left**, **right → up**, etc. (anchors are perpendicular): the engine produces an L-shape (or a 4-segment Z if the target is "behind" the source's exit direction). - **same-side anchors** (e.g. both `[1, 0.5]`): the engine produces a side-bypass U-shape that routes around the outside. Feedback loop routing principle (to avoid overlapping shapes): - Exit from the **top** of the side box (not left), enter the target from the **right** side - Route entirely to the right of the main flow column — no path should pass through a shape - Use `strokeDash: "dashed"` to visually distinguish feedback loops from the main flow ```json { "type": "connector", "lineType": "elbow", "sourceId": "side-box", "targetId": "step-1", "sourceAnchor": [0.5, 0], "targetAnchor": [1, 0.5], "points": [[570, 365], [400, 176]], "strokeDash": "dashed", "strokeColor": "#94a3b8", "arrowEnd": "arrow", "arrowStart": "none", "roughness": 0, "fillColor": "transparent", "fillStyle": "none", "connectable": false, "sizable": "none", "constraints": [], "tags": [], "data": {} } ``` ### Avoiding path conflicts When multiple connectors share overlapping routes, use these techniques to separate them: **1. Use different anchor sides for different directions.** A decision node with 3 branches should not send all edges from the right side. Assign each branch to the side closest to its target: | Branch direction | sourceAnchor | Example | |-----------------|-------------|---------| | Main path (rightward) | `[1, 0.5]` | → next stage | | Secondary path (downward) | `[0.5, 1]` | ↓ error handler | | Feedback loop (upward) | `[0.5, 0]` | ↑ retry from start | **2. Offset anchor ratios when multiple edges share a side.** If two edges must leave the same side, use different ratios so the elbow paths diverge: ``` BAD: both edges use sourceAnchor [1, 0.5] → routes overlap in the gap GOOD: edge-A uses [1, 0.3], edge-B uses [1, 0.7] → routes take different vertical paths ``` General rule: for N edges on the same side, distribute ratios as `i / (N+1)` for i = 1..N (e.g. 2 edges → 0.33 and 0.67; 3 edges → 0.25, 0.5, 0.75). **3. Adjust node positions to naturally separate lines.** If edges from node A to nodes B and C cross each other, stagger B and C vertically (20–40px offset) so the right-angle segments route at different heights: ``` BAD: B.y = 300, C.y = 300 → both horizontal segments at the same y, routes overlap GOOD: B.y = 280, C.y = 340 → horizontal segments at different heights, no overlap ``` **When modules are too close together, elbow bend points often land on the same horizontal or vertical line** and the resulting paths collapse into a single visual stroke — the reader cannot tell which connector goes where. If staggered anchors alone cannot separate them, increase the gap between the two modules (by 40–80px) so each elbow's fold segment routes at a distinctly different x or y coordinate. The goal: every elbow connector should have at least one segment whose coordinate differs from all other nearby connectors by ≥ 20px. **4. Route cross-level edges around the outside.** An edge that skips intermediate nodes should exit from the top or bottom and loop around the diagram boundary, not cut through the middle: - Exit from source top `[0.5, 0]` → route above all nodes → enter target top `[0.5, 0]` or right `[1, 0.5]` - Or exit from source bottom `[0.5, 1]` → route below all nodes → enter target bottom **5. An incoming edge and a reverse outgoing edge on the same node must not share a side.** This is the most common overlap in flowcharts with a feedback/rejection branch. When the main flow *enters* a node on one side, and a reverse edge (e.g. a decision's "No" branch looping back) *leaves* the same node on the same side, their first segments run along the same line in opposite directions and merge into one indistinguishable stroke. ``` BAD: review --[1,0.5]→[0,0.5]--> decision (main flow enters decision's LEFT side, horizontally at y=760) decision --[0,0.5]→...--> step4 ("No" reverse edge leaves decision's LEFT side, also y=760) → both run along y=760 between the two nodes; the rejection line is hidden under the main flow GOOD: review --[1,0.5]→[0,0.5]--> decision (main flow still enters the LEFT side) decision --[0.5,0]→[1,0.5]--> step4 ("No" reverse edge leaves the TOP, routes up and around) → the reverse edge climbs above all nodes and re-enters step4 from the right; no shared corridor ``` Rule of thumb: a decision node's "No"/retry/feedback branch should leave from the **top** `[0.5, 0]` (route over the top of the diagram) or the **bottom** `[0.5, 1]`, never from the same side the main flow uses to enter. Combine with `strokeDash: "dashed"` and a lighter `strokeColor` (`#94a3b8`) so the reverse edge reads as distinct. See also the *Feedback loop routing principle* above. **6. Bidirectional edges must use different sides.** When both A→B and B→A exist, the two connectors must not share the same pair of sides (e.g. both using bottom→top), or their elbow paths will overlap and appear as one line. Route one edge on the left side pair and the other on the right side pair: ``` BAD: A→B exits A bottom [0.5,1] → enters B top [0.5,0] B→A exits B top [0.5,0] → enters A bottom [0.5,1] → paths overlap in the gap between A and B GOOD: A→B exits A left [0,0.5] → enters B left [0,0.5] B→A exits B right [1,0.5] → enters A right [1,0.5] → paths run on opposite sides, clearly separated ``` **6. Incoming and outgoing edges at the same node must use different sides or ratios.** When edge X enters node A from its right side and edge Y leaves A from the same right side, the two lines meet at the same point and look like a single line passing through A — making it impossible to tell which arrow belongs to which edge. Fix: route the incoming edge to a different side (e.g. top), or at minimum use offset ratios (`[1, 0.3]` vs `[1, 0.7]`) on rectangular shapes. **7. Non-rectangular shapes: only use vertex/cardinal anchors.** For non-rectangular shapes, anchor ratios like `[1, 0.3]` land on the bounding box edge but outside the visible shape outline — the connector endpoint floats in empty space. Use only the four cardinal anchors and let the MCP server auto-snap to the actual outline. Shape-specific notes: - **diamond**: all four cardinal anchors `[0.5, 0]`, `[0.5, 1]`, `[0, 0.5]`, `[1, 0.5]` are on the outline (vertices). No adjustment needed. - **ellipse**: all four cardinal anchors are on the outline. No adjustment needed. - **parallelogram**: left/right edges are slanted (default skew=0.2). Auto-corrected: `[0, 0.5]` → `[0.1, 0.5]`, `[1, 0.5]` → `[0.9, 0.5]`. Top/bottom are fine. - **hexagon / pod**: top/bottom edges are indented by w/4. Auto-corrected: `[0.5, 0]` → `[0.25, 0]`, `[0.5, 1]` → `[0.25, 1]`. Left/right are fine. - **triangle**: the apex side is a single point (e.g. `[0.5, 0]` for "up"). Sides are sloped — auto-corrected to fall on the sloped edge. - **cross**: arms occupy only the middle 1/3 of each side. Auto-corrected to the arm boundary. - **cloud**: outline is inset from bounding box. Auto-corrected: left → `[0.06, 0.5]`, right → `[0.94, 0.5]`, top → `[0.5, 0.1]`, bottom → `[0.5, 0.8]`. - **actor** (stick figure): arms extend to 12%–88% of width. Auto-corrected: `[0, 0.5]` → `[0.12, 0.5]`, `[1, 0.5]` → `[0.88, 0.5]`. - **off-page-connector**: bottom has an arrow tip at center. Auto-corrected: `[0.5, 1]` stays, sides capped at 70% height. - **terminator / queue**: these are rounded rectangles — cardinal anchors are fine, no correction needed. - To separate multiple edges on non-rectangular shapes, assign them to **different sides** instead of using ratio offsets on the same side - Ratio offsets (e.g. `[1, 0.3]`, `[1, 0.7]`) are only valid on rectangular shapes: `rect`, `rounded-rect`, `frame`, `button-*`, `server`, `cylinder`, `queue` **8. Every arrow must be visually distinguishable.** If two connectors share a path segment or endpoint, their arrowheads overlap and the user cannot tell which arrow belongs to which line. This is a direct consequence of violating rules 5–7. Whenever you place an arrow, verify that no other connector's arrowhead occupies the same visual position. If they do, adjust anchor sides or node positions until every arrow is independently identifiable. **9. Multiple edges leaving the same side must not cross each other.** When a node has two or more edges exiting the same side, their anchor order (top-to-bottom or left-to-right) must match the vertical order of their target endpoints. If edge A goes to a target that is *above* the target of edge B, then edge A's anchor must be *above* edge B's anchor on the shared side. Reversed ordering causes unnecessary line crossings that make the diagram hard to read. ``` GOOD: catalog right side, 2 edges: db is at y=180 (above catalog at y=256) → use upper anchor [1, 0.33] search is at y=256 (same level as catalog) → use lower anchor [1, 0.67] → lines fan out cleanly without crossing BAD: catalog → search at [1, 0.33], catalog → db at [1, 0.67] → upper line goes level, lower line goes up → they cross in the gap ``` Same principle applies left-to-right: if edge A goes leftward while edge B goes rightward from the same side, anchor A closer to the left, B closer to the right. ### Connector labels (always use line-label) Use `line-label` type for labels on connectors so they follow the line when shapes move. **Never use a plain `text` element** for connector labels — it will not track the line. ```json { "type": "line-label", "text": "Yes", "x": 254, "y": 446, "width": 120, "height": 28, "fontSize": 12, "fontColor": "#64748b", "textAlign": "left", "fillColor": "transparent", "fillStyle": "none", "strokeColor": "transparent", "strokeWidth": 0, "connectable": false, "textEditable": true, "sizable": "free", "constraints": [], "tags": [], "data": { "lineId": "connector-id", "segmentIndex": 0, "segmentT": 0.5, "offsetX": 10, "offsetY": 0 } } ``` `offsetX`/`offsetY` by line direction: - Vertical connector: `offsetX: 10, offsetY: 0` → label to the right, vertically centered - Horizontal connector: `offsetX: 0, offsetY: -20` → label above, horizontally centered **Label collision avoidance:** - A line-label is 120×28px. After placing each label, verify it does not overlap any other label or node shape. If it does, adjust `data.segmentT` (position within the segment, 0–1) or `data.offsetX`/`data.offsetY` to move it away. - **Horizontal connectors with labels need sufficient length.** The label (120px wide) sits above the line. If the horizontal span between source and target is shorter than the label, the label will overflow onto adjacent shapes. Ensure at least 160px horizontal gap between the two connected nodes when a label is present. Increase node spacing if needed. - When two connectors have roughly parallel segments in a narrow gap (e.g. between two frames), their labels at `segmentT: 0.5` will overlap. Stagger them: first label at `segmentT: 0.3`, second at `segmentT: 0.7`. - Minimum center-to-center distance between any two line-labels should be >= 60px. If closer, shift one label's `segmentT` or offset until they separate. ## Layout Guidelines - Place elements with at least **40px gap** between them - **Nodes with 3+ connectors** need extra clearance: use **60–80px gap** on all sides so elbow fold segments have room to route without crossing other shapes - **Decision nodes with branches**: stagger the branch targets by 20–40px vertically (for left-to-right flows) or horizontally (for top-to-bottom flows) so connector routes naturally separate - For vertical flowcharts: increment Y by `height + 80` per step - For horizontal flowcharts: increment X by `width + 100` per step - Start coordinates around `x: 100, y: 100` so content is visible on load - Connector `points` can be `[]` — the renderer calculates the path automatically - **Always nest logically related elements** using `parentId` / `containable: true` / `childrenIds` — pages, functional modules, swimlane steps, card content, labeled groups, anything the user might want to move as a unit. Use `frame` for generic containers; use `rounded-rect`, `rect`, `package`, or `swimlane` when the shape meaning matters. Never just position elements near each other and leave them floating — floating siblings cannot be moved together and lose spatial relationship when the diagram is edited. - **containable: true is required on every parent element in JSON** — without it, children can be dragged outside the container and the group-move behavior is lost. The default is `false`, so you must set it explicitly on any element that has `childrenIds`. ## Browser Frame Container A browser mockup container. Composite structure: outer shell + header chrome + content frame. ```json [ { "id": "browser-1", "type": "browser-frame", "x": 100, "y": 100, "width": 800, "height": 520, "fillColor": "#ffffff", "strokeColor": "#e2e8f0", "strokeWidth": 1, "roughness": 0, "containable": false, "childrenIds": ["browser-header-1", "browser-content-1"], "constraints": [], "tags": [], "data": {} }, { "id": "browser-header-1", "type": "browser-header", "x": 0, "y": 0, "width": 800, "height": 40, "fillColor": "#f1f5f9", "strokeWidth": 0, "parentId": "browser-1", "childrenIds": ["browser-url-1"], "constraints": [{ "id": "fill-parent-width" }], "tags": [], "data": {} }, { "id": "browser-url-1", "type": "text", "x": 80, "y": 9, "width": 640, "height": 22, "text": "https://", "fontSize": 12, "fontWeight": "normal", "textAlign": "center", "verticalAlign": "center", "parentId": "browser-header-1", "childrenIds": [], "constraints": [{ "id": "fill-parent-inset", "top": 9, "right": 80, "bottom": 9, "left": 80 }], "tags": [], "data": {} }, { "id": "browser-content-1", "type": "frame", "x": 0, "y": 40, "width": 800, "height": 480, "fillColor": "#ffffff", "strokeWidth": 0, "clipContent": true, "containable": true, "parentId": "browser-1", "childrenIds": [], "constraints": [{ "id": "fill-parent-inset", "top": 40 }], "tags": [], "data": {} } ] ``` Place your design content as children of `browser-content-1`. ## Mobile Frame Container A smartphone mockup container. Composite structure: outer shell + status bar + screen frame. ```json [ { "id": "mobile-1", "type": "mobile-frame", "x": 100, "y": 100, "width": 375, "height": 812, "fillColor": "#1e293b", "strokeColor": "#334155", "strokeWidth": 2, "roughness": 0, "containable": false, "childrenIds": ["mobile-status-1", "mobile-screen-1"], "constraints": [], "tags": [], "data": {} }, { "id": "mobile-status-1", "type": "mobile-status-bar", "x": 0, "y": 0, "width": 375, "height": 44, "fillColor": "transparent", "strokeWidth": 0, "parentId": "mobile-1", "childrenIds": [], "constraints": [{ "id": "fill-parent-width" }], "tags": [], "data": {} }, { "id": "mobile-screen-1", "type": "frame", "x": 0, "y": 44, "width": 375, "height": 744, "fillColor": "#ffffff", "strokeWidth": 0, "clipContent": true, "containable": true, "parentId": "mobile-1", "childrenIds": [], "constraints": [{ "id": "fill-parent-inset", "top": 44, "bottom": 24 }], "tags": [], "data": {} } ] ``` Place your design content as children of `mobile-screen-1`. ## Font Guidelines **Font size tiers** — pick by role, not by available space. Never use values below `12` (smaller text becomes illegible in the hand-drawn style). The shared `el()` factory defaults to `16`. | role | fontSize | examples | |---|---|---| | page title / hero | `20` – `24` | document title, section banners | | section / card title | `16` – `18` | container headers, swimlane labels | | body text (default) | `14` – `16` | rect / ellipse labels, card content, regular text | | dense secondary text | `13` | tight cards in matrices, annotations next to nodes | | small labels / inline notes | `12` (minimum) | axis labels, footnote-like captions | | form-element text | `14` | `input`, `select` (this is the rendered field text, not a label) | Rules: - Do **not** use `fontSize < 12` — readability fails, especially for Chinese characters. - If your card is too small to fit `14` text, scale **the card** (increase width / height), do not shrink the font. - Body text in flowcharts, mind-maps, kanban cards, etc. should default to `14` or higher. The following font families are available — choose based on the element's role and visual style: | fontFamily | category | when to use | |---|---|---| | `"Inter, sans-serif"` | sans-serif | **Default for all elements** — clean, readable at small sizes. Matches Miro/FigJam/Notion defaults. | | `"Caveat, cursive"` | handwritten | Smooth English handwriting for casual annotations or sticky-note style text (English only). | | `'"Noto Sans SC", sans-serif'` | sans-serif | **Chinese sans-serif (Source Han Sans)** — use when the diagram has Chinese content and needs a clean formal look. | | `"Merriweather, serif"` | serif | English serif for document-style diagrams, reports, or editorial content (English only). | | `'"Noto Serif SC", serif'` | serif | **Chinese serif (Source Han Serif)** — use when the diagram has Chinese content and needs a traditional/elegant look. | | `'"JetBrains Mono", monospace'` | monospace | Monospace font for code blocks, technical diagrams, or terminal-like UIs. | | `'"Permanent Marker", cursive'` | display | Bold marker-pen style for emphasis, callouts, or whiteboard-style highlights (English only). | | `'"ZCOOL KuaiLe", sans-serif'` | display | **Chinese playful/cute style** — use for fun, friendly Chinese diagrams. | **Rules**: - All elements default to `"Inter, sans-serif"` — clean and readable like Miro/FigJam. - For Chinese-heavy content, prefer `Noto Sans SC` (sans-serif) or `Noto Serif SC` (serif). - For English-only content, `Caveat`, `Inter`, `Merriweather`, `Permanent Marker` are all valid alternatives — pick based on tone (handwritten / formal / serif / display). - Use `JetBrains Mono` only for code or terminal content. --- ## Color Guidelines Always ensure sufficient contrast between `fontColor` and `fillColor` so text remains legible: - **Dark fill → light text**: if `fillColor` is a dark color (e.g. `#1e293b`, `#3b82f6`, `#ef4444`), set `fontColor` to `"#ffffff"` or a near-white tone - **Light fill → dark text**: if `fillColor` is light or white (e.g. `#ffffff`, `#f1f5f9`, `#fef9c3`), set `fontColor` to `"#111827"` or `"#333333"` - **Transparent / no fill**: always use a clearly visible `fontColor` that contrasts with the page background - **Avoid same-family colors**: do not pair similar hues at similar brightness (e.g. blue fill + blue text, yellow fill + light-yellow text) - **White text needs a genuinely dark fill**: only set `fontColor: "#ffffff"` when the fill is a deep, saturated color (luminance is low — e.g. `#1b5e20`, `#1e3a5f`, `#4a148c`). Never put white text on a *pale* tint such as `#e8f5e9`, `#e3f2fd`, or `#f3e5f5` — the result is invisible. This is the single most common mistake on **start/end (ellipse/terminator)** nodes: if you give them a pale fill, use a dark `fontColor`; if you want white text, give them a dark fill. Pick both from the **same row** of a palette below — do not mix a process row's pale fill with a start/end row's white text. - **Connector labels**: connector `strokeColor` should contrast with the page background; label `fontColor` should contrast with whatever is behind the label - **Consistent palette**: within a single diagram, pick 2–4 accent colors and use them consistently for the same semantic meaning (e.g. one color for "process", another for "decision", another for "data") Quick reference for common fill colors: | fillColor | recommended fontColor | |-----------|----------------------| | `#ffffff` / `#f8fafc` / light grays | `#111827` | | `#1e293b` / `#0f172a` / dark navy | `#f8fafc` | | `#3b82f6` / `#2563eb` (blue) | `#ffffff` | | `#10b981` / `#059669` (green) | `#ffffff` | | `#ef4444` / `#dc2626` (red) | `#ffffff` | | `#f59e0b` / `#fbbf24` (amber) | `#1c1917` | | `#8b5cf6` / `#7c3aed` (purple) | `#ffffff` | | `#e2e8f0` / `#f1f5f9` (light slate) | `#334155` | ### Recommended Color Palettes Pick one palette per diagram and apply it consistently by node role: **Blue (flowcharts, system diagrams)** - Process/rect: `fillColor: "#e3f2fd"`, `strokeColor: "#1565c0"`, `fontColor: "#1565c0"` - Decision/diamond: `fillColor: "#bbdefb"`, `strokeColor: "#1565c0"`, `fontColor: "#0d47a1"` - Start/end/ellipse: `fillColor: "#1e3a5f"`, `strokeColor: "#1565c0"`, `fontColor: "#ffffff"` ← the dark fill and white text are a pair; never reuse this white `fontColor` on the pale process/decision fills above - Connector: `strokeColor: "#1565c0"` **Green (success flows, onboarding)** - Process/rect: `fillColor: "#e8f5e9"`, `strokeColor: "#2e7d32"`, `fontColor: "#1b5e20"` - Decision/diamond: `fillColor: "#c8e6c9"`, `strokeColor: "#2e7d32"`, `fontColor: "#1b5e20"` - Start/end/ellipse: `fillColor: "#1b5e20"`, `strokeColor: "#2e7d32"`, `fontColor: "#ffffff"` ← dark fill + white text are a pair; do NOT pair white text with the pale `#e8f5e9` process fill (a common low-contrast bug) **Purple (architecture, infra diagrams)** - Process/rect: `fillColor: "#f3e5f5"`, `strokeColor: "#6a1b9a"`, `fontColor: "#4a148c"` - Decision/diamond: `fillColor: "#e1bee7"`, `strokeColor: "#6a1b9a"`, `fontColor: "#4a148c"` - Start/end/ellipse: `fillColor: "#4a148c"`, `strokeColor: "#6a1b9a"`, `fontColor: "#ffffff"` ← dark fill + white text are a pair; never reuse this white text on the pale `#f3e5f5` process fill **Neutral (wireframes, docs)** - All shapes: `fillColor: "#f8fafc"`, `strokeColor: "#475569"`, `fontColor: "#1e293b"` - Emphasis: `fillColor: "#f1f5f9"`, `strokeColor: "#334155"`, `fontColor: "#0f172a"` ## Type-Specific Notes - **mindmap-node**: mind map tree node. Tree layout (x/y) is computed automatically by the mindmap layout engine; connectors between parent and child are rendered as bezier curves at runtime and are NOT stored elements — **never emit `connector` / `line` elements between mindmap-node's**, and never put a mindmap-node inside a `frame` container. See the "Mind Map" section below for the full guide. - Style: `roughness: 0`, `corners: [6,6,6,6]`, `strokeWidth: 1.5`, `strokeColor: "#94a3b8"`, `fillColor: "#ffffff"`, `fontSize: 14`, `containable: false`, `connectable: false`, `movable: "free"` - Tree fields (top-level Element fields, NOT `data.*`, NOT engine `parentId`/`childrenIds`): - `mindmapRootId` — root node's id (root's own `mindmapRootId` equals its own `id`) - `mindmapParentId` — parent mindmap-node's id; `null` on root - `mindmapChildIds` — ordered array of child mindmap-node ids (empty when leaf) - `mindmapDirection` — only meaningful on **root's direct children**: `"left"` or `"right"` decides which side of root that whole subtree lays out on. Descendants inherit the direction from their ancestor. - **actor**: stick figure; top 75% is the figure, bottom 25% is the label; use for users/persons in UML use case or C4 diagrams; pair with `connector` to show interactions - **delay**: rectangle with right-side semicircle; represents a waiting or delay step in a flowchart - **manual-input**: trapezoid with slanted top edge (left side higher); represents human data entry (keyboard input, form fill) - **stored-data**: drum/barrel shape (both ends curved); represents persistent data storage, tape archive, or data file - **fork-bar**: very thin filled rectangle (`height: 8`, `fillColor: "#1e293b"`, `roughness: 0`); UML activity diagram parallel fork or join node; not text-editable; pair two bars with parallel paths between them - **terminator**: capsule shape (corner radius = min(w/2, h/2)); use for flowchart start/end nodes - **component**: rectangle with two UML socket tabs on the left side; use in UML component diagrams; pair with `connector` for dependencies - **end-node**: bull's-eye (outer ellipse ring + inner filled circle, ~60% size); use for UML activity/state diagram terminal node; not text-editable; pairs with `start-node` - **cloud**: multi-bump cloud outline; use for internet / cloud services / network boundaries; pairs well with `connector` edges - **predefined-process**: rectangle with two inner vertical lines (15% from each side); represents a subroutine or predefined process step in flowcharts - **cross**: 12-vertex plus-sign polygon; arm width defaults to 1/3 of bounding box; useful as a marker or medical/alert symbol - **off-page-connector**: downward-pointing pentagon (top rectangle + bottom triangle point); represents a reference to another page in a flowchart - **rounded-rect**: rect with `corners:[12,12,12,12]`; use for modern card-style nodes - **start-node**: solid dark ellipse (`fillColor: "#1e293b"`, `roughness: 0`); UML activity/state diagram start point; not text-editable - **package**: frame-like container (`fillColor: "#f8fafc"`, `clipContent: false`); use for UML package diagrams or module grouping - **swimlane**: wide frame container (`600×200`, `fillColor: "#f1f5f9"`); use for BPMN swimlanes or cross-role process flows - **queue**: parallelogram with gentle skew (`parallelogramSkew: 0.15`); use for message queues, buffers, async channels - **note**: folded top-right corner rectangle (sticky note). Use for hints, reminders, legends, and callouts on a board. **Never leave a note white** (generic `#ffffff` fill / black stroke) — it looks off. Always give it a soft sticky-note color chosen to fit context, emitting `fillColor` + a matching `strokeColor`. Pick from these recommended pairs (or a nearby soft tint), and vary the color when several notes sit near each other so they read as distinct: - yellow `#fff7cc` / `#eab308` — default hint / reminder - blue `#dbeafe` / `#60a5fa` — info / neutral - green `#d1fae5` / `#34d399` — done / positive / tip - pink `#fce7f3` / `#f472b6` — warning / attention - purple `#ede9fe` / `#a78bfa` — idea / question - orange `#ffedd5` / `#fb923c` — action / next step Also set `textAlign: "left"`, `verticalAlign: "top"`; fold size controlled by `data.noteFoldSize` (default 16px). - **hexagon**: flat-top hexagon; use for services/components in C4 or hexagonal architecture diagrams; indent on left/right is 1/4 of width - **cylinder**: typical database/storage node; cap height controlled by `data.cylinderCapRatio` (default 0.25); use `fillColor: "#e3f2fd"` + `strokeColor: "#1565c0"` for a styled DB node - **text**: `strokeWidth: 0`, `fillColor: "transparent"`, `fillStyle: "none"`, `connectable: false` - **textarea**: `roughness: 0`, `corners: [6,6,6,6]`, `fillColor: "#fafafa"`, `strokeColor: "#d1d5db"`, `strokeWidth: 1.5`, `fontWeight: "normal"`, `textAlign: "left"`, `verticalAlign: "top"`, `wordWrap: true`, `containable: false` - **parallelogram**: add `"data": { "parallelogramSkew": 0.2 }` - **document**: add `"data": { "documentWaveAmpRatio": 0.25 }` - **frame**: `containable: true`, `clipContent: true`, `roughness: 0`, `strokeColor: "#94a3b8"` - **table**: native grid with add/remove rows and columns and column/row-resize. Always prefer `table` over a manual `rect` grid for any structured row-column data users will want to edit. Structure: - `table` root: `data.tableColumns` = array of `{ id, width }` per column — **this is the sole source of truth for column widths**; column widths sum to `table.width`; children = `[table-row, table-row, ...]` (no header element) - `table-row`: each row (all children of `table`); `row.height` is the sole source of truth for row height; sum of row heights = `table.height`; `childrenIds` lists one `table-cell` per column - `table-cell`: a single cell inside a row; can have an optional `text` child (use constraints `fill-parent-width` + `fill-parent-height` on the text so `textAlign`/`verticalAlign` work correctly); set `fillColor` for per-cell background - **Header row**: there is no dedicated header element. To style the first row as a header, set the first row's cells' `fillColor` (e.g. `"#e2e8f0"`) and their text's `fontWeight: "bold"`. The rendering treats the first row identically to any other row. - **Invariants (must hold)**: `tableColumns.length` == every row's `childrenIds.length`; at least 1 row and at least 1 column - Recommended style: `roughness: 0.5`, `strokeWidth: 1`, `strokeColor: "#e2e8f0"` - **Never simulate a table with `rect` elements** — doing so prevents users from adding rows, removing columns, or resizing columns; `table` is the correct primitive for RACI charts, feature comparisons, backlogs, schedules, and any "tasks × attributes" matrix Example — minimal `table` (3 columns × 2 rows; first row styled as a header via cell `fillColor` + text `fontWeight`; column widths sum to `table.width`; row heights sum to `table.height`): ```json [ { "id": "tbl", "type": "table", "x": 0, "y": 0, "width": 360, "height": 100, "childrenIds": ["row-0", "row-1"], "data": { "tableColumns": [ { "id": "c0", "width": 120 }, { "id": "c1", "width": 120 }, { "id": "c2", "width": 120 } ] }, "roughness": 0.5, "strokeWidth": 1, "strokeColor": "#e2e8f0", "constraints": [], "tags": [] }, { "id": "row-0", "type": "table-row", "x": 0, "y": 0, "width": 360, "height": 40, "parentId": "tbl", "childrenIds": ["c-0-0", "c-0-1", "c-0-2"], "constraints": [], "tags": [], "data": {} }, { "id": "c-0-0", "type": "table-cell", "x": 0, "y": 0, "width": 120, "height": 40, "parentId": "row-0", "childrenIds": ["t-0-0"], "fillColor": "#e2e8f0", "constraints": [], "tags": [], "data": {} }, { "id": "t-0-0", "type": "text", "x": 0, "y": 0, "width": 120, "height": 40, "text": "Role", "fontWeight": "bold", "textAlign": "center", "verticalAlign": "center", "parentId": "c-0-0", "constraints": [{ "id": "fill-parent-width" }, { "id": "fill-parent-height" }], "tags": [], "data": {} }, { "id": "c-0-1", "type": "table-cell", "x": 120, "y": 0, "width": 120, "height": 40, "parentId": "row-0", "childrenIds": ["t-0-1"], "fillColor": "#e2e8f0", "constraints": [], "tags": [], "data": {} }, { "id": "t-0-1", "type": "text", "x": 0, "y": 0, "width": 120, "height": 40, "text": "Responsible", "fontWeight": "bold", "textAlign": "center", "verticalAlign": "center", "parentId": "c-0-1", "constraints": [{ "id": "fill-parent-width" }, { "id": "fill-parent-height" }], "tags": [], "data": {} }, { "id": "c-0-2", "type": "table-cell", "x": 240, "y": 0, "width": 120, "height": 40, "parentId": "row-0", "childrenIds": ["t-0-2"], "fillColor": "#e2e8f0", "constraints": [], "tags": [], "data": {} }, { "id": "t-0-2", "type": "text", "x": 0, "y": 0, "width": 120, "height": 40, "text": "Deadline", "fontWeight": "bold", "textAlign": "center", "verticalAlign": "center", "parentId": "c-0-2", "constraints": [{ "id": "fill-parent-width" }, { "id": "fill-parent-height" }], "tags": [], "data": {} }, { "id": "row-1", "type": "table-row", "x": 0, "y": 40, "width": 360, "height": 60, "parentId": "tbl", "childrenIds": ["c-1-0", "c-1-1", "c-1-2"], "constraints": [], "tags": [], "data": {} }, { "id": "c-1-0", "type": "table-cell", "x": 0, "y": 0, "width": 120, "height": 60, "parentId": "row-1", "childrenIds": [], "constraints": [], "tags": [], "data": {} }, { "id": "c-1-1", "type": "table-cell", "x": 120, "y": 0, "width": 120, "height": 60, "parentId": "row-1", "childrenIds": [], "constraints": [], "tags": [], "data": {} }, { "id": "c-1-2", "type": "table-cell", "x": 240, "y": 0, "width": 120, "height": 60, "parentId": "row-1", "childrenIds": [], "constraints": [], "tags": [], "data": {} } ] ``` - **line** (standalone): `points: [[x1,y1],[x2,y2]]`, `lineType: "straight"` (default) / `"elbow"` / `"curve"`, `connectable: false`, `sizable: "none"`; supports `arrowStart`/`arrowEnd` (`"none"` / `"arrow"` / `"open"`). The legacy `"freehand"` / `"orthogonal"` / `"horizontal"` / `"vertical"` values are no longer accepted — emit only the three current names. - **line-label**: floating text label attached to a `line` or `connector`; set `data.lineId` to the parent line's ID, `data.segmentIndex` (segment to anchor to, 0 = first) and `data.segmentT` (0–1 position within that segment), and `data.offsetX`/`data.offsetY` for the pixel offset from the anchor; **always pre-compute `x`/`y` using the formula in the Line Label section** — leaving them at 0 puts every label at the canvas origin until the parent line moves. - **link**: `strokeWidth: 0`, `fillColor: "transparent"`, `roughness: 0`, `fontColor: "#2563eb"`, add `"data": { "href": "https://..." }` - **HTTP links on any element**: any selectable element may include `data.href` to act as a hyperlink in the editor/viewer. Only normal HTTP(S) URLs are supported; emit values that start with `http://` or `https://` only. Do not emit `mailto:`, `javascript:`, relative paths, page IDs, or document-internal links. - **icon**: `fillColor: "transparent"`, `fillStyle: "none"`, `roughness: 0.5`, `sizable: "ratio"`, `textEditable: false`, add `"data": { "iconName": "" }`. Available icons by category: - **Arrows**: arrow-up, arrow-down, arrow-left, arrow-right, arrow-up-right, arrow-down-left, arrow-down-right, arrow-up-left, double-arrow-h, double-arrow-v, chevron-up, chevron-down, chevron-left, chevron-right - **People**: avatar, user-group, user-plus, user-minus, user-check - **Symbols**: check, cross, star, warning, info, heart, lightning, plus, minus, question, thumbs-up, thumbs-down, infinity, ban, circle-check, circle-cross, hash, at-sign, timer, loop, flow-merge, split, start-circle, end-circle - **Tech**: server, database, cloud, api, container, code, globe, wifi, shield, terminal, cpu, git-branch, git-merge, key, bug, webhook, microchip, firewall, table, queue, stack, cache, storage, gateway, load-balancer, lambda, docker, kubernetes, dns, certificate - **Devices**: laptop, phone, monitor, tablet, camera, printer, headphones, watch, speaker, gamepad, cursor-click, touch, form, button-ui - **Communication**: chat, email, notification, share, megaphone, video, phone-call, inbox, send, rss - **Documents**: file, folder, clipboard, note, book, image-file, trash, archive, pen, bookmark, layers - **Business**: chart-bar, target, flag, calendar, clock, dollar, euro, trophy, briefcase, pie-chart, trending-up, trending-down, store, handshake, lightbulb, funnel, transform, sync, pipe, org-chart, hierarchy - **Status**: lock, unlock, eye, refresh, settings, search, link, power, toggle-on, toggle-off, download, upload, pin, filter, sort, hourglass, alert-circle, flag-check, progress - **Nature**: sun, moon, tree, leaf, fire, water-drop, snowflake, mountain - **Transport**: car, truck, plane, rocket, ship, bicycle - **Misc**: home, gift, crown, music, palette, wrench, magnet - **checkbox**: `roughness: 0`, `sizable: "ratio"`, `textEditable: false`, add `"data": { "checked": true }` - **radio**: `roughness: 0`, `sizable: "ratio"`, `textEditable: false`, add `"data": { "checked": false }` - **slider**: `roughness: 0`, `sizable: "free"`, `textEditable: false`, add `"data": { "min": 0, "max": 100, "value": 50 }` - **progress**: `sizable: "free"`, `textEditable: false`, `fillColor: "#22c55e"`, `strokeColor: "#d1d5db"`, add `"data": { "value": 60 }` (0-100 percentage). Read-only completion indicator — no draggable thumb. Use it for task/project status, checklists and dashboards; use **slider** instead when the value is meant to be adjusted by the user. - **switch**: `roughness: 0`, `sizable: "ratio"`, `textEditable: false`, add `"data": { "on": false }` - **input**: `roughness: 0`, `sizable: "free"`, `textEditable: true`, `strokeColor: "#d1d5db"`, `fillColor: "#ffffff"`, `strokeWidth: 1`, `fontSize: 14`, `fontColor: "#000000"`, add `"data": { "placeholder": "Input…" }` - **image**: `sizable: "ratio"`, `textEditable: false`, `strokeWidth: 0`, `fillColor: "transparent"`; set `src` field to a data URL or image URL; width/height are automatically adjusted to preserve the original aspect ratio on upload - **button**: rect-based; `roughness: 1`, `corners: [6,6,6,6]`, `fillColor: "#ffffff"`, `strokeColor: "#d1d5db"`, `fontColor: "#333333"`, `text: "Button"` - **button-primary**: rect-based; `fillColor: "#3b82f6"`, `strokeColor: "#2563eb"`, `fontColor: "#ffffff"`, `corners: [6,6,6,6]`, `text: "Button"` - **button-secondary**: rect-based; `fillColor: "#ffffff"`, `strokeColor: "#3b82f6"`, `fontColor: "#3b82f6"`, `corners: [6,6,6,6]`, `text: "Button"` - **button-destructive**: rect-based; `fillColor: "#ef4444"`, `strokeColor: "#dc2626"`, `fontColor: "#ffffff"`, `corners: [6,6,6,6]`, `text: "Button"` - **button-ghost**: rect-based; `fillColor: "transparent"`, `strokeWidth: 0`, `fontColor: "#888888"`, `corners: [6,6,6,6]`, `text: "Button"` - **triangle**: polygon triangle; add `"data": { "triangleDirection": "up" }` — direction options: `"up"` (default) | `"down"` | `"left"` | `"right"` - **callout**: speech bubble with tail; add `"data": { "calloutTailX": 0.15, "calloutBodyRatio": 0.75 }` — `calloutTailX` (0–1) is horizontal position of the tail on the bottom edge; `calloutBodyRatio` (0–1) is the height of the body relative to total height - **server**: rack-server shape; `roughness: 1`, `fillColor: "#f8fafc"`, `strokeColor: "#64748b"`, `strokeWidth: 1.5`; use for physical/virtual servers in system architecture diagrams - **load-balancer**: load balancer icon shape; `roughness: 1`, `fillColor: "#eff6ff"`, `strokeColor: "#3b82f6"`, `strokeWidth: 1.5` - **firewall**: firewall icon shape; `roughness: 1`, `fillColor: "#fff7ed"`, `strokeColor: "#ea580c"`, `strokeWidth: 1.5` - **container**: Docker-style container shape; `roughness: 1`, `containable: true`, `clipContent: false`, `fillColor: "#f0fdf4"`, `strokeColor: "#16a34a"`, `strokeWidth: 1.5`; use as a container for child nodes representing services inside a container - **msg-queue**: message queue shape; `roughness: 1`, `fillColor: "#fefce8"`, `strokeColor: "#ca8a04"`, `strokeWidth: 1.5`; use for SQS, RabbitMQ, Kafka, etc. - **router**: network router icon shape; `roughness: 1`, `fillColor: "#f1f5f9"`, `strokeColor: "#64748b"`, `strokeWidth: 1.5` - **lambda**: serverless function shape; `roughness: 1`, `fillColor: "#fff7ed"`, `strokeColor: "#f97316"`, `strokeWidth: 1.5`; use for AWS Lambda, Cloud Functions, etc. - **cache**: cache node shape; `roughness: 1`, `fillColor: "#fffbeb"`, `strokeColor: "#d97706"`, `strokeWidth: 1.5`; use for Redis, Memcached, CDN caches - **pod**: Kubernetes pod node (hexagon alias); `roughness: 1`, `fillColor: "#ecfeff"`, `strokeColor: "#06b6d4"`, `strokeWidth: 1.5` - **code**: code block with syntax highlighting; `roughness: 0`, `corners: [8,8,8,8]`, monospace font (`'Fira Code', 'Cascadia Code', 'JetBrains Mono', Consolas, monospace`), `fontSize: 14`, `lineHeight: 1.5`, `textEditable: true`, `connectable: true`. The `text` field holds the raw code content (use `\n` for newlines). Theme controls background/text colors automatically — do NOT set `fillColor`/`fontColor` manually. Data fields: `data.language` (shiki language ID, default `"bash"` — options: bash, c, cpp, css, dockerfile, go, html, java, javascript, json, jsx, kotlin, markdown, php, python, ruby, rust, sql, swift, tsx, typescript, yaml), `data.theme` (`"dark"` default — dark bg `#1e1e2e` / `"light"` — white bg), `data.tabWidth` (2 | 4 | 8, default 4), `data.showLineNumbers` (boolean, default true), `data.wrapText` (boolean, default false — when false, width/height auto-fit content; when true, width is fixed and height auto-fits wrapped lines). **Size calculation (CRITICAL — you MUST compute width/height with these formulas and use the results to position adjacent nodes):** - `charWidth = fontSize * 0.65` (monospace, ≈ 9.1 px at fontSize 14 — deliberately generous to guarantee no clipping) - `lineNumW = showLineNumbers ? (floor(log10(lineCount)) + 1) * charWidth + 24 : 0` - `maxLineChars` = character count of the longest line in `text` (after expanding tabs to `tabWidth` spaces) - `codeW = ceil(lineNumW + 32 + maxLineChars * charWidth)` (32 = padX × 2) - `codeH = ceil(24 + lineCount * fontSize * lineHeight)` (24 = padY × 2; at defaults = 24 + lines × 21) - Set `width = codeW`, `height = codeH`. - **Layout rule**: When placing code nodes next to other nodes, always compute `codeW`/`codeH` first, then set the **next** node's `x` = this node's `x + codeW + gap` (gap ≥ 40). Never hard-code positions without considering the calculated size. This prevents overlaps caused by auto-sizing on the client. ```json { "id": "code-1", "type": "code", "text": "const greeting = \"Hello\";\nconsole.log(greeting);", "x": 100, "y": 100, "width": 302, "height": 66, "roughness": 0, "strokeColor": "#313244", "strokeWidth": 1, "strokeDash": "solid", "fillColor": "#1e1e2e", "fillStyle": "solid", "fontFamily": "'Fira Code', 'Cascadia Code', 'JetBrains Mono', Consolas, monospace", "fontSize": 14, "fontColor": "#cdd6f4", "fontWeight": "normal", "lineHeight": 1.5, "corners": [8, 8, 8, 8], "connectable": true, "textEditable": true, "sizable": "free", "constraints": [], "tags": [], "data": { "language": "javascript", "theme": "dark", "tabWidth": 4, "showLineNumbers": true, "wrapText": false } } ``` - **line-chart**: data-driven line/trend chart with axes, grid, and value labels. `roughness: 1`, `fillColor: "#ffffff"`, `strokeColor: "#6366f1"` (the line color — change it to recolor the whole chart, including data points and value labels), `textEditable: false`, `connectable: false`, `sizable: "free"`. **All chart content lives in `data` — there are no child elements and no `points` array.** Data fields: - `data.series` (required): array of `{ "label": "", "value": }`. Points are plotted left-to-right, evenly spaced; keep at least 2 points. - `data.title` (string, default `""`): chart title shown centered at the top. Empty = no title and no reserved space. - `data.autoRange` (boolean, default `true`): when true the Y axis range is computed from the data automatically (lower bound 0 if all values ≥ 0, else the data min; upper bound = data max × 1.1). When false, `data.minValue` / `data.maxValue` are used as the fixed Y range. **Prefer leaving autoRange true** — only set false when a specific fixed scale is needed. - `data.minValue` / `data.maxValue` (numbers): manual Y-axis bounds, only used when `autoRange` is `false`. - `data.showValues` (boolean, default true): show the numeric value above each data point. - `data.showAxes` (boolean, default true): show X/Y axis lines and Y tick labels. - `data.showGrid` (boolean, default true): show horizontal gridlines. ```json { "id": "line-chart-1", "type": "line-chart", "x": 100, "y": 100, "width": 320, "height": 200, "roughness": 1, "strokeColor": "#6366f1", "strokeWidth": 1, "strokeDash": "solid", "fillColor": "#ffffff", "fillStyle": "solid", "connectable": false, "textEditable": false, "sizable": "free", "constraints": [], "tags": [], "data": { "title": "Monthly Revenue", "series": [ { "label": "Jan", "value": 40 }, { "label": "Feb", "value": 65 }, { "label": "Mar", "value": 50 }, { "label": "Apr", "value": 80 }, { "label": "May", "value": 60 } ], "autoRange": true, "minValue": 0, "maxValue": 100, "showValues": true, "showAxes": true, "showGrid": true } } ``` - **select**: dropdown select element; `roughness: 1`, `sizable: "free"`, `textEditable: false`, `corners: [4,4,4,4]`, `strokeColor: "#000000"`, `fillColor: "#ffffff"`, `fontSize: 14`. Options are stored as `data.options` (array of `{ "id": "", "label": "" }`). `data.selectedId` is the `id` of the currently selected option (`null` for no selection). `data.open` controls the dropdown expanded state: when `false` (default) `height` should be `36`; when `true` `height` must be `36 + options.length * 28`. Always generate unique `id` values for each option. ```json { "id": "select-1", "type": "select", "x": 100, "y": 100, "width": 200, "height": 36, "roughness": 1, "strokeColor": "#000000", "strokeWidth": 2, "strokeDash": "solid", "fillColor": "#ffffff", "fillStyle": "solid", "fontSize": 14, "fontColor": "#000000", "corners": [4, 4, 4, 4], "connectable": true, "sizable": "free", "constraints": [], "tags": [], "data": { "options": [ { "id": "opt-1", "label": "Option 1" }, { "id": "opt-2", "label": "Option 2" }, { "id": "opt-3", "label": "Option 3" } ], "selectedId": "opt-1", "open": false } } ``` ## Mind Map Mind maps are a first-class element type built on top of `mindmap-node`. The tree structure, layout, and connectors are all handled by the mindmap engine — you only emit the nodes and their parent-child fields; the editor computes positions and draws bezier connectors at runtime. ### Core rules (READ FIRST) - One `mindmap-node` = one node in the tree. There is exactly one root per mind map. - Use the **top-level Element fields** `mindmapRootId` / `mindmapParentId` / `mindmapChildIds` to express tree structure. Do **NOT** use the engine's `parentId` / `childrenIds` (those would trigger local-coord clipping and are wrong for mind maps). - All mindmap-node `x` / `y` are **absolute canvas coordinates** (like regular top-level elements). Do NOT set them relative to root. - **Never emit `connector` or `line` elements between mindmap-nodes** — parent→child connectors are auto-rendered as bezier curves. - **Never nest a mindmap-node in a `frame`**. - `containable: false`, `connectable: false`, `movable: "free"` on every mindmap-node. - `constraints: []` on every mindmap-node (mind maps do not use the constraint system). ### Required fields on every mindmap-node | Field | Root | Non-root | |-------|------|----------| | `id` | own UUID | own UUID | | `type` | `"mindmap-node"` | `"mindmap-node"` | | `text` | central topic | node label | | `mindmapRootId` | equals own `id` | root's `id` | | `mindmapParentId` | `null` | parent's `id` | | `mindmapChildIds` | ordered array of child ids (or `[]`) | ordered array of child ids (or `[]`) | | `mindmapDirection` | omit (or `undefined`) | **only on root's direct children**: `"left"` or `"right"`; deeper descendants omit this | | `x` / `y` | your chosen anchor position | any placeholder — layout engine will overwrite | | `width` / `height` | 120 / 40 (or measured) | 120 / 40 (or measured) | ### Layout behavior - The layout engine reads the tree rooted at `mindmapRootId` and assigns absolute `x` / `y` to every node. Root stays put; children are placed to the left and/or right based on each root child's `mindmapDirection`. - Children of a non-root node inherit the direction of their nearest ancestor whose direction is set. - If you only set direction on some root children (or none), the remaining default to the right side. - Sibling ordering follows `mindmapChildIds` order (index 0 = topmost). - **When you generate a mindmap, you should still fill in sensible `x` / `y` placeholders** so the JSON validates; the engine will recompute them on load / edit. ### Auto behaviors in the editor (informational) These do not affect JSON generation, but knowing them helps you reason about the UX: - Selecting a mindmap-node shows a small circular "+ add child" button next to it. Root shows one button on each side; children show one button on their side only. - Dragging a mindmap-node uses edge-based detach: when the dragged node's outer edge crosses its current parent's outer edge, the drop target ascends one level; once it crosses root's opposite edge, the node switches to root's other side; then descends into whichever sub-tree its position naturally lands in. - Auto layout re-runs after any add / delete / reparent / rename. ### Minimal root-only example ```json { "id": "mm-root", "type": "mindmap-node", "text": "Central Topic", "x": 400, "y": 300, "width": 140, "height": 44, "rotation": 0, "opacity": 1, "visible": true, "locked": false, "connectable": false, "containable": false, "movable": "free", "roughness": 0, "corners": [6, 6, 6, 6], "strokeWidth": 1.5, "strokeColor": "#94a3b8", "fillColor": "#ffffff", "fontSize": 16, "fontWeight": "bold", "textAlign": "center", "verticalAlign": "center", "mindmapRootId": "mm-root", "mindmapParentId": null, "mindmapChildIds": [], "constraints": [] } ``` ### Full example: bidirectional mind map (root + 2 right + 2 left + a grandchild) Layout will be re-computed by the engine — the `x` / `y` here are placeholders showing the intended structure. ```json [ { "id": "mm-root", "type": "mindmap-node", "text": "Product Launch", "x": 500, "y": 300, "width": 160, "height": 48, "rotation": 0, "opacity": 1, "visible": true, "locked": false, "connectable": false, "containable": false, "movable": "free", "roughness": 0, "corners": [6, 6, 6, 6], "strokeWidth": 1.5, "strokeColor": "#94a3b8", "fillColor": "#ffffff", "fontSize": 16, "fontWeight": "bold", "textAlign": "center", "verticalAlign": "center", "mindmapRootId": "mm-root", "mindmapParentId": null, "mindmapChildIds": ["mm-marketing", "mm-launch-plan", "mm-engineering", "mm-design"], "constraints": [] }, { "id": "mm-marketing", "type": "mindmap-node", "text": "Marketing", "x": 720, "y": 260, "width": 120, "height": 40, "rotation": 0, "opacity": 1, "visible": true, "locked": false, "connectable": false, "containable": false, "movable": "free", "roughness": 0, "corners": [6, 6, 6, 6], "strokeWidth": 1.5, "strokeColor": "#94a3b8", "fillColor": "#ffffff", "fontSize": 14, "textAlign": "center", "verticalAlign": "center", "mindmapRootId": "mm-root", "mindmapParentId": "mm-root", "mindmapChildIds": ["mm-campaign"], "mindmapDirection": "right", "constraints": [] }, { "id": "mm-campaign", "type": "mindmap-node", "text": "Ad Campaign", "x": 880, "y": 260, "width": 120, "height": 40, "rotation": 0, "opacity": 1, "visible": true, "locked": false, "connectable": false, "containable": false, "movable": "free", "roughness": 0, "corners": [6, 6, 6, 6], "strokeWidth": 1.5, "strokeColor": "#94a3b8", "fillColor": "#ffffff", "fontSize": 14, "textAlign": "center", "verticalAlign": "center", "mindmapRootId": "mm-root", "mindmapParentId": "mm-marketing", "mindmapChildIds": [], "constraints": [] }, { "id": "mm-launch-plan", "type": "mindmap-node", "text": "Launch Plan", "x": 720, "y": 320, "width": 120, "height": 40, "rotation": 0, "opacity": 1, "visible": true, "locked": false, "connectable": false, "containable": false, "movable": "free", "roughness": 0, "corners": [6, 6, 6, 6], "strokeWidth": 1.5, "strokeColor": "#94a3b8", "fillColor": "#ffffff", "fontSize": 14, "textAlign": "center", "verticalAlign": "center", "mindmapRootId": "mm-root", "mindmapParentId": "mm-root", "mindmapChildIds": [], "mindmapDirection": "right", "constraints": [] }, { "id": "mm-engineering", "type": "mindmap-node", "text": "Engineering", "x": 280, "y": 260, "width": 120, "height": 40, "rotation": 0, "opacity": 1, "visible": true, "locked": false, "connectable": false, "containable": false, "movable": "free", "roughness": 0, "corners": [6, 6, 6, 6], "strokeWidth": 1.5, "strokeColor": "#94a3b8", "fillColor": "#ffffff", "fontSize": 14, "textAlign": "center", "verticalAlign": "center", "mindmapRootId": "mm-root", "mindmapParentId": "mm-root", "mindmapChildIds": [], "mindmapDirection": "left", "constraints": [] }, { "id": "mm-design", "type": "mindmap-node", "text": "Design", "x": 280, "y": 320, "width": 120, "height": 40, "rotation": 0, "opacity": 1, "visible": true, "locked": false, "connectable": false, "containable": false, "movable": "free", "roughness": 0, "corners": [6, 6, 6, 6], "strokeWidth": 1.5, "strokeColor": "#94a3b8", "fillColor": "#ffffff", "fontSize": 14, "textAlign": "center", "verticalAlign": "center", "mindmapRootId": "mm-root", "mindmapParentId": "mm-root", "mindmapChildIds": [], "mindmapDirection": "left", "constraints": [] } ] ``` ### Common mistakes to avoid - ❌ Emitting `connector` / `line` elements between mindmap-nodes → duplicate visual lines. **Fix**: omit all connector/line elements between mindmap-nodes; the engine draws them. - ❌ Using engine `parentId` / `childrenIds` for tree structure → wrong coordinate space + clipping bugs. **Fix**: use only the `mindmapRootId` / `mindmapParentId` / `mindmapChildIds` fields. - ❌ Setting `mindmapDirection` on grandchildren (non-root-direct children) → ignored. **Fix**: only set it on the root's direct children. - ❌ Nesting a mindmap-node inside a `frame` or another container → clipping + wrong coordinates. **Fix**: keep mindmap-nodes as top-level elements. - ❌ Absolute coordinates set relative to root → engine will overwrite anyway, but confusing. **Fix**: any placeholder works; the layout engine assigns final positions. - ❌ Non-empty `constraints` on a mindmap-node → ignored, potential lint noise. **Fix**: always `constraints: []`. ## Container Pattern (Wireframes / UI Layouts) When designing UI screens, wireframes, or any layout with nested structure, use **`frame`** (not `rect`) for every element that visually contains other elements — pages, sections, panels, cards, modals, sidebars, list items with content inside. **Key benefit**: wrapping elements in a frame means the user can drag the entire module as one unit — all children move together, preserving their relative positions. Always prefer frames over loose element groups for any logical module (a page, a feature block, a swimlane, a legend, etc.). Set `containable: true` and establish the hierarchy via `parentId` / `childrenIds`. Child coordinates are **relative to the parent's top-left corner**. **When to set `containable: true`** — all of the following must hold: 1. The element is a structural container (holds child elements by design) 2. Its siblings at the same level are **not densely packed** (gap ≥ 40px) 3. It is large enough that a user would intuitively drag elements into it **Always `containable: false`**: - Leaf nodes (text, icon, image, decorative shape) - Dense repeated items at the same level (list rows, grid cells, table rows — gap < 40px) **Non-frame shapes with `containable: true`**: only when the shape's visual form is required for the container boundary (e.g. a swimlane background rect, a kanban column), never for flowchart steps or densely packed items. **Flowcharts and multi-module diagrams**: even when individual nodes don't need containment, wrap each top-level logical group (e.g. "User Flow", "Backend Services", "Database Layer") in a frame. This makes the whole group repositionable as one. **Typical layout hierarchy**: ``` Page (frame, containable: true) └── Section / Panel (frame, containable: true) └── Card — sparse layout (frame, containable: true) └── ListItem — dense layout (frame, containable: false) └── Title / Icon / Badge (text/rect, containable: false) ``` **Flowchart with modules**: ``` DiagramRoot (frame, containable: true, strokeWidth: 0, fillColor: "transparent") └── Module A (frame, containable: true) ← drag to reposition entire module └── Step 1 (rect) └── Step 2 (rect) └── Module B (frame, containable: true) └── Step 3 (rect) ``` ## Full Reference - Format guide + full example: https://codepic.cc/ai-guide.md - JSON Schema for validation: https://codepic.cc/schemas/document.json - Default values per type: https://codepic.cc/type-defaults.md