References the flow-layout rule set in the EVOLV superproject
(.claude/rules/node-red-flow-layout.md) so Claude Code sessions working
in this repo know the S88 level, colour, and placement lane for this node.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous oneditprepare ran applyMode(initialMode) early in the
function, which called validateChannelsJson(), which referenced const
declarations (channelsArea, channelsHint) that were declared later in
the same function. JavaScript hoists const into the Temporal Dead Zone,
so accessing them before the declaration line throws a ReferenceError.
That uncaught throw aborted the rest of oneditprepare — including the
waitForMenuData() call that initialises the asset / logger / position
menu placeholders. Symptom for the user: opening a measurement node in
the editor showed Mode + analog fields but the asset menu was empty.
Fixes:
1. Move waitForMenuData() to the very top of oneditprepare so the
shared menu init is independent of any later mode-block work. Even
if the mode logic ever throws again, the asset / logger / position
menus still render.
2. Resolve every DOM reference (modeSelect, analogBlock, digitalBlock,
modeHint, channelsArea, channelsHint) at the top of the function
before any helper that touches them is invoked. validateChannelsJson
and applyMode now read closed-over names that are guaranteed to be
initialised.
3. Guard applyMode(initialMode) with try/catch as defense in depth and
add null-checks on every DOM reference. A future template change
that drops one of the IDs will only no-op rather than break the
editor.
No runtime change. 71/71 tests still green.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prior behaviour: the Mode dropdown existed but nothing consumed it in the
editor — analog fields (Scaling, Source Min/Max, Smoothing, …) were
always visible, and the Channels JSON editor was always visible too.
For a legacy node with no saved mode the dropdown defaulted blank so
users reported "I cant even select digital or analog".
Changes:
- Initialize the Mode <select> from node.mode with an 'analog' fallback
for legacy nodes (safe default — matches pre-digital behaviour).
- Wrap analog-only fields and digital-only fields in labelled containers
and toggle their display based on the selected mode. Mode change is
live — no redeploy needed to see the right form.
- Inline hint under the Mode dropdown tells the user what payload shape
is expected for the current mode.
- Channels JSON gets live validation — shows channel count + names on
valid JSON, warns on missing key/type, errors on invalid JSON.
- Label function appends ' [digital]' so the node visibly differs in a
flow from an analog sibling.
- oneditsave is mode-aware: only warns about incomplete scaling ranges
in analog mode; in digital mode warns if the channels array is empty
or unparseable.
Runtime friendliness:
- nodeClass node-status now shows 'digital · N channel(s)' on startup in
digital mode, and 'digital · N/M ch updated' after each incoming msg
so the editor has a live heartbeat even when there is no single scalar.
- When analog mode receives an object payload (or digital receives a
number), the node logs an actionable warn suggesting the mode switch
instead of silently dropping the message.
Explicit, not auto-detected: mode remains a deployment-time choice
because the two modes take different editor config (scaling/smoothing vs
channels map). Auto-detecting at runtime would leave the node
unconfigured in whichever mode the user hadn't anticipated.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Runtime:
- Fix silent no-op when user selected any camelCase smoothing or outlier
method from the editor. validateEnum in generalFunctions lowercases enum
values (zScore -> zscore, lowPass -> lowpass, ...) but the dispatcher
compared against camelCase keys. Effect: 5 of 11 smoothing methods
(lowPass, highPass, weightedMovingAverage, bandPass, savitzkyGolay) and
2 of 3 outlier methods (zScore, modifiedZScore) silently fell through.
Users got the raw last value or no outlier filtering with no error log.
Review any pre-2026-04-13 flows that relied on these methods.
Fix: normalize method names to lowercase on both sides of the lookup.
- New Channel class (src/channel.js) — self-contained per-channel pipeline:
outlier -> offset -> scaling -> smoothing -> min/max -> constrain -> emit.
Pure domain logic, no Node-RED deps, reusable by future nodes that need
the same signal-conditioning chain.
Digital mode:
- config.mode.current = 'digital' opts in. config.channels declares one
entry per expected JSON key; each channel has its own type, position,
unit, distance, and optional scaling/smoothing/outlierDetection blocks
that override the top-level analog-mode fields. One MQTT-shaped payload
({t:22.5, h:45, p:1013}) dispatches N independent pipelines and emits N
MeasurementContainer slots from a single input message.
- Backward compatible: absent mode config = analog = pre-digital behaviour.
Every existing measurement flow keeps working unchanged.
UI:
- HTML editor: new Mode dropdown and Channels JSON textarea. The Node-RED
help panel is rewritten end-to-end with topic reference, port contracts,
per-mode configuration, smoothing/outlier method tables, and a note
about the pre-fix behaviour.
- README.md rewritten (was a one-line stub).
Tests (12 -> 71, all green):
- test/basic/smoothing-methods.basic.test.js (+16): every smoothing method
including the formerly-broken camelCase ones.
- test/basic/outlier-detection.basic.test.js (+10): every outlier method,
fall-through, toggle.
- test/basic/scaling-and-interpolation.basic.test.js (+10): offset,
interpolateLinear, constrain, handleScaling edge cases, min/max
tracking, updateOutputPercent fallback, updateOutputAbs emit dedup.
- test/basic/calibration-and-stability.basic.test.js (+11): calibrate
(stable and unstable), isStable, evaluateRepeatability refusals,
toggleSimulation, tick simulation on/off.
- test/integration/digital-mode.integration.test.js (+12): channel build
(including malformed entries), payload dispatch, multi-channel emit,
unknown keys, per-channel scaling/smoothing/outlier, empty channels,
non-numeric value rejection, getDigitalOutput shape, analog-default
back-compat.
E2E verified on Dockerized Node-RED: analog regression unchanged; digital
mode deploys with three channels, dispatches MQTT-style payload, emits
per-channel events, accumulates per-channel smoothing, ignores unknown
keys.
Depends on generalFunctions commit e50be2e (permissive unit check +
mode/channels schema).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hardcoded position strings with POSITIONS.* constants.
Prefix unused variables with _ to resolve no-unused-vars warnings.
Fix no-prototype-builtins where applicable.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces manual base config construction with shared buildConfig() method.
Node now only specifies domain-specific config sections.
Part of #1: Extract base config schema
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>