Since 3.3.0 the inventory row owns a device's services and properties, and
every node drawing that device rendered the row wholesale. One row shared by
several canvases meant one rendering: a service the scanner fingerprinted
appeared on every canvas at once (users reported Uptime Kuma and Synology DSM
on hosts running neither — both are port-only signatures), and a property added
on one schematic showed up on all the others.
Order and visibility are presentation, so they move to the node. `display_view`
records, per node, which of the row's services and properties it draws and in
what order, keyed by `port|protocol|name` and by lowercased property key so the
view survives an edit to the fact itself. The facts stay on the row: hiding is
per node, deleting is still device-wide.
An item the view does not list is reported hidden rather than dropped, so what
a scan finds next is one toggle away instead of pushed onto every canvas.
The wire shape is unchanged. A client already sends its lists in display order
with their `visible` flags, so the view is read back out of them rather than
asking for a second field, and `visible` is only stamped when something is
hidden.
Upgrades keep what each canvas showed:
* from 3.2.0, the backfill seeds each node's view from its own legacy columns
before they are dropped;
* from 3.3.0-3.3.2 those columns are gone and the row holds the union of every
canvas, so the layout is recovered from the backup `_backup_db` took before
the 3.3.0 migration — the newest one whose `nodes` table still has the
columns, read read-only and matched by node id;
* with no usable backup the row is the seed, so every canvas keeps showing
exactly what it shows today and only later additions are held back.
Refs #347
ha-relevant: maybe