Skip to content

feat(meshcore): extend in-tab conversation content filter to MeshCore DMs (#3922)#3935

Merged
Yeraze merged 1 commit into
mainfrom
feat/3922-meshcore-content-search
Jul 5, 2026
Merged

feat(meshcore): extend in-tab conversation content filter to MeshCore DMs (#3922)#3935
Yeraze merged 1 commit into
mainfrom
feat/3922-meshcore-content-search

Conversation

@Yeraze

@Yeraze Yeraze commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

Extends the in-tab conversation-list content filter — merged for Meshtastic in PR #3931 (issue #3922) — to the MeshCore Direct Messages view.

Before #3931, the Messages conversation-list filter matched only on the partner's name / node ID. #3931 made the Meshtastic DM filter also match on what was said (message body), but scoped it to the active Meshtastic source and excluded MeshCore. This PR brings the same behavior to MeshCore conversations so filtering the MeshCore DM list by content works the same way.

What changed

  • New pure helper getMeshCoreMessageContentMatchKeys() added alongside the existing getMessageContentMatchNodeIds() in src/utils/messageContentFilter.ts. It mirrors the MeshCore DM predicate used by dmPeers/filtered in MeshCoreDirectMessagesView (skips messageType === 'room_post', skips channel pseudo-keys, requires a toPublicKey) and does a case-insensitive substring match over the in-memory message list. It accepts optional canonicalize and isChannelKey hooks so the returned match keys align with the canonicalized peer keys the DM list already uses (inbound messages arrive with a short pubkey prefix).
  • Wired into MeshCoreDirectMessagesView.tsx: a messageContentMatchKeys useMemo (passing the view's existing canonicalize + isChannelPseudoKey) feeds the filteredPeers filter, which now also surfaces a peer when a DM body matches — in addition to the existing contact-name / public-key match. minLength default of 2 avoids matching nearly every conversation on a single character (mirrors the Meshtastic helper).
  • Placeholder updated "Search contacts…""Search contacts or messages…" (English only: inline defaultValue + a new meshcore.search_contacts key in public/locales/en.json). Other locales route through Weblate.

Design notes

  • The MeshCore message shape differs from Meshtastic (fromPublicKey/toPublicKey/text/messageType vs from/to/channel/portnum), so rather than force one signature, the helper is a sibling pure function in the same module, keeping both DM predicates faithful to their respective views.
  • Scope matches the Meshtastic behavior: DMs only (room/channel posts excluded), single active MeshCore source's in-memory messages. The exhaustive cross-source server-side search remains the global Ctrl+K modal (which already covers MeshCore).

Tests

  • Added helper unit tests mirroring messageContentFilter.test.ts (case-insensitivity, both-parties, substrings, room_post/channel/no-toPublicKey exclusion, empty text, aggregation, prefix canonicalization, custom minLength).
  • Added a MeshCoreDirectMessagesView integration test asserting a "pizza" body match surfaces the right contact while unrelated peers stay hidden.
  • Full Vitest suite: success, 7994 passed, 0 failures (JSON reporter). ESLint clean on changed files. vite build succeeds. Server tsc shows only pre-existing TelemetryChart.tsx errors (byte-identical on origin/main), none from this change.

Extends #3922 / follows up #3931.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n

… DMs (#3922)

PR #3931 (issue #3922) added an in-tab conversation-list filter that matches
message body content, but scoped it to Meshtastic DMs only and excluded
MeshCore. This extends the same behavior to the MeshCore Direct Messages view.

- Add pure helper getMeshCoreMessageContentMatchKeys() alongside the existing
  getMessageContentMatchNodeIds() in src/utils/messageContentFilter.ts. It
  mirrors the MeshCore DM predicate (excludes room_post + channel pseudo-keys,
  requires a toPublicKey) and accepts optional canonicalize / isChannelKey
  hooks so match keys line up with the canonicalized peer keys in the DM list.
- Wire it into MeshCoreDirectMessagesView's filteredPeers useMemo so the
  contact-list search also surfaces conversations by what was said, not just
  the contact name / public key.
- Update the search placeholder ("Search contacts…" -> "Search contacts or
  messages…"), English only (inline default + new en.json key); other locales
  route through Weblate.
- Add Vitest coverage: helper unit tests mirroring messageContentFilter.test.ts
  plus a DM-view integration test asserting a content match surfaces the right
  contact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

Claude finished @Yeraze's task —— View job


PR Review — feat(meshcore): extend in-tab conversation content filter to MeshCore DMs (#3922)

Summary: This PR extends the Meshtastic DM content filter (from PR #3931) to the MeshCore Direct Messages view, enabling users to search conversations by message body text in addition to contact names/keys.


Review Checklist

  • Gather context and understand the request
  • Read changed files
  • Review code quality and best practices
  • Check for potential bugs or issues
  • Review performance considerations
  • Check security concerns
  • Evaluate test coverage

Code Quality & Best Practices ✅

The implementation is clean and well-structured. A few observations:

src/utils/messageContentFilter.ts

  • The new getMeshCoreMessageContentMatchKeys() function mirrors the existing getMessageContentMatchNodeIds() design well — pure function, O(n) single-pass, returns a Set for O(1) caller-side lookup.
  • The MeshCoreContentMessage interface is a good decoupling pattern — it uses only the fields the filter actually inspects, keeping the helper free of React hook dependencies.
  • The options object pattern ({ canonicalize, isChannelKey, minLength }) with sensible defaults is idiomatic and easy to extend.

One minor style note: the if (msg.fromPublicKey) guard on line 120 is technically redundant since fromPublicKey is a required field (non-optional in MeshCoreContentMessage). This is harmless but slightly inconsistent with line 121 which has a valid guard on the optional toPublicKey. Not a bug — just a minor observation.

src/components/MeshCore/MeshCoreDirectMessagesView.tsx

  • The messageContentMatchKeys useMemo (lines 230–237) is correctly positioned — it depends on messages, searchQuery, and canonicalize, all of which are valid memo dependencies.
  • Integration into filteredPeers (line 256) is clean: messageContentMatchKeys.has(key) is an O(1) lookup, so the per-peer filter remains efficient even with large message histories.
  • The canonicalize function is passed as-is from its own useMemo, which means messageContentMatchKeys correctly invalidates when the contacts list changes (since canonicalize referentially changes when contacts/contactsByKey change).

Potential edge case — canonicalize stability: The canonicalize memo (lines 144–153) iterates contacts on every call. This is fine, but note that passing it directly into getMeshCoreMessageContentMatchKeys means the full message scan re-runs on every contacts list change (not just search query changes). In practice, contacts update infrequently, so this is fine at typical mesh scales, but worth noting if users ever have very large contact/message lists.


Potential Bugs 🔍

No outright bugs found. One correctness note:

messageContentFilter.ts line 116: The channel exclusion checks both toPublicKey and fromPublicKey:

if (isChannelKey(msg.toPublicKey) || isChannelKey(msg.fromPublicKey)) continue;

This is correct and matches the dmPeers predicate in the view (line 175 of the component). Good parity.

Test line 592 (MeshCoreDirectMessagesView.test.tsx): The integration test uses self = 'self'.padEnd(64, '0') which matches makeStatus().localNode.publicKey. This is correct and ensures the keysMatch(m.fromPublicKey, selfKey) path fires (outbound message from self to Carl), so the filter correctly surfaces Carl via m.toPublicKey. Well constructed.


Performance ✅

  • Content match precomputation via useMemo avoids rescanning messages on every render — the expensive O(n) scan only runs when messages, searchQuery, or canonicalize changes.
  • The minLength: 2 default prevents near-universal matches on single characters (consistent with the Meshtastic helper).
  • No performance concerns at typical mesh scales (~hundreds of messages).

Security ✅

  • This is a pure client-side in-memory filter — no user-supplied input reaches the network or database layer.
  • No XSS risk: the filter only returns a Set<string> of keys; the display values come from the existing trusted contacts array.
  • The isChannelKey hook prevents synthetic pseudo-keys from leaking into results.

Test Coverage ✅

Test coverage is thorough:

Unit tests (messageContentFilter.test.ts) — 9 cases for getMeshCoreMessageContentMatchKeys:

  • minLength enforcement (empty, whitespace, single char)
  • Case-insensitive matching and both-parties return
  • Substring matching
  • room_post exclusion
  • Missing toPublicKey exclusion
  • Channel pseudo-key exclusion via isChannelKey
  • Empty/undefined text safety
  • Multi-conversation aggregation
  • Prefix canonicalization via canonicalize
  • Custom minLength

Integration test (MeshCoreDirectMessagesView.test.tsx line 587–611):

  • Verifies a "pizza" body match surfaces Companion Carl while Rita/Sam/Cora stay hidden — covers the full filter stack end-to-end.

One gap worth noting: There's no test for the scenario where a contact appears via both content match AND name match simultaneously (double-add to filteredPeers). However, since filteredPeers is filtered from dmPeers (a deduped array), this can't produce duplicates — the existing filter logic is sound, so this isn't a real risk, just an untested path.


Minor Suggestions (Non-blocking)

  1. messageContentFilter.ts line 120 — the if (msg.fromPublicKey) guard is technically dead code since fromPublicKey is required in the interface. Consider removing it for clarity:

    // Before
    if (msg.fromPublicKey) matchKeys.add(canonicalize(msg.fromPublicKey));
    // After
    matchKeys.add(canonicalize(msg.fromPublicKey));

    This is a cosmetic nit and not worth a change request.

  2. Locale key namingmeshcore.search_contacts is reused for the updated placeholder text "Search contacts or messages…". The key name no longer fully describes its value. A rename like meshcore.search_contacts_or_messages would be more precise, but given that Weblate translation workflows may already be picking up this key, the rename cost likely outweighs the benefit.


Verdict

LGTM. The implementation is correct, consistent with the existing Meshtastic DM filter pattern, well-tested, and has no security or performance concerns. The PR description accurately describes the changes and design tradeoffs. The minor items above are cosmetic and non-blocking.

@Yeraze Yeraze merged commit 5e28cbf into main Jul 5, 2026
19 checks passed
@Yeraze Yeraze deleted the feat/3922-meshcore-content-search branch July 5, 2026 17:51
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