Server.Pin nodes could not observe another node the way Executors and
LogicGates do, even though PinMetaData already implemented SourceMetaData.
An OUT pin’s level came only from its own meta.state, editable by hand —
there was no way to wire a digital input so the pin’s output followed it. The
inputs -> invocation wake-wiring deferred for LogicGate (commit 6173e5d70)
meant no node type actually woke from an input change.
Server.Pin nodes were driven entirely by legacy CRUD: ServerPinProcessor.post()
registered/reconfigured/unregistered the pin with krill-pi4j on create/edit/delete
(dispatched from the central CrudLifecycleTask), and an OUT pin’s level came
from its own meta.state. The pin never joined the observer (onInvoke) path, so
setting inputs alone left it inert: it had the data reference but no wake
(sources + SOURCE_INVOKED) to trigger re-evaluation, and no execute path to
read the input and drive the hardware.
Extracted DigitalInputReader (server/.../krillapp/digital/) — the shared
“read one node’s digital boolean” used by both LogicGateCompute and the pin.
ServerPinProcessor.process() now, for an OUT pin with a wired input, reads the
input via DigitalInputReader and drives PiManager.setHigh/setLow (non-digital
input → NodeState.ERROR; no input → idle), riding the existing
onInvoke(EXECUTE) observer path so user edits no longer drive the output. The
pi4j register/reconfigure/unregister moved into a dedicated ServerPinLifecycleTask
(state-gated on DELETING/CREATE_OR_OVERWRITE/USER_EDIT, preserving the USER_EDIT
setStateToNone settle), and the Pin arm (and pinProcessor dep) was removed from
CrudLifecycleTask. EditPin shows a single digital-input selector when
mode == OUT; picking an input sets inputs AND sources + SOURCE_INVOKED
(PinInputsLogic.withInput), which is what makes NodeObservationRegistry wake the
pin; the wired-node render guards a deleted input with readNodeStateOrNull. The
digital-input picker dialog was extracted into a shared composable with an exclude
param (prevents a pin selecting itself).
inputs (data) and
sources + SOURCE_INVOKED (wake) set — setting only one leaves it either inert
or unable to read. The editor is the right place to set both atomically.ServerTask, not the shared
CrudLifecycleTask dispatch — keep invocation (execute) and lifecycle (claim
hardware) on separate paths. When relocating such side effects, audit for
incidental state transitions (e.g. setStateToNone) so the “pure relocation”
doesn’t silently drop behavior.readNodeStateOrNull, never readNodeState — a stale reference to a deleted node
must degrade gracefully, not crash composition.