From 175b08ff32c4c4183bcb8f70e32dbe9dacf43e30 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Fri, 4 Sep 2026 18:37:40 +0300 Subject: [PATCH] fix: panel threw on every detail row (temporal dead zone) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `const moved` sat below the repeatability branch that uses it, so that branch hit "can't access lexical declaration 'moved' before initialization" the moment a row rendered. Valid syntax, so node --check passed it — the failure only exists at runtime. Declarations moved to the top of dpVerdict, and the panel logic now has a test that RUNS every branch instead of parsing it. That is the actual lesson: this file had no runtime coverage at all, and a syntax check cannot see a TDZ. Also: the block panel showed a cumulative count beside a green dot while recording was off, which reads as "still recording". It now says "Recorded earlier (not recording now)" and drops the dot. Turning recording off has never cleared stored profiles and still doesn't — the count is history. --- movie_editor/frontend/detail_verdict.test.js | 57 ++++++++++++++++++++ movie_editor/frontend/settings_window.js | 18 +++++-- 2 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 movie_editor/frontend/detail_verdict.test.js diff --git a/movie_editor/frontend/detail_verdict.test.js b/movie_editor/frontend/detail_verdict.test.js new file mode 100644 index 00000000..845d5ce7 --- /dev/null +++ b/movie_editor/frontend/detail_verdict.test.js @@ -0,0 +1,57 @@ +// The settings panel's verdict logic, exercised rather than syntax-checked. +// +// It shipped a temporal-dead-zone throw ("can't access lexical declaration 'moved' before +// initialization") that `node --check` passed happily, because a `const` used above its own +// declaration is valid syntax and only fails when the branch actually runs. So these RUN +// every branch. +const test = require("node:test"); +const assert = require("node:assert"); +const fs = require("node:fs"); + +const src = fs.readFileSync(__dirname + "/settings_window.js", "utf8"); +const grab = (name) => src.match(new RegExp(`function ${name}[\\s\\S]*?\\n \\}\\n`))[0]; +const { dpVerdict, dpNoiseFloor } = new Function( + grab("dpVerdict") + grab("dpNoiseFloor") + "; return { dpVerdict, dpNoiseFloor };")(); + +const row = (o) => Object.assign( + { changed: {}, same_seed: true, structure: 1, detail: 1, edge_aligned: 0, cond_shift: 0 }, o); + +test("every branch runs without throwing", () => { + const cases = [ + row({}), + row({ structure: 0.42, changed: { h3_repr_steering: [true, false] } }), + row({ changed: { conditioning: [null, 1] }, cond_shift: 1.0, structure: 0.5 }), + row({ changed: { h3_block_repeat: ["", "40"] }, structure: 0.95, detail: 1.3, edge_aligned: 0.7 }), + row({ same_seed: false, changed: { seed: [1, 2] }, structure: 0.3 }), + row({ structure: 0.95, detail: 0.8, changed: { cfg: [1, 2] } }), + ]; + cases.forEach((r) => [null, 0.1].forEach((f) => assert.equal(typeof dpVerdict(r, f), "string"))); +}); + +test("a pair with nothing changed reports the noise floor", () => { + const v = dpVerdict(row({ structure: 0.9 }), 0.1); + assert.match(v, /noise floor: two identical runs differ by 10%/); +}); + +test("movement is expressed against the measured floor", () => { + const v = dpVerdict(row({ structure: 0.42, changed: { h3_repr_steering: [true, false] } }), 0.1); + assert.match(v, /5\.8x your noise floor/); +}); + +test("a real prompt change voids the comparison, a small drift does not", () => { + assert.match(dpVerdict(row({ changed: { conditioning: [null, 1] }, cond_shift: 1.0, structure: 0.5 }), 0.1), + /prompt or scenes changed/); + const drift = dpVerdict(row({ changed: { conditioning: [null, 0.02] }, cond_shift: 0.02, + structure: 0.95, detail: 1.3, edge_aligned: 0.7 }), 0.1); + assert.match(drift, /sharper/); +}); + +test("the floor is the median of the quiet pairs, so one outlier cannot set it", () => { + const rows = [row({ structure: 0.9 }), row({ structure: 0.91 }), row({ structure: 0.2 }), + row({ structure: 0.5, changed: { cfg: [1, 2] } })]; + assert.ok(Math.abs(dpNoiseFloor(rows) - 0.1) < 0.02); +}); + +test("no quiet pairs means no floor rather than a made-up one", () => { + assert.equal(dpNoiseFloor([row({ changed: { cfg: [1, 2] } })]), null); +}); diff --git a/movie_editor/frontend/settings_window.js b/movie_editor/frontend/settings_window.js index a201eb0e..08ed09ff 100644 --- a/movie_editor/frontend/settings_window.js +++ b/movie_editor/frontend/settings_window.js @@ -421,6 +421,12 @@ } function dpVerdict(r, floor) { + // Declared FIRST: every branch below can reach for these, and a `const` used above its + // own declaration is a temporal-dead-zone throw at runtime that a syntax check happily + // passes ("can't access lexical declaration 'moved' before initialization"). + const moved = 1 - r.structure; + const vsFloor = floor && floor > 1e-6 ? ` — ${(moved / floor).toFixed(1)}x your noise floor` + : ""; // A low `structure` has two very different causes and the numbers cannot separate them, // so the seed decides. Different seeds = two different generations and the comparison // never was an A/B (a reference pins the subject, not the sample). Same seed = the @@ -437,9 +443,6 @@ return `noise floor: two identical runs differ by ${(moved * 100).toFixed(0)}% ` + `(detail x${r.detail.toFixed(2)}). Anything smaller than this is not a result.`; } - const moved = 1 - r.structure; - const vsFloor = floor && floor > 1e-6 ? ` — ${(moved / floor).toFixed(1)}x your noise floor` - : ""; if (r.structure < 0.85) { return r.same_seed ? `the shot moved ${(moved * 100).toFixed(0)}%${vsFloor} — this changed the picture, ` @@ -642,9 +645,14 @@ row.append(lbl); rows.append(row); - rows.append(infoRow("Rated generations recorded (this key)", + // The count is HISTORY. Turning recording off does not clear it, and a bare number + // beside a green dot reads as "still recording" — say which it is. + const recording = !!(biState && biState.enabled); + rows.append(infoRow( + recording ? "Rated generations recorded (this key)" + : "Recorded earlier (not recording now)", biState ? `${runs} (${biState.n_liked} liked / ${biState.n_disliked} disliked)` - : "0", runs > 0 ? true : null)); + : "0", recording && runs > 0 ? true : null)); rows.append(actionRow("Save this key's measurement", "Downloads every recorded profile as one file. A rental gets replaced and " + "refinements/ is not in git, so without this the count restarts on the next box.",