feat(messages): search conversations by message content (#3922)#3931
Conversation
The Messages conversation-list filter previously matched only on the conversation partner's long name, short name, or node ID. Issue #3922 asks for the ability to find a past DM by what was actually said. This extends that same filter so a term also matches conversations whose direct-message history contains the term in the message body — a simple case-insensitive substring match over the already in-memory message list, complementing (not duplicating) the exhaustive server-side Ctrl+K search modal. The English placeholder is updated to "Filter nodes or messages..." for discoverability. The match logic is extracted into a pure, unit-tested helper (getMessageContentMatchNodeIds) that mirrors getDMMessages's DM predicate (text messages, channel === -1, non-broadcast) and is precomputed once per filter/message change so both node-list renderers do an O(1) membership check. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n
|
Claude finished @Yeraze's task —— View job PR Review
Overall this is a clean, well-scoped addition. The new utility is properly isolated, the Minor IssuesMagic number for portnum ( if (msg.channel !== -1 || msg.portnum !== 1) continue;The project has a Broadcast check is hardcoded ( if (msg.to === '!ffffffff') continue;Same situation as portnum — this mirrors the existing Code QualityDuplication of DM predicate logic The filter in
Both node-list renderers are updated consistently — the main filtered list at line ~762 and the PerformanceFor the scale MeshMonitor operates at (hundreds to low-thousands of in-memory messages), iterating the full message array on each filter keystroke via SecurityNo concerns — purely client-side substring match over already-loaded data, no new API surface, no user-controlled data reaches the backend. Test Coverage10 test cases cover all the key paths well:
One gap worth noting: there's no test for the case where i18n
Summary: This is a solid, minimally invasive change. The identified items are minor — the lack of a |
… DMs (#3922) (#3935) 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. Claude-Session: https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Closes #3922.
The Messages conversation-list filter previously matched only on the conversation partner's long name / short name / node ID — there was no way to find a past DM by what was said. This PR extends that same filter so a term also matches conversations whose direct-message history contains the term in the message body.
This is intentionally scoped to complement, not duplicate, the existing global full-text search modal (
Ctrl+K/SearchModal, added in #d9229627), which already performs exhaustive server-side search across all channels and DMs. The in-tab filter is a fast, client-side substring match over the messages already held in memory — exactly the "simpleLIKE/ILIKEsubstring match to start" the issue proposed, surfaced on the field users naturally reach for.What changed
src/utils/messageContentFilter.ts→getMessageContentMatchNodeIds(messages, filter, minLength = 2). Returns the set of node IDs whose DM history contains the term. It mirrorsgetDMMessages's predicate (text messages,channel === -1, non-broadcast), matches case-insensitively, and skips matching until the trimmed term is ≥ 2 chars (to avoid matching nearly every conversation on a single keystroke).MessagesTab.tsx: precomputes the match set once per(messages, messagesNodeFilter)change viauseMemo, then both node-list renderers add a singleO(1)membership clause alongside the existing name/id checks.messages.filter_placeholderupdated from"Filter nodes..."→"Filter nodes or messages..."for discoverability. Other locales sync via Hosted Weblate.Testing
src/utils/messageContentFilter.test.ts(10 cases): case-insensitivity, substring matching, both-parties inclusion, min-length gating, and exclusion of broadcasts / channel messages / non-text portnums / empty text.success: true).eslinton changed files: 0 errors.tscon the changed files: no new type errors.Notes
🤖 Generated with Claude Code
https://claude.ai/code/session_01AphLfgUJWaFNAgU5UXdJ4n