fix: prevent messages from vanishing in multi-source unified feed; fix region discovery timeout (#3719, #3734)#3736
Conversation
) Two compounding bugs caused messages to disappear from the unified channel view ~60 seconds after arrival when multiple sources are connected. 1. fetchLimit starvation: the per-source window was limit×2 = 200 rows. A high-traffic MQTT bridge (e.g. a continental Canadaverse bridge at ~3 msg/s) pushes a message exclusive to that source beyond position 200 in ~60 s, making it invisible to the unified endpoint on the next poll. Raised to limit×5 = 500, giving ~167 s of headroom at 3 msg/s — well above the 10 s poll interval in all realistic deployments. 2. _dbchan/_radio suffix breaks dedup key: meshtasticManager inserts server-decrypted copies of a message with IDs ending in `_dbchan` or `_radio`. extractPacketIdFromRowId returned null for these because the last underscore-segment was not numeric, so those copies got a text+timestamp dedupKey instead of the correct packetId-based key. They appeared as duplicate entries or were sorted separately rather than merging with the originals receptions array. Fixed by stripping known server-copy suffixes before extracting the numeric packet ID. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014DhSciCtki5JMrpUrKxwnj
|
Claude finished @Yeraze's task —— View job PR Review: fix: prevent messages from vanishing in multi-source unified feed
SummaryThis PR fixes two compounding bugs causing messages to vanish from the unified multi-source feed. The fixes are well-targeted and the code quality is high. Here's my detailed review: Bug 1 —
|
…ponse tag is unmatched (#3734) The #3702 fix assumed sendToRadioFrame fires Sent with no expectedAckCrc (tag = null → skip matching). In practice the firmware DOES populate expectedAckCrc (CRC of the outgoing ANON_REQ packet), but the repeater's BinaryResponse (0x8C) carries tag = 0 — a different scheme than SendBinaryReq (50), which explicitly echoes the CRC. With tag = non-zero CRC and BinaryResponse.tag = 0, the old guard: if (tag !== null && tag !== 0 && r?.tag !== tag) return; evaluates to true and silently drops every response, causing the 15-second inner timeout and "No regions found" for every user attempt (#3734). Fix: remove the tag-matching guard entirely. CMD_SEND_ANON_REQ (57) does not use CRC-echoing tag correlation; operations are serialized by runExclusiveRadioOp so the first BinaryResponse after sentReceived is guaranteed to be ours. The tag variable is also removed since it is no longer used. Updates: correct the stale runExclusiveRadioOp comment that claimed sendToRadioFrame fires Sent with no expectedAckCrc (it does). Test: add a regression test simulating the exact #3734 scenario — expectedAckCrc = 0xcafe1234 in Sent, tag = 0 in BinaryResponse — and verify the regions are resolved rather than timing out. Fixes #3734 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014DhSciCtki5JMrpUrKxwnj
|
Claude finished @Yeraze's task —— View job PR Review: fix: prevent messages from vanishing in multi-source unified feed
SummaryThis PR fixes two compounding bugs causing messages to vanish from the unified multi-source feed. Both fixes are well-targeted and the code quality is high. Here is my detailed review: Bug 1 —
|
…seen message (#3719) (#3738) The unified feed rendered straight off the latest poll (data.pages), which is a capped newest-N window. A message exclusive to a high-traffic source gets pushed past the window within ~60s as newer traffic arrives, so the next poll drops it from the view even though it's still in the DB — the #3719/#3720 disappearance. Structural fix: fold every poll's pages into a persistent map keyed by dedupKey and render from that, making the live feed append-only. Once a message is shown it stays shown regardless of how the server window shifts; upserts prefer the newest (most-complete) merged copy; sort stays by createdAt (#3122). Reset on channel change; capped at 5000 (oldest dropped, still re-fetchable on scroll). This decouples display persistence from server window size, so it holds at any source traffic rate — unlike raising fetchLimit, which only buys proportional headroom. Complements the server-side fixes in #3736. Claude-Session: https://claude.ai/code/session_01SJPe6J5vKrcbwzt6vCdtrt Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…the window-widening The limit*5 widening in this PR was a mitigation for the #3719 disappearance: it kept a single-source-exclusive message inside the server window for ~167s. The client-side append-only accumulator merged in #3738 now makes a displayed message persist regardless of how the server window moves, at any traffic rate, so the extra fetch headroom is redundant and only adds per-poll cost. Restore the original 2x (same-packet dedup headroom). The _dbchan/_radio dedup-key fix in this PR is a separate correctness bug and is kept. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SJPe6J5vKrcbwzt6vCdtrt
|
Claude finished @Yeraze's task —— View job PR Review: fix: prevent messages vanishing in multi-source unified feed
SummaryThis PR carries three commits fixing two independent bugs. The analysis below covers the final state of the branch (including the mid-PR revert in commit Fix 1 —
|
Fixes #3719, #3720, #3734.
This branch carries two independent bug fixes committed sequentially.
Fix 1 — Messages vanishing in multi-source unified feed (#3719 / #3720)
Two compounding bugs identified from code review and the per-source diagnostic data in #3719:
Bug 1 —
fetchLimitstarvation (primary cause of the ~60 s disappearance)The unified messages endpoint fetched at most
limit × 2 = 200rows per source. A high-traffic MQTT bridge (e.g. the Canadaverse continental bridge at ~3 msg/s) pushes a message that is exclusive to that source past position 200 within ~60 s of new messages arriving. On the next 10-second poll the message is no longer in any source's result set and vanishes from the feed.Fix: raise the per-source window to
limit × 5 = 500rows, giving ~167 s of headroom at 3 msg/s — far beyond the 10 s poll interval in all realistic deployments.Bug 2 —
_dbchan/_radiosuffix breaks dedup keymeshtasticManagerinserts server-decrypted copies of a message with IDs ending in_dbchan(channel-database decrypt) or_radio(radio-channel copy).extractPacketIdFromRowIdreturnednullfor these because the last underscore-segment was not numeric, causing those copies to get a text+timestamp fallback dedupKey instead of the correct packetId-based one. The copies appeared as separate entries in the unified feed rather than merging into the original message'sreceptions[]array.Fix: strip known server-copy suffixes (
dbchan,radio) before extracting the numeric packet ID.Test plan (Fix 1)
extractPacketIdFromRowIdunit tests extended with_dbchanand_radiosuffix casesfetchLimitassertions in existing tests updated to reflect the newlimit × 5multiplierunifiedRoutes.test.tspassFix 2 — "Discover regions from repeaters" always times out (#3734)
Root cause:
CMD_SEND_ANON_REQ(command 57), used for region discovery, works differently fromSendBinaryReq(command 50). WithSendBinaryReq, the repeater echoes the CRC32 from theSentack back inBinaryResponse.tag— so tag-matching is correct. WithCMD_SEND_ANON_REQ, the repeater'sBinaryResponsealways carriestag = 0rather than echoing the CRC.With the pre-fix code the guard evaluated:
Since all radio operations on a single connection are serialized by
runExclusiveRadioOp, the firstBinaryResponseafter theSentack is guaranteed to be ours — no other radio op can run concurrently. Tag matching is therefore unnecessary. The guard has been removed and replaced with just thesentReceivedcheck.A regression test simulates the exact scenario (
expectedAckCrc = 0xcafe1234inSent,tag = 0inBinaryResponse) and verifies regions are parsed correctly instead of timing out.Test plan (Fix 2)
ANON_REQwith mismatched CRC/tag parses regions correctly🤖 Generated with Claude Code