220 lines
12 KiB
Markdown
220 lines
12 KiB
Markdown
# Frontend Architecture
|
|
|
|
Read this document for changes under `web/`, browser entry modes, panel state,
|
|
localization, media, or explicit canvas mutations.
|
|
|
|
## Bootstrap and module ownership
|
|
|
|
ComfyUI loads JavaScript in the extension `WEB_DIRECTORY` as ES modules.
|
|
`web/main.js` registers `Anomalous.ModelBrowser`, creates the shared browser
|
|
instance, and binds extracted modules to it. A syntax error or duplicate
|
|
top-level declaration in any imported module can prevent registration and make
|
|
the entire entry disappear. For affected modules, validate syntax and module linking
|
|
via Node's experimental VM modules (`vm.SourceTextModule`) and verify
|
|
the real ComfyUI runtime creates the configured entry. Floating trigger styling in
|
|
`web/styles.css` uses dynamic `1em` SVG scaling and flex centering to guarantee
|
|
consistent visual presentation across all configured trigger sizes.
|
|
|
|
Major UI panels live in `web/modules/ui_*.js`. Shared browser state remains on
|
|
the `AnomalousBrowser` instance. Pure parsing, normalization, comparison, and
|
|
transaction helpers remain in focused modules rather than acquiring DOM state.
|
|
|
|
The main surfaces are:
|
|
|
|
- Sidebar and folder manager: navigation, folder visibility, scan controls.
|
|
- Grid and model detail: model browsing and metadata/media presentation.
|
|
- Gallery: the user's generated outputs and the full-screen Image Detail Studio Workbench (`ui_gallery_detail.js`).
|
|
- Workspace: Prompt Notes, Workflow Recipes, and the Material Library.
|
|
- Node Assistant/Model Doctor: selected-node actions, diagnostics, parameter
|
|
presets, and missing-model recovery.
|
|
|
|
`ui_dom.js` owns generic `text` and `jsonResponse` helpers.
|
|
`material_inspector.js` shares image metadata helpers and node-parameter rendering
|
|
between `ui_materials.js` and `ui_gallery_detail.js`. The workbench does not import
|
|
the library UI, keeping this dependency chain acyclic. Library discovery uses
|
|
server-side filters and pages with cancellable requests. Detail entry fetches
|
|
metadata and scoped node blocks; the complete workflow loads only on the explicit
|
|
open action. See `material-library.md` for the API and persistence contract.
|
|
|
|
## Entry modes and host integration
|
|
|
|
One shared `openBrowser()` command backs the floating trigger, ComfyUI action-bar
|
|
button, and `Extensions -> Anomalous Model Browser` command. Entry presentations
|
|
are mutually exclusive and reuse the same browser instance. The Extensions
|
|
command remains available as a recovery path in every mode.
|
|
|
|
The native command owns the default `Ctrl + Shift + M` binding. Shortcut
|
|
customization delegates to ComfyUI's command/keybinding panel and recorder; the
|
|
plugin does not install a parallel global keyboard listener or maintain a second
|
|
shortcut preference.
|
|
|
|
Language changes replace the registered Interface setting descriptors with
|
|
freshly translated copies. ComfyUI's reactive settings tree therefore updates
|
|
the open category heading, labels, tooltips, custom controls, and combo options
|
|
immediately without requiring a page refresh.
|
|
|
|
Hidden reusable dialogs must not advertise an active modal state. In particular,
|
|
`aria-modal` is `true` only while the dialog is visible, so ComfyUI's global
|
|
modal guard does not suppress unrelated host shortcuts.
|
|
|
|
## Panel and lifecycle state
|
|
|
|
Main content surfaces are exclusive. Opening one must hide or clean the surface
|
|
it replaces, while preserving only the state required for an explicit return
|
|
path. Search from the sidebar exits model detail and shows the grid. Workspace
|
|
records the previously visible main-browser panel and restores it on close,
|
|
falling back to the grid when the prior state is unavailable.
|
|
|
|
Recipe-to-model navigation is a special recoverable transition described in
|
|
`recipes.md`. It keeps the outer browser open and must not call the browser-wide
|
|
`close()` lifecycle.
|
|
|
|
Grid folder navigation removes old content rather than accumulating hidden
|
|
grids. Before removing DOM that contains video or audio, pause the media and
|
|
release sources where applicable. Closing the browser aborts active listing,
|
|
disconnects observers, pauses media, and releases grid video/audio sources.
|
|
Lightweight cards may remain warm for 90 seconds, after which card DOM and model
|
|
payloads are released. A new listing cancels the previous request and render
|
|
generation.
|
|
|
|
The full ordered model result remains available, but cards are created in
|
|
bounded animation-frame chunks. Images use lazy loading and async decoding.
|
|
Grid video sources are attached only near the viewport and then follow the
|
|
configured `always` or `hover` behavior with muted, loop, and plays-inline
|
|
attributes.
|
|
|
|
When identifying video media from a served URL, inspect the full URL with a
|
|
query-aware extension test such as `/\.(mp4|webm)(?:$|\?|&|#)/i`. Do not use
|
|
`new URL(url).pathname`, because the backend may put the actual filename in a
|
|
query parameter.
|
|
|
|
## Localization and DOM safety
|
|
|
|
`web/modules/locales.js` is the canonical catalog for user-visible runtime
|
|
strings. Resolve strings at render time through the shared translator. Chinese
|
|
and English dictionaries keep identical key sets, have no duplicate keys, and
|
|
cover all statically referenced translation keys.
|
|
|
|
Dynamic filenames, paths, model names, node types, counts, and user-authored
|
|
content remain parameters rather than dictionary entries. Insert untrusted text
|
|
with `textContent` or escaping. Only fields intentionally supporting trusted
|
|
formatting may pass through `setSafeRichHtml()` in `safe_dom.js`; never assign
|
|
metadata directly to `innerHTML`.
|
|
|
|
Persistent localized nodes that survive a language change carry an i18n key and
|
|
are refreshed in place or rebuilt from current state. Missing translation data
|
|
must not prevent a panel or action from rendering.
|
|
|
|
Scope plugin DOM IDs and CSS classes with `anomalous-`. Before introducing a
|
|
dynamic ID, search `styles.css` for collisions. Follow the established z-index
|
|
tiers and ensure a child modal sits above the parent that would otherwise consume
|
|
its clicks.
|
|
|
|
## Graph and widget mutation rules
|
|
|
|
Explicit graph mutations are transactional. Validate the entire intended change
|
|
before mutation, wrap it in `graph.beforeChange()` / `graph.afterChange()`, and
|
|
restore prior nodes, links, and widget values if any operation or callback fails.
|
|
A successful change marks graph and canvas dirty and emits the host change event
|
|
expected by dependent surfaces.
|
|
|
|
Partial Recipe Append clones serialized nodes, remaps IDs and links, supports
|
|
groups, and rolls back everything it created on failure. It never calls
|
|
`loadGraphData` on the live graph and rejects unsupported subgraph definitions
|
|
until they have a complete remapping path. Complete recipes use
|
|
`loadGraphData` intentionally so ComfyUI owns new-workflow canvas creation.
|
|
|
|
`graph_splice.js` handles deliberate MODEL/CLIP insertion. It analyzes declared
|
|
port types rather than slot indexes or display names. Ambiguous downstream
|
|
fan-out is rejected until a user can choose a branch. Picker candidates come
|
|
from the target widget's native combo values; metadata may filter or decorate
|
|
that set but may not introduce foreign category values.
|
|
|
|
Parameter application resolves a serialized match back to a live ComfyUI node
|
|
before accessing widgets or callbacks. It validates all target slots first,
|
|
skips volatile values, and calls the host's widget hooks with the current
|
|
four-argument contract. Serialized workflow records are never treated as live
|
|
widget objects.
|
|
|
|
## Optional capabilities
|
|
|
|
`web/hash_resolver.js` may hook a compatible graph serializer to carry model
|
|
provenance. If the host API is unavailable, it disables only that integration
|
|
with a useful warning. It must not prevent `main.js` from registering or remove
|
|
the visible browser entry.
|
|
|
|
Network-backed enrichment is explicit and recoverable. An unavailable Civitai
|
|
or translation service may produce a local error state; it must not block local
|
|
browsing, editing, or already stored data.
|
|
|
|
## Prompt Studio ownership
|
|
|
|
The studio has one standalone drawer; the former embedded side/full composer
|
|
and material import drawer are removed. Existing browser integration continues
|
|
to call `openPromptStudio(owner)`, and external prompt dispatch uses
|
|
`appendPromptToStudio(owner, text, isPositive, title)`.
|
|
|
|
| Module | Owned state and responsibilities |
|
|
| --- | --- |
|
|
| `ui_prompt_composer.js` | Active drawer, docking width/side, trigger visibility and plan-loading request |
|
|
| `ui_prompt_workbench.js` | Owner-backed draft, active role, block editing, ordering, write/copy/save/export |
|
|
| `ui_prompt_source_deck.js` | Source cards, filters, new-card form, preview popover and current sync request |
|
|
| `ui_prompt_inspector.js` | Full-text inspection window, role tab and its translation lifetime |
|
|
| `prompt_studio_data.js` | Starter cards, display categories, draft/block initialization and synthesis |
|
|
| `prompt_composition.js` | Pure plan conversion, sorting and composition; no DOM ownership |
|
|
| `prompt_material_source.js` | All-page prompt discovery, detail loading and source-card deduplication |
|
|
| `ui_lifecycle.js` | View-scoped listeners, AbortSignal, cleanup callbacks and resizing |
|
|
|
|
Child views receive their container and narrow callbacks. The workbench keeps
|
|
`owner.promptPlanDraft` for reopening and exposes only `updateAll` / `addBlock`
|
|
through `owner.sidePromptComposerControl`. The source deck keeps its state local
|
|
and returns extraction/refresh/sync actions instead of assigning new owner fields.
|
|
|
|
Draft `plan.parts` is the authoritative editable representation. Full-text
|
|
inspector edits merge the enabled blocks in the edited role into one block,
|
|
preserving disabled blocks and the opposite role. The inspector explains this
|
|
behavior before editing. Copy, save and reopen derive text from the same parts.
|
|
|
|
Every close route (button, Escape, backdrop, replacement and parent close) must
|
|
dispose the same view scope. Parent close also closes its inspector. An Escape
|
|
handled by an inspector must not close its parent in the same event dispatch.
|
|
Closing during resize releases move/up listeners and restores body cursor and
|
|
selection styles. UI reads are cancelled on close; a save already sent to the
|
|
server may still complete, but must not reopen or repaint a disposed view.
|
|
|
|
Initial and explicit source synchronization share a paginated loader. List
|
|
summaries are identifiers, not prompt bodies: each unique filename is resolved
|
|
through `material_prompt_data.js`. Deduplication uses prompt role and text, so
|
|
the same text can still appear once in each role. A newer sync cancels the old
|
|
one and only the current result updates the deck.
|
|
|
|
The translator captures graph, node, widget and widget value before an async
|
|
write. It revalidates that destination and the input after translation; close,
|
|
selection changes and intervening widget edits invalidate the pending write.
|
|
Explicit English translation applies to all input languages, including kana
|
|
and Korean. Transport errors and rejected bridge responses remain local errors;
|
|
provider-specific validation belongs to the backend translation route.
|
|
|
|
## Visual styling and theme architecture
|
|
|
|
`styles.css` is the source of truth for current visual values. Shared `--amb-*`
|
|
tokens express surfaces, text, borders and control shapes; theme overrides must
|
|
be scoped to `.theme-abyssal-scarlet` rather than changing unrelated surfaces.
|
|
|
|
Studio drawer rules keep the source deck at the screen edge and the assembly
|
|
track next to the canvas. Common geometry is shared between dock directions;
|
|
direction-specific rules set column order and separators. The removed embedded
|
|
composer's `#anomalous-container.anomalous-docked` overrides must not return.
|
|
Before adding an override or `!important`, locate and edit the owning rule.
|
|
Older component and theme overrides elsewhere in this file still need a
|
|
separate, visually verified consolidation.
|
|
|
|
## Verification
|
|
|
|
Use `node --experimental-vm-modules tests/prompt_ui_lifecycle.mjs` for actual
|
|
module open/close, nested Escape, resize cleanup, block insertion and delayed
|
|
node writes. `tests/prompt_material_source.mjs` covers pagination, deduplication
|
|
and cancellation; `tests/translation_service_contracts.mjs` covers the HTTP
|
|
bridge. The UI fixture simulates DOM and ComfyUI APIs and does not replace
|
|
checking the real host, layout, focus, drag gestures and theme rendering.
|