Upscale Always retry only for the same project and render, told once; timeline header one row from 1024px (1000px clipped while running)

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-08 20:40:11 +00:00
parent 0414df00c9
commit 9b038b2db3
3 changed files with 18 additions and 10 deletions
+3 -3
View File
@@ -1,10 +1,10 @@
// The app at the screens people really have, in a real browser: no page overflow, every window inside the viewport,
// both timeline lanes visible, the timeline header on one row from 1000px wide, idle and while a render is running (a fake ComfyUI queue and socket). Not part of `npm test` (it needs browsers): run it with `npm run test:viewport`.
// both timeline lanes visible, the timeline header on one row from 1024px wide, idle and while a render is running (a fake ComfyUI queue and socket). Not part of `npm test` (it needs browsers): run it with `npm run test:viewport`.
// FUNUI_BROWSERS=chromium,firefox picks the engines (default: every one that is installed).
import playwright from "playwright";
import { start } from "./stub.mjs";
const SIZES = [[1280, 720], [1024, 1024], [1366, 768], [1920, 1080], [2560, 1440], [768, 1024], [720, 1280], [1080, 1920]];
const SIZES = [[1280, 720], [1024, 1024], [1000, 700], [1366, 768], [1920, 1080], [2560, 1440], [768, 1024], [720, 1280], [1080, 1920]];
const WINDOWS = [["Settings", /^Settings$/], ["Log", /^Log$/], ["Composer", /Composer/], ["Sampler", /Sampler/], ["Training", /Training/]];
const CLIENT = "viewport-test";
const wanted = (process.env.FUNUI_BROWSERS || "chromium,firefox").split(",");
@@ -15,7 +15,7 @@ const inspect = () => {
const out = seen.map((e) => ({ name: e.className.toString().split(" ")[0], r: box(e) }));
const nle = document.querySelector(".cx-nle"), lanes = [...document.querySelectorAll(".cx-nle-gutter-lane")];
const head = [...document.querySelectorAll(".cx-panel-head")].find((h) => h.textContent.startsWith("Timeline")), act = head && head.querySelector(".cx-panel-actions");
const header = innerWidth < 1000 || !head ? [] : [box(head).height > 50 && `Timeline header is ${Math.round(box(head).height)}px tall (more than one row)`, act.scrollWidth > act.clientWidth + 1 && `Timeline header clips ${act.scrollWidth - act.clientWidth}px of its controls`].filter(Boolean);
const header = innerWidth < 1024 || !head ? [] : [box(head).height > 50 && `Timeline header is ${Math.round(box(head).height)}px tall (more than one row)`, act.scrollWidth > act.clientWidth + 1 && `Timeline header clips ${act.scrollWidth - act.clientWidth}px of its controls`].filter(Boolean);
return {
header,
over: [document.documentElement.scrollWidth - innerWidth, document.documentElement.scrollHeight - innerHeight],
+12 -4
View File
@@ -36,9 +36,13 @@ export default {
if (pick) app.openSettings(pick);
}
async function run(media, pressed = false) {
let told = false; // "waits" is said once, however many renders wait
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) {
if (!p.project) return;
if (!media || wentThrough(media, "upscale")) return; // never upscale twice, even after an interpolation in between
const key = keyOf(media), pid = p.project.id;
const key = keyOf(media), pid = project || p.project.id;
if (busy.has(key)) return;
busy.add(key); draw();
try {
@@ -50,15 +54,19 @@ export default {
c.toast.info({ text: `Upscaling with ${model}…` });
const out = await upscale(app.api, media, model, undefined, rtxOn() ? rtxOpts() : undefined);
derived(media, out, "upscale");
waited.delete(key); if (!waited.size) told = false;
const swapped = await p.editFor(pid, (pr) => swap(pr, media, out) > 0);
if (!swapped) c.toast.warn({ text: "Upscaled, but that render was replaced meanwhile, so nothing was swapped." });
else c.toast.good({ text: "Upscaled: the render now plays the larger video." });
} catch (err) {
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) < 60) { // Always: not lost, tried again when the generation is over
if (!waited.has(key)) c.toast.info({ text: "Upscale waits for the generation to finish." });
if (!told) { told = true; c.toast.info({ text: "Upscale waits for the generation to finish." }); }
waited.set(key, (waited.get(key) || 0) + 1);
setTimeout(() => run(media), 15000);
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; }
}, 15000);
}
else { gaveUp(media, "upscale"); c.toast.warn({ text: `Upscale failed: ${err.message}` }); }
}
+3 -3
View File
@@ -50,10 +50,10 @@
:root.fp-peek.fp-peek-open .fp-peek-strip { display: none; }
:root.fp-peek .fp-split-bottom { display: none; }
/* The timeline's header is ONE line wherever its controls can physically fit (about 1000px and up). It never spreads
/* The timeline's header is ONE line wherever its controls can physically fit (about 1024px and up). It never spreads
its controls apart: the room goes to the text that can give way (hints, the step counter), which truncates, and
below the widest layouts a button that has an icon shows only the icon (its label stays for screen readers). */
@media (min-width: 1000px) {
@media (min-width: 1024px) {
.fp-main > :nth-child(2) > .cx-panel-head { flex-wrap: nowrap; column-gap: var(--sp-3); }
.fp-main > :nth-child(2) > .cx-panel-head .cx-panel-actions { flex: 1 1 0; flex-wrap: nowrap; min-inline-size: 0; overflow: hidden; }
.fp-main > :nth-child(2) > .cx-panel-head .cx-panel-status { flex: none; flex-wrap: nowrap; gap: var(--sp-2); }
@@ -62,7 +62,7 @@
.fp-main > :nth-child(2) > .cx-panel-head .cx-btn, .fp-main > :nth-child(2) > .cx-panel-head .cx-chip { flex: none; }
.fp-main > :nth-child(2) > .cx-panel-head :is(.cx-hint, .cx-t) { flex: 0 1 auto; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}
@media (min-width: 1000px) and (max-width: 1500px) {
@media (min-width: 1024px) and (max-width: 1500px) {
.fp-main > :nth-child(2) > .cx-panel-head .cx-btn:has(.cx-btn-icon) .cx-btn-label {
position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}