Two-part fix so multiple people can edit /lignes at once without
clobbering each other, and see each other's changes without a manual
reload -- no Socket.io/websocket infra, just what fits the existing
fetch-based architecture:
1. Polling (POLL_INTERVAL_MS = 8s): pollForChanges() asks JSON:API for
any ligne_comptable changed since the last check (filtering on the
`changed` field -- confirmed live that JSON:API only accepts a raw
Unix timestamp for this, not the ISO string it returns in responses,
silently matching everything otherwise) and mergeChangedRows()
patches matching rows in place via Object.assign (not a `rows`
reassignment, so it doesn't re-trigger the reconciliation-resolution
watcher for routine polls). A row currently being edited is skipped
entirely rather than overwritten out from under an in-progress
keystroke.
2. Optimistic locking: every row now carries its `changed` timestamp,
sent back on every inline edit (updateType/updateField). A new
checkConflict() compares it against the node's actual changed time
before saving and rejects with 409 if they differ -- someone else
saved this exact line in between. On a 409, refreshSingleRow()
re-fetches just that node and patches it in place so the view
self-corrects instead of staying stuck on the stale state that
caused the rejection.
Verified live end-to-end against an isolated temporary test node (not
real data): an external edit correctly appeared in the browser within
one poll cycle with no reload; a save using a stale `changed` value was
rejected with the conflict error, confirmed via direct DB query that it
left the node's data completely untouched, and the view auto-corrected
to show the other edit. Test node and its paragraph fully cleaned up
afterward.