- Shipped
- August 5, 2026 at 6:41 PM UTC
- Author
- Kamo
- Commit
- b10794f
Chat entries claimed no direction and attributed every message to its row's author. Social conversations record BOTH sides against a Kamo Member — the visitor's messages against the org's system member — so that labelled the lead's own messages as ours. Direction now comes from MediaObjSocialRef.directionId, and an inbound message is named after the remote contact rather than the system member that owns the row. Refs and contacts are batched per page; resolving either per row would have been two extra queries per chat message. MediaAssetUrls no longer writes during a read: it was back-filling an avatar's fileHash inside the timeline GET, once per author per page, for a value that is a pure function of the avatar id. It also re-read each avatar by id purely to see a subclass field, which was a query per row — the instance is already in hand. Adds TimelinecoderTest. Three pieces of code have to agree on where one page ends and the next begins — isBefore, the SQL predicate, and the merge comparator — and any disagreement duplicates or loses entries with no error. The test walks a batch that shares timestamps one entry at a time and asserts every entry is returned exactly once, in merge order.