The outbound half of the three controls whose codecs were already back. The
long-press menu gains Edit, Pin/Unpin and Forward; the thread gains a pinned
banner; a bubble says `edited` and `Forwarded`.
🔴 An edit that silently replaces the words is worse than the typo. The reader
has no way to tell a correction from their own misremembering, and no way to
know the message they replied to is not the message that is there now. So the
bubble is marked, on both ends, and the edit sheet says plainly that everyone in
the conversation sees the new wording and a mark on their copy.
🔴 A forward carries the words and NOTHING about where they came from — no
author, no thread, no quote — and the test asserts the ABSENCE of a reference
rather than only the presence of the flag. The obvious "improvement", carrying
a reference so the reader can see the original, would republish a private
message's author to somebody who was never in that conversation. It also gets a
NEW message id: reusing the original's would make the copy a target for edits
and reactions aimed at the original, one of which would silently rewrite the
other.
⚠️ Only your own message can be edited, checked in `Session` and not only in
the menu. The receiving side enforces the same rule independently, so neither
end is trusting the other. Anyone may PIN, by contrast: a pin is a shared
banner rather than a permission, and restricting it to the author would mean the
useful message in a group is exactly the one nobody but its writer can pin.
⚠️ Edits and pins are applied locally first and rolled back if the send fails,
the same shape `react` already uses. Waiting on a round trip to see your own
correction appear makes it feel broken on a slow connection; leaving one that
exists only on this phone is worse, because you would believe you had fixed it.
⚠️ A forward does not carry an attachment yet. Re-sending a blob means
re-uploading it under a new token, and passing on the ORIGINAL token would hand
the new recipient a capability to a file in a conversation they are not part of.
Text only until that upload exists.
⚠️ The pinned banner resolves the id against the lines on screen, so a pinned
message that was since deleted shows nothing rather than a stale copy. Clearing
a thread clears its pin.
Two things found while wiring it: `Content.text` has no `groupId` parameter, so
a group forward needs `groupText` — a compile error rather than a silent drop.
And the edit/forward state is hoisted above the message list, because a row's
own `remember` dies when it scrolls out and an edit box vanishing mid-typing is
the worst way to lose a correction.
9 more unit tests. App builds, every JVM suite green. The three PARITY rows are
now ✅.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>