diff --git a/src/dashboard.ts b/src/dashboard.ts
index 9e4b857e..1e61b975 100644
--- a/src/dashboard.ts
+++ b/src/dashboard.ts
@@ -20,7 +20,7 @@ initTheme();
function truncatedNoticeHtml(key: string, total: number | undefined): string {
if (total === undefined || total <= UI_TRUNCATION_MAX) return "";
- return `
Showing last ${UI_TRUNCATION_MAX} of ${total} ${key}
`;
+ return `Showing last ${UI_TRUNCATION_MAX} of ${total} ${escapeHtml(key)}
`;
}
function truncatedNoticeText(key: string, total: number | undefined): string {
@@ -1075,14 +1075,16 @@ document.addEventListener("DOMContentLoaded", async () => {
function createTranscriptEntryHTML(entry: TranscriptEntry): string {
const timeStr = escapeHtml(entry.timestampLabel || formatDuration(entry.timestamp || 0));
const speaker = escapeHtml(entry.speaker || "Unknown");
- const initials = (entry.speaker || "Unknown")
+ const initials = speaker
.split(" ")
.filter(Boolean)
.map((w) => w[0])
.join("")
.toUpperCase()
.slice(0, 2);
- const isAudio = (entry.speaker || "") === "Audio";
+ // speaker is already escapeHtml'd above, and "Audio" is pure ASCII
+ // so the comparison is safe (escapeHtml is a no-op for plain ASCII).
+ const isAudio = speaker === "Audio";
const text = escapeHtml(entry.text || "");
const chunkId = entry.id ? `transcript-${escapeHtml(entry.id)}` : "";
@@ -1095,9 +1097,9 @@ document.addEventListener("DOMContentLoaded", async () => {
${text}