feat(chat): resolve message capabilities and stage edits optimistically - #1392
Merged
Conversation
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.
This was referenced Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Editing and deleting a chat message need three things the Android transcript did not have: a single
answer to what the viewer may do to a message, a way to show the result before the server confirms
it, and a mutation path on the coordinator. This adds all three; nothing calls them yet.
resolveCapabilitiesis the one place edit and delete availability is decided. It takes the messageand a
MessagePolicy(today just the edit window) and returns aSet<MessageCapability>. Grouproles, when they arrive, become another input to that function rather than another branch at every
menu site — which is why the policy is a parameter and not a constant.
PendingMutationis the optimistic overlay.ChatMessage.applying(mutation)returns the message asthe user should already see it, and it no-ops once
eventSequencehas moved past the sequence themutation was staged against, so a server update that has already landed wins over a stale local one.
MessagingDelegatestages a mutation before the RPC, reconciles it against the response, and dropsit on failure, so a rejected edit rolls back to the stored text rather than sticking.
The RPCs and the storage this needs were already in place —
Deletedcontent,event_sequenceandlast_edited_ts_epoch_msall exist in the published contract package and in Room — so there is noproto or migration change here.
Rules the tests pin down:
eventSequence == 0) offers nothing until it lands