Files
amethyst/amethyst
Claude 44e112fcdc feat(cordn): close the rest of the gap to Marmot's chat
Six items, all of which the room either lacked outright or showed as a
truncated label.

**Quoted replies.** A reply used to carry "Replying to: <first 60
characters>", which said a reply existed without ever saying who it
answered — in a group, most of what you need. `CordnQuotedMessage` names
the author in their own pubkey-derived colour behind an accent bar, and
is used in two places so what you are answering looks the same while you
write it as it does once it is sent.

**Jump to the quoted message.** Tapping that quote scrolls to what it
answers and flashes it, through ChatBubbleLayout's shouldHighlight —
which the bubble already supported and nothing drove. A quote whose
target is older than what is loaded flashes nothing rather than
scrolling somewhere arbitrary.

**Who reacted.** Long-pressing a chip opens the senders per emoji. The
fold keys reactions to a set of pubkeys, so the sheet is the whole truth
the room holds — there is no separate receipt and no count that could
disagree with the list under it.

**Unread divider.** From a cursor snapshotted when the room opens:
markRead() runs on open, so a divider read from the live cursor would
erase itself at the moment it became useful. Own traffic is excluded, to
match what unreadCount already counts — a message of yours echoing back
is not news. It draws below the day separator, since in a reversed list
the later-emitted row draws higher.

**Faces and a member count in the top bar**, with the whole title as the
way in to group info, as in every other group chat.

**A confirm step before sending a file.** Attaching was irreversible the
instant the picker closed: no preview, no caption, no way back from the
wrong file — and a cordn attachment is encrypted, uploaded and announced
to the room in one action that cannot be recalled. The dialog previews
images (bounds-first subsampled decode, on IO), takes a caption that
rides as the message's own content so an attachment with something
written about it is one message rather than two, and holds itself open
on failure so retrying is one tap rather than the picker again.

Its progress bar is indeterminate on purpose: the upload is a single
suspend call that encrypts and posts and reports nothing in between, so
a percentage would be invented. A stage string written for a two-phase
label was dropped rather than wired to a phase that isn't observable.

It also fixes the filename. `uri.lastPathSegment` is a document id on a
content:// URI, not a name — it is what every cordn attachment has been
named. The dialog resolves the real one through OpenableColumns, off the
main thread, and hands it to the upload.

Left alone deliberately: zaps and the shared note-action inventory need
a Note, which a cordn room may not have. Un-reacting still needs the
reaction's own envelope id, which the annotation index does not keep —
protocol work, not UI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012BfD4txdnsaPRXmNXbup9n
2026-09-24 13:35:04 +00:00
..
2024-06-24 14:13:55 -04:00