Files
olivv-cs-ComfyUI-FunPack/app/shell/layout.js
T
digital-garbage f37f526268 app: Generate wired to a real run -- queued, progress, cancel, result
ComfyUI's own queue and its own socket, not a second one alongside them. A
FunPack queue would be a second thing tracking what is running, and the one the
user believes is whichever spoke last -- so this speaks to /prompt, /interrupt
and /ws as any ComfyUI client does and holds only a view of the server's state.
The graph is asked for, never assembled here, for the same reason.

The client id is remembered: ComfyUI addresses progress to the client that
queued the run, so a reload with a fresh id hears nothing about the generation
already in flight -- the run continues, the GPU time is spent, and the app says
Ready.

Faults found on the way, each in code that already had passing tests:

* `[hidden]` was inert across the whole kit. The UA rule is element-level, so
  `.cx-btn { display: inline-flex }` beats it -- Cancel was marked hidden, sat
  there, and nothing reported anything. jsdom cannot see it: the attribute was
  always correct. One reset rule, and a browser test over four element kinds.
* A panel passed no height to its body and a split pane passed none to its
  panes, so the viewer asked for 100% of `auto` and collapsed to a single line
  inside a panel that was the right size around it.
* subscribe() delivered the first state outside the guard every later delivery
  gets, so one bad listener took down whoever was wiring the UI up -- at
  startup, before anything existed to report it.
* A whole tests directory was never run: the test script globbed
  composer/tests only. `node --test` discovers them itself now, so there is no
  list to forget.
* `empty` was not in the content rule's key list, so three elements were never
  asked to prove their empty-state text comes back as text rather than markup.

The dev server now mounts the REAL routes -- register() already took a route
table -- instead of reimplementing them over http.server. It answered POST
/api/pipeline with 501, so the edit-and-queue path could not be looked at, which
is the one thing the dev server exists for.
2026-08-30 03:36:40 +03:00

95 lines
4.0 KiB
JavaScript

// The app's regions, and the mount points each one offers.
//
// A region declares what it can host. Core never learns these names, and no
// list of them exists anywhere else -- a module asks for one by name, and gets
// it or does not.
//
// The shape is the one v4 called Simple mode, built the other way round. v4's
// Simple mode was the Editor with about ten `display: none !important` rules
// over it, which is not a smaller app but the same app wearing a mask -- and it
// broke: a collapsed column was marked [hidden], and nothing can slide a
// display:none element into view, so a panel button did nothing at all and said
// nothing. So here a region that does not exist yet is ABSENT. The timeline, the
// media bin's view modes, the pipeline window and the wheel are not hidden
// below; they have not been built, and adding one is adding a region rather
// than removing a rule.
import { composer } from "../composer/composer.js";
import { offer } from "./mounts.js";
import { createTransport } from "./transport.js";
export function build(root, handlers = {}) {
// Left: what a project is made of. Empty until the media bin exists, and an
// empty state rather than an absent panel, because "there is nothing here
// yet" and "this part of the app is missing" must not look the same.
const assets = composer.panel.default({
title: "Assets",
body: composer.emptyState.default({
icon: "▤",
title: "Nothing in the bin",
hint: "Media you add to the project appears here.",
}),
});
// Centre: the result, and the prompt under it. The prompt is the one control
// that is always on screen, because it is the one always being edited.
const viewer = composer.viewer.media({ empty: "The result of the last run appears here." });
const preview = composer.panel.default({ title: "Preview", body: viewer });
const prompt = composer.panel.default({
title: "Prompt",
body: composer.hint.default({ text: "Modules that announced generation.prompt." }),
});
const centre = composer.splitPane.v({
size: 62,
label: "Preview and prompt",
panes: [preview, prompt],
});
// Right: everything about the run. Generation is open, settings folded away
// -- one is read constantly and the other rarely, and a fold says which is
// which without needing a second place to look. A settings WINDOW is a
// separate thing that has not been built; this is not a stand-in for it.
const generation = composer.panel.default({
title: "Generation",
body: composer.hint.default({ text: "Modules that announced a generation.* mount point." }),
});
const settings = composer.panel.default({
body: composer.hint.default({ text: "Modules that announced settings.general." }),
});
const properties = composer.group.default({
rows: [generation, composer.collapsible.default({ label: "Settings", body: settings })],
});
const workspace = composer.workspace.docked({
id: "main",
leftLabel: "Assets",
rightLabel: "Properties",
left: assets,
centre,
right: properties,
});
// The transport. One row, always visible, and the place a run is started and
// reported -- an inert setting is said HERE, next to Generate, not in the
// panel that would have enabled it.
const transport = createTransport(handlers);
const page = composer.frame.app({ main: workspace, footer: transport });
root.replaceChildren(page.node);
// The mount point IS the contract with modules; the region behind it can be
// rearranged freely as long as the name survives.
offer("assets.library", assets.body);
offer("generation.prompt", prompt.body);
offer("settings.general", settings.body);
offer("generation.model", generation.body);
offer("generation.latent", generation.body);
offer("generation.sampling", generation.body);
offer("generation.timing", generation.body);
offer("generation.post", generation.body);
return { workspace, assets, preview, viewer, prompt, generation, settings,
properties, transport, page };
}