Skip to content

feat(chat): render tombstones and the edited marker - #1393

Merged
bmc08gt merged 2 commits into
code/cashfrom
feat/chat-message-presentation
Sep 2, 2026
Merged

feat(chat): render tombstones and the edited marker#1393
bmc08gt merged 2 commits into
code/cashfrom
feat/chat-message-presentation

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #1392.

Gives a deleted message and an edited message something to look like. Still nothing that triggers
either — the surfaces that do land in the next PR.

A tombstone reuses the plain-text bubble: same item key, same content type, only the body and the
styling differ. That makes a delete an in-place update of the row already on screen rather than a
remove-and-insert, which is what a distinct content type would have cost. It reads "You deleted this
message" or "This message was deleted" depending on who deleted it, and the chat list preview shows
the same for the last message.

The "Edited" marker is pinned to the bubble's bottom-trailing corner, tucked beside the timestamp
rather than trailing the text. Reserving the space is an InlineTextContent placeholder at the end
of the body, so the last line wraps around the marker instead of running under it. The placeholder
only reserves space; the marker itself is a real Text, so it stays in the accessibility tree and
is read out with the message.

Receipts are suppressed on a tombstone — a deleted message has no delivery state worth showing.

Groundwork for editing and deleting chat messages. No UI yet.

Two seams, both in `:apps:flipcash:shared:chat`:

`resolveCapabilities` answers what the viewer may do to a message — copy,
reply, edit, delete — from the message and a `MessagePolicy`. Call sites read
the set; they do not re-derive it. Group roles become another argument here
rather than another branch at each menu. Cash is deliberately neither editable
nor deletable, and an unconfirmed message resolves to nothing because
`expected_event_sequence` is validated `>= 1`, so no valid request can be built
for one.

`PendingMutation` is the overlay shown between sending a mutation and the
server answering. It lives in memory, never in the database, so a rollback is
dropping a map entry rather than a compensating write. `ChatMessage.applying`
retires an overlay as soon as the stored row carries a higher `eventSequence`,
which lets `MessagingDelegate` persist the server's answer before it drops the
overlay without flashing the pre-edit text in between.

A conflict does not retry. The edit was written against a version that no
longer exists, so re-applying it would clobber whoever got there first;
instead the message is re-read and stored, and the caller is left to tell the
user.

Transport was already in place — `ChatMessagingController.editMessage` and
`deleteMessage`, the `Deleted` content type, and the `event_sequence` and
`last_edited_ts_epoch_ms` columns all exist. No proto or schema change.
Deleting a message is an in-place update of the row already on screen, not a
remove and insert: a tombstone shares the text bubble's `itemContentType` and
its key, and renders through the same `TextBubble` in a muted italic variant
with no link detection and nothing selectable. Copy follows the deleter — "You
deleted this message" or "This message was deleted"; a null `deletedBy` is a
moderation removal and reads as someone else's.

The "Edited" marker cannot ride the receipt line: the receipt attaches only to
the newest confirmed self message, and most edited messages will not have one.
It takes WhatsApp's position instead, pinned to the bubble's bottom-trailing
corner, with the body reserving exactly its width through an empty inline
placeholder in the text flow. The placeholder sits on the last line where
there is room and wraps onto its own line where there isn't, without dragging
the last word with it. It holds no glyphs, so selection and the accessibility
tree see only the marker itself.

A tombstone no longer anchors a receipt — captioning a deleted bubble "Read"
describes the delivery of something that is not there any more — but it still
belongs to its sender's group for bubble shaping, so the receipt search steps
over it rather than treating it as a group boundary.

The conversation list says "Message deleted" rather than going blank, which
would otherwise read as a conversation with no messages at all.
@bmc08gt bmc08gt self-assigned this Sep 2, 2026
@github-actions github-actions Bot added the type: feature New functionality label Sep 2, 2026
@bmc08gt
bmc08gt changed the base branch from feat/chat-message-capabilities to code/cash September 2, 2026 22:12
@bmc08gt
bmc08gt merged commit b4f4762 into code/cash Sep 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feature New functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant