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) {