on_message only matched channels enumerated during the backlog scan,
so a brand-new channel (or any type not scanned, like threads) was
silently ignored until the next restart. Now falls back to a
guild-level lookup and registers the channel for live watching going
forward the first time a message arrives there.
Stats panel: a persistent spinner marks whichever target is currently
being scanned/reacted to, and jittered waits show a live-ticking
countdown bar instead of a static "waiting Xs" line. Both the status
line and countdown row are always rendered (blank when idle) so their
appearing/disappearing no longer shifts the table's height.
Backlog: channel_scan_state now tracks oldest_covered_at alongside
newest_id_seen. If days_back is widened after a channel already has a
resume checkpoint, the forward-only diff would never look back far
enough to notice — now the newly-exposed gap gets backfilled first.
Shows what's happening right now underneath the table (connecting,
resolving the friend, discovering targets, fetching channel history,
waiting out a jittered delay before reacting, sending a reaction,
removing a reaction during --undo, etc). Status updates are cheap
and DB-free, using the last fetched stats for re-rendering.
Split each channel's backlog scan into a find phase (settles the
found count first) and a react phase, so the progress display no
longer grows both numbers in lockstep. Found messages are logged in
a dedicated, message_id-deduped table and recovered across restarts
independent of the resume checkpoint, so nothing found is silently
abandoned if the script stops mid-react.
Also drops the separately-maintained messages_found counter, which
proved prone to drift under repeated interruptions/rescans, in favor
of computing it live from the dedup table - self-healing regardless
of how many times a channel gets rescanned.
Was doing an extra fetch_message per reaction to confirm the
reaction stuck. Turned out to be unrelated to the earlier
missing-reactions bug (that was the scan-state resume issue), so
drop it to save the API calls.
React in 1:1 DMs alongside servers/groups, surface the resolved friend
username at startup and in the stats panel, verify reactions actually
stick after adding them (with jump links for manual spot-checking),
and prune stale targets from stats on each run so only the currently
mutual set is shown.