Files
olivv-cs-ComfyUI-FunPack/app/boot.js
T
digital-garbage 4bc6a1e400 app: one client id per TAB, one run per click, and a reason that survives
Two faults from the round, and a third I introduced fixing them.

ComfyUI keys its socket table by client id and pops the old entry when a new
socket arrives with the same one, so two tabs sharing an id meant the second
took the first's updates -- the first froze mid-progress while the GPU carried
on -- and the socket's `finally` pops that id again on close, so closing the
dead tab disconnected the live one. The id lives in sessionStorage now: it
survives a reload of this tab, which is what reattach needs, and does not reach
another tab. Duplicating a tab copies sessionStorage and is still a collision;
said in the comment rather than pretended away.

Generate only went dead when the RUN reached "queued", and reaching it takes a
round trip to ask what to queue. For the whole of that round trip the button was
live, so two clicks each posted their own /prompt: two jobs burning GPU time,
with the UI following one.

Both lived in boot.js, which runs on import and needs a document, so none of it
could be driven by a test. That wiring is now in shell/session.js and tested --
and the first version of those tests used a hand-rolled transport whose draw()
did nothing, which hid the fault the change introduced: the real draw() writes
the status from the run state, and a refused attempt leaves the run idle, so the
reason was overwritten with "Ready" in the same breath as it appeared. The tests
use the real transport, and a refusal now outlives the redraw that follows it.
2026-08-30 04:09:02 +03:00

79 lines
2.9 KiB
JavaScript

// Entry point.
//
// The whole of what the app does at startup: build the regions, ask the server
// what exists, and mount whatever announced itself. Nothing here names a module.
import { build } from "./shell/layout.js";
import { fetchManifest } from "./shell/manifest.js";
import { mountAll } from "./shell/panels.js";
import { all as allValues } from "./shell/values.js";
import { composer } from "./composer/composer.js";
import { createRun, viewUrl } from "./shell/run.js";
import { clientId, connect, runningFor, finishedFor } from "./shell/client.js";
import { createGenerator, reattach } from "./shell/session.js";
import { check } from "./shell/pipeline.js";
const root = document.querySelector("#app");
async function start() {
const id = clientId();
const run = createRun({ clientId: id, connect });
const page = build(root, {
onGenerate: () => generate(),
onCancel: () => run.cancel(),
});
const generate = createGenerator({ run, transport: page.transport, check });
// The socket opens at load, not at Generate. A generation queued before a
// reload keeps running on the server, and a page that only listens once it
// starts one of its own hears nothing about it -- sits at Ready, and queues a
// SECOND job if the user presses Generate believing nothing is running.
run.listen();
reattach(run, id, { runningFor, finishedFor });
run.subscribe((state) => {
page.transport.draw(state);
const last = state.images[state.images.length - 1];
if (last) page.viewer.setSource(viewUrl(last), kindOf(last));
});
let manifest;
try {
manifest = await fetchManifest();
} catch (err) {
// The one failure the user must see: with no manifest there is no app, so
// silence here would be an empty window with no explanation.
root.replaceChildren(composer.emptyState.default({
icon: "▲",
title: "Could not reach FunPack",
hint: `${err.message}. Is ComfyUI running?`,
}).node);
return;
}
const { mounted, hidden } = await mountAll(manifest);
const failed = manifest.failed || [];
console.info(`[FunPack] ${mounted.length} module(s) mounted`,
hidden.length ? `· ${hidden.length} hidden` : "",
failed.length ? `· ${failed.length} failed to load` : "");
for (const { id, why } of hidden) console.warn(`[FunPack] ${id} is hidden: ${why}`);
// These never reached the manifest, so no panel could be missing "in a way
// the user notices" -- which is exactly why they have to be said. A module
// that failed to import looks identical to one nobody installed.
for (const { where, why } of failed) console.warn(`[FunPack] ${where} did not load: ${why}`);
window.FunPack = {
manifest, values: allValues, failed, hidden, run,
mounted: mounted.map((m) => m.id),
};
}
/** Video and image results arrive the same way and cannot be shown the same way. */
function kindOf(image) {
return /\.(mp4|webm|mov|mkv)$/i.test(image.filename || "") ? "video" : "image";
}
start();