Review round 11: project and queue checks after the last upscale wait, retry bookkeeping cleared on stop, tests that fail without the fixes, px lint ignores case

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tw9fjX7o2YfeGQE18MEtPu
This commit is contained in:
Claude
2026-11-09 10:46:27 +00:00
parent 9d4d7ca107
commit 769f1f2179
5 changed files with 10 additions and 8 deletions
@@ -18,7 +18,7 @@ test("element CSS has no literal pixel sizes", () => {
const css = readFileSync(f, "utf8").replace(/\/\*[\s\S]*?\*\//g, "");
for (const decl of css.split(/[;{}]/)) {
if (/^\s*(--|@media|@container)/.test(decl)) continue; // a token definition is where a number belongs; a breakpoint cannot use one
found += [...decl.matchAll(/(?<![\w])-?(\d*\.?\d+)px\b/g)].filter((m) => ![0, 1].includes(Math.abs(Number(m[1])))).length; // 0 and a 1px hairline are exempt; 1.5px and .5px... are not // 1px lines and 0 are borders and resets
found += [...decl.matchAll(/(?<![\w])-?(\d*\.?\d+)px\b/gi)].filter((m) => ![0, 1].includes(Math.abs(Number(m[1])))).length; // 0 and a 1px hairline are exempt; 1.5px and .5px... are not // 1px lines and 0 are borders and resets
}
}
assert.equal(found, 0, `${found} literal px values in element CSS: use a --sp-* step or a component size from tokens.css`);
+2 -2
View File
@@ -286,10 +286,10 @@ test("the next shot waits while an upscale model runs", async () => {
let queued = 0, busyNow = false, asked = 0;
const { app } = rig("idle", { generate: async () => { queued += 1; return true; } });
app.has = new Set();
app.api.upscaleBusy = async () => { asked += 1; if (asked === 2) busyNow = true; return { busy: asked === 2 }; }; // free at the click, busy once before the first shot
app.api.upscaleBusy = async () => { asked += 1; if (asked === 3) busyNow = true; return { busy: asked === 3 }; }; // free at the click and before the first shot, busy only right before queueing
await app.runner.units(["a"], 1);
assert.equal(queued, 1);
assert.ok(busyNow && asked >= 3, "the shot asked again and went on when the server said free");
assert.ok(busyNow && asked >= 4, "the shot asked again right before queueing and went on when the server said free");
});
test("the wait for an upscale model is said once per Generate, not once per shot", async () => {
+3 -2
View File
@@ -131,10 +131,11 @@ export default {
for (const x of placed || []) app.api.carryLog(pid, { scene_id: root.id, ...x }).catch(() => {}); // the Log records where a carried picture went; no toast
if (unusedPicture) tell(`The scene's picture (${names.get(unusedPicture) || unusedPicture}) is not used: this pipeline starts from a video, not a picture.`);
if (unwired) tell(unwiredText(unwired, left));
// The page's own guard does not see another tab: ComfyUI's queue does. Asked per shot: between this run's shots it is empty.
if (app.api.projectQueued && (await app.api.projectQueued(pid))) { tell("This project already has a run in ComfyUI's queue (another tab or window?). Wait for it, or Stop it there."); break; }
await waitForUpscale(); // asked again right before queueing: the shot's inputs took time to build
if (stopped) break;
if (!p.project || p.project.id !== pid) { tell("Stopped: another project was opened."); break; } // the wait can take minutes
// The page's own guard does not see another tab: ComfyUI's queue does. Asked per shot: between this run's shots it is empty.
if (app.api.projectQueued && (await app.api.projectQueued(pid))) { tell("This project already has a run in ComfyUI's queue (another tab or window?). Wait for it, or Stop it there."); break; }
const done = g.waitForTerminal(); // listening before the run starts, so a fast one is not missed
said = false;
current = snap.scenes.findIndex((s) => s.id === root.id) + 1;
+3 -2
View File
@@ -41,6 +41,7 @@ export default {
const stillThere = (pid, key) => p.project && p.project.id === pid
&& Object.values(p.project.scene_renders || {}).some((r) => r && r.media && keyOf(r.media) === key);
async function run(media, pressed = false, project = null) {
let again = false; // a retry is on its way: the wait count stays
if (!p.project) return;
if (!media || wentThrough(media, "upscale")) return; // never upscale twice, even after an interpolation in between
const key = keyOf(media), pid = project || p.project.id;
@@ -64,7 +65,7 @@ export default {
if (err.stopped) c.toast.info({ text: "Upscale stopped. The render stays as it was." });
else if (!pressed && /^ComfyUI is generating/.test(err.message || "") && (waited.get(key) || 0) < RETRIES) { // Always: not lost, tried again when the generation is over
if (!told) { told = true; c.toast.info({ text: "Upscale waits for the generation to finish." }); }
waited.set(key, (waited.get(key) || 0) + 1);
waited.set(key, (waited.get(key) || 0) + 1); again = true;
setTimeout(() => { // only for the same project, the same render and while Always is still on
if (stillThere(pid, key) && p.pref("upscaleMode", "never") === "always") run(media, false, pid);
else { waited.delete(key); if (!waited.size) told = false; }
@@ -72,7 +73,7 @@ export default {
}
else { waited.delete(key); if (!waited.size) told = false; gaveUp(media, "upscale"); c.toast.warn({ text: `Upscale failed: ${err.message}` }); }
}
finally { busy.delete(key); heavy.delete(key); if (!heavy.size) app.has.delete("upscale.model"); draw(); }
finally { if (!again) { waited.delete(key); if (!waited.size) told = false; } busy.delete(key); heavy.delete(key); if (!heavy.size) app.has.delete("upscale.model"); draw(); }
}
function draw() {
+1 -1
View File
@@ -64,7 +64,7 @@ test("Always tries a render again after a running generation, for as long as it
});
test("Always does not try again when the render was replaced, the mode was switched off or another project is open", async () => {
for (const change of [(d) => { d.scene_renders = { a: d.scene_renders.a }; }, (d) => { d.editor_settings.upscaleMode = "never"; }]) {
for (const change of [(d) => { d.scene_renders = { a: d.scene_renders.a }; }, (d) => { d.editor_settings.upscaleMode = "never"; }, (d) => { d.id = "Q"; }]) {
mock.timers.enable({ apis: ["setTimeout"] });
try {
const { doc, render, calls } = always(BUSY);