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.
55 lines
2.1 KiB
JavaScript
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"); },
|
|
};
|
|
}
|