Fix ticket timeline transcript typing and expansion.
Some checks failed
build-deploy-junohub-ec2 / deploy (push) Has been cancelled

Avoid nullable timeline entries by building a strict TimelineMessage array and keep storefront transcript splitting behavior stable for sorting and expansion controls.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Saad Mohsin 2026-05-14 00:22:35 +05:00
parent c0d2fed17a
commit accd30c9d5

View File

@ -316,36 +316,48 @@ export default function TicketDetailPage() {
const timelineMessages = useMemo<TimelineMessage[]>(() => { const timelineMessages = useMemo<TimelineMessage[]>(() => {
if (!ticket) return [] if (!ticket) return []
return ticket.messages.flatMap((message) => { const expanded: TimelineMessage[] = []
if (message.trigger !== "STOREFRONT_TICKET") return [{ ...message }] for (const message of ticket.messages) {
if (message.trigger !== "STOREFRONT_TICKET") {
expanded.push({ ...message })
continue
}
const raw = cleanDisplayText(message.bodyPreview || "") const raw = cleanDisplayText(message.bodyPreview || "")
if (!raw) return [{ ...message }] if (!raw) {
expanded.push({ ...message })
continue
}
const lines = raw const lines = raw
.split(/\r?\n/) .split(/\r?\n/)
.map((line) => line.trim()) .map((line) => line.trim())
.filter(Boolean) .filter(Boolean)
const parsed = lines const parsed: TimelineMessage[] = []
.map((line, idx) => { for (let idx = 0; idx < lines.length; idx += 1) {
const match = line.match(/^\[(.+?)\]\s+(CUSTOMER|ASSISTANT):\s*(.+)$/i) const line = lines[idx]
if (!match) return null const match = line.match(/^\[(.+?)\]\s+(CUSTOMER|ASSISTANT):\s*(.+)$/i)
const sentAtMaybe = new Date(match[1]) if (!match) continue
const sentAt = Number.isNaN(sentAtMaybe.getTime()) const sentAtMaybe = new Date(match[1])
? message.sentAt const sentAt = Number.isNaN(sentAtMaybe.getTime())
: sentAtMaybe.toISOString() ? message.sentAt
return { : sentAtMaybe.toISOString()
id: `${message.id}-part-${idx}`, parsed.push({
subject: message.subject, id: `${message.id}-part-${idx}`,
bodyPreview: match[3], subject: message.subject,
status: message.status, bodyPreview: match[3],
trigger: message.trigger, status: message.status,
sentAt, trigger: message.trigger,
senderOverride: sentAt,
match[2].toUpperCase() === "CUSTOMER" ? ("CUSTOMER" as const) : ("ASSISTANT" as const), senderOverride:
} satisfies TimelineMessage match[2].toUpperCase() === "CUSTOMER" ? ("CUSTOMER" as const) : ("ASSISTANT" as const),
}) })
.filter((row): row is TimelineMessage => Boolean(row)) }
return parsed.length >= 2 ? parsed : [{ ...message }] if (parsed.length >= 2) {
}) expanded.push(...parsed)
} else {
expanded.push({ ...message })
}
}
return expanded
}, [ticket]) }, [ticket])
const sourceLabel = (trigger: string | null): string => { const sourceLabel = (trigger: string | null): string => {