diff --git a/src/components/Conversation.jsx b/src/components/Conversation.jsx index ccc9583..c9a762f 100644 --- a/src/components/Conversation.jsx +++ b/src/components/Conversation.jsx @@ -11,18 +11,31 @@ import './Conversation.css' * right. * * WHAT IT CAN AND CANNOT SHOW, said plainly at the foot of the panel rather - * than left for someone to discover. Every message the workflow RECORDS is - * here: the customer's replies (customer_reply) and the replies written back - * (customer_answer). The three automatic sends are not. The quote goes out as - * a registered WhatsApp template, and the acknowledgement and the acceptance - * confirmation are composed inside trigger nodes and never written to a field. - * A thread that quietly omitted them would be worse than one that says which - * parts it holds. + * than left for someone to discover. The console can only show what the + * workflow RECORDS, so anything sent from inside a trigger node and never + * written to a field is invisible here however certainly the customer received + * it. The acceptance confirmation used to be exactly that — the panel showed + * nothing after "I confirm", which read as though we had ignored somebody + * agreeing to buy — so 90 writes it into customer_answer and it appears. + * + * Two sends remain absent by choice: the quote, which is a registered WhatsApp + * template rather than text we compose, and the "I have your message" receipt, + * which would put a line between every question and its answer. + * + * A thread that quietly omitted any of this would be worse than one that says + * which parts it holds. */ function turnsFrom(rows) { if (!Array.isArray(rows)) return [] const out = [] - const ordered = [...rows].sort((a, b) => String(a.created_at).localeCompare(String(b.created_at))) + // Sorted on (timestamp, id). One submission writes three rows in the same + // second — the trigger commit, its repeat, and the settle — so a timestamp + // alone leaves their order to the sort's stability and puts a reply before + // the message it answers whenever the two land in the same second. + const ordered = [...rows].sort((a, b) => { + const t = String(a.created_at).localeCompare(String(b.created_at)) + return t !== 0 ? t : (Number(a.id) || 0) - (Number(b.id) || 0) + }) for (const r of ordered) { const fields = Array.isArray(r.fields) && r.fields.length ? r.fields @@ -35,15 +48,17 @@ function turnsFrom(rows) { if (base === 'customer_answer') out.push({ side: 'us', text: v, at: r.created_at, key: r.id + '-out' }) } } - // One submission can be recorded more than once (the trigger commit and the - // settle both carry the payload), so the same sentence would print twice. - // De-duped on side + text rather than on the row id for that reason. - const seen = new Set() - return out.filter((t) => { - const k = t.side + ' ' + t.text - if (seen.has(k)) return false - seen.add(k) - return true + // One submission is recorded three times — the trigger commit, its repeat, + // and the settle — so the same sentence would print three times. + // + // De-duped against the PREVIOUS turn only, not against the whole thread. A + // customer who asks the same thing twice because the first went unanswered + // has said it twice, and a thread that silently showed it once would hide + // exactly the impatience an operator needs to see. Only an immediate repeat + // of the same side and the same words is the platform talking to itself. + return out.filter((t, i) => { + const prev = out[i - 1] + return !(prev && prev.side === t.side && prev.text === t.text) }) } @@ -105,9 +120,11 @@ export default function Conversation({ rows, name, onClose }) {

- Shows the customer's messages and the replies written back. The quote - itself, the read receipt and the acceptance confirmation are sent - automatically and are not recorded as text, so they do not appear here. + Shows the customer's messages and everything written back to them, + including the acceptance confirmation. Two automatic sends are not here: + the quote, which goes out as a registered WhatsApp template rather than + text we compose, and the “I have your message” receipt sent + before anything has read it.