diff --git a/server/src/services/replayEngineV2.js b/server/src/services/replayEngineV2.js index cfd5ab89..b094c74a 100644 --- a/server/src/services/replayEngineV2.js +++ b/server/src/services/replayEngineV2.js @@ -395,12 +395,11 @@ function buildDriverBatterySnapshot(selectedRoverIds = []) { return lines; } -function buildChatEventsForWindow(startMs, endMs, limit = 18) { +function buildChatEventsForWindow(startMs, endMs, limit = 22, preWindowCount = 10) { const all = getRecentMessages(300, { includeSystem: false }); - const windowed = all - .filter((msg) => Number.isFinite(msg?.ts) && msg.ts >= startMs && msg.ts <= endMs) - .slice(-limit); - return windowed.map((msg) => { + const normalized = all + .filter((msg) => Number.isFinite(msg?.ts)) + .map((msg) => { const nickname = String(msg?.nickname || msg?.discordUserName || 'user').trim() || 'user'; const text = String(msg?.text || '').replace(/\s+/g, ' ').trim(); return { @@ -413,6 +412,9 @@ function buildChatEventsForWindow(startMs, endMs, limit = 18) { roverColor: msg?.roverColor ? String(msg.roverColor) : '', }; }); + const beforeWindow = normalized.filter((msg) => msg.ts < startMs).slice(-preWindowCount); + const inWindow = normalized.filter((msg) => msg.ts >= startMs && msg.ts <= endMs).slice(-limit); + return [...beforeWindow, ...inWindow].sort((a, b) => a.ts - b.ts); } function escapeXml(text) { @@ -476,7 +478,7 @@ function renderSidebarSvg({ const titleParts = wrapTextLines(title, 20).slice(0, 4).map((line) => escapeXml(line)); const statLines = driverBatteryLines.slice(0, 8).map((line) => escapeXml(line)); const normalizedChats = chatLines.slice(-12).map((entry) => { - const wrapped = wrapTextLines(entry.text || '', 20).slice(0, 4).map((line) => escapeXml(line)); + const wrapped = wrapTextLines(entry.text || '', 22).slice(0, 4).map((line) => escapeXml(line)); const nick = escapeXml(entry.nickname || 'user'); const roverId = escapeXml(entry.roverId || ''); const roverRgb = hexToRgb(entry.roverColor || ''); @@ -536,25 +538,39 @@ function renderSidebarSvg({ } else { for (let i = 0; i < normalizedChats.length; i += 1) { const block = normalizedChats[i]; - const bubbleH = 20 + block.wrapped.length * 14; + const nameW = Math.min(95, block.nick.length * 6); + const badgeW = block.roverId ? Math.min(58, Math.max(24, block.roverId.length * 6 + 10)) : 0; + const badgeGap = block.roverId ? 6 : 0; + const prefixChars = Math.ceil((nameW + badgeW + badgeGap + 18) / 5.8); + const firstLineRaw = String(block.wrapped[0] || '').trim(); + const firstLine = firstLineRaw ? firstLineRaw : ''; + const remainingRaw = block.wrapped.slice(firstLineRaw ? 1 : 0).map((line) => String(line || '').trim()).filter(Boolean); + const fullText = (firstLine ? [firstLine, ...remainingRaw] : remainingRaw).join(' '); + const inlineWrapped = wrapTextLines(fullText, Math.max(8, 26 - prefixChars)).slice(0, 4).map((line) => escapeXml(line)); + const bubbleH = 16 + inlineWrapped.length * 14; if (cy + bubbleH + 4 > y + chatCardH - 5) break; shapes.push( ``, ); - textRows.push(`${block.nick}`); + const nameX = bubbleX + 6; + const textStartX = nameX + nameW + 4 + (block.roverId ? badgeW + badgeGap : 0); + textRows.push(`${block.nick}`); if (block.fromDiscord) { - textRows.push(``); + textRows.push(``); } if (block.roverId) { - const badgeTextX = bubbleX + bubbleW - 56; + const badgeX = nameX + nameW + 4; + const badgeTextX = badgeX + 5; shapes.push( - ``, + ``, ); textRows.push(`${block.roverId}`); } - let by = cy + 25; - for (const line of block.wrapped) { - textRows.push(`${line}`); + let by = cy + 13; + for (let lineIdx = 0; lineIdx < inlineWrapped.length; lineIdx += 1) { + const line = inlineWrapped[lineIdx]; + const lineX = lineIdx === 0 ? textStartX : bubbleX + 6; + textRows.push(`${line}`); by += 14; } cy += bubbleH + 4;