From f3fdc1ac5e19f585a7f7ea352d3f419861231da2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 18:14:37 +0000 Subject: [PATCH 1/3] Initial plan From 71e5de66f2a65cc9619265aa74ceb29a521e6f12 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 18:30:32 +0000 Subject: [PATCH 2/3] Fix streaming HTML parser to properly handle nested div tags Co-authored-by: tsmarvin <57049894+tsmarvin@users.noreply.github.com> --- src/Web/Views/Home/Index.cshtml | 78 ++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/src/Web/Views/Home/Index.cshtml b/src/Web/Views/Home/Index.cshtml index 36c52b8..12ad600 100644 --- a/src/Web/Views/Home/Index.cshtml +++ b/src/Web/Views/Home/Index.cshtml @@ -504,17 +504,85 @@ var cspNonce = Context.Items["CSPNonce"] as string ?? string.Empty; let buffer = ''; let cardCount = 0; + // Helper function to find the end of a balanced div element + // Returns the index after the closing of a complete element, or -1 if not found + function findBalancedDivEnd(html, startIndex = 0) { + let depth = 0; + let i = startIndex; + const openTag = /]/gi; + const closeTag = /<\/div>/gi; + + // Find all opening and closing div tags and track their positions + const tags = []; + let match; + + openTag.lastIndex = i; + while ((match = openTag.exec(html)) !== null) { + tags.push({ pos: match.index, type: 'open' }); + } + + closeTag.lastIndex = i; + while ((match = closeTag.exec(html)) !== null) { + tags.push({ pos: match.index, type: 'close', endPos: match.index + 6 }); + } + + // Sort by position + tags.sort((a, b) => a.pos - b.pos); + + // Walk through tags to find balanced end + for (const tag of tags) { + if (tag.type === 'open') { + depth++; + } else { + depth--; + if (depth === 0) { + return tag.endPos; + } + } + } + + return -1; // No balanced end found + } + + // Function to extract all complete top-level elements from buffer + function extractCompleteElements(buf) { + let completeHtml = ''; + let remaining = buf; + let searchStart = 0; + + while (true) { + // Look for the start of a div element + const divStart = remaining.indexOf(''); - if (lastClosingDiv !== -1) { - const completeHTML = buffer.substring(0, lastClosingDiv + 6); - buffer = buffer.substring(lastClosingDiv + 6); + // Process complete HTML elements from buffer using balanced div detection + const { completeHtml, remaining } = extractCompleteElements(buffer); + + if (completeHtml) { + buffer = remaining; + const completeHTML = completeHtml; // Check for completion marker const tempDiv = document.createElement('div'); From 41ce7366335fec18a3520b727b8975e46740a7e6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 18:33:21 +0000 Subject: [PATCH 3/3] Address code review feedback: improve regex and remove redundant variable Co-authored-by: tsmarvin <57049894+tsmarvin@users.noreply.github.com> --- src/Web/Views/Home/Index.cshtml | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Web/Views/Home/Index.cshtml b/src/Web/Views/Home/Index.cshtml index 12ad600..74c5da1 100644 --- a/src/Web/Views/Home/Index.cshtml +++ b/src/Web/Views/Home/Index.cshtml @@ -506,24 +506,24 @@ var cspNonce = Context.Items["CSPNonce"] as string ?? string.Empty; // Helper function to find the end of a balanced div element // Returns the index after the closing of a complete element, or -1 if not found + const CLOSE_DIV_TAG = ''; function findBalancedDivEnd(html, startIndex = 0) { let depth = 0; - let i = startIndex; - const openTag = /]/gi; - const closeTag = /<\/div>/gi; + const searchHtml = html.substring(startIndex); + + // Match
or
+ const openTagPattern = /]*)?>|
/gi; + const closeTagPattern = /<\/div>/gi; // Find all opening and closing div tags and track their positions const tags = []; - let match; - openTag.lastIndex = i; - while ((match = openTag.exec(html)) !== null) { - tags.push({ pos: match.index, type: 'open' }); + for (const match of searchHtml.matchAll(openTagPattern)) { + tags.push({ pos: startIndex + match.index, type: 'open' }); } - closeTag.lastIndex = i; - while ((match = closeTag.exec(html)) !== null) { - tags.push({ pos: match.index, type: 'close', endPos: match.index + 6 }); + for (const match of searchHtml.matchAll(closeTagPattern)) { + tags.push({ pos: startIndex + match.index, type: 'close', endPos: startIndex + match.index + CLOSE_DIV_TAG.length }); } // Sort by position @@ -582,11 +582,10 @@ var cspNonce = Context.Items["CSPNonce"] as string ?? string.Empty; if (completeHtml) { buffer = remaining; - const completeHTML = completeHtml; // Check for completion marker const tempDiv = document.createElement('div'); - tempDiv.innerHTML = completeHTML; + tempDiv.innerHTML = completeHtml; const completionMarker = tempDiv.querySelector('[data-stream-complete="true"]'); if (completionMarker) {