diff --git a/components/quotes/ticket-detail-modal.tsx b/components/quotes/ticket-detail-modal.tsx index 68f3c0e..8e6a631 100644 --- a/components/quotes/ticket-detail-modal.tsx +++ b/components/quotes/ticket-detail-modal.tsx @@ -34,7 +34,7 @@ import { // noteType values that are system/automated (workflow rules, monitoring, auto-close, etc.) const SYSTEM_NOTE_TYPES = new Set([13, 91, 93, 94, 99, 101]); -const URL_REGEX = /https?:\/\/[^\s<>"')]+/g; +const URL_REGEX = /https?:\/\/[^\s<>"'\[\]()]+/g; function renderTextWithLinks(text: string): React.ReactNode[] { const parts: React.ReactNode[] = []; @@ -43,9 +43,11 @@ function renderTextWithLinks(text: string): React.ReactNode[] { URL_REGEX.lastIndex = 0; while ((match = URL_REGEX.exec(text)) !== null) { if (match.index > lastIndex) { - parts.push(text.slice(lastIndex, match.index)); + // Strip orphaned bracket that wraps the URL e.g. "link [https://..." + const before = text.slice(lastIndex, match.index).replace(/\s*\[$/, ' '); + if (before) parts.push(before); } - const url = match[0].replace(/[.,;:!?)]+$/, ''); + const url = match[0].replace(/[.,;:!?]+$/, ''); let label: string; try { label = new URL(url).hostname.replace(/^www\./, ''); @@ -64,6 +66,8 @@ function renderTextWithLinks(text: string): React.ReactNode[] { ); lastIndex = match.index + match[0].length; + // Consume trailing bracket/paren that closed around the URL + if (lastIndex < text.length && /^[\])]/.test(text[lastIndex])) lastIndex++; } if (lastIndex < text.length) parts.push(text.slice(lastIndex)); return parts; @@ -450,7 +454,10 @@ export function TicketDetailModal({ ticketNumber, open, onOpenChange }: TicketDe