Send the quote with a reply, not just the pointer

FeatureMediaService
Shipped
August 30, 2026 at 7:40 PM UTC
Author
Kamo
Commit
055c92d

media_objs_msg.parent_id has been there all along and this endpoint has accepted parentId on the way in since threading was modelled, but a reply crossed the wire carrying only the id of what it answered. That is enough to thread it and not enough to draw it: the quote above a reply names an author and shows a line of what they wrote. A client could resolve that from the messages it already holds, and for most replies it would — people answer what they can still see. It is the rest that decides this. A reply to something older than the loaded page, a conversation reopened onto its newest page, a support thread running to hundreds of messages: in all of those the parent is not on the client at all, and a quote that renders empty in exactly the cases the feature exists for is not worth having. So parentSenderName, parentMessage and parentIsRemoved travel with the reply. It is close to free. Both history queries already LEFT JOIN FETCH msg.parent, so the parent's body is in the persistence context before ChatReplyPreviewService is called. Only the AUTHOR needs fetching, and only for parents that are not themselves on the page — a page whose replies point within itself issues no query at all. That is the shape **************** already established. Three things the naive version gets wrong: The send response omitted parentId entirely. Accepted on the way in, echoed nowhere on the way out — so the one window guaranteed to be showing the message the instant it is sent was the one window with nothing to quote from. It now carries the same preview the live frame does, and both are built from one resolution so the two sides of the conversation cannot disagree. On a public chat the visitor's messages are stored under the org's SYSTEM member, which is usually the owner answering them. Naming a parent's author from that member would put the agent's name on the visitor's own words — the same trap the sender name is already guarded against, so the preview takes the same visitor override, on history and on the live frame. Truncation cuts on a code point. substring counts UTF-16 units, and every emoji is a surrogate pair; a boundary landing inside one yields a lone surrogate, which is not a character and does not survive JSON intact. findAllByIdIn deliberately does not filter isRemoved: a reply to a message that was later unsent still draws a quote, and the quote's whole job there is to say the original is gone.

All changes

Like what you see shipping?

Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.

Start Free ForeverView Pricing