- config/machineGroup.json: every non-row panel now annotated with
meta.emittedFields (mode, scaling, abs/relDistFromPeak, flow.total/group,
power.total/group). Per-pump fields (ctrl, state, runtime, pressure,
temperature) deliberately absent — those live on rotatingMachine children
per #39's no-data-duplication contract.
- Timeseries panels gain byRegexp dashed-bounds overrides for .min$/.max$
(same pattern as #38).
- test/basic/slice40-mgc-template.basic.test.js: 4 cases — no per-pump
fields leak in, every non-row annotated, dashed overrides present on TS,
composer dedup applies when a child claims an MGC-level field.
Closes#40
When generateDashboardsForGraph builds a root dashboard for a parent (e.g.
pumpingStation) and a set of child dashboards (e.g. measurements), it now
removes any non-row panel from the root whose meta.emittedFields are fully
covered by panels declared in any child dashboard. Result: the parent
shows only metrics its children don't already plot, eliminating redundant
rendering of the same series in two dashboards.
- config/pumpingStation.json: 11 non-row panels annotated with
meta.emittedFields (Direction, Time Left, Flow Source, Fill %, Level (x2),
Volume, Net Flow Rate, Inflow+Outflow, Heights, Volume Limits).
- src/specificClass.js: generateDashboardsForGraph runs the parent-panel
filter after composing children; row panels always kept; panels without
emittedFields declaration always kept (no silent removal).
- test/basic/slice39-no-duplication.basic.test.js: 4 cases — annotation
presence, child-covered removal, no-overlap preservation, row preservation.
Closes#39
Applies the byRegexp(\\.min$ | \\.max$) → custom.lineStyle dashed pattern to
all 4 timeseries panels in config/machine.json — pattern confirmed via S2
spike (#33). Forward-compatible: nodes that don't yet emit .min/.max fields
see no change in rendering (regex won't match).
- config/machine.json: 4 timeseries panels gain byRegexp overrides for both
.min$ and .max$, dashed [10,10], orange (min) / red (max).
- test/basic/slice38-dashed-bounds.basic.test.js: 2 cases (presence per ts
panel, anchor-to-end forward compatibility).
Companion-field emission helper (generalFunctions.outputUtils — produces
<field>, <field>.min, <field>.max from a bounds-aware source) is a
generalFunctions submodule change and lands in a follow-up PR — out of
scope for this dashboardAPI-only slice.
Closes#38
Adds per-panel `meta.emittedFields` to machine.json (rotatingMachine) and
machineGroup.json (MGC) templates. Each non-row panel declares the Influx
field paths it visualizes, so a parent template's composer can filter out
panels already covered by its children (#39 no-data-duplication rule).
- config/machine.json: 13 non-row panels annotated.
- config/machineGroup.json: panels annotated.
- src/specificClass.js: collectEmittedFields(dashboard) helper.
- test/basic/slice37-emitted-fields.basic.test.js: 4 cases (template loads
with annotations, aggregation, missing-meta graceful, null input).
PRD F-6 panel set audit: machine.json already covers all the PRD-required
panels (State/Mode/Ctrl%/Runtime/NCog%/Flow/Efficiency/Pressure/Temperature/
Diagnostics) — substantially more than asked. No new panels added.
PRD F-7 predicted-vs-measured side-by-side: deferred. Current architecture
is "1 dashboard per node" (each child gets its own dashboard, cross-linked
from the parent), not "1 dashboard with N composed panels." Side-by-side
rendering of predicted (rotatingMachine dashboard) + measured (measurement
child dashboard) lives naturally as drill-down navigation today. Refactor
to a single-dashboard composition model would be substantial — flagged in
the issue comment for v2 if the drill-down UX proves insufficient.
Closes#37
Subscribes to Node-RED's flows:started runtime event, caches the {diff}
payload on the dashboardAPI source, and short-circuits the child.register
handler when none of {dashboardAPI id, child id, grandchild ids} appears in
diff.added/changed/removed/rewired. Predicate verified by S1 spike (#32).
- src/nodeClass.js: _attachLifecycleHook subscribes, _attachCloseHandler
cleans up. No-op when RED.events isn't available (unit-test friendly).
- src/specificClass.js: subtreeChanged() predicate + subtreeIdsFor() helper.
- src/commands/handlers.js: registerChild consults predicate before composing;
logs {event:'regen-skipped', outcome:'no-diff'} when skipping.
- test/basic/slice36-diff-predicate.basic.test.js: 8 cases — null/empty diff,
affected/unaffected ids, tab-id over-triggering avoidance, grandchild
inclusion, no-grandchild case.
Cold start (no cached diff yet) always regenerates — safe default. Edge case
documented in #32: when a brand-new child is wired to a registered parent,
the new child's id is in diff.added but not yet in registeredChildren when
flows:started fires. Mitigation (b) per spike findings: one-deploy race
accepted for R&D — next deploy picks up the new registration.
Closes#36
Architectural note: existing composition is "1 dashboardAPI → root dashboard
+ 1 per child", not "1 dashboardAPI → 1 dashboard with N panels" as the PRD
assumed. Each generated dashboard is laid out at template-authoring time
(explicit gridPos per panel inside config/<softwareType>.json); the composer's
job is to substitute per-instance templating variables and assemble the
cross-link list. So the PRD's "non-overlapping gridPos for N panels" lands as:
- perf: 50 children compose in <500ms (PRD N-1).
- uid-uniqueness: stableUid keyed on softwareType:nodeId never collides.
- byte-identical idempotency (PRD N-2): two consecutive compositions match.
- root links: one link per registered child.
No production code change — this slice just adds the perf/uniqueness/idempotency
guarantees as explicit tests so we can't regress.
Closes#35
Encrypts the Grafana bearer token via Node-RED credentials block instead of
plain config (F-11). Adds folderUid config field threaded through to the
buildUpsertRequest payload (F-8, resolves PRD O-5). Migration path: legacy
plain bearerToken still loads, with one-time warn() prompting user to re-save.
Composition + URL + headers + per-instance UID were already in place; only
the credentials + folderUid + tests are new.
- dashboardAPI.html: bearerToken moved to credentials block; folderUid added.
- dashboardAPI.js: registerType options pass credentials descriptor.
- src/nodeClass.js: read token from node.credentials; legacy fallback warns.
- src/specificClass.js: buildUpsertRequest emits folderUid when set.
- src/commands/handlers.js: pass folderUid from config to buildUpsertRequest.
- test/basic/slice34-credentials-and-folder.basic.test.js: 5 new tests.
Diff-based regeneration (F-1) and the explicit flows:started lifecycle hook
land in #36 once the S1 spike predicate is wired. Until then, the existing
child.register message trigger continues to drive composition on every
startup-time child registration.
Closes#34
Aligns the entry-file naming with the folder-name convention from
.claude/rules/node-architecture.md / superproject CLAUDE.md.
NON-BREAKING: the Node-RED type id stays lowercase
(`RED.nodes.registerType('dashboardapi', ...)`) so every deployed flow
that references this node continues to load. Only the file paths
change. Admin endpoints `/dashboardapi/menu.js` and
`/dashboardapi/configData.js` are unaffected — they follow the type
id, not the filename.
Updated:
- package.json `main` + `node-red.nodes` value
- test/basic/structure-module-load.basic.test.js require path
- CLAUDE.md: legacy-drift warning replaced with a note explaining the
type-id preservation strategy
Same approach recommended for the remaining two legacy renames (mgc,
vgc); the superproject CLAUDE.md drift table now spells that out.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P6.7 converted test/basic/. Convert test/edge/ and test/integration/ the
same way: describe/it/expect → test/assert. No behavioural change.
5 / 5 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refactor of dashboardAPI to use BaseNodeAdapter + commandRegistry + statusBadge.
dashboardAPI follows the platform refactor plan in .claude/refactor/MODULE_SPLIT.md.
Tests stay green; CONTRACT.md generated; legacy aliases preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>