Add node/edge editing UI: rename, delete, shape picker, add node#19
Conversation
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: 4915b98 |
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: 37e126f |
- Delete Node: removes node declaration and all connected edges - Delete Edge: click edge to open menu, then delete - Rename Node: renames leaf segment in all references (works for both dot-notation and block-scope nodes) - Edit Edge Label: set or clear the label on any edge - Add Node: + button opens popup with name input and full shape picker - Change Shape: shape picker with lazy-loaded SVG previews of all 22 D2 shapes (callout through text, sorted alphabetically) Also: - Extracts parseD2Range/segVal/findMapKeyAt/buildEdgeSegLookup/ lineRangeInSrc from moveNode to module scope for reuse - Edges now open the action menu (with Edit Label + Delete) instead of just showing a key readout - CLAUDE.md: note CI + deploy wait before checking preview URL https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Edges now open the action menu directly, so the old key-readout shortcut is no longer called. https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
- ensureShapePreviews: render shapes sequentially (was Promise.all which caused all 22 concurrent D2 WASM renders to fail) - changeNodeShape: fix regex /\S+/ → /[^\s}]+/ so it stops matching the closing brace when changing shape on a block-scope node - renameNode: when declaration has a label (e.g. `api: API Server`), update the label text instead of the key identifier; fall back to key rename only when no label exists - showRenamePopup: pre-fill input with current label text (not key) https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
37e126f to
c50178a
Compare
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: c50178a |
Instead of rendering shapes lazily at runtime (which required D2 WASM to run 22 extra compile+render cycles in the browser), pre-render them at build time and load from a companion file. - tools/d2-shape-previews.js: pre-rendered SVGs for all 22 D2 shapes as an ES module (imported by d2-editor.html at module load time) - tools/generate-shape-previews.mjs: generator script to regenerate d2-shape-previews.js if shapes change - d2-editor.html: import SHAPE_PREVIEW_SVGS from the companion file; remove ensureShapePreviews(), shapePreviewSvgs state, and all lazy-loading code — shape picker now opens instantly https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: 2c20acc |
node_modules/, package.json, and package-lock.json were created locally when generating d2-shape-previews.js and should not be tracked. https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: a37d049 |
Shape buttons now show only the SVG preview (name available via tooltip). The image shape gets a picture-icon placeholder SVG since it requires an icon URL and cannot be rendered as a standalone D2 diagram. https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: 6883047 |
… + image shape Layout: - Shape picker and add-node popup are now much wider (up to 520px) with larger shape previews (82×62px vs 54×38px) and no button borders (outline used for active state instead) - Mobile (≤600px): both popups take over the full screen - Added header with title and ✕ close button to both popups - positionPopupNearEl skips JS positioning on mobile (CSS handles it) Undo fix: - pushUndo() was skipping the save when undoStack was empty because it compared editor.value against snapValue (always equal after commitSnap) — now only deduplicates against the actual last stack entry image shape: - Selecting image in the shape picker now shows a toast explaining it requires an "icon" field, rather than applying a change that causes a D2 render error https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: ad6f784 |
- Crop D2's built-in 101px padding from SVG viewBox in buildShapeGrid so each shape fills its button (85px crop each side, xMidYMid meet) - Replace image shape toast with a proper icon URL popup: shows an input pre-linked to icons.terrastruct.com, applies `shape: image` + `icon: <url>` to the node declaration on confirm - Added .d2-image-icon-popup styles (reuses existing popup patterns) - hideImageIconPopup() wired into resetInteraction() https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: ec0549a |
- Shape buttons now use white backgrounds so D2 shapes render naturally (removes fill:none/stroke overrides that made shapes invisible on dark) - Active state uses light green tint + accent outline instead of dark bg - viewBox crop now only applies to D2's large SVGs (>200 units) to avoid over-cropping the small image placeholder (80×60 viewBox) https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: ceaab01 |
D2 SVGs have a nested structure: outer <svg viewBox="0 0 W H"> wraps an inner <svg viewBox="-101 -101 W H">. The inner SVG fills 100%×100% of its parent so the outer viewBox has no visual effect — the crop must target the inner SVG. For D2 SVGs (detected by negative x0/y0), crop the inner viewBox from "-101 -101 W H" to "-20 -20 (content_w+40) (content_h+40)" — strips D2's 101px padding, leaving 20px breathing room around the shape. https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: fce276d |
- class shape now renders with sample fields/method so it's visually distinct (was empty box); sql_table similarly gets sample rows - showShapePickerPopup now calls getCurrentShape() to pre-select the node's current shape (defaults to rectangle if none set) https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: fad09eb |
'class' is a reserved keyword in D2 — using it as a node name produces an empty SVG. Using 'MyClass' renders the full UML class box with fields and methods. https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: 8ba9782 |
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: 4f37e14 |
- showEdgeLabelPopup now reads the current label from the AST and pre-fills the input so users can edit rather than retype - tagShapeGroups adds invisible 20px-wide stroke overlay paths on connection <path> elements, making thin edges much easier to tap https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: 8850be9 |
On narrow screens the horizontal button row was overflowing the viewport. On ≤600px the menu now uses flex-column so all buttons are visible. https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: f4ec5f3 |
Simpler and easier to read/tap on all screen sizes. https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: cc60ec3 |
- All three label popups now show a hint line ("Label edge", "Label node",
"Label edge (blank to remove)") so it's clear what is being labelled
- Connect flow: "Label edge" popup now appears at the midpoint between
the source and target nodes instead of below the target node
- Popup layout changed to column so the hint sits above the input row
https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: f2991e8 |
Was using segs[0] (the outermost path segment) to find the declaration, so for network.subnet it found network's map_key and deleted the whole parent block. Now uses segs[kpi] — the segment that corresponds to the actual node being deleted — matching the logic in findNodeTargetObj. https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: 88eea48 |
- Move edit-label button before delete so Delete is last (matching node menu) - Set edge label button text to "Rename" if edge has a label, "Name" if not - Fix invisible hit overlay: use pointer-events:all and rgba(0,0,0,0) stroke (transparent wasn't reliably hittable), increase stroke-width to 30px https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb
Deploy PreviewPreview URL: https://tools.bobwhitelock.co.uk/pr-19/ Deployed commit: 1cffa18 |
Summary
Adds comprehensive node and edge editing capabilities to the D2 editor with new UI popups and action menu options. Users can now rename nodes, delete nodes/edges, change node shapes, edit edge labels, and add new nodes directly from the editor interface.
Key Changes
deleteNode(),deleteEdge(),renameNode(),editEdgeLabel(),addNode(), andchangeNodeShape()to safely modify the D2 source codeparseD2Range(),segVal(),findMapKeyAt(),buildEdgeSegLookup(),lineRangeInSrc(),findNodeTargetObj()) for AST traversal and source manipulationImplementation Details
pushUndo()https://claude.ai/code/session_0188HqZ2PyajbSEGNhNmg9Tb