--- paths: - "nodes/*/*.js" - "nodes/*/*.html" - "nodes/*/src/**" --- # Node Architecture Rules ## 3-Tier Structure Every node follows entry → nodeClass → specificClass: 1. **Entry file** (`nodes//.js`): Registers with Node-RED via `RED.nodes.registerType`, exposes admin HTTP endpoints. 2. **nodeClass** (`nodes//src/nodeClass.js`): Handles Node-RED runtime concerns — message routing, output formatting, tick loops, status updates, `RED.*` API calls. 3. **specificClass** (`nodes//src/specificClass.js`): Pure domain logic — physics, control algorithms, state machines. ## Separation Rules - **specificClass must never call `RED.*` directly** — all Node-RED interaction goes through nodeClass. - specificClass is the source of truth for domain behavior. - nodeClass is the adapter between Node-RED and domain logic. - Entry file is minimal — registration and admin endpoints only. ## Output Port Convention - Port 0: Process data (control outputs, state, setpoints) - Port 1: InfluxDB telemetry payload - Port 2: Registration/control plumbing (parent-child handshakes) ## File-Naming Convention The folder name is the canonical node name and every per-node file MUST match it exactly (case-sensitive). No abbreviations. | Path | Required name | |---|---| | Folder | `nodes//` | | Entry file | `nodes//.js` | | Editor HTML | `nodes//.html` | | nodeClass | `nodes//src/nodeClass.js` | | specificClass | `nodes//src/specificClass.js` | | Editor JS modules | `nodes//src/editor/*.js` | `machineGroupControl/mgc.js`, `valveGroupControl/vgc.js`, and `dashboardAPI/dashboardapi.js` are legacy drift. New nodes MUST use the full folder name; legacy nodes get renamed when next touched (rename = update entry file, HTML file, `package.json#node-red.nodes`, and any test imports in one commit). ## Editor JS Layout — `src/editor/` Editor-side JavaScript that exceeds a couple of dozen lines lives in modular files under `nodes//src/editor/`, served by the entry file via: ```js const path = require('path'); RED.httpAdmin.get(`/${nameOfNode}/editor/:file`, (req, res) => { const safe = String(req.params.file || '').replace(/[^a-zA-Z0-9._-]/g, ''); if (!safe.endsWith('.js')) return res.status(400).send('// invalid'); res.type('application/javascript'); res.sendFile(path.join(__dirname, 'src', 'editor', safe), (err) => { if (err && !res.headersSent) res.status(404).send('// editor module not found'); }); }); ``` The HTML file then loads them as plain `