Files
DemonGatanjieu-Anomalous_Mo…/ARCHITECTURE.md
T
DemonGatanjieuandClaude Opus 5.5 6cb9ea39be feat(audio): pronunciation table for GPT-SoVITS characters
"Pronunciation" on a character card edits the node's `replace` setting
(script text -> what the character reads). Each row can be heard as the
character reads it now (the saved table applies) and as replaced, through
a Preview Audio run that stays out of the audio gallery; one preview at a
time, cancelled when the dialog closes. The file is re-read before editing
and every other settings field is kept. buildTtsPrompt gains `preview`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 02:41:44 +08:00

35 KiB
Raw Blame History

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 deep 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.py owns configured paths and active model-folder types.

  • api/path_utils.py owns containment, filename validation, atomic JSON writes, and resolving a saved model value inside the model folders; api/utils.py is a compatibility export surface.

  • api/metadata.py owns sidecar and safetensors metadata extraction.

  • api/model_catalog.py, api/model_resolution.py, api/model_metadata.py, and api/model_media.py own model listing, identity recovery, mutation, and covers; api/models.py is a compatibility facade; api/model_constants.py holds the model, media and sidecar extensions they share.

  • api/scanner.py and scraper.py own scan orchestration and enrichment.

  • api/workflow_schema.py, api/recipe_schema.py, api/recipe_images.py, and api/recipe_store.py own recipe validation/shaping, images, CRUD, history, and integrity receipts; api/recipes.py is the HTTP facade. api/recipe_constants.py holds the size and count limits shared by recipes, parameters and materials.

  • api/recipe_packages.py owns bounded inspect-stage-commit package handling.

  • api/parameters.py owns Parameter Notebook persistence and lookup.

  • api/notebooks.py owns Prompt Note persistence and recoverable legacy copying.

  • api/material_schema.py, api/material_assets.py, and api/material_store.py own curated material shaping, private assets, persistence/cache, search and lifecycle; api/materials.py owns HTTP mapping and compatibility entry points.

  • api/media_routes.py, api/gallery_routes.py, api/translation_routes.py, and api/folder_types.py own the formerly mixed utility route families.

  • model_policies.py owns shared backend rename and protected-category policy.

  • model_identity.py owns file SHA-256 evidence shared with the standalone scanner.

  • api/audio_catalog.py owns voice scanning, streaming, output-audio history and voice ingestion. Voices follow the ComfyUI-F5-TTS multi-voice layout: Character.wav + .txt is the main sample, Character.<voice>.wav + .txt are variants addressed as {<voice>}; <stem>.orig.txt keeps a native-script transcript for display. Ingestion validates the target folder, refuses to overwrite without overwrite=1, and stages all files before swapping them in.

  • api/audio_metadata.py reads the ComfyUI prompt comment from FLAC and Ogg/Opus files (read-only) and extracts the TTS node's speech, sample and seed for the audio gallery.

  • api/romanizer.py converts Japanese (text containing kana) and Korean to Latin script for transcripts; Han-only text is left unchanged, and missing optional dependencies (pykakasi, hangul-romanize) are reported, not hidden.

  • api/image_search.py powers the output gallery search (gallery_images?q=): it reads only the PNG text chunks before the pixel data (ComfyUI prompt and workflow, A1111 parameters), caches one record per image by mtime, and matches all query terms; hex terms of 8+ characters also match recorded model SHA256 values and, via collect_model_hash_index in model_resolution.py, local model files with that hash. Terms arrive as repeated term params, one phrase each. ui_search_chips.js is the reusable search-block input (Enter or a comma commits a block); ui_gallery.js places it above the gallery.

  • api/version_manager.py owns the plugin's own version: the installed tag/branch (/anomalous/version, local only), published releases fetched only on request (GitHub releases API, falling back to git ls-remote tags; 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.js is 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.js coordinates extension registration (with ?v=... versioned module imports busting aggressive browser ES Module caching and unconditional legacy storage key purging). browser.js owns the shared browser class and extracted-method wiring; browser_entry.js owns the single browser instance plus floating/topbar/menu entry behavior (pointer-capture drag, pre-mount positioning to avoid a flash, anomalous_trigger_pos_v3 persistence); the unsaved default position is defined in CSS; entry_controls.js owns entry mode, trigger size/style normalization, viewport clamping that keeps the button out of the sidebar dock, and saved-position storage (isValidSavedTriggerPosition); api/__init__.py sends Cache-Control: no-cache for this plugin's static files so a normal refresh revalidates them (modules are imported without ?v= query strings, which would create second module instances); and interface_settings.js owns language and theme preferences.

  • ui_domain_switcher.js owns the visual/audio domain toggle and its stored choice; browser.switchAudioTab is the single entry for audio navigation (header tabs, sidebar entries, domain switch) and hideAllPanels stops audio playback.

  • audio_engines.js lists the speech engines the audio page can manage (F5-TTS, GPT-SoVITS via the separate Anomalous_TTS node pack) and detects them at runtime through /object_info/<node class>. Every engine's voices are normalised into one voice-group shape (node_value = what the node's voice widget takes), so cards, sidebar and Script Director share one implementation. A missing engine stays in the switch marked "not installed" with install steps; nothing else depends on it. GPT-SoVITS data comes only from the node's HTTP contract, version 9 (/anomalous_tts/characters, /audio, /settings, /status, the storage, library, pretrained, browse, import and import preview routes; the node repo's docs/INTERFACE.md, mirrored as the project doc anomalous-tts-interface.md); Anomalous never reads or writes its model folders. loadGptSovitsStatus caches 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; fetchGptSovitsCharacter gets 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, and invalidateEngineCache({ rescan: true }) (Refresh button) also makes the node re-read its folders (?refresh=1). ui_audio_tts_editor.js edits 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.js holds 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 / sectionState for the form's colours, setupSummary, pretrainedReminder, missingForLanguage); no DOM. ui_tts_setup.js is the GPT-SoVITS settings dialog, opened from the sidebar footer (setupAttention gives that entry its dot). Missing pretrained files only get a dismissable dot when the studio's characters need them (dismissed ids in localStorage; 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.js is 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's rows; a row keeps its own name in original and gets a clash-free name in a draft, sent to the node as the file's name), a debounced inspect per draft, and imports the ready drafts one after another (each commit on its own, without the rows leftOut names; closing discards uncommitted uploads). draw() rebuilds the cards on structural changes, refresh() only repaints status, so typing never loses focus. ui_tts_import_uploads.js sends browser files in chunks, three at a time, once they are in a draft (tray files wait); ui_tts_import_player.js plays one clip at a time for comparing (browser files from memory, local paths through the node's preview route). tts_import_groups.js holds 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 .list files 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.js brings 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.js draws 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.js holds the fixed screens (the first question, the batch drop area, the "add more" menu) and the tour steps. ui_tts_file_drop.js reads 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.js picks server-side folders or files through the node's browse route, since the browser cannot see local paths.

  • audio_node_targets.js is the single table of canvas nodes the audio studio writes into and what each takes: F5-TTS (F5TTSAudio, F5TTSAudioAdvanced, F5TTSAudioFromModel) takes a whole character (main voice in sample, emotions via {tags} in speech); ComfyUI's LoadAudio takes one exact clip. planVoiceDrop decides every 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.js owns the voice cards, preview playback, tag copying, drags (card header = character, row = clip) through the shared bindMaterialDrag with targetHint/rejectHint telling the user what releasing does. audio_script.js holds the pure script rules: splitting, bundling (buildScriptPackage, with [take:N] for GPT-SoVITS retakes), combo value matching, and buildTtsPrompt (a GPT-SoVITS script as a ComfyUI API prompt saving to output/audio/<character>/). ui_script_director.js owns the script drawer: one character, an emotion chip row per line card, and a bundle that is pushed to the selected/only script-capable target node or dragged onto one; it writes the voice widget (main voice) and the speech widget together. The studio feeds it the voice groups after each fetch. ui_script_run.js is the drawer's "Generate" section for GPT-SoVITS: 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 its defaults (only values that differ from the node's own defaults, which it reads from /object_info; needs Anomalous_TTS interface 11). ui_tts_pronunciation.js edits a GPT-SoVITS character's pronunciation table (the node's replace setting) 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.js queues one API prompt with this page's client id and follows it (websocket status, /history result, cancel = queue delete or targeted /interrupt); it never touches the canvas.

  • In the audio domain the header ! opens AUDIO_USAGE_GUIDE (a how-to, see docs/guides/audio-studio.md) instead of the visual update guide.

  • ui_audio_uploader.js owns the voice ingestion modal (createViewScope lifecycle, overwrite confirmation, optional romanization keeping the original script).

  • ui_audio_sidebar.js owns the audio navigation and the active audio filter: characters grouped by language (GPT-SoVITS) or folder (F5-TTS), groups folded in localStorage, 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, and for GPT-SoVITS the settings entry at the bottom and file drops on a character. Canvas drags go through audio_voice_drag.js, shared with the studio cards.

  • ui_audio_gallery.js owns 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 into output/audio/.

  • ui_sidebar.js creates the browser shell, domain-aware header tabs, and folder navigation. ui_settings_hub.js owns settings and model-card preferences; ui_toolbox.js owns the tool catalog, fixed shortcut bar, and tool dispatch; ui_browser_navigation.js owns shared panel hiding/cleanup and workspace return, including Esc on the workspace panel (nbPanel, which starts below the header). Scan-wizard launch, single-model precision scans (triggerDirectModelScan with strictly factual Civitai vs non-Civitai feedback reporting inferred base-model or match status directly within the bottom-right progress panel and toasts without blocking browser alerts), modal lifecycle ergonomics (backdrop click and Escape key dismissal with listener detachment, scrollable content area with sticky footer actions), post-scan frontend hash and native combo refreshes (app.refreshComboInNodes(), window.anomalous_reload_hashes()), and polling live in ui_scan_wizard.js; folder visibility/order lives in ui_folder_manager.js; and help content lives in ui_help.js. scan_progress.js owns the bottom-right scan progress panel (updateScanProgress / finishScanProgress / failScanProgress). shortcut_controls.js owns 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.js owns 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, and ui_material_application.js own the Material Library UI, category navigation, card presentation (with grab cursor affordances, explicit drag tooltips, and polymorphic card dragging via bindPolymorphicMaterialCardDrag), 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-instantiate CLIPTextEncode nodes for prompt materials with standard colors or open full workflows. node_material_actions.js owns prompt envelope extraction (extractMaterialPromptEnvelope) shared by detail views, cards and canvas actions.

  • ui_update_guide.js and update_guide_data.js own the non-intrusive update guide modal (accessible via header button #anomalous-update-notice-btn and Help modal; version ID 2026-09-recipes-and-studios), presenting a 4-step milestone walkthrough (Workflow Recipe Studio, Material Library & Prompt Studio, Model Sources Hub, and Precision Direct Scan with canvas addition) with full bilingual localization. ui_spotlight_tour.js provides the interactive spotlight mask tour (startSpotlightTour), gliding smooth focal box highlights across topbar workspaces and bottom dock actions with directional tooltip cards and keyboard navigation. Other views pass their own steps (text from locale keys) and an optional onClose; the GPT-SoVITS import window does.

  • sidebar_actions.js owns the sidebar bottom action hover-reveal short labels (100ms), singleton dynamic DOM tooltip bubbles (#anomalous-sidebar-tooltip-bubble, 600ms), click/pointerdown instant text/tooltip suppression guards, isBottomModalOpen tooltip occlusion guards, and anti-flicker pointer stability.

  • tool_registry.js centralizes metadata, SVG icons (enlarged 20px crisp vector outlines with 2px stroke, #cbd5e1 contrast), and stable IDs for the 9 catalog tools (including Prompt Notes / 提示词笔记) and 2 fixed anchors (Toolbox and Settings).

  • The bottom shortcut bar (built in ui_toolbox.js, not user-arrangeable) maintains the clean fixed 4-tool setup (scan, doctor, assistant, materials) plus two anchors (toolbox, settings) housed in prominent 36px buttons with full click/active text suppression and .is-active toggled styling.

  • ui_toolbox.js's Toolbox modal strictly filters out all tools already present on the bottom bar, presenting a sleek 216px 3-row utility catalog with compact, frameless 44px tiles (providing an elevated silhouette with breathing room for catalog discovery), downward anchor caret pointing to the toolbox trigger button, 0.18s smooth spring pop-in animation, clean click action execution, and zero obstructive text or beta footers.

  • ui_grid.js and model-detail modules own model presentation: ui_grid.js manages 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.js coordinates detail display, ui_model_editor.js owns metadata editing, and ui_model_selector.js owns advanced selection. ui_gallery.js and ui_gallery_detail.js own generated-image browsing and workbench lifecycle, with stage interaction in ui_image_stage.js and metadata tabs in ui_image_inspector.js.

  • ui_recipes.js / ui_recipe_detail.js, ui_notebooks.js, and ui_materials.js own their respective workspace surfaces and persistence flows. ui_recipes.js owns 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.js owns 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.js owns 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 in locales.js), and notebook_canvas.js owns 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.js coordinates the Workflow Recipe detail session and model composition. ui_recipe_overview.js owns the Overview prompt showcase (with entry.text fallback, guarded non-shrinking primary action CTA, and floating Popover More dropdown menu), and ui_recipe_parameters.js owns the responsive Parameter Presets workspace (featuring default-expanded raw node parameter inspection, a clamp(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.js owns Material Library discovery and pagination, ui_material_cards.js owns catalog cards and drag-and-drop (same-type blocks, or prompt-bearing materials onto nodes with prompt slots), ui_material_detail.js owns the full detail surface, and ui_material_application.js owns selected-node tracking and explicit material application (same-type block application first, then cross-node prompt injection). node_material_actions.js inspects prompt slots (inspectNodePromptSlots), extracts prompt envelopes without model file paths, and plans injection (planPromptInjection): both texts into two-slot nodes, otherwise the matching role or a role-neutral slot. Text never crosses roles; writes are one undo step. Within recipe detail, ui_recipe_versions.js owns history comparison/restore, ui_recipe_gallery.js owns result cards and direct Image Detail Workbench handoff, ui_recipe_model_matching.js owns preview resolution and explicit local replacement, ui_recipe_metadata.js owns inline persistence, and ui_recipe_detail_dom.js owns the DOM/copy helpers shared by detail subviews. recipe_identity.js derives model references from native loaders plus a table of verified all-in-one loader layouts (ALL_IN_ONE_LOADER_SPECS, mirrored in api/recipe_schema.py); other third-party widgets stay parameters. References are keyed by (node_id, widget_index, category, saved_value). recipe_parser.js summarizes 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.js owns 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.js owns cards and card actions (including grab drag affordance, cover 可拖拽 badge, harmonized multi-state model readiness pill with getRecipeReadiness synchronizing available, missing, and pending matches with detail overview, and direct canvas drag-and-drop), ui_recipe_dialogs.js owns save/edit dialogs, and ui_recipe_media.js owns shared cover helpers. Detail sessions synchronize detected model availability back to owner.recipeRecords via syncRecipeReferencesToCatalog.

  • ui_prompt_composer.js owns the standalone Prompt Studio drawer. Its child views are ui_prompt_source_deck.js, ui_prompt_workbench.js, and ui_prompt_inspector.js. Assembly plan data, track-vs-role separation, and cross-role tail smart-sorting are owned by prompt_composition.js and prompt_studio_data.js. ui_prompt_source_deck.js owns the card preview popover with narrow bridging corridors, differentiated hide timers, and fast dismissal when hovering or clicking library blank space.

  • ui_prompt_translator.js owns 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 use ui_lifecycle.js for global listeners, request cancellation and resize cleanup. Translation requests go through translation_service.js.

  • ui_dialog.js owns the plugin's own alert / confirm / prompt dialogs (anomalousAlert, anomalousConfirm, anomalousPrompt), used instead of the browser's native ones. ui_prompt_toast.js is the short toast shared by the Prompt Studio views and the Model Source Hub.

  • model_policies.js mirrors model_policies.py for 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.js provides small DOM/JSON helpers; material_inspector.js owns material-specific metadata and parameter rendering.

  • ui_doctor.js owns diagnostics and global scans; ui_node_assistant.js owns selected-node assistant history, ui_node_model_picker.js owns native combo replacement, and ui_node_presets.js owns parameter preset rendering and application. model_picker.js, node_material_actions.js, and graph_splice.js own the remaining explicit graph changes.

  • ui_model_sources.js owns the Model Source Hub (模型来源统一中控中心), providing dual-scope (Workflow and Library) source inspection, external platform jumping, canvas Note node generation, workflow.extra.anomalous_model_sources metadata synchronization, automated asynchronous model metadata resolution (resolveWorkflowModelsMetadata) via /anomalous/resolve_paths_to_previews with 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.js is the shared runtime string catalog. Existing inline bilingual UI strings remain migration debt; new strings belong in the catalog.

  • styles.css is the ordered import manifest for web/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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Host state is preserved. Browser panels are mutually exclusive, Workspace/model-detail transitions are recoverable, and graph edits use ComfyUI's change/callback contracts.
  6. 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 through safe_dom.js.
  7. 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.
  8. 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.py may read the legacy root config.json only as a compatibility fallback. API keys are not stored in browser localStorage.
  • 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.md separately 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.