Files
olivv-cs-ComfyUI-FunPack/app/shell/panels.js
T
digital-garbage 835fab7864 core+shell: the bridge, and the first module through it
One declaration does three jobs: renders the panel, carries the defaults, names
the values that come back. A module writes no DOM and reads no DOM.

Validation refuses rather than repairs -- unknown type, a renderer its type does
not have, a label that is not text, a default outside its own bounds, a when
naming a key the module never declared. A module that is wrong does not get a
best-effort panel; it does not load, and says why once.

Ordering is by declared relation and stage, never priority numbers, and a cycle
drops only the modules in it. Compatibility is by trait, never model name.

The shell hands a module { composer, values, on, shell } and nothing else: no
node, no document, no kit internals. It cannot style anything because it is
never given anything that could.

catalogue/hide.html puts one good module and five broken ones through the real
shell and shows the result: one panel, five absent, no placeholders. The lint
fixtures make the scanner prove it catches each rule, so a clean scan means
something.

First slice: modules/system/appearance. Two settings, an optional ui.js, and
clicking Light actually turns the app light.
2026-08-28 19:58:06 +03:00

77 lines
2.8 KiB
JavaScript

// Turning announcements into panels.
//
// Everything a module can reach is decided here, and it is deliberately little:
// { composer, values, on }. No DOM node, no document, no kit internals. A module
// cannot style anything because it is never handed anything that could.
import { composer } from "../composer/composer.js";
import { renderPanel, defaultsOf } from "../composer/panel.js";
import { hostFor, offered } from "./mounts.js";
import * as values from "./values.js";
import { services } from "./services.js";
/**
* mountAll(manifest) -> { mounted, hidden }
*
* `hidden` is diagnostic only. Nothing about a hidden module reaches the screen:
* no placeholder, no greyed row, no warning chip. A module that failed is
* indistinguishable from one that was never installed, which is the point --
* the alternative is a UI full of things that cannot be used.
*/
export async function mountAll(manifest, { load = (path) => import(path) } = {}) {
const mounted = [];
const hidden = [];
for (const spec of manifest.modules || []) {
const host = hostFor(spec.mount);
if (!host) {
hidden.push({ id: spec.id, why: `no region offers "${spec.mount}" (offered: ${offered().join(", ") || "none"})` });
continue;
}
try {
// Values first: a module's ui.js may read them during setup, and a
// headless run has them without any of this having happened.
values.seed(spec.id, defaultsOf(spec));
let ui = null;
if (spec.ui) ui = await load(spec.ui);
const panel = renderPanel(spec, {
values: values.valuesOf(spec.id),
onChange: (key, value) => values.set(spec.id, key, value),
});
// Only now, with a complete panel in hand, does anything reach the DOM.
host.appendChild(panel.node);
if (ui && typeof ui.setup === "function") {
ui.setup({
composer,
values: {
get: () => values.valuesOf(spec.id),
set: (key, value) => values.set(spec.id, key, value),
},
// Scoped to this module: a module hearing about everyone else's
// edits would have to filter, and filtering needs an id it should
// not need to know.
on: (fn) => values.onChange((id, key, value) => {
if (id === spec.id) fn(key, value);
}),
// The app services a module may use, named explicitly. A short,
// reviewable list beats reaching for a global, which is how a module
// ends up depending on something nobody meant to expose.
shell: services,
});
}
mounted.push({ id: spec.id, panel });
} catch (err) {
hidden.push({ id: spec.id, why: `${err.name}: ${err.message}` });
console.warn(`[FunPack] ${spec.id} did not mount: ${err.message}`);
}
}
return { mounted, hidden };
}