Skip to content

Commit 5c554ad

Browse files
committed
Frontend: a literal newline inside a JS string broke the whole app
The setup-code prompt string in api() carried a real line break instead of an escaped one - the inline script no longer parsed, window.onload never ran, and the setup overlay (visible by default, hidden by JS) greeted a fully configured install with 'Curatarr Setup' after the v1.0.1-beta restart. Backend and .env were fine throughout. New guard: tests/test_frontend_syntax.py runs every inline script through node --check (CI has node; local runs skip honestly without it), plus a node-free check for string literals that span lines.
1 parent c4405fb commit 5c554ad

2 files changed

Lines changed: 66 additions & 3 deletions

File tree

‎frontend/index.html‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2128,9 +2128,7 @@ <h2 style="font-size:16px;font-weight:600;color:var(--amber);margin-bottom:8px">
21282128
if (r.status === 401 && path.startsWith('/api/setup/') && !_retried) {
21292129
const msg = await r.clone().text();
21302130
if (/setup code/i.test(msg)) {
2131-
const entered = prompt('This Curatarr is being set up from another device.
2132-
2133-
Enter the one-time setup code printed in the Curatarr console window (start.bat) or log file:');
2131+
const entered = prompt('This Curatarr is being set up from another device.\n\nEnter the one-time setup code printed in the Curatarr console window (start.bat) or log file:');
21342132
if (entered && entered.trim()) {
21352133
sessionStorage.setItem('curatarr_setup_code', entered.trim().toUpperCase());
21362134
return api(path, method, body, true);

‎tests/test_frontend_syntax.py‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""The single-file frontend must parse — all of it, or nothing runs.
2+
3+
2026-09-05: a patch inserted a literal newline inside a JS string literal
4+
in index.html. The whole inline script failed to parse, so window.onload
5+
never ran, so the setup overlay (visible by default, hidden by JS) greeted
6+
the owner with "Curatarr Setup" on a fully configured install. Python-side
7+
tests could not see it. Node can: every inline <script> block goes through
8+
`node --check`. Skips honestly when node is not installed (CI has it).
9+
10+
python tests/test_frontend_syntax.py
11+
"""
12+
import pathlib
13+
import re
14+
import shutil
15+
import subprocess
16+
import sys
17+
import tempfile
18+
19+
_ROOT = pathlib.Path(__file__).resolve().parents[1]
20+
_INDEX = _ROOT / "frontend" / "index.html"
21+
22+
23+
def _inline_scripts(html: str) -> list[str]:
24+
return [m.group(1) for m in
25+
re.finditer(r"<script(?![^>]*\bsrc=)[^>]*>(.*?)</script>", html, re.S)]
26+
27+
28+
def test_every_inline_script_parses():
29+
node = shutil.which("node")
30+
html = _INDEX.read_text(encoding="utf-8")
31+
scripts = _inline_scripts(html)
32+
assert scripts, "index.html carries its app logic inline - none found?"
33+
if not node:
34+
print(" (node not installed - JS syntax check skipped)")
35+
return
36+
tmp = pathlib.Path(tempfile.mkdtemp())
37+
for i, src in enumerate(scripts):
38+
p = tmp / f"inline_{i}.js"
39+
p.write_text(src, encoding="utf-8")
40+
r = subprocess.run([node, "--check", str(p)], capture_output=True, text=True)
41+
assert r.returncode == 0, f"inline script #{i} does not parse:\n{r.stderr[:800]}"
42+
43+
44+
def test_no_js_string_literal_spans_a_line():
45+
"""Cheap node-free guard for the exact failure: a single-quoted string
46+
that opens in a prompt()/alert() call and never closes on its line."""
47+
html = _INDEX.read_text(encoding="utf-8")
48+
for n, line in enumerate(html.split("\n"), 1):
49+
s = line.strip()
50+
if re.match(r"(const|let|var)\s+\w+\s*=\s*(prompt|alert|confirm)\('", s):
51+
assert s.count("'") % 2 == 0 or s.endswith("',") or s.endswith("');"), \
52+
f"line {n}: string literal appears to span lines: {s[:80]}"
53+
54+
55+
if __name__ == "__main__":
56+
fails = 0
57+
for name, fn in list(globals().items()):
58+
if name.startswith("test_") and callable(fn):
59+
try:
60+
fn()
61+
print(f" PASS {name}")
62+
except Exception as e: # noqa: BLE001
63+
fails += 1
64+
print(f" FAIL {name}: {type(e).__name__}: {e}")
65+
sys.exit(1 if fails else 0)

0 commit comments

Comments
 (0)