township: the page's whole script block has been dead since v0.2.41

The template picker rendered as a box you could not choose from. The cause is
much worse than the symptom: the main <script> block has not parsed since
v0.2.41, so NOTHING in it ran — status polling, the template buttons, the
per-stage buttons, Save config, Stop. The page still rendered perfectly, which is
why screenshots looked right and nothing in the logs complained.

One character did it. The page is emitted from a Python f-string, so

    side.textContent = running ? ('Running\n'+(label||'')) : ...

puts a REAL newline inside the JS string literal. The literal never closes, the
block dies with a SyntaxError, and every function it defines is gone. It needed
\\n, which is what it has now.

Found by probing the server: the browser never requested /templates, nor /status,
nor anything else the init block does — and a marker written as the first
statement of that block never ran either. That ruled out the picker and pointed
at the parse.

Two things in the picker itself, both real:

- tplRefresh() was only ever called AFTER a save or a delete, from the first
  templates commit (c1a65e1d). A freshly loaded page never listed what was on
  disk, so the picker was empty even with the script working — and my v0.2.40
  claim that "the picker is never an empty box" was simply wrong.
- The select had no options at all before its first fill, which is why it read as
  an input field rather than a dropdown. It now ships a placeholder and is width-
  constrained.

Verified against the real templates directory: all four script blocks parse, and
the picker lists "big frames", "LongCat - few long scenes" and "Wan - many short
scenes" with their dates and option counts.

The test starts the real server, fetches the real page, and parses every script
block with node. It is verified to FAIL on the defect: reintroducing the bare \n
makes it fail with the SyntaxError. A dependency-free scanner was written first
and deleted — it reported the known-broken block as clean, and a check that
passes on a file proven broken is worse than no check.

115 tests.
Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182DhR1eFNPQyGmDrpHbedY
parent bebe940d
......@@ -16,7 +16,7 @@
# Canonical product version for CoderAI — single source of truth. Both the API
# metadata and the admin web UI read from here.
__version__ = "0.2.44"
__version__ = "0.2.45"
__tagline__ = "Complete orchestration, distribution and escalation of remotizable advanced inference."
# Configure the CUDA caching allocator BEFORE torch is imported anywhere.
......
"""The township page's JavaScript must actually parse.
It is emitted from a Python f-string, which makes one mistake invisible and fatal:
a bare \\n in the Python source becomes a REAL newline inside the JS string
literal, the <script> dies with a SyntaxError, and EVERYTHING in that block stops
working — status polling, the template picker, the per-stage buttons, Save config,
Stop. The page still renders perfectly, so screenshots look fine and nothing in
the server logs complains.
That shipped in v0.2.41 and was only noticed when the template picker turned out
to be an empty box. This starts the real server, fetches the real page and parses
every script block it serves.
Parsing is done by node, not by a hand-rolled scanner. One was written first and
deleted: it reported the known-broken block as clean, because tracking JS string,
comment and regex state correctly is its own project. A check that passes on a
file you have proven is broken is worse than no check at all.
"""
import os
import pathlib
import re
import shutil
import subprocess
import sys
import time
import urllib.error
import urllib.request
import pytest
ROOT = pathlib.Path(__file__).resolve().parents[1]
TOOL = ROOT / "tools" / "gen_township_fighters.py"
PORT = 7987
@pytest.fixture(scope="module")
def page(tmp_path_factory):
out = tmp_path_factory.mktemp("township")
proc = subprocess.Popen(
[sys.executable, str(TOOL), "--web-port", str(PORT), "--out-dir", str(out)],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
cwd=str(ROOT),
)
try:
html = None
for _ in range(100):
if proc.poll() is not None:
pytest.skip(f"the tool exited ({proc.returncode}) instead of serving")
try:
with urllib.request.urlopen(f"http://127.0.0.1:{PORT}/", timeout=1) as r:
html = r.read().decode("utf-8", "replace")
break
except (urllib.error.URLError, OSError):
time.sleep(0.1)
if html is None:
pytest.skip("the web UI did not come up")
yield html
finally:
proc.terminate()
try:
proc.wait(timeout=10)
except subprocess.TimeoutExpired:
proc.kill()
def _blocks(html):
return [b for b in re.findall(r"<script[^>]*>(.*?)</script>", html, re.S) if b.strip()]
def test_the_page_serves_script(page):
"""A page with no script would make every check below vacuous."""
assert len(_blocks(page)) >= 3
@pytest.mark.skipif(not shutil.which("node"), reason="node is not installed")
def test_every_script_block_parses(page, tmp_path):
"""The real check: hand each block to a JS engine."""
for n, block in enumerate(_blocks(page)):
path = tmp_path / f"block{n}.js"
path.write_text(block, encoding="utf-8")
proc = subprocess.run(["node", "--check", str(path)],
capture_output=True, text=True)
assert proc.returncode == 0, (
f"script block {n} does not parse:\n{proc.stderr.strip()[:800]}")
def test_the_template_picker_is_filled_on_load(page):
"""tplRefresh() existed from the first templates commit but was only ever
called AFTER a save or a delete, so a freshly loaded page listed nothing and
the picker rendered as a box you could not choose from."""
assert "tplRefresh();" in page
init = page.split("Restore state on page load", 1)
assert len(init) == 2, "the page-load init block is gone"
assert "tplRefresh();" in init[1][:600], "nothing fills the picker on load"
......@@ -7767,7 +7767,8 @@ try{ if(localStorage.getItem('tf-dock')==='1') toggleDock(true); }catch(e){}
<div class=bar-split>
<h2>Templates <span class=hint>(a saved set of every option on this page)</span></h2>
<div class=hstack>
<select id=tpl-select title="Saved configurations. Loading one replaces every option on this page."></select>
<select id=tpl-select class=w-mid
title="Saved configurations. Loading one replaces every option on this page."><option value="">Loading templates…</option></select>
<button class="btn btn-secondary btn-sm" type=button onclick="tplLoad()"
title="Apply this template to the current session, then reload the page to show it">📂 Load</button>
<button class="btn btn-secondary btn-sm" type=button onclick="tplSave()"
......@@ -8222,7 +8223,10 @@ function setStatus(running, done, label){{
else{{ pill.className='status-pill status-idle'; pill.textContent='Idle'; }}
if(side){{
side.className = 'side-status' + (running?' is-run':done?' is-done':'');
side.textContent = running ? ('Running\n'+(label||'')) : (done?'Done':'Idle');
// '\\n' must survive into the JS as two characters: this block is emitted from
// a Python f-string, so a bare \\n here becomes a REAL newline inside the string
// literal and kills the whole <script> with a SyntaxError.
side.textContent = running ? ('Running\\n'+(label||'')) : (done?'Done':'Idle');
}}
if(hint) hint.textContent = running ? (label||'rendering') : '';
startBtn.style.display = running ? 'none' : '';
......@@ -8264,6 +8268,10 @@ async function stopRun(){{
}}
// Restore state on page load
toggleConsFields();
// Fill the template picker. Without this it renders as an empty box you cannot
// choose anything from: tplRefresh() was only ever called AFTER a save or a
// delete, so a freshly loaded page never listed what was already on disk.
tplRefresh();
fetch('/status').then(r=>r.json()).then(d=>{{
setStatus(d.running,d.done,d.label);
if(d.running) startSSE();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment