[object Object]
in template l","item":"https://projects.agentabrams.com/p/dw-collections-viewer/c/f3e88a0"}]}

← back to Dw Collections Viewer

Fix HTML tokenizer breaking outer script — escape nested </script> in template literal

f3e88a09ef28fe38e6fa71ab4d13cd6fba575dff · 2026-05-06 19:47:36 -0700 · SteveStudio2

renderDetail() injects a giant template-literal HTML block via
innerHTML; that block contains a nested <script>(restore deploy-sort)</script>
helper. Browsers' HTML tokenizer doesn't respect JS template literals — when
it hits the inner </script>, it terminates the OUTER <script> block too.
Everything after that closing tag (including the outer template's closing
backtick) became raw HTML, leaving the main JS body unterminated:
'Unexpected end of input'. Page rendered shell only, no site cards.

Fix: split the open tag as <` + `script> and escape the close as <\/script>
inside the template literal. Tokenizer no longer sees a script boundary.

Verified via debate-team-fast (Claude + Kimi + Qwen3 all 3/3): correct fix.
Future refactor (ticket): move the inline restore script OUT of the template
literal entirely (data-attribute + DOMContentLoaded hook). Add a pre-commit
grep for unescaped </script> inside backtick template literals.

Files touched

Diff

commit f3e88a09ef28fe38e6fa71ab4d13cd6fba575dff
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 6 19:47:36 2026 -0700

    Fix HTML tokenizer breaking outer script — escape nested </script> in template literal
    
    renderDetail() injects a giant template-literal HTML block via
    innerHTML; that block contains a nested <script>(restore deploy-sort)</script>
    helper. Browsers' HTML tokenizer doesn't respect JS template literals — when
    it hits the inner </script>, it terminates the OUTER <script> block too.
    Everything after that closing tag (including the outer template's closing
    backtick) became raw HTML, leaving the main JS body unterminated:
    'Unexpected end of input'. Page rendered shell only, no site cards.
    
    Fix: split the open tag as <` + `script> and escape the close as <\/script>
    inside the template literal. Tokenizer no longer sees a script boundary.
    
    Verified via debate-team-fast (Claude + Kimi + Qwen3 all 3/3): correct fix.
    Future refactor (ticket): move the inline restore script OUT of the template
    literal entirely (data-attribute + DOMContentLoaded hook). Add a pre-commit
    grep for unescaped </script> inside backtick template literals.
---
 public/index.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/public/index.html b/public/index.html
index b71c893..a4fcae1 100644
--- a/public/index.html
+++ b/public/index.html
@@ -918,7 +918,7 @@ function renderDetail() {
       <button class="btn" onclick="renderSite()" ${assigned.length === 0 ? 'disabled' : ''}>Render now (live)</button>
       <a class="btn" href="https://${s.domain}" target="_blank" style="text-decoration:none">Open site ↗</a>
     </div>
-    <script>
+    <` + `script>
       // Restore saved deploy sort. Precedence: per-site rules.sort (server) > localStorage > default.
       (function(){
         const sel = document.getElementById('deploySortSel');
@@ -928,7 +928,7 @@ function renderDetail() {
         const v = perSite || fallback;
         if (v && Array.from(sel.options).some(o => o.value === v)) sel.value = v;
       })();
-    </script>
+    <\/script>
   `;
   // Hydrate the products slider after the detail HTML is in the DOM.
   renderProductsSlider();

← a0cea68 save+deploy: persist chosen sort to per-site rules; UI pre-p  ·  back to Dw Collections Viewer  ·  dw-collections-viewer: stale-products pill (>30 days mtime) 15a2c8f →