The Node Assistant becomes "Current node" (当前节点): one page instead of the "actions" / "parameter sets" tabs, model actions on top, Parameters below. - node_parameter_sets.js: the node type's saved values from the material library and from recipes' parameter sets as one list; the same values saved in both are one entry naming both sources. For the selected node, exactly what an entry would change; seeds (and their after-run control), model files and choices this computer lacks are kept. - ui_node_parameters.js: the list, "apply these N" (one undo in the receipt) and "save these values". - api/node_material.py: POST /anomalous/save_node_material keeps one node's values as a node_parameter_selection material, deduplicated by type and values; the library lists it under parameters. - Removed: ui_node_presets.js with its hover popovers, the beta notice and their styles and strings. The panel and help texts use the new name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
38 KiB
Anomalous Model Browser Architecture
This is the short entry point for maintainers and AI agents. It describes the system map, ownership boundaries, and rules that apply across subsystems. Read only the linked topic document relevant to the task; do not load every document by default.
Reading map
Before implementation, follow AGENTS.md, the canonical development and maintenance rules; its section 0 is required for every commit. GEMINI.md and CLAUDE.md are reading entry points only.
The prompt studio reads material prompt payloads through web/modules/material_prompt_data.js;
list summaries do not contain prompt bodies. See the Material Library contract below.
| When changing... | Read... |
|---|---|
| Python routes, storage, paths, metadata, covers, or scan state | docs/architecture/backend.md |
| Browser lifecycle, UI state, localization, media, or graph edits | docs/architecture/frontend.md |
| Update-guide content/versioning or sidebar hover labels | docs/architecture/update-guide.md |
| Workflow Recipes, packages, galleries, Parameter Notebooks, or prompt roles | docs/architecture/recipes.md |
| Material Library snapshots, image parameter details, or reusable node blocks | docs/architecture/material-library.md |
| Model Doctor, provenance hashes, missing-model recovery, or model scanning | docs/architecture/model-resolution.md |
| Browser audits, E2E functional bug reports, or verification sign-offs | docs/audits/README.md |
| Why a current product boundary exists | docs/decisions/README.md |
| Recurring implementation mistakes and post-mortems | .agents/logs/ai_lessons.md |
README.md is user-facing documentation and CHANGELOG.md is user-facing
release history. Neither is the source of truth for internal architecture.
System shape
Anomalous Model Browser is a UI-only ComfyUI extension. It registers no custom
nodes (NODE_CLASS_MAPPINGS is empty). ComfyUI loads the Vanilla JavaScript/CSS
frontend from web/; the Python package registers /anomalous/ aiohttp
routes and performs local filesystem, metadata, recipe, and scan operations.
ComfyUI frontend
web/main.js
-> web/modules/* UI, graph integration, localization
-> /anomalous/* JSON and bounded media requests
ComfyUI Python server
__init__.py
-> api/__init__.py route registration
-> api/*.py storage and domain operations
-> scraper.py explicit metadata/hash enrichment
User-owned data
ComfyUI model folders models and sidecars
user/.../anomalous_recipes workflow recipes and recipe assets
user/.../anomalous_parameters immutable parameter snapshots
user/.../anomalous_materials curated image/workflow material bundles
user/.../anomalous_notebooks prompt notes, with legacy originals preserved
The frontend and backend communicate through narrow JSON contracts. The frontend must not infer filesystem authority, and the backend must not depend on DOM or live LiteGraph state.
Ownership map
This map, with the topic documents, is also the file directory: every source
module under api/, web/ and web/modules/ and each root Python file is
named with its owner. node tools/check_structure.mjs checks this. Styles are
covered by the styles.css manifest.
Backend
-
api/config.pyowns configured paths and active model-folder types. -
api/path_utils.pyowns containment, filename validation, atomic JSON writes, and resolving a saved model value inside the model folders;api/utils.pyis a compatibility export surface. -
api/metadata.pyowns sidecar and safetensors metadata extraction. A model's information has three layers, the user's first:<model>.anomalous.json(what the model editor set; only the editor writes it), the scan result (<model>.info, or another tool's.civitai.infowith Civitai ids;id: -1means inferred from the file) and the file itself. Metadata reportsinfo_source(civitai/local/ empty) anduser_fields. A cover is Civitai's only while it is byte-identical to<model>.civitai_bak.*; any other cover is the user's, and no scan replaces or deletes it. -
api/model_catalog.py,api/model_resolution.py,api/model_metadata.py, andapi/model_media.pyown model listing, identity recovery, mutation, and covers;api/models.pyis a compatibility facade;api/model_constants.pyholds the model, media and sidecar extensions they share.api/model_type_listing.pylists a whole models folder with its subfolders for the models page's type chips. -
api/scanner.pyandscraper.pyown scan orchestration and enrichment; both scan routes map request fields to scraper switches in one place (_scraper_flags).civitai_client.pyis the scraper's network side (version by hash, model page, media), telling "Civitai does not know the file" from "Civitai could not be asked".api/scan_report.pyturns the scraper's per-model report lines into one result per scan: kept as the last scan (/anomalous/last_scan), recorded in the activity log, each model located so the browser can open it (/anomalous/scan_model).api/scan_summary.pycounts models matched on Civitai, unmatched and not yet scanned for the scan page and lists the last two, by the same sidecar rules the scraper uses (sidecar_info,is_unmatched,unmatched_reasoninmodel_identity.py). -
api/workflow_schema.py,api/recipe_schema.py,api/recipe_images.py, andapi/recipe_store.pyown recipe validation/shaping, images, CRUD, history, and integrity receipts;api/recipes.pyis the HTTP facade.api/recipe_constants.pyholds the size and count limits shared by recipes, parameters and materials. -
api/recipe_packages.pyowns bounded inspect-stage-commit package handling. -
api/parameters.pyowns Parameter Notebook persistence and lookup. -
api/notebooks.pyowns Prompt Note persistence and recoverable legacy copying. -
api/material_schema.py,api/material_assets.py, andapi/material_store.pyown curated material shaping, private assets, persistence/cache, search and lifecycle;api/materials.pyowns HTTP mapping and compatibility entry points.api/node_material.pysaves one canvas node's current values as anode_parameter_selectionmaterial (the current-node panel's "save"). -
api/media_routes.py,api/gallery_routes.py,api/translation_routes.py, andapi/folder_types.pyown the formerly mixed utility route families. -
model_policies.pyowns shared backend rename and protected-category policy. -
model_identity.pyowns file SHA-256 evidence shared with the standalone scanner. -
recycle_bin.pyis the only way the plugin deletes the user's files (models, covers, outputs, recipes, materials, notes, audio): to the system Recycle Bin, together, or not at all (TrashUnavailableon drives without one).api/trash.pyis the API's import of it plus the message shown when nothing was deleted. The plugin's own temporary files, caches and bounded recipe history are still removed directly. -
api/audio_catalog.pyowns the generated-audio side: the output-audio history, temp Preview Audio results (listed, and copied intooutput/audio/on save), deletion, and streaming files from the output and temp folders. Characters and their reference clips belong to the Anomalous_TTS node and come from its routes. -
api/audio_metadata.pyreads the ComfyUIpromptcomment from FLAC and Ogg/Opus files (read-only) and extracts the TTS node's speech, sample and seed for the audio gallery. -
api/image_search.pypowers the output gallery search (gallery_images?q=): it reads only the PNG text chunks before the pixel data (ComfyUI prompt and workflow, A1111parameters), caches one record per image by mtime, and matches all query terms; hex terms of 8+ characters also match recorded model SHA256 values and, viacollect_model_hash_indexinmodel_resolution.py, local model files with that hash. Terms arrive as repeatedtermparams, one phrase each.ui_search_chips.jsis the reusable search-block input (Enter or a comma commits a block);ui_gallery.jsplaces it above the gallery. -
api/version_manager.pyowns the plugin's own version: the installed tag/branch (/anomalous/version, local only), published releases fetched only on request (GitHub releases API, falling back togit ls-remotetags; drafts skipped, pre-releases never counted as latest), switching to a published tag (detached checkout), returning to the default branch (fast-forward only), and undoing the last switch (recorded in the ignored.anomalous_version.local.json). Every switch refuses to run over modified tracked files.ui_version_manager.jsis the version line (shown in the update guide opened by the header "!" button) and panel; nothing goes online until "check for updates" is clicked, and restarts go through ComfyUI Manager's reboot route when present. -
web/main.jscoordinates extension registration (with?v=...versioned module imports busting aggressive browser ES Module caching and unconditional legacy storage key purging).browser.jsowns the shared browser class and extracted-method wiring;browser_entry.jsowns the single browser instance plus floating/topbar/menu entry behavior (pointer-capture drag, pre-mount positioning to avoid a flash,anomalous_trigger_pos_v3persistence); the unsaved default position is defined in CSS;entry_controls.jsowns entry mode, trigger size/style normalization, viewport clamping that keeps the button out of the sidebar dock, and saved-position storage (isValidSavedTriggerPosition);api/__init__.pysendsCache-Control: no-cachefor this plugin's static files so a normal refresh revalidates them (modules are imported without?v=query strings, which would create second module instances); andinterface_settings.jsowns language and theme preferences. -
ui_domain_switcher.jsstores which side is active (image or audio: which list the list column shows, which guide "!" opens); the rail's pages switch it throughui_shell_nav.js.browser.switchAudioTabis the single entry for the audio panels (rail pages, the audio list's own entries) andhideAllPanelsstops audio playback. -
audio_engines.jsowns the audio page's one speech engine, GPT-SoVITS through the separate Anomalous_TTS node pack (docs/decisions AD-016), detected at runtime through/object_info/<node class>. Characters are normalised into one voice-group shape (node_value= what the node'scharacterwidget takes), so cards, sidebar and Script Director share one implementation. While the pack is missing the studio shows install steps instead of cards; nothing else depends on it. Its data comes only from the node's HTTP contract, version 11 (/anomalous_tts/characters,/audio,/settings,/status, the storage, library, pretrained, browse, import and import preview routes; the node repo'sdocs/INTERFACE.md, mirrored as the project docanomalous-tts-interface.md); Anomalous never reads or writes its model folders.loadGptSovitsStatuscaches the setup status with the engine data (null for a node without/status, which hides setup and import). The character list is a summary without file lists;fetchGptSovitsCharactergets one character's files. Engine presence and loaded voice groups are cached (MAX_AGE_MS) so re-renders from sidebar clicks, domain switches and the studio + sidebar pair cost no requests;invalidateEngineCache()drops them after a save or an added voice, andinvalidateEngineCache({ rescan: true })(Refresh button) also makes the node re-read its folders (?refresh=1).ui_audio_tts_editor.jsedits a character's emotion references through that API: it opens at once, loads the character's audio list in the background (save waits for it), and keeps settings fields it does not know.tts_setup_api.jsholds the other calls (storage place and moving, forgetting an earlier place, pretrained sources and downloads, folder browse, chunked upload, inspect, commit, discard) and the pure import-form rules (importKind,pickWeights,nameConflict,textFromFile,buildImportBody,importProblem,rowState/sectionStatefor the form's colours,setupSummary,pretrainedReminder,missingForLanguage); no DOM.ui_tts_setup.jsis the GPT-SoVITS settings dialog, opened from the sidebar footer (setupAttentiongives that entry its dot). Missing pretrained files only get a dismissable dot when the studio's characters need them (dismissed ids inlocalStorage; a newly needed file brings it back); missing packages get a red one. Inside: one storage place (changing it asks whether the characters move along), other places still read, pretrained files, packages. It polls the status only while a download or move runs and it is open, and redraws the studio when a move ends.ui_tts_import.jsis the import window. It first asks what the user has (mode:single= one card led by its checklist,batch= a drop area then one card per draft, one unfolded at a time,add= files for an existing character, also opened straight from a card's "Add files"), and runs a spotlight tour once per screen ("?" replays it). It owns the drafts (characters being built;target= files added to an existing one), the unassigned tray, the characters waiting for the next batch (a large add opens one batch; the next opens on request or once the batch is imported), every file row (in exactly one draft'srows; a row keeps its own name inoriginaland gets a clash-freenamein a draft, sent to the node as the file'sname), a debounced inspect per draft, and imports the ready drafts one after another (each commit on its own, without the rowsleftOutnames; closing discards uncommitted uploads).draw()rebuilds the cards on structural changes,refresh()only repaints status, so typing never loses focus.ui_tts_import_uploads.jssends browser files in chunks, three at a time, once they are in a draft (tray files wait);ui_tts_import_player.jsplays one clip at a time for comparing (browser files from memory, local paths through the node's preview route).tts_import_groups.jsholds the pure rules: which folders are never taken (skipFolder/isPackage, the same rules as the node's scan: Python environments and base models anywhere, a package's program and training folders only inside a package, never the chosen folder), which draft a file goes to (groupFiles: a folder with one character's weights and clips is that character; other weights by stem; other files follow the character folder, the weights in their nearest folder, then folder names or file-name prefixes; unclaimed.listfiles go to every character), how an add splits into groups and batches (planGroups,splitBatch), new names for files that would clash inside a character (clashFreeNames), which files are left out (leftOut: clips outside 3–10 s and their line files), where a draft stands, and its checklist (the next step first).ui_tts_import_sources.jsbrings files in: the browser's dialogs and drops (uploaded), the node's picker (paths); every source gives folders starting with the chosen one, says which folders were left out or too deep, and offers the picker before a very large upload.ui_tts_import_draft.jsdraws a draft's card (name in the header, steps left, the checklist with the next step's button and the chosen weights, one line per clip with its owner picker, the first clips with "show all", left-out files folded, line files, language) and the unassigned card (one folded line per folder when large), and builds each row's elements once, so they survive redraws and moves;ui_tts_import_screens.jsholds the fixed screens (the first question, the batch drop area, the "add more" menu) and the tour steps.ui_tts_file_drop.jsreads OS drops (walking dropped folders, only files an import can use, up to 5000) for the studio, the sidebar and the workbench.ui_tts_path_picker.jspicks server-side folders or files through the node's browse route, since the browser cannot see local paths. -
audio_node_targets.jsis the single table of canvas nodes the audio studio writes into (the character widget and the script widget ofAnomalousTTS_CharacterSpeech).planVoiceDropdecides every character drop and names the reason for each refusal; unlisted nodes are refused, never matched by widget name. Supporting a node means adding one entry plus a test. -
ui_audio_studio.jsowns the character cards, preview playback, tag copying, and dragging a card header onto a node through the sharedbindMaterialDragwithtargetHint/rejectHinttelling the user what releasing does.audio_script.jsholds the pure script rules: splitting, bundling (buildScriptPackage, with[take:N]for retakes), combo value matching, andbuildTtsPrompt(a GPT-SoVITS script as a ComfyUI API prompt saving tooutput/audio/<character>/).ui_script_director.jsowns the script drawer: one character, an emotion chip row per line card, and a bundle that is generated in the drawer, or pushed to the selected/only target node or dragged onto one (writing the character and script widgets together). The studio feeds it the voice groups after each fetch. The drawer puts the cards on one side and generating on the other: beside them once the drawer is wide, below them when it is narrow (docked browser).ui_script_run.jsis the drawer's "Generate" section (a folded settings line and a short bar): it runs the script without the canvas, plays the result, retakes the whole script (new seed) or one line ([take:N], the node caches the rest), and saves a character's language, speed and folded sampling parameters to itsdefaults(only values that differ from the node's own defaults, which it reads from/object_info; needs Anomalous_TTS interface 11).ui_tts_pronunciation.jsedits a GPT-SoVITS character's pronunciation table (the node'sreplacesetting) from its card; each row can be heard as it reads now and as replaced, through a Preview Audio run, and saving keeps every other settings field.audio_tts_run.jsqueues one API prompt with this page's client id and follows it (websocket status,/historyresult, cancel = queue delete or targeted/interrupt); it never touches the canvas. -
In the audio domain the header ! opens
AUDIO_USAGE_GUIDE(a how-to, seedocs/guides/audio-studio.md) instead of the visual update guide. -
ui_audio_sidebar.jsowns the audio navigation and the active audio filter: characters grouped by language, groups folded inlocalStorage, a character unfolds into its clips (a click plays the clip through its studio row, switching the studio to that character when needed), a search box from six characters on, a red dot for characters that need a look, the GPT-SoVITS settings entry at the bottom and file drops on a character. Canvas drags go throughaudio_voice_drag.js, shared with the studio cards. -
ui_audio_gallery.jsowns the generated-audio history (output folder): playback, seeking, search, paging, download and deletion, showing the speech/voice/seed recorded in each file, plus a section for unsaved temp previews that can be copied intooutput/audio/. -
ui_sidebar.jsassembles the browser window (rail, list column, header, page panels) and renders the model folder list.ui_shell_rail.jsowns the left icon rail: one entry per page, the tool slots and the settings slot.ui_shell_nav.jsowns page navigation (goTo): the domain each page needs, which pages have a list and whether it is open (remembered per page; closed and overlaying below 760 px), the header's page title, and the page reopened next time.ui_home.jsowns the home page (task cards, first steps).ui_shell_frame.jsowns dragging, resizing and keeping the floating window on screen;ui_scan_watch.jspolls scan status for the rail's scan button, the progress panel and the model reload afterwards. The activity log (docs/decisions AD-018):api/activity_log.pykeeps the entries (newest first, bounded, in the ComfyUI user folder), records this plugin's and Anomalous_TTS's successful write requests in a middleware, and serves/anomalous/activity(a handler may attach what it changed,request[ACTIVITY_DETAIL]; scans record themselves when they end);activity_canvas.jsrecords canvas changes by comparing a snapshot taken when the user presses inside Anomalous with the canvas when they next press or type outside it (activity_diff.jsholds the pure snapshot and difference);activity_log.jsis the client and the words for each entry;ui_activity.jsrenders the activity page and the home page's recent list.ui_settings_hub.jsowns settings and model-card preferences;ui_toolbox.jsowns the tool catalog, the rail's tool buttons, and tool dispatch;ui_browser_navigation.jsowns shared panel hiding/cleanup and workspace return, including Esc on the workspace panel (nbPanel, below the header and right of the rail; the list column steps aside while it is open).ui_scan_page.jsis the scan page (a tool page like the doctor): counts, scan and look-up-again buttons, advanced options;ui_scan_lists.jsrenders its last-scan result and the unmatched / not-scanned lists, whose rows open a model or scan it;scan_results.jsholds the words for scan results, shared with the activity page.scan_runner.jsstarts and follows scans (every folder, picked or listed models, or one model from its card), shows the result and refreshes node drop-downs, hashes and the grid afterwards; folder visibility/order lives inui_folder_manager.js; and help content lives inui_help.js.scan_progress.jsowns the scan progress panel (updateScanProgress/finishScanProgress/failScanProgress): inside the scan page while it is shown (setScanProgressHost), floating at the bottom right otherwise.shortcut_controls.jsowns the open-browser and material-library keyboard shortcuts, their fallback when ComfyUI's keybinding does not fire, and the settings control that opens ComfyUI's keybinding editor. -
ui_model_sources.jsowns the Model Sources Hub, managing workflow-model and global-library source detection, Civitai/HuggingFace URL attribution, sidecar persistence, and resilient scope switching between active workflow and full local library (with cached library state preservation and reliable re-rendering). -
ui_materials.js,ui_material_cards.js, andui_material_application.jsown the Material Library UI, category navigation, card presentation (with grab cursor affordances, explicit drag tooltips, and polymorphic card dragging viabindPolymorphicMaterialCardDrag), context-aware drag guidance, relaxed third-party node prompt widget sniffing and injection, and the structured empty state onboarding blueprint guiding users through collection, canvas drag, and prompt studio mixing. Drag precedence prioritizes node hits over blank canvas drops; blank canvas drops auto-instantiateCLIPTextEncodenodes for prompt materials with standard colors or open full workflows.node_material_actions.jsowns prompt envelope extraction (extractMaterialPromptEnvelope) shared by detail views, cards and canvas actions. -
ui_update_guide.jsandupdate_guide_data.jsown the non-intrusive update guide modal (accessible via header button#anomalous-update-notice-btn, the Help modal and Home's "What's new"; the current guide's ID and steps live inupdate_guide_data.js) with full bilingual localization.ui_spotlight_tour.jsprovides the interactive spotlight mask tour (startSpotlightTour), gliding smooth focal box highlights across the rail's pages and tools with directional tooltip cards and keyboard navigation; steps whose target is not on screen are skipped. Other views pass their ownsteps(text from locale keys) and an optionalonClose; the GPT-SoVITS import window does. -
tool_registry.jsholds the tools' names, icons and stable IDs: the catalog tools (including Prompt Notes / 提示词笔记) and the two anchors (Toolbox and Settings). -
The rail's tool slots (built in
ui_toolbox.js, fixed) hold scan, doctor and assistant, then the toolbox; settings sits at the rail's bottom. Materials is a rail page. The Toolbox popover lists the remaining catalog tools and opens beside the rail. -
ui_model_types.jsowns the models page's type chips (one per models folder, with its count) andowner.modelScope, what the grid lists: a whole type, or one list folder shown as a crumb. The grid's cards setcurrentType/PathIdx/Subfolderto their own model's folder (focusModel), which the editor, the scanner and "add to canvas" read. -
model_source.jsshows where a model's information came from: the card badge (marked only when inferred from the file, ≈, or not scanned yet) and the detail header's source line with the fields the user set. -
ui_grid.jsand model-detail modules own model presentation:ui_grid.jsmanages chunked card rendering, card placeholder ergonomics (eliminating misleading unclickable text in favor of pure centered icon and status), card action buttons (one-click canvas addition with plus icon, model metadata editor, direct precision scanner without wizard modal popups) with absolute positioning cascades immune to tooltip target conflicts, vibrant hover affordance, safe docked sidebar preservation upon node addition, and multi-type node dispatch;ui_detail.jscoordinates detail display,ui_model_editor.jsowns metadata editing, andui_model_selector.jsowns advanced selection.ui_gallery.jsandui_gallery_detail.jsown generated-image browsing and workbench lifecycle, with stage interaction inui_image_stage.jsand metadata tabs inui_image_inspector.js. -
ui_recipes.js/ui_recipe_detail.js,ui_notebooks.js, andui_materials.jsown their respective workspace surfaces and persistence flows.ui_recipes.jsowns the Workflow Recipe studio catalog workspace with search/filter tags, grid/list layout toggle, dedicated top-right modal close anchor (permanently decoupled from the tool button row to prevent wrapping displacement), streamlined action header (preserving active workflow saving while pruning unfinished package import entrypoints), and card browsing.ui_notebooks.jsowns Prompt Note catalog, sidebar dual-group management (note list + floor quick jump anchor navigation with scrollspy active tracking and tooltip hints), and persistence;ui_notebook_editor.jsowns unfolded card editing (modularized into single-responsibility sub-functions adhering to the 50-line rule: sticky top action toolbar with floating More popover dropdown and timed two-step delete safety guard, unfolded companion models card with unconstrained multi-column tile flow eliminating nested gallery scrollbars, prompt composer with dynamic field-sizing and compact inline find & replace toolbar, flat material library archiving card with clean single-icon feedback, and unified dark slim scrollbar ergonomics with complete bilingual dictionary coverage inlocales.js), andnotebook_canvas.jsowns LiteGraph creation. Prompt Notes are integrated as a standard tool in the Toolbox with defensive workspace return state restoration, TDZ-safe summary initialization, and responsive empty-state fallback rendering.ui_recipe_detail.jscoordinates the Workflow Recipe detail session and model composition.ui_recipe_overview.jsowns the Overview prompt showcase (withentry.textfallback, guarded non-shrinking primary action CTA, and floating Popover More dropdown menu), andui_recipe_parameters.jsowns the responsive Parameter Presets workspace (featuring default-expanded raw node parameter inspection, aclamp(230px, 24vw, 290px)sidebar with guarded card actions, uncluttered console action bars with deferred status feedback,minmax(130px, 1fr)Bento Grid with universal click-to-copy, LoRA cards with flexbox truncation guards, and sticky editor headers).ui_materials.jsowns Material Library discovery and pagination,ui_material_cards.jsowns catalog cards and drag-and-drop (same-type blocks, or prompt-bearing materials onto nodes with prompt slots),ui_material_detail.jsowns the full detail surface, andui_material_application.jsowns selected-node tracking and explicit material application (same-type block application first, then cross-node prompt injection).prompt_boxes.jsis the one place that finds prompt boxes on the live canvas: a multiline STRING input by the node's ComfyUI definition, whatever it is called; its role is the box's own name (positive / negative) or else the wiring (outputs followed to an input named positive / negative). It also plans which box takes which text and finds a box's opposite-role partner on the same sampler.prompt_drop.jsis the prompt drag on the canvas: boxes outlined by role, the box under the pointer as the target, and a hint saying what release writes where.node_material_actions.jsextracts prompt envelopes without model file paths and writes them (fillPrompt); text never crosses roles unless the user picked the box; writes are one undo step. Within recipe detail,ui_recipe_versions.jsowns history comparison/restore,ui_recipe_gallery.jsowns result cards and direct Image Detail Workbench handoff,ui_recipe_model_matching.jsowns preview resolution and explicit local replacement,ui_recipe_metadata.jsowns inline persistence, andui_recipe_detail_dom.jsowns the DOM/copy helpers shared by detail subviews.recipe_identity.jsderives model references from native loaders plus a table of verified all-in-one loader layouts (ALL_IN_ONE_LOADER_SPECS, mirrored inapi/recipe_schema.py); other third-party widgets stay parameters. References are keyed by(node_id, widget_index, category, saved_value).recipe_parser.jssummarizes models, LoRAs, samplers and prompts (linked prompt nodes before embedded loader prompts) and keeps the summary in step with widget edits using the same node rules.ui_recipe_catalog.jsowns recipe filters, navigation, dismissible topbar drag guidance strip with localStorage persistence, the 3-step empty-state onboarding blueprint (renderRecipeEmptyGuide), and background catalog-wide model readiness resolution (resolveCatalogRecipeReadiness),ui_recipe_cards.jsowns cards and card actions (includinggrabdrag affordance, cover可拖拽badge, harmonized multi-state model readiness pill withgetRecipeReadinesssynchronizing available, missing, and pending matches with detail overview, and direct canvas drag-and-drop),ui_recipe_dialogs.jsowns save/edit dialogs, andui_recipe_media.jsowns shared cover helpers. Detail sessions synchronize detected model availability back toowner.recipeRecordsviasyncRecipeReferencesToCatalog. -
ui_prompt_composer.jsowns the standalone Prompt Studio drawer. Its child views areui_prompt_source_deck.js,ui_prompt_workbench.js, andui_prompt_inspector.js. Assembly plan data, track-vs-role separation, and cross-role tail smart-sorting are owned byprompt_composition.jsandprompt_studio_data.js.ui_prompt_source_deck.jsowns the card preview popover with narrow bridging corridors, differentiated hide timers, and fast dismissal when hovering or clicking library blank space. -
ui_prompt_translator.jsowns the standalone Prompt Translator, featuring robust multilingual/Chinese node prompt extraction (extractPromptFromNode), real-time canvas selection synchronization (app.canvas.onNodeSelected), automatic prompt injection on open/docked mode, on-demand read/sync controls, guarded selection writeback across single and multi-tab workflows, compact streamlined button ergonomics preventing multi-row wrapping, elastic vertical flex textareas maximizing canvas-side vertical space, and an expanded 460px default sidebar width with automatic backward-compatible width migration. Both translator and studio useui_lifecycle.jsfor global listeners, request cancellation and resize cleanup. Translation requests go throughtranslation_service.js. -
ui_dialog.jsowns the plugin's own alert / confirm / prompt dialogs (anomalousAlert,anomalousConfirm,anomalousPrompt), used instead of the browser's native ones.ui_prompt_toast.jsis the short toast shared by the Prompt Studio views and the Model Source Hub. -
model_policies.jsmirrorsmodel_policies.pyfor the frontend: which folder types a loader widget holds, which are never physically renamed, and which need a workflow-carried hash before Model Doctor recovers them. -
ui_dom.jsprovides small DOM/JSON helpers;material_inspector.jsowns material-specific metadata and parameter rendering. -
model_check.jschecks the open workflow's models against this computer (Model Doctor's verdicts, no DOM) and puts a found file into its node;ui_doctor.jsis the doctor page andui_doctor_banner.jsthe bar over the canvas when an opened workflow misses models.ui_node_assistant.jsowns the current-node panel (model actions, LoRA insertion, model cards and history),ui_node_model_picker.jsowns native combo replacement,ui_node_parameters.jsrenders the panel's parameters section, andnode_parameter_sets.jsmerges the node type's saved values (materials and recipe parameter sets) and works out what each would change on the node, keeping seeds, model files and missing choices.model_picker.js,node_material_actions.js, andgraph_splice.jsown the remaining explicit graph changes. -
ui_model_sources.jsowns the Model Source Hub (模型来源统一中控中心), providing dual-scope (Workflow and Library) source inspection, external platform jumping, canvasNotenode generation,workflow.extra.anomalous_model_sourcesmetadata synchronization, automated asynchronous model metadata resolution (resolveWorkflowModelsMetadata) via/anomalous/resolve_paths_to_previewswith local sidecar priority detection, and protected read-only link display with deliberate edit-mode unlocking and dirty-state dynamic local persistence (hiding redundant[Save Local]buttons until links are modified). -
locales.jsis the shared runtime string catalog. Existing inline bilingual UI strings remain migration debt; new strings belong in the catalog. -
styles.cssis the ordered import manifest forweb/styles/*.css, which own presentation and theme overrides. Color values, dimensions and visual design descriptions are not duplicated as architectural contracts.
Cross-system invariants
These rules are intentionally summarized here and specified in the linked topic documents.
- Identity is provenance, not naming. Model Doctor may use a cryptographic hash, exact byte size under the allowed category policy, and target category. Paths, filenames, display names, previews, and fuzzy similarity are never identity evidence.
- Filesystem input is untrusted. Backend request paths must pass the shared
containment and filename helpers. Checking only for
..is insufficient on Windows and in the presence of alternate separators, UNC paths, or symlinks. - User files are changed transactionally and conservatively. Recipe writes, imports, graph mutations, and sidecar operations validate before mutation and either complete coherently or restore the prior state.
- The event loop stays responsive. Recursive walks, hashing, metadata parsing, and other potentially large disk operations run off the aiohttp event loop. UI rendering and media loading are bounded and cancellable.
- Host state is preserved. Browser panels are mutually exclusive, Workspace/model-detail transitions are recoverable, and graph edits use ComfyUI's change/callback contracts.
- Runtime strings are localized safely. User-visible copy comes from
locales.js; dynamic values stay outside dictionaries and enter the DOM as text. Only allowlisted rich content goes throughsafe_dom.js. - Optional integrations fail locally. Missing graph APIs, metadata, network availability, or an optional resolver may disable that capability but must not make the main extension disappear.
- Presentation data is not authority. Covers, thumbnails, summaries, cached names, and workflow fingerprints never replace the authoritative serialized workflow or model provenance record.
Data and compatibility boundaries
- Runtime settings and newly saved API keys live in
api/config.json.scraper.pymay read the legacy rootconfig.jsononly as a compatibility fallback. API keys are not stored in browserlocalStorage. - Recipes, Parameter Notebooks, and Material Library records/assets are user data outside the extension directory. They are never bundled with or silently migrated into the plugin source.
- The extension may integrate with Civitai and optional translation services only through explicit product behavior. External content and dependencies keep their own terms.
- Project code and documentation use the MIT license.
TRADEMARKS.mdseparately defines the project-name and official-branding boundary. - Internal property names and established routes may remain stable when a user-facing surface is renamed. Do not churn compatibility contracts merely to match presentation wording.
Change and snapshot protocol
The per-commit requirements (which document to update, the structure check, verification, snapshots and pushing) live in section 0 of AGENTS.md and are not repeated here. Update this entry point only when the system map, cross-system invariants, or reading map change; everything else goes in the narrowest topic document.
Decision records explain enduring choices; they are not a chronological diary. Git history is the authoritative record of implementation changes. Planning-only documents must be clearly labeled as proposals and must not describe unshipped behavior as current architecture.