@gmag11/nodered-mcp-server 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +162 -0
- package/index.js +133 -0
- package/package.json +58 -0
- package/resources/skills/nodered-flow-builder/SKILL.md +659 -0
- package/resources/skills/nodered-flow-layout/SKILL.md +395 -0
- package/resources/skills/nodered-flowfuse-dashboard/SKILL.md +941 -0
- package/resources/skills/nodered-fundamentals/SKILL.md +323 -0
- package/resources/skills/nodered-jsonata/SKILL.md +1039 -0
- package/resources/skills/nodered-mustache/SKILL.md +588 -0
- package/resources/skills/nodered-node-reference/SKILL.md +1020 -0
- package/resources/skills/nodered-node-reference/examples/common.json +113 -0
- package/resources/skills/nodered-node-reference/examples/network.json +107 -0
- package/resources/skills/nodered-node-reference/examples/parser.json +147 -0
- package/resources/skills/nodered-node-reference/examples/sequence.json +141 -0
- package/resources/skills/nodered-node-reference/examples/storage.json +104 -0
- package/resources/skills/nodered-patterns/SKILL.md +414 -0
- package/resources/skills/nodered-patterns/examples/error-handler.json +72 -0
- package/resources/skills/nodered-patterns/examples/http-endpoint.json +42 -0
- package/resources/skills/nodered-patterns/examples/mqtt-subscriber.json +47 -0
- package/resources/skills/nodered-patterns/examples/timer-flow.json +50 -0
- package/resources/skills/nodered-subflows/SKILL.md +261 -0
- package/resources/skills/nodered-uibuilder/SKILL.md +500 -0
- package/src/auth/api-key-verifier.js +36 -0
- package/src/auth/composite-verifier.js +59 -0
- package/src/auth/config.js +106 -0
- package/src/auth/oauth-clients-store.js +107 -0
- package/src/auth/oauth-provider.js +149 -0
- package/src/auth/oauth-token-store.js +312 -0
- package/src/nodered/auth.js +158 -0
- package/src/nodered/client.js +199 -0
- package/src/nodered/comms-client.js +500 -0
- package/src/renderer/colors.js +161 -0
- package/src/renderer/geometry.js +115 -0
- package/src/renderer/html-builder.js +571 -0
- package/src/renderer/index.js +51 -0
- package/src/renderer/ir-builder.js +161 -0
- package/src/renderer/layout.js +126 -0
- package/src/renderer/mermaid-builder.js +109 -0
- package/src/renderer/svg-builder.js +228 -0
- package/src/schemas/responses.js +283 -0
- package/src/server.js +844 -0
- package/src/skills/loader.js +84 -0
- package/src/staging-store.js +258 -0
- package/src/tools/add-nodes-to-group.js +216 -0
- package/src/tools/connect-nodes.js +115 -0
- package/src/tools/constants.js +45 -0
- package/src/tools/create-flow.js +87 -0
- package/src/tools/create-node.js +126 -0
- package/src/tools/create-subflow-instance.js +123 -0
- package/src/tools/create-subflow.js +101 -0
- package/src/tools/delete-context.js +60 -0
- package/src/tools/delete-flow.js +81 -0
- package/src/tools/delete-group.js +116 -0
- package/src/tools/delete-node.js +73 -0
- package/src/tools/delete-subflow.js +103 -0
- package/src/tools/deploy.js +94 -0
- package/src/tools/disconnect-nodes.js +158 -0
- package/src/tools/export-flow.js +161 -0
- package/src/tools/export-subflow.js +78 -0
- package/src/tools/flow-utils.js +376 -0
- package/src/tools/get-config-nodes.js +86 -0
- package/src/tools/get-context.js +76 -0
- package/src/tools/get-flow-diagram.js +99 -0
- package/src/tools/get-flow-nodes.js +116 -0
- package/src/tools/get-flows.js +74 -0
- package/src/tools/get-node-detail.js +77 -0
- package/src/tools/get-node-type-detail.js +92 -0
- package/src/tools/get-palette-nodes.js +63 -0
- package/src/tools/get-staging-status.js +34 -0
- package/src/tools/get-subflow-detail.js +110 -0
- package/src/tools/get-subflows.js +105 -0
- package/src/tools/import-flow.js +310 -0
- package/src/tools/inject-message.js +117 -0
- package/src/tools/install-node.js +31 -0
- package/src/tools/read-debug-messages.js +155 -0
- package/src/tools/refresh-staging.js +62 -0
- package/src/tools/remove-nodes-from-group.js +162 -0
- package/src/tools/render-staging.js +69 -0
- package/src/tools/response-utils.js +42 -0
- package/src/tools/search-nodes.js +134 -0
- package/src/tools/uninstall-node.js +31 -0
- package/src/tools/update-flow.js +95 -0
- package/src/tools/update-group.js +77 -0
- package/src/tools/update-node.js +132 -0
- package/src/tools/update-subflow.js +84 -0
- package/src/transport/http.js +252 -0
- package/src/transport/stdio.js +16 -0
- package/src/transport/ws-server.js +223 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nodered-flow-layout
|
|
3
|
+
description: >-
|
|
4
|
+
Comprehensive rules for positioning and arranging nodes in Node-RED flows.
|
|
5
|
+
Covers debug node placement, vertical centering around branch points,
|
|
6
|
+
horizontal spacing standards, comment placement, group spacing, and
|
|
7
|
+
bounding-box calculations. Use whenever creating or rearranging multi-node
|
|
8
|
+
flows to ensure visual clarity and avoid overlapping elements.
|
|
9
|
+
tools:
|
|
10
|
+
- refresh-staging
|
|
11
|
+
- get-flows
|
|
12
|
+
- get-flow-nodes
|
|
13
|
+
- create-node
|
|
14
|
+
- add-nodes-to-group
|
|
15
|
+
- remove-nodes-from-group
|
|
16
|
+
- update-group
|
|
17
|
+
- update-node
|
|
18
|
+
- deploy
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Node-RED Flow Layout
|
|
22
|
+
|
|
23
|
+
Positioning rules extracted from real-world flow analysis. These ensure flows are readable, well-organized, and visually balanced. Use these rules when creating multi-node flows, adding groups, or rearranging existing flows.
|
|
24
|
+
|
|
25
|
+
> **Prerequisites:** Read `nodered-fundamentals` for core vocabulary. See `nodered-flow-builder` for node dimension tables and the `estimateNodeHeight()` formula.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 1️⃣ Debug Node Placement
|
|
30
|
+
|
|
31
|
+
Debug nodes can be placed **above** or **below** the node they monitor, never on the same horizontal row — unless they are the final terminal node of a branch.
|
|
32
|
+
|
|
33
|
+
| Scenario | Placement | ΔY | Example |
|
|
34
|
+
|----------|-----------|-----|---------|
|
|
35
|
+
| Intermediate debug | Above source node | −40px | `Alerts (y=540)` above `Alert (y=580)` |
|
|
36
|
+
| Intermediate debug | Below source node | +40px | `Out A (y=120)` below `Process A (y=80)` |
|
|
37
|
+
| Terminal debug (end of branch) | Same row as predecessor | +0px | Allowed only if no further nodes follow |
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
✅ CORRECT:
|
|
41
|
+
[function: Alert] [function: Alert]
|
|
42
|
+
↓ ↓
|
|
43
|
+
[debug: Alerts] (y−40) [debug: Alerts] (y+40)
|
|
44
|
+
|
|
45
|
+
✅ CORRECT (terminal only):
|
|
46
|
+
[function: Process A]──[debug: Out A] (same y, end of branch)
|
|
47
|
+
|
|
48
|
+
❌ WRONG:
|
|
49
|
+
[function: Alert]──[debug: Alerts]──[function: Next] (debug inline in middle of chain)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Rule of thumb:** Debug nodes are diagnostic tools. Place them where they don't interrupt the visual flow of the main processing chain. Above/below their source node keeps the main row clean.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 2️⃣ Vertical Centering Around Branch Points
|
|
57
|
+
|
|
58
|
+
### Before a split (switch / multi-output)
|
|
59
|
+
|
|
60
|
+
Position the splitting node at the **vertical midpoint** of its output branches.
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
/**
|
|
64
|
+
* Calculate the Y position for a node that fans out to multiple branches.
|
|
65
|
+
* @param {number[]} branchYValues - Y coordinates of each branch target
|
|
66
|
+
* @returns {number} Centered Y coordinate
|
|
67
|
+
*/
|
|
68
|
+
function centerYForSplit(branchYValues) {
|
|
69
|
+
const sum = branchYValues.reduce((a, b) => a + b, 0);
|
|
70
|
+
return Math.round(sum / branchYValues.length);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
Example: Route switch → 3 branches at y=80, y=220, y=400
|
|
76
|
+
centerY = (80 + 220 + 400) / 3 = 233 → rounded to 220
|
|
77
|
+
Route placed at y=220 ✓
|
|
78
|
+
|
|
79
|
+
Example: Threshold? switch → 2 branches at y=580, y=680
|
|
80
|
+
centerY = (580 + 680) / 2 = 630 → rounded to 640
|
|
81
|
+
Threshold? placed at y=640 ✓
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### After a merge (join / link in)
|
|
85
|
+
|
|
86
|
+
Same principle — position the merged node at the vertical center of its input branches.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
Branch A (y=200) ──┐
|
|
90
|
+
├── [join: Merge] (centered y)
|
|
91
|
+
Branch B (y=300) ──┘
|
|
92
|
+
|
|
93
|
+
centerY = (200 + 300) / 2 = 250
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### General centering rule
|
|
97
|
+
|
|
98
|
+
Whenever N wires converge on or diverge from a single node, that node's Y should equal the **arithmetic mean** of the Y coordinates at the other ends of those wires.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 3️⃣ Horizontal Spacing Standards
|
|
103
|
+
|
|
104
|
+
| Transition | ΔX | Use Case |
|
|
105
|
+
|------------|-----|----------|
|
|
106
|
+
| inject → function | **+170 to +190** | Starting a row |
|
|
107
|
+
| function → function | **+170 to +200** | Sequential processing |
|
|
108
|
+
| function/change → debug (adjacent) | **+10** | Debug alongside predecessor |
|
|
109
|
+
| switch → branch node | **+240** | Crossing group boundaries |
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
Measured from real reference flows:
|
|
113
|
+
Start(110) → Validate(290) → Route(480) Δx = 180, 190
|
|
114
|
+
Sanitize(300) → Enrich(470) → Validate(640) Δx = 170, 170
|
|
115
|
+
Out A(930) right next to Process A(920) Δx = 10 (debug adjacent)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Long-name node adjustment
|
|
119
|
+
|
|
120
|
+
When a node has a name >25 characters, increase the horizontal gap:
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
function spacingForNode(nodeName) {
|
|
124
|
+
const baseSpacing = 180;
|
|
125
|
+
if (nodeName.length > 25) {
|
|
126
|
+
return baseSpacing + (nodeName.length - 25) * 8; // +8px per extra char
|
|
127
|
+
}
|
|
128
|
+
return baseSpacing;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Examples:
|
|
132
|
+
spacingForNode("Process A") // → 180
|
|
133
|
+
spacingForNode("Handle Type C (extended)") // → 180 + 2*8 = 196 → ~200
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## 4️⃣ Comment Node Placement
|
|
139
|
+
|
|
140
|
+
| Comment refers to... | Placement | Position |
|
|
141
|
+
|----------------------|-----------|----------|
|
|
142
|
+
| **A group** | Inside that group | Top of group rectangle (y = group.y + margin) |
|
|
143
|
+
| **A single node** | Above the node | Directly above, Δy ≈ −30px |
|
|
144
|
+
| **A section (no group)** | Above the first node | Left-aligned with first node, above it |
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
✅ Comments inside groups:
|
|
148
|
+
┌─ Main Router ─────────────────────────────┐
|
|
149
|
+
│ 🟢 Main Routing Flow — routes by category │ ← comment inside group
|
|
150
|
+
│ [Start] → [Validate] → [Route] │
|
|
151
|
+
└───────────────────────────────────────────┘
|
|
152
|
+
|
|
153
|
+
✅ Comment above a single node:
|
|
154
|
+
⚠️ Alert Path — threshold ≥ 10 ← comment above node (Δy = −30)
|
|
155
|
+
[function: Alert] → [debug: Alerts]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**When creating a group with a comment:**
|
|
159
|
+
1. Create the comment node first (at the group's intended top)
|
|
160
|
+
2. Create the group, including the comment in its `nodeIds`
|
|
161
|
+
3. Position functional nodes below the comment within the group
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 5️⃣ Group-to-Group Vertical Spacing
|
|
166
|
+
|
|
167
|
+
When stacking independent groups vertically, use the following spacing conventions:
|
|
168
|
+
|
|
169
|
+
| Relationship | Gap | Use |
|
|
170
|
+
|-------------|-----|-----|
|
|
171
|
+
| **Closely related** (branches of same switch) | ~40px | Branch A → Branch B → Branch C |
|
|
172
|
+
| **Loosely related** (same flow section) | ~80px | Branch C → Scheduled Pipeline |
|
|
173
|
+
| **Unrelated** (different flow section) | ~100px | Scheduled Pipeline → Manual ETL |
|
|
174
|
+
|
|
175
|
+
### Vertical Packing Formula
|
|
176
|
+
|
|
177
|
+
Calculate the gap based on what sits between rows:
|
|
178
|
+
|
|
179
|
+
```javascript
|
|
180
|
+
/**
|
|
181
|
+
* Calculate vertical gap between consecutive rows.
|
|
182
|
+
* @param {number} debugCount - Number of debug nodes between rows (0-2)
|
|
183
|
+
* @param {boolean} hasComment - Whether next row starts with a comment
|
|
184
|
+
* @param {string} relationship - 'same-group' | 'related' | 'unrelated'
|
|
185
|
+
* @returns {number} Gap in pixels
|
|
186
|
+
*/
|
|
187
|
+
function verticalGap(debugCount = 0, hasComment = true, relationship = 'related') {
|
|
188
|
+
const baseGaps = { 'same-group': 40, 'related': 60, 'unrelated': 80 };
|
|
189
|
+
const debugOverhead = debugCount * 20; // each debug adds half its offset
|
|
190
|
+
const commentOverhead = hasComment ? 10 : 0;
|
|
191
|
+
return baseGaps[relationship] + debugOverhead + commentOverhead;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Examples:
|
|
195
|
+
verticalGap(0, true, 'same-group') // → 50 (comment overhead inside group)
|
|
196
|
+
verticalGap(1, false, 'related') // → 80 (one debug between related groups)
|
|
197
|
+
verticalGap(0, true, 'unrelated') // → 90 (comment to unrelated section)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
Measured from compact layout:
|
|
202
|
+
|
|
203
|
+
Group A bottom (y + h): ~310
|
|
204
|
+
↓ +80px (related sections with debug overhead)
|
|
205
|
+
Group B top (y): ~390
|
|
206
|
+
|
|
207
|
+
Group B bottom: ~440
|
|
208
|
+
↓ +100px (unrelated sections)
|
|
209
|
+
Group C top: ~540
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 6️⃣ Group Bounding Box Calculation
|
|
215
|
+
|
|
216
|
+
When creating groups via `add-nodes-to-group`, calculate the bounding box to enclose all member nodes plus any comments:
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
/**
|
|
220
|
+
* Calculate group bounding box from member node positions.
|
|
221
|
+
* @param {Array<{x: number, y: number}>} positions - Node coordinates
|
|
222
|
+
* @param {Object} options
|
|
223
|
+
* @param {number} options.padding - Edge padding (default 20)
|
|
224
|
+
* @param {boolean} options.hasComment - Extra top space for a comment (default true)
|
|
225
|
+
* @returns {{x: number, y: number, w: number, h: number}}
|
|
226
|
+
*/
|
|
227
|
+
function calculateGroupBounds(positions, options = {}) {
|
|
228
|
+
const { padding = 10, hasComment = true } = options;
|
|
229
|
+
const xs = positions.map(n => n.x);
|
|
230
|
+
const ys = positions.map(n => n.y);
|
|
231
|
+
|
|
232
|
+
const topCommentSpace = hasComment ? 30 : 0;
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
x: Math.min(...xs) - padding,
|
|
236
|
+
y: Math.min(...ys) - padding - topCommentSpace,
|
|
237
|
+
w: Math.max(...xs) - Math.min(...xs) + 20, // 10px margin each side
|
|
238
|
+
h: Math.max(...ys) - Math.min(...ys) + 20 + topCommentSpace // 10px margin + comment
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Usage with add-nodes-to-group:
|
|
243
|
+
// const bounds = calculateGroupBounds(memberPositions);
|
|
244
|
+
// Then update the group:
|
|
245
|
+
// update-group(groupId, { properties: { x: bounds.x, y: bounds.y, w: bounds.w, h: bounds.h } });
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Constants used:**
|
|
249
|
+
- Node width: **160px** (standard)
|
|
250
|
+
- Node height: **40px** (standard, 1-3 outputs), **60px** (4-6 outputs)
|
|
251
|
+
- Comment top space: **30px** (allows one line of text)
|
|
252
|
+
- Edge padding: **10px** (tight, minimum distance from group border to nodes)
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## 7️⃣ Compact Layout Principles
|
|
257
|
+
|
|
258
|
+
These principles produce tighter, more readable flows than the baseline rules alone. Apply after the basic positioning rules above.
|
|
259
|
+
|
|
260
|
+
### 7.1 Column Grid System
|
|
261
|
+
|
|
262
|
+
Align every node to a shared set of X columns. Nodes of the same functional role share the same column regardless of which chain they belong to. This creates a grid that is easier to scan and reduces total width.
|
|
263
|
+
|
|
264
|
+
| Column | X | Node types |
|
|
265
|
+
|--------|------|-----------------------------------------------|
|
|
266
|
+
| C0 | 120 | `inject`, `http in`, `comment` |
|
|
267
|
+
| C1 | 300 | `function` (Δx = 180 from C0) |
|
|
268
|
+
| C2 | 480 | `http response`, `template`, `debug` (mid-chain) |
|
|
269
|
+
| C3 | 660 | `http response` (extended chain, Δx = 180 from C2) |
|
|
270
|
+
| C4 | 840 | `change`, `switch` (branch processing, Δx = 240 from C3) |
|
|
271
|
+
| C5 | 1020 | `debug` (terminal, end of branch, Δx = 180 from C4) |
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
Column alignment example (top-down view):
|
|
275
|
+
|
|
276
|
+
C0 (120) C1 (300) C2 (480) C3 (660) C4 (780) C5 (1000)
|
|
277
|
+
──────────────────────────────────────────────────────────────────────
|
|
278
|
+
[HTTP In] → [Function] → [Response] [Debug] ← status chain
|
|
279
|
+
[HTTP In] → [Function] → [Template] → [Response] ← health chain
|
|
280
|
+
[Inject ] → [Function] → [Debug] ← test chain
|
|
281
|
+
[Comment] [Switch ] → [Change] → [Debug] ← alert chain
|
|
282
|
+
↘ [Change] → [Debug]
|
|
283
|
+
↘ [Delay ] → [Debug]
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### 7.2 Switch Bridge Placement
|
|
287
|
+
|
|
288
|
+
When a switch node bridges two groups (source group → switch → target group with branches), position it close to its source, not centered among its branches:
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
/**
|
|
292
|
+
* Y for a switch bridging between groups.
|
|
293
|
+
* @param {number} sourceY - Y of the upstream node
|
|
294
|
+
* @param {number[]} branchYValues - Y coordinates of branch targets
|
|
295
|
+
* @returns {number} Switch Y position
|
|
296
|
+
*/
|
|
297
|
+
function switchBridgeY(sourceY, branchYValues) {
|
|
298
|
+
const minBranch = Math.min(...branchYValues);
|
|
299
|
+
// Hug the top of the branches, but don't go above source + 40
|
|
300
|
+
return Math.max(sourceY + 40, minBranch - 20);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Example: source at y=80, branches at [560, 660, 780]
|
|
304
|
+
// max(80+40, 560-20) = max(120, 540) = 540
|
|
305
|
+
// switch at y=540 ✓ (close to source, near top of branches)
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
| Rule | Old (center) | New (bridge) |
|
|
309
|
+
|------|-------------|--------------|
|
|
310
|
+
| Switch Y | avg(branches) → 660 | max(source+40, min(branch)-20) → 540 |
|
|
311
|
+
| Visual effect | Switch floats mid-group | Switch hugs top, wire shorter |
|
|
312
|
+
|
|
313
|
+
### 7.3 Debug-on-Response
|
|
314
|
+
|
|
315
|
+
When a debug node shares its X column with an `http response` or terminal node, place it **on the response**, offset vertically by −20px, rather than on the source function:
|
|
316
|
+
|
|
317
|
+
```
|
|
318
|
+
❌ Debug on function: ✅ Debug on response:
|
|
319
|
+
[Func]──[Resp] [Func]──[Resp]
|
|
320
|
+
↘ [Debug] (y-40) [Debug] (y-20, shares X with Resp)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
```javascript
|
|
324
|
+
function debugY(sourceNode, sameColumnNode) {
|
|
325
|
+
// If debug shares X with a terminal node, attach to terminal
|
|
326
|
+
if (sameColumnNode && isTerminal(sameColumnNode)) {
|
|
327
|
+
return sameColumnNode.y - 20;
|
|
328
|
+
}
|
|
329
|
+
// Otherwise, standard ±40px from source
|
|
330
|
+
return sourceNode.y - 40;
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### 7.4 Tight Group Boxing
|
|
335
|
+
|
|
336
|
+
When groups have no internal comment, reduce the top padding:
|
|
337
|
+
|
|
338
|
+
```javascript
|
|
339
|
+
function tightGroupBounds(positions, hasComment = true) {
|
|
340
|
+
const xs = positions.map(n => n.x);
|
|
341
|
+
const ys = positions.map(n => n.y);
|
|
342
|
+
const margin = 10;
|
|
343
|
+
const commentSpace = hasComment ? 30 : 0;
|
|
344
|
+
return {
|
|
345
|
+
x: Math.min(...xs) - margin,
|
|
346
|
+
y: Math.min(...ys) - margin - commentSpace,
|
|
347
|
+
w: Math.max(...xs) - Math.min(...xs) + margin * 2,
|
|
348
|
+
h: Math.max(...ys) - Math.min(...ys) + margin * 2 + commentSpace
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### 7.5 Row Height Budget
|
|
354
|
+
|
|
355
|
+
Each functional row uses approximately **60px** (40px node + 20px clearance). When planning vertical layout, count rows:
|
|
356
|
+
|
|
357
|
+
```
|
|
358
|
+
Row budget = (numberOfRows * 60) + (numberOfInterRowGaps * avgGapSize)
|
|
359
|
+
|
|
360
|
+
Example: 4 rows with 3 gaps
|
|
361
|
+
Rows: 4 × 60 = 240px
|
|
362
|
+
Gaps: (50 + 80 + 90) = 220px
|
|
363
|
+
Total: 460px vertical footprint
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## 🎯 Quick Reference Card
|
|
369
|
+
|
|
370
|
+
| Rule | Key Value |
|
|
371
|
+
|------|-----------|
|
|
372
|
+
| Horizontal spacing (node→node) | **170–190px** |
|
|
373
|
+
| Horizontal spacing (long names >25 chars) | **+8px per extra char** |
|
|
374
|
+
| Debug offset from parent | **±40px** vertical |
|
|
375
|
+
| Debug on terminal node | **−20px** from terminal Y |
|
|
376
|
+
| Debug terminal inline | Same row allowed |
|
|
377
|
+
| Comment above single node | **Δy = −30px** |
|
|
378
|
+
| Comment inside group | At group top (+30px in bounding box) |
|
|
379
|
+
| Branch point centering | **Average** of branch Y values |
|
|
380
|
+
| Switch bridging groups | **max(src+40, min(branch)−20)** |
|
|
381
|
+
| Unrelated group gap | **~100px** |
|
|
382
|
+
| Related group (same section) gap | **~80px** |
|
|
383
|
+
| Same-group row gap | **~50px** |
|
|
384
|
+
| First node position | **(x=120, y=80)** absolute |
|
|
385
|
+
| Node width (standard) | **160px** |
|
|
386
|
+
| Node height (standard) | **40px** (1-3 outs), **60px** (4-6 outs) |
|
|
387
|
+
| Edge padding (groups) | **10px** (tight) |
|
|
388
|
+
| Row height budget | **60px** per row (node + clearance) |
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## 🔗 Related Skills
|
|
393
|
+
|
|
394
|
+
- **`nodered-flow-builder`** — Node dimension tables, `estimateNodeHeight()` formula, and basic positioning grid. Always consult before placing nodes.
|
|
395
|
+
- **`nodered-fundamentals`** — Core vocabulary: nodes, flows, wires, groups.
|