[READ-ONLY] Mirror of https://github.com/SantaClaas/embedded-fan-control.

Build the interface: watch the bus, or talk to a device master

The two jobs share one port and are mutually exclusive, so they are two tabs rather than two panes. Watching is passive and safe to leave running while the fan controller drives the bus; talking sends requests and must not be used at the same time. Both say so in the panel itself rather than only in a README. `connection.ts` serves both from a single read loop, because a port has exactly one reader. Every chunk goes to the subscribers, which is how the monitor sees traffic, and while a request is outstanding it also goes to whatever is waiting for the reply. That reply is found by checksum rather than by counting bytes, which is also what lets it step over the echo of the request: some RS-485 adapters put what they transmit back on the receive line — `debug-listener` carries a flag for exactly that — and an echo is a perfectly valid frame with the right address and function code, so nothing but recognising the bytes rules it out. Tested. `client.ts` batches reads. Modbus reads a range, so the runs worked out in the layer below turn a device's whole register list into a handful of requests. A range the device refuses is recorded and the rest are still attempted, because one unreadable register should not cost the reading of every other one. Two things came from the `modern-web-guidance` skill and are worth not undoing: - Register write fields use `:user-invalid` rather than `:invalid`, so a field is not flagged red while it is still being typed — only once it has been left. The bounds are real min/max/step attributes taken from the register definition, which makes the browser's own validation the source of truth; the Write button is gated on `validity.valid` rather than on a second copy of the bounds. - The monitor's rows carry `content-visibility: auto` with `contain-intrinsic-size`, so a few hundred kept frames cost only what is on screen, without the scrollbar jumping. Verified against a fake port replaying the manuals' own frames: request and response are identified and paired, an unpaired read response is labelled with the range its request asked for, deliberately corrupt bytes appear as an "unaccounted" row, and the monitor resynchronises straight back into the conversation afterwards. Three defects that only showed up there are fixed: "1 holding registers", the RadiCal's D-prefix being applied to the relay's 0x03E8 by address magnitude, and port-setting labels wrapping away from the controls they name. Solid 2.0 notes for the next reader: there is no `onMount` — a component body already runs once during setup — and `createEffect` takes a compute and an effect function rather than one closure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>