Three related /lignes fixes:
1. Column widths: shrink Date/Facture/Libellé/Signalement (the four
editable text columns) to free up room for the amount columns
(Montant HT/TTC, the 8 compte columns, Écart), which were cramped.
Date/Facture stay nowrap (already short: jj/mm/aa, Fxxxxxxx); Libellé/
Signalement keep wrapping.
2. Signalement editing: replaced the comma-separated inline text input
with a small modal -- one tag per line, each with its own remove
button, plus an add field at the bottom. Clearer than parsing/
retyping a whole comma list to drop one tag. Backend endpoint is
unchanged (still takes a comma-joined value); only the front-end
interaction model changed.
3. Scroll storm: a continuous scroll gesture fires many native 'scroll'
events, and checkEdges() ran on every one of them -- each qualifying
event queued its own loadOlder()/loadNewer() call (queuing, not
dropping, was the previous session's fix for a *different* bug), and
every queued call did a real fetch + scroll compensation regardless
of whether an earlier one already moved the window away from the
edge. That pileup is what looked like the same request firing over
and over and dragged the scroll position around unpredictably.
Fixed by guarding checkEdges() with the existing loadingOlder/
loadingNewer flags so it stops queuing once one's already in flight.
(A first attempt at this suppressed the compensation write's own
resulting scroll event via a flag cleared on requestAnimationFrame --
reproduced, live, the exact "stuck forever" failure already fixed
once this session for the old rAF-based scroll throttle, because rAF
doesn't reliably fire in this environment. Removed: turns out no
suppression is needed at all, since that event finds loadingOlder
already true and the checkEdges() guard blocks it on its own.)
Also added a re-entrancy guard to pollForChanges(), which had no
protection against a slow response overlapping with the next
setInterval tick.
Verified with scripted scroll stress tests (dense bursts of 40-100
events, and repeated attempts to scroll back from the very top): exactly
one fetch per genuine edge crossing, no duplicate requests, scroll
position stays correctly anchored, never snaps back down.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>