Skip to content

fix(chat): render system messages in the transcript - #1054

Merged
bmc08gt merged 5 commits into
mainfrom
fix/chat-system-messages
Oct 14, 2026
Merged

bmc08gt merged 5 commits into
mainfrom
fix/chat-system-messages

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator

Private group chats now carry server system messages (SystemContent, e.g. "A new member joined the group"). iOS threw them away when they arrived (case .system, .none: return nil in ConversationMessage), so they never reached the database, the transcript or the chat list.

They're now kept as Content.system(fallbackText:), stored as content kind 6, which bumps Database.schemaVersion to 51.

In the transcript they render as ChatSystemMessageCell: an info.circle glyph and the text, centred, in appTextCaption and textSecondary, wrapping at 250pt. There's no bubble, sender, timestamp or gestures; MessageCapability treats .system like .deleted. Design is option D in the private groups iteration section, node 11721:879.

How a system row behaves in the transcript:

  • It breaks author grouping above and below it, and is keyed sys-<stableID>.
  • It never takes the unread divider. inboundMessageCount and firstInboundMessageID already exclude kind 6; the in-memory divider placement in ChatItem+Conversation now skips system rows too, with a new UnreadDividerTests case.
  • It doesn't buzz on arrival (buzzesOnReceive), but latestConfirmedMessage still includes it, so mark-read sees it.

Blank text is trimmed to "" and stored, so read state stays intact, but it renders no row.

Chat list: a system message previews as its text with no sender prefix, and the latest-message query skips system messages with empty text.

The encrypted path (ChatSeal) still drops .system; that's unchanged here.

Includes the plan in .claude/plans/2026-10-14-chat-system-messages.md.

Android counterpart: code-payments/code-android-app#1769.

Server SystemContent messages map to Content.system(fallbackText:), stored as kind 6
(schemaVersion 51). Blank text is stored as "" and draws no row; the chat-list preview
skips blank notices. A notice never counts as unread, never anchors a receipt, and does
not trigger the receive haptic.
The divider headed the first non-self message after the read-through, so a system
message in the unread run could take it. Place it on the first real message instead,
comparing against the nearest real message above.
@bmc08gt bmc08gt self-assigned this Oct 14, 2026
@bmc08gt
bmc08gt merged commit 451e075 into main Oct 14, 2026
2 of 3 checks passed
bmc08gt added a commit that referenced this pull request Oct 14, 2026
* commit '16a24ad1eb3eeac2097eac72d32150ba2c368f18':
  fix(ui): draw UIKit caption in Avenir Medium like SwiftUI (#1064)
  fix(members): host chip and member count label to match Android (#1058)
  chore(chat): log the attach lifecycle the same way in both hosts (#1062)
  refactor(chat): decide attach close events in one place (#1061)
  refactor(chat): measure the attach card's frame once (#1060)
  refactor(chat): read the attach menu rows live over the keyboard (#1059)
  refactor(chat): build the attach actions once for both hosts (#1056)
  fix(chat): render system messages in the transcript (#1054)

# Conflicts:
#	Flipcash/Core/Screens/Conversation/ChatScreenRepresentable.swift
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