Skip to content

Expose the chat a message belongs to and match own messages by chat - #18

Merged
mattt merged 2 commits into
mattt:mainfrom
patp:message-chat
Sep 2, 2026
Merged

mattt merged 2 commits into
mattt:mainfrom
patp:message-chat

Conversation

@patp

@patp patp commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Problem

Messages the current user sends from another device (iPhone, iPad — synced through iCloud) land in chat.db with message.handle_id = 0. The participantHandles predicate only looks at message.handle_id, so those messages are never returned when fetching a conversation with someone, and Message gives callers no way to tell which chat such a message belongs to.

On my own database that is most of what I send: of 2 610 outgoing messages over six months, only 715 carry a handle; 1 660 are 1:1 messages with handle_id = 0. The only thing tying them to a conversation is chat_message_join.

Changes

  • Message.chatID: Chat.ID? — resolved through chat_message_join. When the predicate already joins the chat tables (.chatID), the joined chat is reported; otherwise a correlated subquery looks the chat up per message (it hits chat_message_join_idx_message_id_only, checked with EXPLAIN QUERY PLAN on a real database).
  • MessagePredicate.participantHandles also matches messages from the current user that belong to a chat with any of the given handles (is_from_me = 1 + chat_message_join/chat_handle_join). Inbound messages keep the previous semantics — only the given senders — so what other people wrote in a shared group chat is still excluded.
  • ChatPredicate.id(Chat.ID) — look a chat up by identifier, e.g. from a message's chatID. The typed request API had no way to do that.
  • Tests: chatID on both query paths and for a message linked to no chat; a from-me message with handle_id = 0 in a chat; the three participant cases (handle in one chat, in both chats, other member's messages excluded); chat lookup by id. Existing expectations that only counted inbound messages were updated (testFetchMessagesByParticipant, testFetchMessages, testMessagePredicateComposition).
  • README: a note on the predicate semantics and a "Fetching Chats" example.

Message's memberwise initializer gains a chatID: parameter (it is internal, so no public API change); the Codable representation gains an optional chatID key.

Why

I hit this through iMCP's messages_fetch: a client can't thread the user's own replies, because most of them come back with neither a handle nor a conversation. A follow-up PR there uses chatID and ChatPredicate.id to report the conversation on each message.

Messages the current user sends from another device are synced to
chat.db with handle_id = 0, so the participantHandles predicate (which
only looked at message.handle_id) never returned them, and callers had
no way to tell which conversation such a message belonged to.

- Add Message.chatID, resolved through chat_message_join (the joined
  chat when the predicate already joins the chat tables, a per-message
  lookup otherwise).
- participantHandles now also matches messages from the current user
  that belong to a chat with any of the given handles.
- Add ChatPredicate.id to look a chat up by identifier.
- Tests for all three, including a from-me message with handle_id = 0.

Copilot AI 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.

🟢 Approval recommended

The implementation is consistent with the described behavior and includes focused coverage for both query paths and edge cases.

Pull request overview

Adds chat context to messages and improves participant-based matching for synced outgoing messages.

Changes:

  • Exposes Message.chatID and supports chat lookup by ID.
  • Includes the user’s outgoing messages when filtering by chat participants.
  • Adds documentation and comprehensive database tests.
File summaries
File Description
Sources/iMessage/Message.swift Adds optional chat identification.
Sources/iMessage/FetchRequest.swift Extends message and chat predicates.
Sources/iMessage/Database.swift Resolves chat IDs and updates SQL filtering.
Tests/iMessageTests/DatabaseTests.swift Covers new query behavior and edge cases.
README.md Documents participant semantics and chat lookup.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@mattt

mattt commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Thanks for this, @patp. Matching own messages through the chat is the right fix for the synced-from-iPhone case. Merging now.

@mattt
mattt merged commit b591805 into mattt:main Sep 2, 2026
3 checks passed
patp added a commit to patp/iMCP that referenced this pull request Sep 4, 2026
Messages the user sends from another device are synced to chat.db
without a sender handle, so until now most of the user's own messages
came back with sender "me" and nothing to tie them to a conversation;
a client could not thread them or tell who they were sent to.

Each message now carries an "isPartOf" Conversation with the chat
identifier, its display name (group chats) and its participants.
Chats are looked up once per call and shared by their messages.

Relies on Message.chatID and ChatPredicate.id from Madrid 0.5.0
(mattt/Madrid#18), which the project already depends on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYn7auXKTFrqS1PSR6rQKn
mattt added a commit to mattt/iMCP that referenced this pull request Sep 25, 2026
Messages the user sends from another device are synced to chat.db
without a sender handle, so until now most of the user's own messages
came back with sender "me" and nothing to tie them to a conversation;
a client could not thread them or tell who they were sent to.

Each message now carries an "isPartOf" Conversation with the chat
identifier, its display name (group chats) and its participants.
Chats are looked up once per call and shared by their messages.

Relies on Message.chatID and ChatPredicate.id from Madrid 0.5.0
(mattt/Madrid#18), which the project already depends on.

Co-authored-by: patp <boumagent@gmail.com>
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.

3 participants