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.
37 lines
1.1 KiB
JavaScript
37 lines
1.1 KiB
JavaScript
// The live value of every setting, keyed exactly as the declaration keys them.
|
|
//
|
|
// This is the only thing in the app that reads the UI. A module never does: it
|
|
// declares what it needs and is handed values, so the same module works with a
|
|
// panel on screen and with no browser at all.
|
|
|
|
const store = new Map(); // moduleId -> { key: value }
|
|
const listeners = new Set();
|
|
|
|
export function seed(moduleId, defaults) {
|
|
store.set(moduleId, { ...defaults });
|
|
}
|
|
|
|
export function set(moduleId, key, value) {
|
|
const current = store.get(moduleId) || {};
|
|
current[key] = value;
|
|
store.set(moduleId, current);
|
|
for (const fn of listeners) {
|
|
try { fn(moduleId, key, value); } catch { /* a listener must not break the edit */ }
|
|
}
|
|
}
|
|
|
|
export const valuesOf = (moduleId) => ({ ...(store.get(moduleId) || {}) });
|
|
|
|
/** Everything, as it would be sent with a generation request. */
|
|
export const all = () => Object.fromEntries([...store].map(([id, v]) => [id, { ...v }]));
|
|
|
|
export function onChange(fn) {
|
|
listeners.add(fn);
|
|
return () => listeners.delete(fn);
|
|
}
|
|
|
|
export function _reset() {
|
|
store.clear();
|
|
listeners.clear();
|
|
}
|