console: a thread that shows what the customer actually received

The order was right — the audit rows are in sequence and the panel
rendered them in sequence. Two other things were wrong.

A MESSAGE THE CUSTOMER GOT WAS NOT IN THE THREAD. After "Okay I confirm
the policy go ahead" the panel showed nothing from us until their next
question, so it read as though we had ignored somebody agreeing to buy.
We had not — the confirmation went out immediately — but it was composed
inside a trigger node and never written to a field, and the console can
only show what the workflow records. 90 stores it in customer_answer, so
it appears here and in the trail. An operator reviewing an acceptance no
human took needs to see exactly what the customer was told at the moment
they said yes.

ORDERING IS NOW (timestamp, id). One submission writes three rows in the
same second — the trigger commit, its repeat, the settle — so a timestamp
alone left their order to the sort's stability, and a reply could print
before the message it answered whenever the two landed in the same
second. It had not happened yet; it was waiting to.

DE-DUPLICATION ONLY COLLAPSES AN IMMEDIATE REPEAT, not any repeat
anywhere in the 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 a same-side, same-words turn directly after its twin is the
platform talking to itself.

The footer says which sends are still absent and why: the quote (a
registered template, not text we compose) and the read receipt (which
would sit between every question and its answer).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yashas 2026-09-08 15:47:49 +05:30
parent f4c67310cb
commit 5d146e9ffb

View File

@ -11,18 +11,31 @@ import './Conversation.css'
* right. * right.
* *
* WHAT IT CAN AND CANNOT SHOW, said plainly at the foot of the panel rather * 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 * than left for someone to discover. The console can only show what the
* here: the customer's replies (customer_reply) and the replies written back * workflow RECORDS, so anything sent from inside a trigger node and never
* (customer_answer). The three automatic sends are not. The quote goes out as * written to a field is invisible here however certainly the customer received
* a registered WhatsApp template, and the acknowledgement and the acceptance * it. The acceptance confirmation used to be exactly that the panel showed
* confirmation are composed inside trigger nodes and never written to a field. * nothing after "I confirm", which read as though we had ignored somebody
* A thread that quietly omitted them would be worse than one that says which * agreeing to buy so 90 writes it into customer_answer and it appears.
* parts it holds. *
* 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) { function turnsFrom(rows) {
if (!Array.isArray(rows)) return [] if (!Array.isArray(rows)) return []
const out = [] 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) { for (const r of ordered) {
const fields = Array.isArray(r.fields) && r.fields.length const fields = Array.isArray(r.fields) && r.fields.length
? r.fields ? 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' }) 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 // One submission is recorded three times the trigger commit, its repeat,
// settle both carry the payload), so the same sentence would print twice. // and the settle so the same sentence would print three times.
// De-duped on side + text rather than on the row id for that reason. //
const seen = new Set() // De-duped against the PREVIOUS turn only, not against the whole thread. A
return out.filter((t) => { // customer who asks the same thing twice because the first went unanswered
const k = t.side + ' ' + t.text // has said it twice, and a thread that silently showed it once would hide
if (seen.has(k)) return false // exactly the impatience an operator needs to see. Only an immediate repeat
seen.add(k) // of the same side and the same words is the platform talking to itself.
return true 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 }) {
</div> </div>
<p className="conv__note"> <p className="conv__note">
Shows the customer&apos;s messages and the replies written back. The quote Shows the customer&apos;s messages and everything written back to them,
itself, the read receipt and the acceptance confirmation are sent including the acceptance confirmation. Two automatic sends are not here:
automatically and are not recorded as text, so they do not appear here. the quote, which goes out as a registered WhatsApp template rather than
text we compose, and the &ldquo;I have your message&rdquo; receipt sent
before anything has read it.
</p> </p>
</div> </div>
</div> </div>