Skip to content

fix(meshcore): restore chat hop pills for packed pathLen and late RF - #761

Merged
rinchen merged 3 commits into
mainfrom
hops
Aug 1, 2026
Merged

fix(meshcore): restore chat hop pills for packed pathLen and late RF#761
rinchen merged 3 commits into
mainfrom
hops

Conversation

@rinchen

@rinchen rinchen commented Aug 1, 2026

Copy link
Copy Markdown
Member

Summary

  • Unpack companion flood pathLen via meshcoreUnpackPathLenByte so multibyte path-hash modes (packed bytes ≥ 64) show hop pills again instead of only direct/0xFF traffic.
  • Harden same-tick DM hop fallback to accept attributed TXT_MSG raw-log rows, and enrich chat rows from late event 136 when hops are missing or RF disagrees (persist to SQLite).
  • Briefly accent the hop label when RF corrects an already-stored count (Updated from RF path), respecting Reduce motion.

Test plan

  • Unit tests: packed pathLen, attributed TXT_MSG correlate, late-136 enrichment (fill/replace/no-op), ChatRfHopLabel accent/title
  • Manual: MeshCore RF chat on a 2-byte path-hash radio — flood multi-hop and direct messages show hop pills
  • Manual: when late event 136 changes a hop number, label briefly warms then settles
  • Manual: MQTT-only messages still omit hop pills

Summary by CodeRabbit

  • New Features

    • Incoming MeshCore chat messages now display RF hop counts with improved translations and accessibility labels.
    • Hop counts can be updated when late packet information arrives.
    • Corrected hop counts are visibly marked and include explanatory details.
    • Packed MeshCore path-length values are now decoded correctly, including multibyte formats.
  • Bug Fixes

    • Improved matching of incoming packets to chat messages, including attributed packets and sender-specific matching.
    • Added safeguards for invalid hop and path-length values.

Unpack companion flood pathLen (multibyte hash modes), correlate attributed
TXT_MSG rows, and enrich chat hops from late event 136 with a brief UI cue
when RF corrects a stored count.
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

MeshCore path decoding and packet correlation now support late RF hop enrichment. Matching chat records are updated in memory and SQLite, corrected values receive temporary UI markers, and ChatPanel renders them through ChatRfHopLabel.

Changes

MeshCore late RF hop enrichment

Layer / File(s) Summary
Packet decoding and correlation
src/renderer/lib/meshcoreUtils.ts, src/renderer/lib/meshcoreRawPacketCorrelate.ts, src/renderer/lib/ingest/meshcoreIngest.ts, src/renderer/lib/protocols/MeshCoreProtocol.test.ts, related tests
Packed path lengths now decode hop counts from the low six bits. Recent TXT_MSG correlation supports sender and fingerprint constraints.
Late hop matching and persistence
src/renderer/lib/meshcoreLateRfHopEnrichment.ts, src/renderer/lib/meshcoreLateRfHopEnrichment.test.ts
The enrichment module validates packet data, selects matching chat records, updates hop fields, persists changes, and tracks temporary correction marks.
RF receive integration
src/renderer/lib/meshcore/meshcoreRfRxRuntime.ts, vitest.config.mts
Parsed TXT_MSG and GRP_TXT packets invoke late RF hop enrichment after raw packet logging.
Corrected hop label rendering
src/renderer/components/ChatRfHopLabel.tsx, src/renderer/components/ChatPanel.tsx, src/renderer/components/ChatRfHopLabel.test.tsx
ChatRfHopLabel subscribes to correction state and renders translated hop text, accessibility titles, refined styling, and reduced-motion behavior. ChatPanel uses the component for incoming messages.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MeshCore
  participant meshcoreRfRxRuntime
  participant applyMeshcoreLateRfHopEnrichment
  participant ChatStore
  participant ChatPanel
  MeshCore->>meshcoreRfRxRuntime: deliver parsed text packet
  meshcoreRfRxRuntime->>applyMeshcoreLateRfHopEnrichment: provide hop and message identity
  applyMeshcoreLateRfHopEnrichment->>ChatStore: update matching message and persist record
  ChatStore-->>ChatPanel: expose correction state
  ChatPanel->>ChatPanel: render ChatRfHopLabel
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: restoring chat hop pills for packed pathLen values and late RF data.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hops

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/renderer/lib/meshcoreRawPacketCorrelate.ts (1)

62-72: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Add sender-aware correlation for DM hop counts.

resolveMeshcoreIngestRxHops does not receive the event sender. A newer TXT_MSG from another peer can therefore provide the hop count for the current message within the 3-second window. Match raw fromNodeId to event.payload.from or use the message fingerprint, and add an interleaved-sender regression test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/lib/meshcoreRawPacketCorrelate.ts` around lines 62 - 72, Update
resolveMeshcoreIngestRxHops and meshcoreFindRecentTxtMsgRawPacket so TXT_MSG
correlation only uses a raw packet from the current event sender, matching raw
fromNodeId to event.payload.from or the message fingerprint. Preserve the
existing time-window behavior, and add a regression test covering interleaved
senders within the 3-second window.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/renderer/components/ChatRfHopLabel.test.tsx`:
- Around line 1-52: Add vitest-axe accessibility coverage to the ChatRfHopLabel
tests, using hydrateAxeThemeColors() so contrast uses real theme values rather
than mocked themeColors. Run axe against the rendered default and corrected
ChatRfHopLabel subtrees and assert toHaveNoViolations() for both, while
preserving the existing title and accent assertions.

In `@src/renderer/lib/meshcoreLateRfHopEnrichment.ts`:
- Around line 137-139: The GRP_TXT fallback in isBroadcastChannelCandidate must
not treat the payload hash as a channel index. Resolve innerPayload[0] against
configured channels and apply the fallback only when exactly one channel
matches; skip it when there is no match or multiple matches. Preserve existing
fingerprint-match behavior and avoid relying on meshcoreRfRxRuntime.ts to
provide the hash.

---

Outside diff comments:
In `@src/renderer/lib/meshcoreRawPacketCorrelate.ts`:
- Around line 62-72: Update resolveMeshcoreIngestRxHops and
meshcoreFindRecentTxtMsgRawPacket so TXT_MSG correlation only uses a raw packet
from the current event sender, matching raw fromNodeId to event.payload.from or
the message fingerprint. Preserve the existing time-window behavior, and add a
regression test covering interleaved senders within the 3-second window.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: b8abfe07-21e7-422a-b2ff-34c8a40e3461

📥 Commits

Reviewing files that changed from the base of the PR and between a125aa7 and f3f5309.

⛔ Files ignored due to path filters (16)
  • src/renderer/locales/cs/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/de/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/en/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/es/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/fr/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/id/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/it/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/ja/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/ko/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/nl/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/pl/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/pt-BR/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/ru/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/tr/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/uk/translation.json is excluded by !src/renderer/locales/**
  • src/renderer/locales/zh/translation.json is excluded by !src/renderer/locales/**
📒 Files selected for processing (12)
  • src/renderer/components/ChatPanel.tsx
  • src/renderer/components/ChatRfHopLabel.test.tsx
  • src/renderer/components/ChatRfHopLabel.tsx
  • src/renderer/lib/meshcore/meshcoreRfRxRuntime.ts
  • src/renderer/lib/meshcoreLateRfHopEnrichment.test.ts
  • src/renderer/lib/meshcoreLateRfHopEnrichment.ts
  • src/renderer/lib/meshcoreRawPacketCorrelate.test.ts
  • src/renderer/lib/meshcoreRawPacketCorrelate.ts
  • src/renderer/lib/meshcoreUtils.test.ts
  • src/renderer/lib/meshcoreUtils.ts
  • src/renderer/lib/protocols/MeshCoreProtocol.test.ts
  • vitest.config.mts

Comment thread src/renderer/components/ChatRfHopLabel.test.tsx
Comment on lines +137 to +139
function isBroadcastChannelCandidate(c: MeshcoreLateHopCandidate): boolean {
return c.channel >= 0 && c.roomServerId == null && c.channel !== MESHCORE_ROOM_MESSAGE_CHANNEL;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether channel index is available anywhere in RF parse context for GRP_TXT packets.
rg -n "channelIdx|channel_idx|channelIndex" src/renderer/lib/meshcore/meshcoreRfRxRuntime.ts
rg -n "buildMeshcoreRfParseContext" -A 60 src/renderer/lib/meshcore/meshcoreRfRxRuntime.ts | head -100

Repository: Colorado-Mesh/mesh-client

Length of output: 4026


🏁 Script executed:

#!/bin/bash
set -eu

echo '--- enrichment implementation ---'
sed -n '1,380p' src/renderer/lib/meshcoreLateRfHopEnrichment.ts

echo '--- message types and adapter ---'
rg -n "interface ChatMessage|type ChatMessage|messageRecordToChatMessage|storeId" src/renderer/lib -g '*.ts' -g '*.tsx' | head -160

echo '--- relevant adapter/type definitions ---'
ast-grep outline src/renderer/lib/types.ts --match 'ChatMessage' --view expanded 2>/dev/null || true
ast-grep outline src/renderer/lib/storeRecordAdapters.ts --match 'messageRecordToChatMessage' --view expanded 2>/dev/null || true

echo '--- parser payload definitions and GRP_TXT handling ---'
rg -n "GRP_TXT|channel|channelIdx|channelIndex|roomServerId|messageFingerprintHex" src/renderer/lib -g '*.ts' -g '*.tsx' | head -260

Repository: Colorado-Mesh/mesh-client

Length of output: 47980


🏁 Script executed:

#!/bin/bash
set -eu

echo '--- MeshCore parser files ---'
fd -i 'meshcore' src/renderer/lib | sort | head -120
rg -n "parseMeshCoreRf|parseMeshCoreRfPacket|payloadTypeNibble|MESHCORE_PAYLOAD_TYPE_GRP_TXT|channel_hash|channelHash|GRP_TXT" src/renderer/lib -g '*.ts' -g '*.tsx' | head -300

echo '--- MessageRecord and adapter details ---'
sed -n '80,145p' src/renderer/lib/storeRecordAdapters.ts
sed -n '350,435p' src/renderer/lib/types.ts
rg -n "interface MessageRecord|type MessageRecord" src/renderer/lib -g '*.ts' -g '*.tsx'

echo '--- UI key call sites ---'
rg -n "meshcoreChatHopUiKey|ChatRfHopLabel|isMeshcoreHopCorrected|viewMessages" src/renderer -g '*.ts' -g '*.tsx' | head -180

Repository: Colorado-Mesh/mesh-client

Length of output: 23596


🏁 Script executed:

#!/bin/bash
set -eu

echo '--- shared parser ---'
wc -l shared/meshcoreRfPacketParse.ts
ast-grep outline shared/meshcoreRfPacketParse.ts --match 'parseMeshCoreRfPacket' --view expanded 2>/dev/null || true
rg -n "GRP_TXT|channel|hash|fingerprint|payload" shared/meshcoreRfPacketParse.ts | head -180
sed -n '1,280p' shared/meshcoreRfPacketParse.ts

echo '--- MessageRecord definition ---'
rg -n "MessageRecord" src/renderer/stores/messageStore.ts src/renderer/lib/types.ts src -g '*.ts' -g '*.tsx' | head -100
sed -n '1,130p' src/renderer/stores/messageStore.ts

echo '--- ChatPanel and label implementation ---'
sed -n '900,945p' src/renderer/components/ChatPanel.tsx
sed -n '1,90p' src/renderer/components/ChatRfHopLabel.tsx

Repository: Colorado-Mesh/mesh-client

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -eu

echo '--- locate parser ---'
fd -t f -i 'meshcoreRfPacketParse.ts' . || true
fd -t d 'shared' . | head -40
rg -l "function parseMeshCoreRfPacket|parseMeshCoreRfPacket" . -g '*.ts' -g '*.tsx' | head -40

Repository: Colorado-Mesh/mesh-client

Length of output: 713


🏁 Script executed:

#!/bin/bash
set -eu

echo '--- parser ---'
wc -l src/shared/meshcoreRfPacketParse.ts
rg -n "GRP_TXT|channel|hash|fingerprint|payload" src/shared/meshcoreRfPacketParse.ts | head -220
sed -n '1,320p' src/shared/meshcoreRfPacketParse.ts

echo '--- MessageRecord definition ---'
rg -n "MessageRecord" src/renderer/stores/messageStore.ts src/renderer/lib/types.ts src -g '*.ts' -g '*.tsx' | head -100
sed -n '1,150p' src/renderer/stores/messageStore.ts

echo '--- ChatPanel and label implementation ---'
sed -n '920,945p' src/renderer/components/ChatPanel.tsx
sed -n '1,90p' src/renderer/components/ChatRfHopLabel.tsx

Repository: Colorado-Mesh/mesh-client

Length of output: 29216


🏁 Script executed:

#!/bin/bash
set -eu

echo '--- channel-hash decoding and configured-channel mapping ---'
rg -n "channel.?hash|hash.*channel|channel.*hash|derive.*channel|channelSecret|meshcoreConfiguredChannel|channelConfigs|secret" src shared -g '*.ts' -g '*.tsx' | head -320

echo '--- RF runtime dependencies and parsed context ---'
sed -n '1,115p' src/renderer/lib/meshcore/meshcoreRfRxRuntime.ts
sed -n '225,265p' src/renderer/lib/meshcore/meshcoreRfRxRuntime.ts
sed -n '630,665p' src/renderer/lib/meshcore/meshcoreRfRxRuntime.ts

echo '--- hop enrichment tests for intended fallback behavior ---'
sed -n '1,280p' src/renderer/lib/meshcoreLateRfHopEnrichment.test.ts

Repository: Colorado-Mesh/mesh-client

Length of output: 48482


🏁 Script executed:

#!/bin/bash
set -eu

echo '--- GRP_TXT raw payload handling ---'
sed -n '130,245p' src/renderer/lib/meshcoreRawPacketSender.ts
rg -n "GRP_TXT|channel hash|channel_hash|innerPayload|ciphertext|MAC" src/renderer/components/RawPacketLogPanel.tsx src/renderer/components/RawPacketLogPanel.test.tsx src/renderer/lib -g '*.ts' -g '*.tsx' | head -220

echo '--- MeshCore channel source/runtime wiring ---'
sed -n '1200,1270p' src/renderer/App.tsx
rg -n "meshcoreRfRx|attachMeshcoreConnSideEffects|MeshcoreRfRxDeps|channelsRef|channel.*Ref" src/renderer/runtime/useMeshcoreRuntime.ts src/renderer/lib/meshcore -g '*.ts' -g '*.tsx' | head -180

Repository: Colorado-Mesh/mesh-client

Length of output: 19830


🏁 Script executed:

#!/bin/bash
set -eu

sed -n '240,350p' src/renderer/components/RawPacketLogPanel.tsx
rg -n "channelHash|channel_hash|hashHex|bytesToHex|hash.*secret|secret.*hash|sha|HMAC|SHA" src/renderer/components/RawPacketLogPanel.tsx src/renderer/lib src/shared -g '*.ts' -g '*.tsx' | head -240

Repository: Colorado-Mesh/mesh-client

Length of output: 33436


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from dataclasses import dataclass
from math import inf

ROOM = -2

`@dataclass`
class Candidate:
    store_id: str
    channel: int
    room_server_id: int | None
    timestamp: int
    rx_hops: int | None = None
    sender_id: int = 7
    status: str | None = None
    received_via: str | None = 'rf'

def is_broadcast(c):
    return c.channel >= 0 and c.room_server_id is None and c.channel != ROOM

def choose(candidates, now, window_ms, hop_count):
    best = None
    best_ts = -inf
    for c in reversed(candidates):
        if not is_broadcast(c): continue
        if c.status == 'sending': continue
        if c.received_via not in (None, 'rf', 'both', 'mqtt'): continue
        if abs(now - c.timestamp) > window_ms: continue
        if c.rx_hops is not None and c.rx_hops == hop_count: continue
        if best is None or c.timestamp > best_ts:
            best, best_ts = c, c.timestamp
    return best

now = 1_700_000_000_000
candidates = [
    Candidate('channel-0', 0, None, now - 200),
    Candidate('channel-1', 1, None, now - 100),
]
selected = choose(candidates, now, 1000, 4)
assert selected is not None and selected.store_id == 'channel-1'
print('fallback selected:', selected.store_id, 'channel=', selected.channel)
print('incoming channel identity is not an input to the selection function')
PY

Repository: Colorado-Mesh/mesh-client

Length of output: 344


🌐 Web query:

MeshCore protocol GRP_TXT channel_hash channel index mapping

💡 Result:

In the MeshCore protocol, the GRP_TXT (Group Text) payload uses a channel-specific hash to allow nodes to efficiently filter and identify relevant traffic [1][2]. The relationship between the channel, its secret, and this hash is defined as follows: Channel Secret and Hash Derivation 1. Channel Secret: Each group channel uses a 32-byte secret [3]. For private channels, this is a user-provided 16-byte pre-shared key (PSK) that is padded to 32 bytes with zeros [3][4]. For public hashtag channels (e.g., #mesh), the secret is derived deterministically from the channel name: SHA-256(name)[:16] (padded to 32 bytes) [5][6]. 2. Channel Hash: The channel_hash is a 1-byte identifier [5][2]. It is calculated as the first byte of the SHA-256 hash of the 16-byte channel secret (specifically, the unpadded PSK) [3][7][6]: channel_hash = SHA-256(secret_16_bytes) [5][7][6] Channel Index Mapping The channel index (0-7) is used primarily for Companion Protocol communication between a MeshCore device and a connected companion (e.g., a phone via BLE) [8][9]. - Index 0: Reserved for public channels (no secret) [8][9]. - Indices 1-7: Available for private channels [8][9]. The channel index is not transmitted within the over-the-air GRP_TXT packet header itself; the over-the-air packet only contains the 1-byte channel_hash [7][2]. Devices map incoming GRP_TXT packets to local channels by comparing the packet's channel_hash against the pre-calculated channel_hash of all known local channels [10][4]. If a collision occurs (multiple channels sharing the same 1-byte hash), the device must attempt to verify the MAC or decrypt the payload using each potential candidate secret to identify the correct channel [10][4]. Packet Structure (GRP_TXT) The GRP_TXT payload (Payload Type 0x05) is structured as follows [7][2]: - channel_hash: 1 byte [2] - cipher_mac: 2 bytes (truncated HMAC-SHA256 of the ciphertext) [7][2] - ciphertext: Variable length (AES-128-ECB encrypted) [7][2]

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from dataclasses import dataclass
from math import inf
from typing import Optional

ROOM = -2

`@dataclass`
class Candidate:
    store_id: str
    channel: int
    room_server_id: Optional[int]
    timestamp: int
    rx_hops: Optional[int] = None
    sender_id: int = 7
    status: Optional[str] = None
    received_via: Optional[str] = 'rf'

def is_broadcast(c):
    return c.channel >= 0 and c.room_server_id is None and c.channel != ROOM

def choose(candidates, now, window_ms, hop_count):
    best = None
    best_ts = -inf
    for c in reversed(candidates):
        if not is_broadcast(c):
            continue
        if c.status == 'sending':
            continue
        if c.received_via not in (None, 'rf', 'both', 'mqtt'):
            continue
        if abs(now - c.timestamp) > window_ms:
            continue
        if c.rx_hops is not None and c.rx_hops == hop_count:
            continue
        if best is None or c.timestamp > best_ts:
            best, best_ts = c, c.timestamp
    return best

now = 1700000000000
candidates = [
    Candidate('channel-0', 0, None, now - 200),
    Candidate('channel-1', 1, None, now - 100),
]
selected = choose(candidates, now, 1000, 4)
assert selected is not None and selected.store_id == 'channel-1'
print('fallback selected:', selected.store_id, 'channel=', selected.channel)
print('incoming channel identity is not an input to the selection function')
PY

Repository: Colorado-Mesh/mesh-client

Length of output: 271


Avoid ambiguous GRP_TXT hop corrections.

GRP_TXT carries a one-byte channel hash, not a channel index. The parser exposes it only through innerPayload[0], and meshcoreRfRxRuntime.ts does not pass it to enrichment. When fingerprint matching fails, the fallback selects the newest eligible broadcast message across all channels and can persist the wrong hop count.

Resolve the hash only when it maps to one unambiguous configured channel. Otherwise, skip the GRP_TXT fallback. Keep fingerprint matches unchanged.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/lib/meshcoreLateRfHopEnrichment.ts` around lines 137 - 139, The
GRP_TXT fallback in isBroadcastChannelCandidate must not treat the payload hash
as a channel index. Resolve innerPayload[0] against configured channels and
apply the fallback only when exactly one channel matches; skip it when there is
no match or multiple matches. Preserve existing fingerprint-match behavior and
avoid relying on meshcoreRfRxRuntime.ts to provide the hash.

Match TXT_MSG raw-log hops by event sender or fingerprint so interleaved
DMs cannot steal counts, and add vitest-axe coverage for ChatRfHopLabel.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/renderer/components/ChatRfHopLabel.test.tsx`:
- Around line 46-47: Update the production color used by ChatRfHopLabel or its
theme token so the resolved default color contrasts sufficiently with the
`#1e293b` background for small text, then keep the test fixture aligned with that
production value and verify axe reports no violations.

In `@src/renderer/lib/meshcoreRawPacketCorrelate.ts`:
- Around line 27-50: Update meshcoreFindRecentTxtMsgRawPacket and its session
state to track selected rawPacketsRef rows with a bounded, session-scoped
consumed-row marker, and skip marked rows during correlation so one RF row
cannot be reused by multiple DMs. Ensure fromNodeId 0 does not fall back to rows
from another sender by requiring a valid sender or fingerprint match before
selecting a row. Add tests covering duplicate same-sender DMs and the fromNodeId
0 cross-sender case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Pro Plus

Run ID: c9c89029-ec62-48ba-906d-1faf5aea6894

📥 Commits

Reviewing files that changed from the base of the PR and between f3f5309 and 7ca82e9.

📒 Files selected for processing (5)
  • src/renderer/components/ChatRfHopLabel.test.tsx
  • src/renderer/lib/ingest/meshcoreIngest.test.ts
  • src/renderer/lib/ingest/meshcoreIngest.ts
  • src/renderer/lib/meshcoreRawPacketCorrelate.test.ts
  • src/renderer/lib/meshcoreRawPacketCorrelate.ts

Comment thread src/renderer/components/ChatRfHopLabel.test.tsx Outdated
Comment thread src/renderer/lib/meshcoreRawPacketCorrelate.ts
Use gray-400 for small hop text on slate chat chrome, and consume matched
TXT_MSG raw rows so one RF packet cannot enrich multiple DMs (no from=0 fallback).
@rinchen
rinchen merged commit 61de317 into main Aug 1, 2026
12 checks passed
@rinchen
rinchen deleted the hops branch August 1, 2026 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant