Files
olivv-cs-ComfyUI-FunPack/app/shell/constructor.js
T
digital-garbage 62f92ecaf8 app: a menu bar, a timeline, and the prompt in a window of its own
v4's arrangement, which is the one that reads as an editor: a zone head
holds what acts on THAT zone, so the app grows a bar of its own for what
acts on the app.

* The menu bar. Brand, View and Settings, and whether ComfyUI is still on
  the other end. Only menus with something real in them exist -- an empty
  File menu is a promise the app does not keep.
* The prompt is written in the CONSTRUCTOR, a window. v4 called this the
  Composer; that name is taken here by the element kit, and two things
  called Composer in one repo is one thing nobody can search for. It is
  the longest thing a person writes and the least often read, and a
  permanent third of the centre column is the room the timeline needs.
  The host is built at startup and outlives every opening, so a window
  that opens twice does not throw away what was typed.
* The timeline replaces it, and Generate moves into its head with what
  the run is doing at the far end of the same head. No bar across the
  bottom: one that spans the window belongs to no region.

Four faults found on the way, three of them mine and one much older:

* THE OVERLAY ROOT WAS A STACKING CONTEXT. It documents at length that it
  must never be one -- and then set `position: fixed`, which makes one on
  its own. So the whole z ladder was measured inside that single element:
  an open menu at z 500 painted UNDER a button at z 1. Every overlay in
  the app was one positive z-index away from being buried, and the only
  reason nobody had seen it is that almost nothing claims a z-index. The
  root is static and zero-sized now; every layer in it is fixed already.
* Generate's hover ran `filter: brightness()`, which lightens the LABEL
  along with the fill under it -- the one thing that had to stay readable
  lost contrast in both themes. Only the fill moves now, and which way it
  moves depends on the theme: dark's accent carries near-black text so it
  goes lighter, light's carries white so it goes darker. Measured: no
  gradient stop under 4.1:1, hovered or at rest.
* A fixed-height head could not shrink below the width of everything in
  it, and a head now holds a title, three controls and a status. That
  min-content became the centre column's, so a 768px window laid out
  878px wide and scrolled sideways. It wraps now, as v4's does.
* A CSS patch of mine silently did nothing because its anchor text had
  moved and I had left the assertion off that one replacement. Every
  patch asserts now.
2026-08-30 22:11:53 +03:00

55 lines
2.1 KiB
JavaScript

// The Constructor: where a run is written, in a window of its own.
//
// v4 called this the Composer. The name is taken here -- `app/composer` is the
// element kit -- and two things called Composer in one codebase is one thing
// nobody can search for.
//
// The prompt is not a panel on the main window. It is the longest thing a
// person writes, it is edited in bursts and read rarely, and giving it a
// permanent third of the centre column cost the timeline the room it needs. So
// it opens, is written in, and closes.
//
// The HOST is built once, at startup, and outlives every opening. Two reasons,
// and both were faults before this file existed: a mount point has to exist
// when modules mount, which is long before anyone opens a window; and a window
// that rebuilt its contents on each open would throw away whatever was typed
// and not saved.
import { composer } from "../composer/composer.js";
export function createConstructor({ title = "Constructor" } = {}) {
const empty = composer.emptyState.default({
icon: "✎",
title: "Nothing to write yet",
hint: "The pipeline decides which of its inputs are written here.",
});
// The host is a plain region: it is handed to the modal as a body and handed
// back when the modal closes, which is why the modal never owns it.
const host = composer.region.stack({ gap: "sm", label: "Constructor", fill: true,
children: [empty] });
let window_ = null;
function open() {
if (window_) return window_; // one window onto one prompt
window_ = composer.modal.generic({
title,
subtitle: "What this run is made of.",
size: "lg",
body: host,
actions: [composer.button.md({ label: "Done", tone: "primary",
onClick: () => window_ && window_.close("done") })],
onClose: () => { window_ = null; },
});
return window_;
}
return {
host, empty, open,
/** Open, and mounted. Tests and the shell both ask. */
get isOpen() { return Boolean(window_); },
close() { if (window_) window_.close("close"); },
};
}