What's improved
readCtwaAttributionFromMessagesnow enforcesdirection === "inbound"inside its predicate so the listConversations path (which pre-filters to inbound) and the single-conversation hydrator (which receives the full thread) cannot diverge. CTWA referrals only ever ride on inbound messages — an outbound row that ever started carrying a referral-shaped field (echo metadata, future templating) is now ignored at every call site.fetchAttributionEnrichmentForConversationis now gated on a cheap in-memory referral check insidehydrateConversationRow. Non-CTWA conversations (the vast majority) skip the extra Supabase round-trip on every mark-read / chat switch. CTWA conversations still get the full attribution merge end to end.
Notes
Pure performance + invariant tightening pass. No behavior change for users — the operator-only orange Meta-ad info card still pops up exactly when it should and stays sticky across read/unread cycles.