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.
A font filename carries family, weight range and subset, so its bytes never
change -- re-fetching 133 KB on every reload to stay honest about a file that
cannot go stale is the wrong trade. Code stays no-store: the editor is edited
while it is running.
*.woff2 declared binary so autocrlf can never rewrite one.
Self-hosted, so the editor is correct offline and behind firewalls that block
Google's CDN -- v4 linked the CDN and silently rendered in a fallback there.
133 KB: Figtree and Archivo are variable, one file per subset; latin and
latin-ext only.
The OFL texts ship beside the fonts because redistribution requires it, and a
test fails if a bundled family arrives without one.
A missing face degrades silently into the fallback stack, so the faces declared
in fonts.css are checked against what is actually on disk, both directions.
routes.py had no coverage. register() now takes the route table so the real
routes mount on a throwaway app.
The tests write the request line onto the socket instead of using aiohttp's
TestClient, which normalises the URL before sending -- ask it for
modules/%2e%2e/app/x.css and the server receives a plain app/x.css, so the test
looks like a routing bypass when it is the client tidying up. On the wire the
modules route does match, dots intact, and resolve() refuses it.
Splitting the aiohttp and PromptServer imports: one try around both nulled
web whenever ComfyUI's server module was absent, which is every test run.
Static serving routes through a pure resolve(), so the extension allowlist and
the traversal guard are tested without aiohttp. modules/ serves .js only: a
module cannot ship a stylesheet even if one is written.