[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6DHTNB64TKDAM45MB2HDJAM":3,"skill-same:01M6DHTNB64TKDAM45MB2HDJAM":107},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":38,"linkOnly":39,"markdownContent":40,"markdownBody":41,"frontMatter":42,"refName":43,"commitSha":44,"skillDirectory":45,"forksCount":46,"repositoryHtmlUrl":47,"repositoryHomepageUrl":6,"ownerHtmlUrl":48,"ownerBlogUrl":49,"ownerDescription":6,"updatedAt":50,"install":51,"resources":64},"01M6DHTNB64TKDAM45MB2HDJAM","diagram",null,"Draw, edit, and review diagrams -- Mermaid for version-controlled flowcharts, sequences, and architecture; Excalidraw for mind maps, concept maps, PKB graphs, and sketches. Covers layout routing, house palette, pkb-excalidraw CLI, SVG\u002FPNG rendering, visual QA, fractional index validation, and PKB export\u002Fdiff\u002Fsync. Not for plotting quantitative data or UI mockups.","nicsuzor\u002FacademicOps","nicsuzor","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F4496978?v=4","01M6DGWCFNDYP03M4DQAHA6DN1","Nicolas Suzor","01M6DGWCPXG37BMXRZZKXR0A1Z",5,"MIT","https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Ftools-agy\u002Fskills\u002Fdiagram\u002FSKILL.md",[],[5,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37],"edit","SVG","excalidraw cli","layout","plotting","visual","maps","diagrams","diff","version","review","excalidraw","qa","validation","routing","mermaid","architecture","sync","cli","2026-11-10 10:12:30",false,"---\nname: diagram\ndescription: Draw, edit, and review diagrams -- Mermaid for version-controlled flowcharts, sequences, and architecture; Excalidraw for mind maps, concept maps, PKB graphs, and sketches. Covers layout routing, house palette, pkb-excalidraw CLI, SVG\u002FPNG rendering, visual QA, fractional index validation, and PKB export\u002Fdiff\u002Fsync. Not for plotting quantitative data or UI mockups.\n---\n\n# Diagram\n\nMermaid and Excalidraw diagramming standards, layout routing, and visual invariants.\n\n## Objective and layout routing\n\nIdentify the chart's core objective (the action or decision it enables) and reader context before drawing.\n\n| Reader question | Layout family | Guidance |\n| - | - | - |\n| \"What do I do next?\" | Containment + state | Group by navigation paths; emphasize active items. |\n| \"How does this work?\" | Flow \u002F sequence | Directional arrows; default to Mermaid. |\n| \"Where does this fit?\" | Containment \u002F nesting | Boundary nesting up to ~3 levels deep. |\n| \"How do these compare?\" | Matrix \u002F grid | Uniform grid with comparable cells. |\n| \"What changed \u002F is off?\" | Anomaly \u002F comparison | Highlight outliers without data smoothing. |\n| \"When does this happen?\" | Timeline | Position strictly encodes chronological time. |\n| \"What is central here?\" | Radial \u002F hub | Focal node centered with balanced spokes. |\n\n### Visual channel rules\n\n- **Bind channels to real fields**: Derive weights, status, and structure from source data -- never author arbitrary hierarchies.\n- **One channel, one meaning**: Document encodings in a legend; unassigned channels remain uniform.\n- **Explicit omission**: Label truncated containers (e.g., \"showing 3 of 12\"); never invent category names for unselected items.\n- **Respect live files**: When editing an open diagram, diff against mtime\u002Fdisk state to preserve external user edits.\n\n## Craft rules\n\n- **Single message**: One primary argument per chart; sub-processes get dedicated diagrams.\n- **Chunking**: Limit clusters to 6--9 nodes; align happy path along a straight spine.\n- **Labels**: Terse, verb-first labels (3--9 words). State current facts, not change logs or historical commentary.\n- **Shapes**: Process (rectangles), decision (diamonds), terminal\u002Factors (circles), mind maps (ellipses).\n\n## Mermaid conventions\n\n- **Default orientation**: Use `LR`. Use `TD` only for wide fan-outs. In subgraphs, use `direction TB`.\n- **Subgraphs**: Link subgraph to subgraph, not internal nodes to external nodes.\n- **Scale**: Past ~15 nodes or heavy cross-links, switch to ELK (`layout: elk`, `mergeEdges: true`, `nodePlacementStrategy: SIMPLE`).\n- **Phases**: Group 10+ steps into numbered phases (① ② ③) colored start (green) -> work (gold) -> end (gray).\n\n## Excalidraw conventions\n\n### Aesthetic defaults\n\n| Property | Value | Rationale |\n| - | - | - |\n| `roughness` | `2` | Sketch aesthetic |\n| `fontFamily` | `1` (Virgil) | Hand-drawn typeface |\n| `fillStyle` | `\"hachure\"` | Sketch hatching |\n| `strokeStyle` | `\"solid\"` | Consistent hand-drawn stroke |\n| Background | White (`#ffffff`) | High contrast and printability |\n\n### Layered composition\n\n1. **Scenery**: Zones and boundaries (`--preset zone`).\n2. **Spine**: Focal landmarks and critical paths (`--preset hero`).\n3. **Satellites**: Standard operational nodes.\n4. **Annotations**: Contextual commentary (`--preset sticky`).\n5. **Connectors**: Solid flows or dashed telemetry (`--curved`, `--stroke-style dashed`).\n\n### Hierarchies: nest first, arrow last\n\nA tree (outline, taxonomy, claim structure, org chart) reads best when containment carries the hierarchy and arrows are rare. Rings, radii, sunbursts and evenly spaced fans read as spiderweb and hide the levels they were meant to show.\n\n- **Headlines, not boxes, for the top levels.** The root and each major group are one sentence in large type (two to three sizes above the leaves), with white space around it. Size carries rank; a reader sees the structure from across the room.\n- **Panels for groups, columns for sub-groups, small boxes for leaves.** Each group is a plain panel led by its headline; its sub-groups are columns inside it with their own headline; leaves pack beneath their column in reading order. Nesting replaces parent-child arrows entirely.\n- **Arrows only for moves between groups, each labelled.** Draw a connector only where one group hands something to another, and write the inference on it in words. Route by grid position: side to side between neighbours, over the top across a row, through the gap between rows, round the margin when rows are far apart.\n- **Uneven density on purpose.** Headline claims get room; detail is packed small. Even spacing is what makes a generated layout look mechanical.\n- **Size boxes to the text.** Wrap at `(width - 28) \u002F (font * 0.62)` characters for Virgil and set widths to the longest line. The CLI render draws bound text unwrapped, so text fit is checked from the stored lines, not from the picture.\n- **Five colours at most**, bound to the kind of node. Finer status goes in the text as a prefix. Group identity goes on captions and borders, not fills.\n- **Free layouts (clusters, mind maps) need relax-then-repair.** Push overlapping boxes apart with a weak spring to each parent, then test every arrow's real polyline (both segments of a curved arrow) against every unrelated box and nudge offenders off the chord, re-relaxing each round. Curve arrows with a control point 12% of the chord off-centre, capped at 60 px.\n- **Generate from source data**, keep ids stable by slugging node titles, and validate with `pkb-excalidraw check` plus an own segment-versus-box check: the CLI `arrows-check` tests chords and reports false positives on routed or curved arrows.\n\n### CLI tools and invariants\n\n`pkb-excalidraw` is the preferred way to read and edit `.excalidraw` files; use it before reading or writing the JSON directly, because its mutations refuse to save a file that fails validation. Run `pkb-excalidraw --help` for the full command set. When creating a PKB note derived from an Excalidraw canvas (via `pkb.create`), pass the canvas's PKB-relative path as `source` (e.g. `\u003Csubproject>\u002F\u003Ccanvas>.excalidraw`), or set `parent` to a node whose file lives in that directory, so the home-directory placement rule files the note in that sub-project directory.\n\n```bash\npkb-excalidraw FILE [summary | map | nodes | edges | style | check | overlap | arrows-check]\npkb-excalidraw FILE add-node --type \u003Ctype> --text \"\u003Ctext>\" [--preset hero|sticky|zone] [--role \u003Crole>] [--at X,Y]\npkb-excalidraw FILE connect --from \u003Cid1> --to \u003Cid2> [--label \"\u003Clabel>\"] [--curved] [--stroke-style dashed] [--color \u003Chex>]\npkb-excalidraw FILE [set-text \u003Cid> \"\u003Ctext>\" | fit \u003Cid> \"\u003Ctext>\" | move-elem \u003Cid> --by DX,DY | delete-elem \u003Cid>]\npkb-excalidraw FILE update \u003Cid> --set '{\"strokeStyle\": \"dashed\"}'\npkb-excalidraw FILE screenshot [--out \u003Cpath>] [--format svg|png] [--no-background]\n```\n\n- **Invariants**: Text binds to container (`containerId`\u002F`boundElements`); arrows bind both ends (`startBinding`\u002F`endBinding`).\n- Change text with `set-text` or `fit`, which update `text` and `originalText` together. Change any other property with `update \u003Cid> --set '\u003Cjson>'`.\n- Validate after every edit with `pkb-excalidraw FILE check` and `pkb-excalidraw FILE overlap`. `check` does not flag line-wrap-only differences between `text` and `originalText`.\n- Requires `pkb-excalidraw` from `mem >= v0.3.101` or newer, which natively trims `connect` arrow endpoints to shape outlines and mints valid fractional index keys.\n- **Structural arrow binding check and edge editability**: Inspect every arrow against Excalidraw's element schema so edges remain interactively selectable and draggable in the Excalidraw editor:\n  1. `startBinding` and `endBinding` must be valid objects with `elementId` matching existing elements, numeric `focus` (between -1.0 and 1.0), numeric `gap`, and optional `fixedPoint` (`[x_ratio, y_ratio]` normalized between 0.0 and 1.0).\n  2. Reciprocal `boundElements`: The source and target elements must each list `{ \"id\": arrow_id, \"type\": \"arrow\" }` in `boundElements`.\n  3. Points envelope: `points` array must start at `[0, 0]`, with `width` and `height` matching the points' bounding box envelope.\n  4. Editability cause: Canvases whose arrows pass static checks and static rendering can still fail interactive editing in Excalidraw (edges cannot be selected or dragged, or detach on movement). The cause is malformed bindings, out-of-envelope points, or missing `boundElements` backreferences that Excalidraw's interactive editor engine rejects.\n- **`add-node` and `connect` invalid index key defect**: `pkb-excalidraw add-node` and `connect` mint fractional `index` keys by appending digit pairs (`00`, `01`) to the previous key (`a000`, `a001`, `a00100`, `a00101`, `a0010100` ...), leaving trailing zeros in the fractional part. While `pkb-excalidraw check` passes these files (verifying only monotonic ordering), `fractional-indexing` (`validateOrderKey`\u002F`midpoint`) strictly rejects keys with trailing zeros in their fractional part (`invalid order key`). Excalidraw 0.18.1 validates keys whenever minting a key next to an existing element (e.g. on arrow labelling, duplicate, or z-order adjustments), throwing `invalid order key` and causing operations to silently fail, leaving edges uneditable.\n- **Structural fractional index check**: Validate every element's `index` key against Excalidraw's `fractional-indexing` schema:\n  1. Base-62 character set: Keys must consist only of base-62 digits (`0-9`, `A-Z`, `a-z`).\n  2. Integer head and length: For keys starting with lowercase `a-z`, integer part length is `head.charCodeAt(0) - 97 + 2` (e.g. `'a'` is 2 characters: `a0`--`az`; `'b'` is 3 characters: `b00`--`bzz`). For uppercase `A-Z`, integer part length is `90 - head.charCodeAt(0) + 2` (`'Z'` is 2 characters, `'A'` is 27 characters).\n  3. Reject trailing zeros in fractional part: The fractional part comprises all characters after the integer part (`index.slice(integerLength)`). If the fractional part is non-empty, its final character must NOT be `'0'`. Reject any key where `fractionalPart.endsWith('0')` (e.g. `a000`, `a00100`, `a0010100`).\n\n### Rendering and visual QA\n\nEvery finished canvas must be rendered to vector SVG or PNG using `pkb-excalidraw` and pass visual inspection against the rendered image before presentation, handback, or release.\n\n```bash\npkb-excalidraw FILE screenshot --out \u003Cpath>.svg --format svg\n# Or if a PNG deliverable is required:\npkb-excalidraw FILE screenshot --out \u003Cpath>.png --format png\n```\n\n- **What the render shows**: `screenshot` draws the canvas's stored geometry: shape outlines, arrow points, and bound text as its stored lines. It names Virgil without embedding it and draws fills flat, so text in the image uses Virgil only where Virgil is installed. Judge arrow placement, overlaps, and crossings from the image directly; treat a label that sits close to its container edge as a possible overflow in Excalidraw.\n- **CLI check blind spots**: `pkb-excalidraw check`, `overlap`, and `arrows-check` cannot catch arrows ending inside boxes. `check` tests structural references and half-bound arrows, `overlap` tests only shape AABBs, and `arrows-check` only checks whether arrow polylines intersect unrelated intermediate boxes (ignoring endpoints at connected shapes). All three checks will pass a canvas whose every arrow ends inside its box. Rendering via `pkb-excalidraw FILE screenshot` and visually inspecting the resulting image is mandatory to verify that arrowheads terminate outside shapes.\n- **Missing tool or converter**:\n  - If `pkb-excalidraw` is missing or fails to render, halt immediately and report the failure verbatim. Never substitute an ad-hoc or homemade renderer (such as manual SVG construction or custom canvas scripts); alternative renderers produce incorrect typography, drop bindings, and conceal layout defects.\n  - When `--format png` is requested without a system rasterizer (`resvg`, `rsvg-convert`, `magick`) on `PATH`, the tool outputs a notice and saves an SVG fallback to `\u003Cpath>.svg`. Use this SVG render for visual QA. If a PNG image is explicitly required by acceptance criteria and no rasterizer exists, halt and surface the missing dependency.\n- **Visual QA criteria**:\n  Inspect the rendered image directly to verify:\n  1. **Arrowhead placement**: Arrows terminate cleanly at container boundaries; no arrowheads are buried inside boxes or cut across labels.\n  2. **Label overflow**: Text does not exceed container bounds, spill across borders, or wrap awkwardly across lines.\n  3. **Overlaps**: Text elements and shapes do not collide with or obscure neighbouring elements.\n  4. **Crossing arrows**: Connector polylines do not cut through unrelated boxes or labels.\n  5. **At-a-glance legibility**: Visual hierarchy is obvious, labels are readable at standard zoom, and flows are intuitive without clutter.\n\n### Editing an existing diagram\n\nWhen the diagram already exists and the ask changes the plan or state it shows, edit the existing elements in place so the delta is visible on the canvas:\n\n- **Moved** thing: `move-elem` the existing element.\n- **Renamed** thing: `set-text` or `fit` the existing element.\n- **Changed status**: set `strokeColor`\u002F`backgroundColor` on the existing element to the palette role for its new state.\n- **Removed** thing: `delete-elem` it, and its bound text and arrows.\n- **Genuinely new** thing: `add-node` only for this.\n\nKeep the `id` of every surviving element. `pkb excalidraw diff` and `sync` match canvas elements to PKB nodes by `id`; a replaced element reads as a deletion plus an unrelated addition.\n\n**Anti-pattern -- the parallel system.** Do not draw a fresh set of elements beside the old ones to depict the new state. It leaves two contradictory versions on one canvas, hides what changed, and severs every `id` binding. Before adding any element, check whether an existing element already represents that thing; if one does, edit it.\n\n### PKB export and sync\n\n```bash\npkb excalidraw export \u003Coutput_path> [--focus \u003Cnode_id>] [--hops \u003CH>]\npkb excalidraw diff \u003Ccanvas_path> [--base \u003Csnapshot>] [--json]\npkb excalidraw sync \u003Ccanvas_path> [--base \u003Csnapshot>] [--dry-run]\n```\n\nReconcile user canvas edits with `diff` and `sync` back to markdown frontmatter rather than clobbering.\n\n## Palette\n\n| Role | Hex | Role | Hex |\n| - | - | - | - |\n| Emphasis \u002F headers | `#c9b458` (gold) | Error \u002F blocked | `#ff6666` (soft red) |\n| Success \u002F active | `#8fbc8f` (soft green) | Text \u002F labels | `#1a1a1a` (primary dark) |\n| Active accent | `#76c893` (bright green) | De-emphasized text | `#888888` (muted) |\n| Info \u002F links | `#7a9fbf` (soft blue) | Borders \u002F arrows | `#404040` (charcoal) |\n| Warning \u002F queued | `#ffa500` (orange) | Completed fills | `#252525` (surface) |\n\n- Maximum 4--6 colors per diagram.\n- Opacity: 10--35% for fills with `#1a1a1a` text; 4.5:1 minimum contrast.\n","# Diagram\n\nMermaid and Excalidraw diagramming standards, layout routing, and visual invariants.\n\n## Objective and layout routing\n\nIdentify the chart's core objective (the action or decision it enables) and reader context before drawing.\n\n| Reader question | Layout family | Guidance |\n| - | - | - |\n| \"What do I do next?\" | Containment + state | Group by navigation paths; emphasize active items. |\n| \"How does this work?\" | Flow \u002F sequence | Directional arrows; default to Mermaid. |\n| \"Where does this fit?\" | Containment \u002F nesting | Boundary nesting up to ~3 levels deep. |\n| \"How do these compare?\" | Matrix \u002F grid | Uniform grid with comparable cells. |\n| \"What changed \u002F is off?\" | Anomaly \u002F comparison | Highlight outliers without data smoothing. |\n| \"When does this happen?\" | Timeline | Position strictly encodes chronological time. |\n| \"What is central here?\" | Radial \u002F hub | Focal node centered with balanced spokes. |\n\n### Visual channel rules\n\n- **Bind channels to real fields**: Derive weights, status, and structure from source data -- never author arbitrary hierarchies.\n- **One channel, one meaning**: Document encodings in a legend; unassigned channels remain uniform.\n- **Explicit omission**: Label truncated containers (e.g., \"showing 3 of 12\"); never invent category names for unselected items.\n- **Respect live files**: When editing an open diagram, diff against mtime\u002Fdisk state to preserve external user edits.\n\n## Craft rules\n\n- **Single message**: One primary argument per chart; sub-processes get dedicated diagrams.\n- **Chunking**: Limit clusters to 6--9 nodes; align happy path along a straight spine.\n- **Labels**: Terse, verb-first labels (3--9 words). State current facts, not change logs or historical commentary.\n- **Shapes**: Process (rectangles), decision (diamonds), terminal\u002Factors (circles), mind maps (ellipses).\n\n## Mermaid conventions\n\n- **Default orientation**: Use `LR`. Use `TD` only for wide fan-outs. In subgraphs, use `direction TB`.\n- **Subgraphs**: Link subgraph to subgraph, not internal nodes to external nodes.\n- **Scale**: Past ~15 nodes or heavy cross-links, switch to ELK (`layout: elk`, `mergeEdges: true`, `nodePlacementStrategy: SIMPLE`).\n- **Phases**: Group 10+ steps into numbered phases (① ② ③) colored start (green) -> work (gold) -> end (gray).\n\n## Excalidraw conventions\n\n### Aesthetic defaults\n\n| Property | Value | Rationale |\n| - | - | - |\n| `roughness` | `2` | Sketch aesthetic |\n| `fontFamily` | `1` (Virgil) | Hand-drawn typeface |\n| `fillStyle` | `\"hachure\"` | Sketch hatching |\n| `strokeStyle` | `\"solid\"` | Consistent hand-drawn stroke |\n| Background | White (`#ffffff`) | High contrast and printability |\n\n### Layered composition\n\n1. **Scenery**: Zones and boundaries (`--preset zone`).\n2. **Spine**: Focal landmarks and critical paths (`--preset hero`).\n3. **Satellites**: Standard operational nodes.\n4. **Annotations**: Contextual commentary (`--preset sticky`).\n5. **Connectors**: Solid flows or dashed telemetry (`--curved`, `--stroke-style dashed`).\n\n### Hierarchies: nest first, arrow last\n\nA tree (outline, taxonomy, claim structure, org chart) reads best when containment carries the hierarchy and arrows are rare. Rings, radii, sunbursts and evenly spaced fans read as spiderweb and hide the levels they were meant to show.\n\n- **Headlines, not boxes, for the top levels.** The root and each major group are one sentence in large type (two to three sizes above the leaves), with white space around it. Size carries rank; a reader sees the structure from across the room.\n- **Panels for groups, columns for sub-groups, small boxes for leaves.** Each group is a plain panel led by its headline; its sub-groups are columns inside it with their own headline; leaves pack beneath their column in reading order. Nesting replaces parent-child arrows entirely.\n- **Arrows only for moves between groups, each labelled.** Draw a connector only where one group hands something to another, and write the inference on it in words. Route by grid position: side to side between neighbours, over the top across a row, through the gap between rows, round the margin when rows are far apart.\n- **Uneven density on purpose.** Headline claims get room; detail is packed small. Even spacing is what makes a generated layout look mechanical.\n- **Size boxes to the text.** Wrap at `(width - 28) \u002F (font * 0.62)` characters for Virgil and set widths to the longest line. The CLI render draws bound text unwrapped, so text fit is checked from the stored lines, not from the picture.\n- **Five colours at most**, bound to the kind of node. Finer status goes in the text as a prefix. Group identity goes on captions and borders, not fills.\n- **Free layouts (clusters, mind maps) need relax-then-repair.** Push overlapping boxes apart with a weak spring to each parent, then test every arrow's real polyline (both segments of a curved arrow) against every unrelated box and nudge offenders off the chord, re-relaxing each round. Curve arrows with a control point 12% of the chord off-centre, capped at 60 px.\n- **Generate from source data**, keep ids stable by slugging node titles, and validate with `pkb-excalidraw check` plus an own segment-versus-box check: the CLI `arrows-check` tests chords and reports false positives on routed or curved arrows.\n\n### CLI tools and invariants\n\n`pkb-excalidraw` is the preferred way to read and edit `.excalidraw` files; use it before reading or writing the JSON directly, because its mutations refuse to save a file that fails validation. Run `pkb-excalidraw --help` for the full command set. When creating a PKB note derived from an Excalidraw canvas (via `pkb.create`), pass the canvas's PKB-relative path as `source` (e.g. `\u003Csubproject>\u002F\u003Ccanvas>.excalidraw`), or set `parent` to a node whose file lives in that directory, so the home-directory placement rule files the note in that sub-project directory.\n\n```bash\npkb-excalidraw FILE [summary | map | nodes | edges | style | check | overlap | arrows-check]\npkb-excalidraw FILE add-node --type \u003Ctype> --text \"\u003Ctext>\" [--preset hero|sticky|zone] [--role \u003Crole>] [--at X,Y]\npkb-excalidraw FILE connect --from \u003Cid1> --to \u003Cid2> [--label \"\u003Clabel>\"] [--curved] [--stroke-style dashed] [--color \u003Chex>]\npkb-excalidraw FILE [set-text \u003Cid> \"\u003Ctext>\" | fit \u003Cid> \"\u003Ctext>\" | move-elem \u003Cid> --by DX,DY | delete-elem \u003Cid>]\npkb-excalidraw FILE update \u003Cid> --set '{\"strokeStyle\": \"dashed\"}'\npkb-excalidraw FILE screenshot [--out \u003Cpath>] [--format svg|png] [--no-background]\n```\n\n- **Invariants**: Text binds to container (`containerId`\u002F`boundElements`); arrows bind both ends (`startBinding`\u002F`endBinding`).\n- Change text with `set-text` or `fit`, which update `text` and `originalText` together. Change any other property with `update \u003Cid> --set '\u003Cjson>'`.\n- Validate after every edit with `pkb-excalidraw FILE check` and `pkb-excalidraw FILE overlap`. `check` does not flag line-wrap-only differences between `text` and `originalText`.\n- Requires `pkb-excalidraw` from `mem >= v0.3.101` or newer, which natively trims `connect` arrow endpoints to shape outlines and mints valid fractional index keys.\n- **Structural arrow binding check and edge editability**: Inspect every arrow against Excalidraw's element schema so edges remain interactively selectable and draggable in the Excalidraw editor:\n  1. `startBinding` and `endBinding` must be valid objects with `elementId` matching existing elements, numeric `focus` (between -1.0 and 1.0), numeric `gap`, and optional `fixedPoint` (`[x_ratio, y_ratio]` normalized between 0.0 and 1.0).\n  2. Reciprocal `boundElements`: The source and target elements must each list `{ \"id\": arrow_id, \"type\": \"arrow\" }` in `boundElements`.\n  3. Points envelope: `points` array must start at `[0, 0]`, with `width` and `height` matching the points' bounding box envelope.\n  4. Editability cause: Canvases whose arrows pass static checks and static rendering can still fail interactive editing in Excalidraw (edges cannot be selected or dragged, or detach on movement). The cause is malformed bindings, out-of-envelope points, or missing `boundElements` backreferences that Excalidraw's interactive editor engine rejects.\n- **`add-node` and `connect` invalid index key defect**: `pkb-excalidraw add-node` and `connect` mint fractional `index` keys by appending digit pairs (`00`, `01`) to the previous key (`a000`, `a001`, `a00100`, `a00101`, `a0010100` ...), leaving trailing zeros in the fractional part. While `pkb-excalidraw check` passes these files (verifying only monotonic ordering), `fractional-indexing` (`validateOrderKey`\u002F`midpoint`) strictly rejects keys with trailing zeros in their fractional part (`invalid order key`). Excalidraw 0.18.1 validates keys whenever minting a key next to an existing element (e.g. on arrow labelling, duplicate, or z-order adjustments), throwing `invalid order key` and causing operations to silently fail, leaving edges uneditable.\n- **Structural fractional index check**: Validate every element's `index` key against Excalidraw's `fractional-indexing` schema:\n  1. Base-62 character set: Keys must consist only of base-62 digits (`0-9`, `A-Z`, `a-z`).\n  2. Integer head and length: For keys starting with lowercase `a-z`, integer part length is `head.charCodeAt(0) - 97 + 2` (e.g. `'a'` is 2 characters: `a0`--`az`; `'b'` is 3 characters: `b00`--`bzz`). For uppercase `A-Z`, integer part length is `90 - head.charCodeAt(0) + 2` (`'Z'` is 2 characters, `'A'` is 27 characters).\n  3. Reject trailing zeros in fractional part: The fractional part comprises all characters after the integer part (`index.slice(integerLength)`). If the fractional part is non-empty, its final character must NOT be `'0'`. Reject any key where `fractionalPart.endsWith('0')` (e.g. `a000`, `a00100`, `a0010100`).\n\n### Rendering and visual QA\n\nEvery finished canvas must be rendered to vector SVG or PNG using `pkb-excalidraw` and pass visual inspection against the rendered image before presentation, handback, or release.\n\n```bash\npkb-excalidraw FILE screenshot --out \u003Cpath>.svg --format svg\n# Or if a PNG deliverable is required:\npkb-excalidraw FILE screenshot --out \u003Cpath>.png --format png\n```\n\n- **What the render shows**: `screenshot` draws the canvas's stored geometry: shape outlines, arrow points, and bound text as its stored lines. It names Virgil without embedding it and draws fills flat, so text in the image uses Virgil only where Virgil is installed. Judge arrow placement, overlaps, and crossings from the image directly; treat a label that sits close to its container edge as a possible overflow in Excalidraw.\n- **CLI check blind spots**: `pkb-excalidraw check`, `overlap`, and `arrows-check` cannot catch arrows ending inside boxes. `check` tests structural references and half-bound arrows, `overlap` tests only shape AABBs, and `arrows-check` only checks whether arrow polylines intersect unrelated intermediate boxes (ignoring endpoints at connected shapes). All three checks will pass a canvas whose every arrow ends inside its box. Rendering via `pkb-excalidraw FILE screenshot` and visually inspecting the resulting image is mandatory to verify that arrowheads terminate outside shapes.\n- **Missing tool or converter**:\n  - If `pkb-excalidraw` is missing or fails to render, halt immediately and report the failure verbatim. Never substitute an ad-hoc or homemade renderer (such as manual SVG construction or custom canvas scripts); alternative renderers produce incorrect typography, drop bindings, and conceal layout defects.\n  - When `--format png` is requested without a system rasterizer (`resvg`, `rsvg-convert`, `magick`) on `PATH`, the tool outputs a notice and saves an SVG fallback to `\u003Cpath>.svg`. Use this SVG render for visual QA. If a PNG image is explicitly required by acceptance criteria and no rasterizer exists, halt and surface the missing dependency.\n- **Visual QA criteria**:\n  Inspect the rendered image directly to verify:\n  1. **Arrowhead placement**: Arrows terminate cleanly at container boundaries; no arrowheads are buried inside boxes or cut across labels.\n  2. **Label overflow**: Text does not exceed container bounds, spill across borders, or wrap awkwardly across lines.\n  3. **Overlaps**: Text elements and shapes do not collide with or obscure neighbouring elements.\n  4. **Crossing arrows**: Connector polylines do not cut through unrelated boxes or labels.\n  5. **At-a-glance legibility**: Visual hierarchy is obvious, labels are readable at standard zoom, and flows are intuitive without clutter.\n\n### Editing an existing diagram\n\nWhen the diagram already exists and the ask changes the plan or state it shows, edit the existing elements in place so the delta is visible on the canvas:\n\n- **Moved** thing: `move-elem` the existing element.\n- **Renamed** thing: `set-text` or `fit` the existing element.\n- **Changed status**: set `strokeColor`\u002F`backgroundColor` on the existing element to the palette role for its new state.\n- **Removed** thing: `delete-elem` it, and its bound text and arrows.\n- **Genuinely new** thing: `add-node` only for this.\n\nKeep the `id` of every surviving element. `pkb excalidraw diff` and `sync` match canvas elements to PKB nodes by `id`; a replaced element reads as a deletion plus an unrelated addition.\n\n**Anti-pattern -- the parallel system.** Do not draw a fresh set of elements beside the old ones to depict the new state. It leaves two contradictory versions on one canvas, hides what changed, and severs every `id` binding. Before adding any element, check whether an existing element already represents that thing; if one does, edit it.\n\n### PKB export and sync\n\n```bash\npkb excalidraw export \u003Coutput_path> [--focus \u003Cnode_id>] [--hops \u003CH>]\npkb excalidraw diff \u003Ccanvas_path> [--base \u003Csnapshot>] [--json]\npkb excalidraw sync \u003Ccanvas_path> [--base \u003Csnapshot>] [--dry-run]\n```\n\nReconcile user canvas edits with `diff` and `sync` back to markdown frontmatter rather than clobbering.\n\n## Palette\n\n| Role | Hex | Role | Hex |\n| - | - | - | - |\n| Emphasis \u002F headers | `#c9b458` (gold) | Error \u002F blocked | `#ff6666` (soft red) |\n| Success \u002F active | `#8fbc8f` (soft green) | Text \u002F labels | `#1a1a1a` (primary dark) |\n| Active accent | `#76c893` (bright green) | De-emphasized text | `#888888` (muted) |\n| Info \u002F links | `#7a9fbf` (soft blue) | Borders \u002F arrows | `#404040` (charcoal) |\n| Warning \u002F queued | `#ffa500` (orange) | Completed fills | `#252525` (surface) |\n\n- Maximum 4--6 colors per diagram.\n- Opacity: 10--35% for fills with `#1a1a1a` text; 4.5:1 minimum contrast.\n",{"name":5,"description":7},"main","22a4e466ac352d09f636273685845a312ee07a15","tools-agy\u002Fskills\u002Fdiagram",3,"https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps","https:\u002F\u002Fgithub.com\u002Fnicsuzor","https:\u002F\u002Fnic.suzor.net\u002F","2026-11-10 16:57:47",{"source":8,"skillName":5,"commands":52,"prompt":62,"zipAvailable":63},[53,56,59],{"tool":54,"command":55},"npx","npx skills add nicsuzor\u002FacademicOps --skill diagram",{"tool":57,"command":58},"bunx","bunx skills add nicsuzor\u002FacademicOps --skill diagram",{"tool":60,"command":61},"pnpm","pnpm dlx skills add nicsuzor\u002FacademicOps --skill diagram","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Ftools-agy\u002Fskills\u002Fdiagram\u002FSKILL.md",true,[65,71,75,79,83,87,91,95,99,103],{"path":66,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":69,"sourceHtmlUrl":70},"libraries\u002Fbanners.excalidrawlib","OTHER","其他",61110,"https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Ftools-agy\u002Fskills\u002Fdiagram\u002Flibraries\u002Fbanners.excalidrawlib",{"path":72,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":73,"sourceHtmlUrl":74},"libraries\u002Fcalendar.excalidrawlib",668243,"https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Ftools-agy\u002Fskills\u002Fdiagram\u002Flibraries\u002Fcalendar.excalidrawlib",{"path":76,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":77,"sourceHtmlUrl":78},"libraries\u002Fclouds.excalidrawlib",29082,"https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Ftools-agy\u002Fskills\u002Fdiagram\u002Flibraries\u002Fclouds.excalidrawlib",{"path":80,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":81,"sourceHtmlUrl":82},"libraries\u002Fdata-processing.excalidrawlib",591281,"https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Ftools-agy\u002Fskills\u002Fdiagram\u002Flibraries\u002Fdata-processing.excalidrawlib",{"path":84,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":85,"sourceHtmlUrl":86},"libraries\u002Fflow-chart-symbols.excalidrawlib",57673,"https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Ftools-agy\u002Fskills\u002Fdiagram\u002Flibraries\u002Fflow-chart-symbols.excalidrawlib",{"path":88,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":89,"sourceHtmlUrl":90},"libraries\u002Fmathematical-symbols.excalidrawlib",50937,"https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Ftools-agy\u002Fskills\u002Fdiagram\u002Flibraries\u002Fmathematical-symbols.excalidrawlib",{"path":92,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":93,"sourceHtmlUrl":94},"libraries\u002Forganization-chart.excalidrawlib",34075,"https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Ftools-agy\u002Fskills\u002Fdiagram\u002Flibraries\u002Forganization-chart.excalidrawlib",{"path":96,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":97,"sourceHtmlUrl":98},"libraries\u002Fsimple-sticky-notes.excalidrawlib",14586,"https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Ftools-agy\u002Fskills\u002Fdiagram\u002Flibraries\u002Fsimple-sticky-notes.excalidrawlib",{"path":100,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":101,"sourceHtmlUrl":102},"libraries\u002Fstick-figures-collaboration.excalidrawlib",287174,"https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Ftools-agy\u002Fskills\u002Fdiagram\u002Flibraries\u002Fstick-figures-collaboration.excalidrawlib",{"path":104,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":105,"sourceHtmlUrl":106},"libraries\u002Fstick-figures.excalidrawlib",93323,"https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Ftools-agy\u002Fskills\u002Fdiagram\u002Flibraries\u002Fstick-figures.excalidrawlib",[108,135,140,145,150,174],{"skillNo":109,"name":110,"nameZh":6,"description":111,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":114,"publishedAt":38},"01M6M6JR6VXPDA3MDPVS36DSSP","refresh-questions","Sweep and refresh open questions in the Telegram Mini App by closing questions that are answered elsewhere, superseded, or asking for completed actions, and ensuring active open questions carry recommendations and context. Use when asked to refresh questions, sweep stale questions, or keep the Mini App decision queue current. Exclude for asking new questions or routine chat replies.","https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Fopenclaw\u002Fida-telegram\u002Fskills\u002Frefresh-questions\u002FSKILL.md",[],[115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134],"refresh","refresh questions","questions","actions","telegram mini app","open","decision","context","use","queue","telegram","stale","chat","active","recommendations","app","keep","replies","closing","for",{"skillNo":136,"name":110,"nameZh":6,"description":111,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":137,"categories":138,"keywords":139,"publishedAt":38},"01M6M6JGW7Q0SC5B28TQ7PKE8E","https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Fida-telegram-openclaw\u002Fskills\u002Frefresh-questions\u002FSKILL.md",[],[115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134],{"skillNo":141,"name":110,"nameZh":6,"description":111,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":144,"publishedAt":38},"01M6M6J7D0ZTQ4VSS5ADRSA12E","https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Fida-telegram-claude\u002Fskills\u002Frefresh-questions\u002FSKILL.md",[],[115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134],{"skillNo":146,"name":110,"nameZh":6,"description":111,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":147,"categories":148,"keywords":149,"publishedAt":38},"01M6M6EP6BK1DDN2212S7Y95G0","https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Fcowork\u002Fida-telegram\u002Fskills\u002Frefresh-questions\u002FSKILL.md",[],[115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134],{"skillNo":151,"name":152,"nameZh":6,"description":153,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":154,"categories":155,"keywords":156,"publishedAt":38},"01M6G9Q8538NENA2TE2V4HPRSF","dispatch-queue","Show the user the tasks ready to dispatch, ranked with a recommendation on each, and start workers on the ones the user approves -- redirects to universal workflow template wf-dispatch-queue. Use for \"what's ready\", \"what should we dispatch\", or \"dispatch the queue\". Exclude for starting workers on named tasks without the user's choice (use dispatch) or graph truth maintenance (use reconcile).","https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Fopenclaw\u002Fida\u002Fskills\u002Fdispatch-queue\u002FSKILL.md",[],[157,124,158,159,160,161,162,163,164,165,123,166,167,168,169,170,171,172,173],"dispatch","dispatch queue","workflow","tasks","graph","start","template","choice","user","reconcile","ready","maintenance","workers","show","wf","recommendation","with",{"skillNo":175,"name":152,"nameZh":6,"description":153,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":176,"categories":177,"keywords":178,"publishedAt":38},"01M6G9P9M3PQXPX3647RA5E4D0","https:\u002F\u002Fgithub.com\u002Fnicsuzor\u002FacademicOps\u002Fblob\u002Fmain\u002Fida-openclaw\u002Fskills\u002Fdispatch-queue\u002FSKILL.md",[],[157,124,158,159,160,161,162,163,164,165,123,166,167,168,169,170,171,172,173]]