This document provides guidance for AI assistants working on the Image MetaHub codebase.
Image MetaHub is a desktop application (Electron + React + TypeScript) for browsing, searching, and organizing AI-generated images locally. It focuses on performance with large collections, powerful metadata filtering, and complete privacy.
Key Technologies:
- Frontend: React 18 with TypeScript
- Desktop: Electron 38 with auto-updater
- Storage: IndexedDB for caching
- Build: Vite
- Testing: Vitest
- Styling: Tailwind CSS
/
├── App.tsx # Main React application component
├── electron.mjs # Electron main process
├── preload.js # Electron preload script
├── cli.ts # CLI tool for metadata parsing
├── types.ts # TypeScript type definitions
├── components/ # React components
├── services/ # Business logic services
├── store/ # Zustand state management
├── utils/ # Utility functions
├── hooks/ # Custom React hooks
├── src/ # Additional source files
├── scripts/ # Maintenance and release scripts
├── __tests__/ # Test files
└── public/ # Static assets
- README.md: User-facing documentation and features
- ARCHITECTURE.md: Technical architecture and design decisions
- CHANGELOG.md: Version history and changes
- RELEASE-GUIDE.md: Release workflow for maintainers
- CLI-README.md: CLI tool documentation
# Browser-only development
npm run dev
# Electron app development
npm run dev:app
# With specific directory
npm run dev:app -- --dir "/path/to/images"
# Run tests
npm test
# Build for production
npm run build
# Create distributable
npm run electron-dist- Use TypeScript strict mode
- Follow existing naming conventions
- Use functional React components with hooks
- Prefer explicit typing over
any - Keep components focused and single-responsibility
The ComfyUI parser is the most complex metadata parser in the project. It uses a rule-based, declarative architecture to handle ComfyUI's graph-based workflow format, recently refactored to separate data extraction from presentation logic.
Location: services/parsers/comfyui/
Key Components:
-
Graph Construction (
comfyUIParser.ts)- Merges
workflow(UI data with widgets_values) andprompt(execution data) - Handles NaN sanitization and incomplete exports
- Overlays workflow nodes onto prompt data for complete graph representation
- Merges
-
Traversal Engine (
traversalEngine.ts)- Traverses graph backwards from SINK nodes (like KSampler)
- Skips muted nodes (mode 2/4)
- Supports multiple traversal strategies:
- Single Path: For unique parameters (seed)
- Multi-Path: For prompts (explores all paths)
- Pass-Through: For routing nodes
- Generic Accumulation: Uses declarative
accumulate: booleanflag on param rules instead of hardcoded logic - Structured Output:
resolveFacts()returns type-safeWorkflowFactsobject with prompts, model, loras, sampling, and dimensions
-
Node Registry (
nodeRegistry.ts)- Declarative node definitions with roles, inputs, outputs, and parameter mappings
- WorkflowFacts Interface: Structured type for extracted workflow metadata
- Accumulate Flag: Mark parameters that should collect values from all nodes in path (e.g.,
lora: { source: 'widget', key: 'lora_name', accumulate: true }) - See
services/parsers/comfyui/DEVELOPMENT.mdfor complete reference
-
Reusable Extractors (
extractors.ts)- Composable extraction functions for common patterns:
concatTextExtractor: Concatenate multiple text inputsextractLorasFromText: Extract LoRA tags from<lora:name>syntaxremoveLoraTagsFromText: Strip LoRA tags from promptscleanWildcardText: Remove unresolved wildcard artifactsextractLorasFromStack: Parse LoRA Stack widget arraysgetWildcardOrPopulatedText: Prioritize populated over template text
- Reduces code duplication by 80-90% across node definitions
- Composable extraction functions for common patterns:
Adding New ComfyUI Nodes:
- Add node definition to
nodeRegistry.ts:
'NodeTypeName': {
category: 'SAMPLING' | 'LOADING' | 'CONDITIONING' | 'ROUTING',
roles: ['SOURCE', 'SINK', 'TRANSFORM', 'PASS_THROUGH'],
inputs: { input_name: { type: 'MODEL' | 'CONDITIONING' | ... } },
outputs: { output_name: { type: 'MODEL' | 'CONDITIONING' | ... } },
param_mapping: {
steps: { source: 'widget', key: 'steps' }, // Extract from widgets_values
seed: { source: 'trace', input: 'seed' }, // Follow connection
lora: { source: 'widget', key: 'lora_name', accumulate: true }, // Collect from all nodes
prompt: {
source: 'custom_extractor',
extractor: (node, state, graph, traverse) =>
extractors.concatTextExtractor(node, state, graph, traverse, ['text1', 'text2'])
}
},
widget_order: ['widget1', 'widget2', ...] // CRITICAL: Must match PNG export order
}-
Use Extractors When Possible: Check
extractors.tsfor reusable functions before writing custom extractors. Common patterns like text concatenation, LoRA extraction, and wildcard cleaning are already implemented. -
Accumulate Flag: For parameters that should collect values from multiple nodes in the graph (like LoRAs), add
accumulate: trueto the param mapping rule. The traversal engine will automatically collect values from all nodes instead of stopping at the first match. -
widget_order is CRITICAL: The array must match the exact sequence in embedded PNG
widgets_valuesdata. Mismatches cause value swapping bugs (e.g., steps=0, cfg=28 instead of steps=28, cfg=3). -
Add tests in
__tests__/comfyui/with real workflow fixtures -
Verify with actual ComfyUI PNG exports
Common Issues:
- Value Swapping: Missing
__unknown__placeholders inwidget_order - Unknown Nodes: Add logging and fallback behavior in NodeRegistry
- Missing Prompts: Check if CLIPTextEncode nodes are properly traced
- Dimensions: Always read from image file properties, not workflow settings (images may be upscaled/cropped)
Testing ComfyUI Parser:
# Unit tests for specific nodes
npm test -- comfyui
# Test with real workflows
npm run cli:parse -- path/to/comfyui-image.pngFor detailed documentation, see services/parsers/comfyui/DEVELOPMENT.md.
The application supports multiple AI image generators:
- InvokeAI
- Automatic1111
- ComfyUI
- SwarmUI
- Easy Diffusion
- Midjourney/Niji Journey
- Forge
- DALL-E
- Adobe Firefly
- DreamStudio
- Draw Things
- Flux (via various UIs)
Supported File Formats:
- Images: PNG, JPEG, WEBP, GIF
- Animation: GIF
- Video: MP4, WEBM, MKV, MOV, AVI
- Audio: MP3, WAV, FLAC, OGG/OGA, M4A, AAC, OPUS, AIFF/AIF, WMA
Metadata sources:
- PNG chunks (tEXt, iTXt, zTXt)
- JPEG EXIF/XMP
- Sidecar JSON files
- C2PA manifests
- Privacy: All processing is local, no external connections (except auto-updater and A1111 integration)
- Performance: Optimized for 18,000+ images with smart caching
- Metadata Search: Full-text search across all metadata fields
- Multi-Format Support: Handle various AI generator formats
- File Operations: Rename, delete, export metadata (desktop only)
- A1111 Integration: Send images back to Automatic1111 for editing or regeneration
- Video & GIF Support: Indexing, playback, and thumbnail support for MP4, WEBM, and GIF files
- Audio Support: Indexing, metadata extraction, filtering, and playback for common audio formats
- Shadow Metadata: View original metadata and revert changes (non-destructive editing)
- Subfolder Management: Ability to exclude specific subfolders from indexing
- Image Adjustments: Brightness, contrast, saturation, and hue adjustments with metadata-preserving PNG Save As / Overwrite desktop workflows
- ComfyUI Workspace: Embedded ComfyUI browser in Electron with image context, library thumbnails, workflow metadata tabs, and direct grid/table/viewer entry points
- External ComfyUI Queue Monitoring: Optional detection of ComfyUI jobs started outside Image MetaHub in the shared generation queue
- Local Visual Search: Opt-in Find Similar for visually related files, including images without metadata. Experimental text-to-image queries are secondary; no model download occurs without a separate explicit action.
- Clustering & Stacks:
services/clusteringEngine.ts,services/workers/clusteringWorker.ts,components/SmartLibrary.tsx,components/StackCard.tsx,components/StackExpandedView.tsx - Auto-Tags (TF-IDF):
services/autoTaggingEngine.ts,services/workers/autoTaggingWorker.ts,components/TagsAndFavorites.tsx,components/ImageModal.tsx,components/ImagePreviewSidebar.tsx - Deduplication Helper:
services/deduplicationEngine.ts,components/DeduplicationHelper.tsx,components/ImageGrid.tsx - Cluster Cache:
services/clusterCacheManager.ts(atomic writes, userData path resolution)
Find Similar is the primary supported Local Visual Search workflow for v0.19 (services/embeddings/, services/workers/embeddingWorker.ts, services/workers/vectorSearchWorker.ts, store/useSemanticStore.ts). Text-to-image queries are experimental and must not be promoted as the main search experience without explicit authorization. Do not alter the traditional deterministic metadata, prompt, model, LoRA, or workflow search paths to accommodate CLIP.
Key pieces:
- Vector format (
services/embeddings/embeddingFormat.ts): int8-quantized vectors with a per-vector scale, in append-only segment files (${safeCacheId}_emb_seg_*.bin) next to the metadata cache. A manifest (${safeCacheId}_emb_manifest.json) is the source of truth for how much of each segment is real — a segment longer than its declared row count holds bytes from an uncommitted flush and is discarded on load. - On-disk store (
services/embeddings/embeddingStore.ts,EmbeddingIndex): owns append/flush/tombstone/rename for one library-wide index, keyed by the fixed idSEMANTIC_CACHE_ID = 'imh-visual-search'(not per-directory, since the store flattens every directory into oneimages[]). - Backfill (
services/embeddings/embeddingIndexer.ts,runBackfill): resumable, newest-first, pause/cancel-able indexing job. This is the only place that reconciles the index against the live image set (tombstones vectors for images no longer present) — it is the only caller with the authoritative, fully-hydrated image array. Do not add reconciliation to a mount effect or anywhere else that can fire before the library has finished loading from cache: an empty/partial image list there reads as "every image left the library" and wipes the index. - Search (
services/embeddings/semanticSearchEngine.ts+services/workers/vectorSearchWorker.ts): brute-force cosine over the in-memory matrix in a long-lived worker, top-K via a min-heap, relevance decided by a z-score cutoff over the query's own score distribution (CLIP text↔image cosines are compressed and query-dependent, so an absolute floor doesn't work). - Embedding worker (
services/workers/embeddingWorker.ts,services/embeddings/embeddingService.ts): runs the CLIP towers via@huggingface/transformers. WASM/q8 is the always-present CPU baseline; an opt-in WebGPU/fp16 accelerator falls back to CPU automatically on adapter orshader-f16load failure. - Model files: served to the worker over a dedicated
imh-model://protocol from<userData>/models/, registered inelectron.mjsalongside the embedding sidecar IPC handlers (read/write/append-embedding-*,*-embedding-model*). - UI:
components/SemanticSearchBar.tsx(sidebar toggle + query input),components/settings/VisualSearchSettingsPanel.tsx(model download, index build/pause/resume, GPU toggle),components/VisualSearchOnboarding.tsx(dismissible intro card). - Store integration: a visual query lives in
useImageStore.semanticResult.scoreById(aMap, never onIndexedImage) and replaces the text-search predicate rather than combining with it, driving a'relevance'sort order.
Defaults and gating: settings.semanticSearchEnabled is off by default — while off, no model status check, no index open, no file write. The onboarding card and the Settings tab stay visible regardless, so the feature is still discoverable; turning it on is what first opens the index. Free tier caps the backfill at SEMANTIC_FREE_TIER_LIMIT (2,000) most-recent images (hooks/useFeatureAccess.ts); Pro is unlimited.
Invariants for future changes:
- Model files must never download without an explicit user action, downloads must keep SHA-256 integrity verification, and all processing must remain local after the explicit download.
- Embeddings and their index are derived, reconstructible cache data. Index format changes must not trigger a
PARSER_VERSIONbump unless parser output itself changed. - WebGPU initialization or runtime failures must preserve a functional WASM fallback.
- Changes to model loading, workers, Electron boundaries, or packaging require a packaged-app smoke test.
- UI and documentation must not promise retrieval precision that tests do not support. Find Similar stays the supported headline; text-to-image stays clearly experimental.
The application includes bidirectional workflow with Automatic1111 WebUI, allowing users to send image metadata back to A1111 for editing or regeneration.
Location: services/a1111ApiClient.ts, hooks/useCopyToA1111.ts, hooks/useGenerateWithA1111.ts, utils/a1111Formatter.ts
Key Components:
-
A1111 API Client (
services/a1111ApiClient.ts)- REST API client for A1111 WebUI
- Connection testing (
/sdapi/v1/options) - Sampler list fetching with 5-minute cache
- Fuzzy sampler matching (case-insensitive, removes underscores/spaces)
- Image generation endpoint (
/sdapi/v1/txt2img) - 3-minute timeout for longer generations
-
Metadata Formatter (
utils/a1111Formatter.ts)- Converts
BaseMetadatato A1111 parseable format - Three-line format:
- Line 1: Positive prompt
- Line 2:
Negative prompt: [text](if exists) - Line 3: Comma-separated parameters (Steps, Sampler, CFG, Seed, Size, Model)
- Compatible with A1111's "Read generation parameters" feature (blue arrow button)
- Converts
-
Copy to Clipboard Hook (
hooks/useCopyToA1111.ts)- Formats metadata and copies to clipboard
- Toast notification: "Copied! Paste into A1111 prompt box and click the Blue Arrow."
- Error handling for clipboard API failures
- Primary workflow for manual parameter editing
-
Background Generation Hook (
hooks/useGenerateWithA1111.ts)- Sends metadata directly to A1111 API
- Always starts generation (
autoStart: true) - Toast notifications for success/failure
- Secondary workflow for quick image variations
UI Integration:
- ImagePreviewSidebar: Split button (Copy primary, Generate in dropdown)
- ImageModal: Split button (same design)
- Context Menu: Two separate items (Copy to A1111, Quick Generate)
User Workflows:
-
Copy for Manual Editing (Primary):
- Click "Copy to A1111"
- Paste (Ctrl+V) into A1111 prompt box
- Click blue arrow icon ("Read generation parameters")
- All fields populate automatically
- User edits parameters before generating
-
Quick Generate (Secondary):
- Click dropdown → "Quick Generate"
- Image generates immediately in background
- No UI interaction required on A1111 side
Configuration:
Settings in store/useSettingsStore.ts:
a1111ServerUrl: Defaulthttp://127.0.0.1:7860a1111LastConnectionStatus: Connection state tracking- Connection test button in Settings modal
A1111 Setup Requirements:
User must start A1111 with API flags:
--api --cors-allow-origins=http://localhost:5173Common Issues:
- CORS errors: Missing
--cors-allow-originsflag - Connection timeout: A1111 not running or wrong port
- Generation timeout: Increase timeout in
a1111ApiClient.tsif using slow models - Sampler mismatch: Fuzzy matching handles most cases, but custom samplers may not match
Testing A1111 Integration:
# Start A1111 with API enabled
webui.bat --api --cors-allow-origins=http://localhost:5173
# In app:
# 1. Settings → A1111 Integration → Test Connection
# 2. Open image → Click "Copy to A1111"
# 3. Paste in A1111 → Click blue arrow
# 4. Verify all fields populated correctlyThe application includes bidirectional workflow with ComfyUI, allowing users to generate image variations by sending workflow data to ComfyUI via API. Unlike A1111's polling approach, ComfyUI integration uses real-time WebSocket communication for progress tracking.
As of v0.16.0, ComfyUI support has two major surfaces:
- Generation modal: Builds queued jobs from either an executable original workflow or a simple metadata rebuild.
- ComfyUI Workspace: Embeds a local ComfyUI browser in Electron and pairs it with Image MetaHub context, thumbnails, workflow/raw metadata, copy/generate actions, directory scoping, and external-browser fallback in non-desktop builds.
Location: services/comfyUIApiClient.ts, services/comfyUIWorkflowBuilder.ts, components/ComfyUIGenerateModal.tsx, components/ComfyUIWorkspace.tsx, hooks/useCopyToComfyUI.ts, hooks/useGenerateWithComfyUI.ts, hooks/useComfyUIQueueMonitor.ts, contexts/ComfyUIProgressContext.tsx, utils/telemetryDetection.ts
Key Components:
-
ComfyUI API Client (
services/comfyUIApiClient.ts)- REST API client for ComfyUI backend
- Connection testing (
/system_stats) - WebSocket connection for real-time progress tracking (
ws://127.0.0.1:8188/ws) - Workflow submission endpoint (
POST /prompt) - Image generation with progress callbacks
- 5-minute timeout for long generations
- Graceful error handling with connection state tracking
-
Workflow Builder (
services/comfyUIWorkflowBuilder.ts/ ComfyUI API helpers)- Converts
BaseMetadatato simple txt2img ComfyUI workflow - Generates a minimal working workflow when original workflow mode is unavailable or not desired
- Original workflow mode can reuse executable embedded prompt graphs and apply safe overrides
- Linear pipeline structure:
CheckpointLoader → MetaHub Timer → CLIPTextEncode (positive/negative) ↓ EmptyLatent → KSampler → VAEDecode → MetaHub Save Node - Preserved Parameters:
- Positive and negative prompts
- Model name (checkpoint)
- Seed, steps, CFG scale
- Sampler and scheduler
- Image dimensions (width/height)
- MetaHub Timer Node Integration:
- Automatically included in generated workflows
- Records timestamp when it executes
- Save Node calculates elapsed time from timestamp
- Ensures accurate
generation_time_msandsteps_per_secondmetrics
- Not Preserved (Advanced Features):
- ControlNet inputs and preprocessing
- Upscalers and high-res fixes
- Refiner models and switch points
- Custom node configurations
- Multi-stage workflows
- Advanced LoRA configurations beyond basic weights
- Approach Rationale:
- Reliable generation from any source image (A1111, ComfyUI, Fooocus, etc.)
- Consistent parameter extraction across different formats
- Compatibility across different ComfyUI setups and versions
- Fast workflow execution with minimal overhead
- No dependency on complex custom nodes
- Converts
-
Progress Tracking (
contexts/ComfyUIProgressContext.tsx)- Real-time WebSocket-based progress tracking (unlike A1111's polling)
- Context provider wraps entire app (see
index.tsx) - Tracks: current node, total nodes, progress percentage, current image
- Preview image updates during generation
- Automatic cleanup on completion/error
- Connection state management
-
Copy to Clipboard Hook (
hooks/useCopyToComfyUI.ts)- Formats metadata as JSON workflow
- Copies complete workflow to clipboard
- Toast notification: "Workflow copied! Load it in ComfyUI."
- Error handling for clipboard API failures
- Use case: Manual workflow customization in ComfyUI
-
Background Generation Hook (
hooks/useGenerateWithComfyUI.ts)- Builds workflow from metadata
- Sends workflow directly to ComfyUI API via
POST /prompt - Real-time progress tracking via WebSocket
- Toast notifications for success/failure
- Generated images automatically saved by MetaHub Save Node with full metadata
- Modal UI with customizable generation parameters:
- Seed (randomize or specify)
- Steps (1-100 range)
- CFG scale (0-20 range)
- Prompt editing (positive/negative)
- Primary workflow for quick image variations
-
Embedded Workspace (
components/ComfyUIWorkspace.tsx)- Electron-only embedded ComfyUI browser with URL persistence and external open actions
- Image context panel with selected image details, workflow metadata, raw metadata, and thumbnails
- Library filter scoping, directory selection, keyboard-friendly navigation, and direct image modal / compare entry points
- Suspends the embedded browser while Image MetaHub-owned ComfyUI generation is active to avoid view conflicts
-
Queue Monitor (
hooks/useComfyUIQueueMonitor.ts)- Optional setting for detecting ComfyUI prompts started outside Image MetaHub
- Adds external ComfyUI jobs to the shared generation queue with waiting/processing/done/failed state
- Tracks output previews and supports cancel where ComfyUI exposes prompt cancellation
MetaHub Save Node (Official Companion Node):
The ComfyUI integration relies on the MetaHub Save Node custom node for complete metadata support.
Repository: d:\ImageMetaHub-ComfyUI-Save (additional working directory)
Features:
- Auto-Extraction: Detects sampler params, prompts, model/VAE, and LoRAs directly from workflow
- Performance Metrics: Auto-tracks GPU usage, VRAM peak, generation time, and software versions
- User Inputs: Supports custom tags and notes fields that are automatically imported into Image MetaHub
- Tags: Automatically added to ImageAnnotations system, available for filtering and search
- Notes: Displayed in ImageModal and ImagePreviewSidebar as read-only metadata
- Dual Metadata Format:
- PNG tEXt "parameters" (A1111/Civitai compatible)
- PNG iTXt "imagemetahub_data" (extended JSON with full workflow, tags, and notes)
- Model Hashes: Calculates SHA256 hashes (AutoV2 format) for models and LoRAs
- Never Fails: Silent fallback on errors - generation never stops
Performance Metrics Structure:
{
"_analytics": {
"vram_peak_mb": 3165.44, // CUDA GPUs only
"gpu_device": "NVIDIA GeForce RTX 3060 Ti",
"generation_time_ms": 1523, // Requires MetaHub Timer node
"steps_per_second": 13.2, // Calculated from time + steps
"comfyui_version": "0.1.0", // Can be null (Colab environments)
"torch_version": "2.6.0+cu124",
"python_version": "3.10.6"
}
}Installation:
cd ComfyUI/custom_nodes
git clone https://github.com/LuqP2/ImageMetaHub-ComfyUI-Save.git
cd ImageMetaHub-ComfyUI-Save
pip install -r requirements.txtTags and Notes Integration:
When images are generated with the MetaHub Save Node and contain tags or notes:
-
Tags Automatic Import:
- During indexing, tags from the
imagemetahub_datametadata are automatically imported into the ImageAnnotations system - Tags become immediately available for filtering in Advanced Filters
- Tags appear in the autocomplete suggestions when adding tags to other images
- Users can add additional tags or remove imported tags just like manually added tags
- Tags are normalized (lowercase, trimmed) to maintain consistency
- During indexing, tags from the
-
Notes Display:
- Notes are displayed as read-only metadata in both ImageModal and ImagePreviewSidebar
- Appear in a dedicated section with purple styling to indicate MetaHub Save Node origin
- Formatted as pre-formatted text to preserve line breaks and spacing
- Cannot be edited within Image MetaHub (must be changed in ComfyUI workflow)
Verified Telemetry System:
The app includes a visual badge and filter system to distinguish images generated with MetaHub Save Node from images saved with default ComfyUI Save Image node.
Location: utils/telemetryDetection.ts, components/ImageModal.tsx, components/ImagePreviewSidebar.tsx, components/AdvancedFilters.tsx, store/useImageStore.ts
Detection Logic (hasVerifiedTelemetry()):
Images are considered to have "verified telemetry" if they contain:
- VRAM Peak (
vram_peak_mb): Must be number > 0 - GPU Device (
gpu_device): Must be non-empty string - Software Versions: At least one of:
comfyui_version(can be null for Colab)torch_versionpython_version
Optional Metrics (Not Required):
generation_time_ms: Only present if MetaHub Timer node in workflowsteps_per_second: Only present if MetaHub Timer node in workflow
Metadata Field Access Pattern:
// Try both analytics and _analytics (underscore version used in normalizedMetadata)
const analytics =
image.metadata?.normalizedMetadata?.analytics ||
(image.metadata?.normalizedMetadata as any)?._analytics;Badge UI Implementation:
ImageModal.tsx (lines 592-602):
{
hasVerifiedTelemetry(image) && (
<span
className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-gradient-to-r from-green-500/20 to-emerald-500/20 text-green-400 border border-green-500/30 shadow-sm shadow-green-500/20"
title="Verified Telemetry - Generated with MetaHub Save Node. Includes accurate performance metrics: generation time, VRAM usage, GPU device, and software versions."
>
<CheckCircle size={14} className="flex-shrink-0" />
<span className="whitespace-nowrap">Verified Telemetry</span>
</span>
);
}ImagePreviewSidebar.tsx (lines 275-286):
- Same badge design with shorter text: "Verified" instead of "Verified Telemetry"
AdvancedFilters.tsx (lines 362-382):
- Checkbox filter with green styling
- Label: "Verified Telemetry Only (MetaHub Save Node)"
- Explanation text about complete performance metrics
Filter Implementation (store/useImageStore.ts, lines 603-605):
if (advancedFilters.hasVerifiedTelemetry === true) {
results = results.filter((image) => hasVerifiedTelemetry(image));
}Design Intent:
- Create "desire for completeness" encouraging users to use MetaHub Save Node
- Visual distinction between images with/without complete metrics
- Easy filtering to find images with verified telemetry data
UI Integration:
- ImagePreviewSidebar: Split button (Copy to ComfyUI primary, Generate in dropdown)
- ImageModal: Split button (same design) + Generate Variation modal
- Grid/Table/Toolbar context: Open in ComfyUI Workspace when Pro ComfyUI features are enabled
- ComfyUIWorkspace: Embedded browser, image context, metadata tabs, thumbnail navigation, compare/open actions, copy, and generate
- ComfyUIGenerateModal: Full-screen modal with parameter customization
- Seed input with randomize button
- Steps slider (1-100)
- CFG slider (0-20)
- Positive/negative prompt text areas
- Real-time progress bar with node tracking
- Preview image updates during generation
User Workflows:
-
Copy for Manual Editing:
- Click "Copy to ComfyUI"
- Open ComfyUI web interface
- Load workflow from clipboard (Ctrl+V or Load button)
- Customize workflow with additional nodes (ControlNet, upscalers, etc.)
- Generate manually
-
Quick Generate (Primary):
- Click "Generate with ComfyUI" button
- Modal opens with pre-filled parameters
- Customize seed, steps, CFG, prompts as needed
- Click "Generate"
- Real-time progress tracking via WebSocket
- Generated image automatically saved by MetaHub Save Node
- Image includes verified telemetry badge
-
Workspace Flow:
- Open an image in the ComfyUI Workspace from the grid, table, toolbar, or viewer
- Browse ComfyUI inside the app while keeping source images, metadata, and thumbnails visible
- Copy/generate from the current image or open the same image in the regular viewer/compare workflows
Configuration:
Settings in store/useSettingsStore.ts:
comfyUIServerUrl: Defaulthttp://127.0.0.1:8188comfyUIConnectionStatus: Connection state tracking ('connected' | 'disconnected' | 'checking')comfyUIQueueMonitoringEnabled: Enables external ComfyUI queue detectioncomfyUIWorkspaceLastUrl,comfyUIWorkspacePanelWidth,comfyUIWorkspaceAutoOpenSelectedImage: Workspace persistence and behavior- Connection test button in Settings modal (tests
/system_statsendpoint)
ComfyUI Setup Requirements:
User must:
- Install ComfyUI locally
- Install MetaHub Save Node custom node
- Install MetaHub Timer node (included with Save Node package)
- ComfyUI API runs by default on
http://127.0.0.1:8188(no flags needed) - Ensure CORS is configured if using non-default ports
Common Issues:
-
Connection timeout: ComfyUI not running or wrong port
- Check ComfyUI is running:
http://127.0.0.1:8188should load web interface - Verify port in Settings → ComfyUI Integration
- Check ComfyUI is running:
-
"MetaHub Save Node not found" error: Custom node not installed
- Clone repo to
ComfyUI/custom_nodes/ImageMetaHub-ComfyUI-Save - Run
pip install -r requirements.txt - Restart ComfyUI
- Clone repo to
-
Generation works but no verified telemetry badge: Old workflow without Timer node
- This is expected for images generated before Timer node was added to workflow builder
- Badge requires: VRAM, GPU device, software versions (timing metrics optional)
- Update to latest version for Timer node integration
-
WebSocket connection fails: CORS or firewall issue
- Check browser console for WebSocket errors
- Verify ComfyUI allows WebSocket connections
- Try disabling browser extensions
-
Workflow fails with "node type not found": Model or sampler not available
- Check that model referenced in metadata exists in
ComfyUI/models/checkpoints/ - Workflow builder may fall back to default model if specified model not found
- Check that model referenced in metadata exists in
-
Generated images missing LoRAs: Workflow builder limitation
- Current implementation preserves basic LoRA info but doesn't include in generated workflow
- Load generated workflow in ComfyUI and manually add LoRA Loader nodes
-
Images from Google Colab show
comfyui_version: null: Expected behavior- Colab environments don't report ComfyUI version
- Detection still works via torch_version and python_version
- Badge will still appear
Testing ComfyUI Integration:
# Start ComfyUI (default port 8188)
python main.py
# In app:
# 1. Settings → ComfyUI Integration → Test Connection
# 2. Verify "Connected" status
# 3. Open image with metadata → Click "Generate with ComfyUI"
# 4. Customize parameters in modal
# 5. Click Generate and watch real-time progress
# 6. Verify generated image appears with verified telemetry badge
# 7. Check image metadata includes _analytics field with performance metrics
# Test verified telemetry filter:
# 1. Advanced Filters → Check "Verified Telemetry Only"
# 2. Verify only images with MetaHub Save Node metadata appear
# 3. Verify badge appears on all visible imagesMetaHub Timer Node Integration:
Generated workflows automatically include the MetaHub Timer node for accurate timing:
- Placed between CheckpointLoader and CLIPTextEncode nodes
- Records timestamp when it executes
- Save Node calculates elapsed time from this timestamp
- Ensures
generation_time_msandsteps_per_secondare populated - Required for complete verified telemetry (VRAM + GPU device + timing + software versions)
Workflow Generation vs Original Workflow:
Image MetaHub supports two ComfyUI generation paths:
-
Original workflow mode:
- Uses executable embedded ComfyUI prompt graphs when present
- Applies safe parameter/resource/source-image overrides through workflow patching
- Preserves more of the original graph structure, including advanced custom-node workflows when the user's ComfyUI installation supports them
-
Simple rebuild mode:
- Extracts normalized parameters from metadata (regardless of source: A1111, ComfyUI, Fooocus, etc.)
- Generates a minimal txt2img workflow focused on prompts, model, seed, steps, CFG, sampler, scheduler, and dimensions
- Intentionally omits advanced features like ControlNet, upscalers, refiner models, and multi-stage custom setups
Use simple rebuild as the universal fallback and original workflow mode when the embedded workflow is executable and the user's local ComfyUI has the needed nodes/assets.
For advanced manual workflows, users can:
- Open the image in ComfyUI Workspace or use "Copy to ComfyUI"
- Load or continue editing inside ComfyUI
- Add advanced nodes (ControlNet, upscaler, custom pipelines, etc.)
- Save custom workflow templates for reuse
- Update clustering or auto-tagging logic in
services/orutils/ - Update UI in
components/SmartLibrary.tsxor stack components - Extend store state in
store/useImageStore.tsif needed - Add/adjust worker logic in
services/workers/for background tasks - Add tests in
__tests__/when parser or tokenizer changes
- Add parser in
services/orutils/ - Update type definitions in
types.ts - Add tests in
__tests__/ - Update CLI parser if applicable
- Document in CHANGELOG.md
- Create component in
components/ - Add state management in
store/if needed - Update App.tsx to integrate
- Consider Electron/browser compatibility
- Test with large collections
- Keep adjustment logic in
services/imageEditingService.ts - Use
components/ImageAdjustmentPanel.tsxfor viewer controls - Preserve metadata when exporting adjusted PNGs
- Treat Overwrite as desktop-only and PNG-only unless the save pipeline explicitly changes
- Reindex or refresh the saved image path after desktop save/overwrite operations
Measure first. Do not infer the bottleneck from reading code — this codebase has repeatedly punished that. A reported "delete is slow" was diagnosed as the store's filter pipeline and optimized across three PRs with zero effect; the actual cost was 326 MB of cache chunks being read and rewritten per deleted file, in the main process. Add instrumentation, reproduce, read the numbers, then fix what the numbers point at.
Then measure again, more than once. Development happens on a machine with
games, antivirus and external drives on it, and a single sample there is worth
very little: the same delete has measured 3082 ms, 1395 ms and 145 ms across
runs, and an IPC call that only hits ENOENT — no file, no work at all —
measured 1227 ms in one run and 1.1 ms in another. Take two or three samples,
treat a spread wider than about 2x as environmental until proven otherwise, and
get a quiet-machine number before concluding that a change did nothing. One bad
sample nearly buried a 155x improvement as "no difference".
- Instrument the suspect path with
recordPerformanceDuration/beginPerformanceFlow(utils/performanceDiagnostics.ts). Enable Performance Diagnostics in Settings, or usewindow.__IMH_PERF__—printSummary(),getEvents(),setConsoleLogging(true). A long-task observer is already attached. - Check whether the freeze is even in the renderer. Work awaited over IPC does
not block the renderer's main thread, but it does occupy the Electron
main process, and every other IPC call queues behind it — including the image
reads that grid and modal navigation need. A UI that stutters during a
background write is usually main-process starvation, not a slow component.
Note that a timing taken around an
awaitin the renderer cannot tell those apart on its own: it charges the handler's own work, the main process being busy when the message arrives, and this thread being too busy to run the continuation, all to the same number. To split them, temporarily return the handler's own elapsed time with the reply and queue asetTimeout(0)alongside the call — if it fires late, the renderer was starved and the main process is innocent. - For anything touching the metadata cache, look at chunk file sizes before
anything else (
services/cacheManager.ts). Chunks are read and rewritten whole, so their size is the cost of every single-image edit. A ComfyUI library carries a full workflow graph per entry, which makes per-entry costs roughly 25x what an A1111 library shows. - Anything that serializes a whole collection across the IPC boundary (registry snapshots, worker datasets, cache records) belongs off the interactive path — debounce and coalesce it.
- Profile with large collections, and prefer lazy loading and background work.
- Unit tests for parsers and utilities
- Component tests for React components
- Integration tests for metadata extraction
- Manual testing with various AI generator outputs
- Performance testing with 10,000+ images
- Main branch:
main - Always commit with descriptive messages
- Push to feature branches, not main
- Follow conventional commit style
Some features are desktop-only (Electron):
- File system operations (rename, delete, show in folder)
- Command-line arguments
- Auto-updater
- Native file dialogs
Browser version uses File System Access API with limited capabilities.
- Always test with large image collections (10,000+ images), and on a ComfyUI library — entries there are far heavier than A1111 ones and are what surface per-entry costs
- Use React.memo() for expensive components
- Implement proper virtualization for lists
- Cache metadata aggressively, but keep cached entries small: large raw metadata is compacted on write and rehydrated on demand (
services/rawMetadataHydration.ts) - Prefer touching one cache chunk over rewriting a directory cache; the id→chunk index exists for exactly this, and any write that finalizes a cache record must also rewrite that index. Deletes touch no chunk at all — they tombstone the id in
{cacheId}_removed.jsonand let a later rewrite compact it. If you add a path that writes a cache record, decide what it does to that sidecar (finalize-cache-writemakes you say so) and readutils/cacheTombstones.mjsfirst: a record and a sidecar that disagree must be left disagreeing, never quietly reconciled - Process files in background threads when possible
- Use synchronous I/O (
fs.openSync) for header reads during indexing to prevent disk contention (Phase B optimization) - Viewer navigation latency is the full image's fetch and decode, not React work. A prefetch that only resolves a source warms nothing —
mediaSourceCachehands back a URL string without reading a byte — so neighbours are decoded ahead of time throughservices/mediaDecodeCache.ts, andImageModalreads that warmth during render to swap in a single commit. Assigningsrctwice (thumbnail, then full) costs a second decode and a visible resolution pop, so it is worth avoiding whenever the full source is already warm
- Metadata Parsing: Different generators use different formats and field names
- File System: Path handling differs between Windows/macOS/Linux
- Memory: Large images can cause memory issues if not handled properly
- Caching: Invalid cache can cause stale data - include cache versioning
- Electron IPC: Properly handle async communication between main and renderer
This is an Electron desktop app with a robust multi-platform release pipeline combining local scripts and GitHub Actions.
Pattern: Semantic Versioning (SemVer) - MAJOR.MINOR.PATCH[-PRERELEASE]
Files to sync:
package.json- version fieldARCHITECTURE.md- version field- Git tags - format
v{VERSION}(e.g.,v0.9.6)
Prerelease Support:
- Format:
0.9.6-rc,1.0.0-beta.1 - Used for testing before stable releases
Three main scripts available in package.json:
1. npm run auto-release <version> (Fully Automated - RECOMMENDED)
npm run auto-release 0.9.6Executes complete pipeline:
- Runs
npm run build(compile + test) - Updates
package.jsonversion - Updates
ARCHITECTURE.mdversion - Generates release notes via
scripts/generate-release.js - Creates git commit with standardized message
- Creates git tag
v{VERSION} - Pushes branch and tag to origin
- Waits for GitHub Actions to trigger
2. npm run release-workflow <version> (Automated, No Build)
npm run release-workflow 0.9.6Same as above but skips build step (safe for pre-tested changes):
- Does NOT run tests/build
- Updates versions and creates tag
- Generates release notes
- Opens GitHub releases page for final manual step
3. Manual Process (see RELEASE-GUIDE.md)
npm version 0.9.6
node scripts/generate-release.js 0.9.6
git tag v0.9.6
git push origin main v0.9.6Tag Creation:
git tag v0.9.6 # Create locally
git push origin v0.9.6 # Push to GitHubGitHub Actions Trigger:
.github/workflows/publish.ymlautomatically triggers on any tag matchingv*- Builds Windows, macOS, and Linux installers in parallel
- Creates draft GitHub Release and uploads all artifacts
- Publishes release (removes draft flag) after all builds complete
Tag Convention:
- Always use
vprefix - Match version in
package.jsonexactly - Never push tags directly to main branch; push separately
Three parallel build jobs execute on tag push:
build-windows (runs-on: windows-latest)
- Builds Electron app with electron-builder
- Generates Windows installer (
.exe) and ZIP - Creates GitHub Release (draft mode)
- Uploads assets and YAML update manifest
build-macos (runs-on: macos-latest)
- Builds for macOS
- Generates DMG installer
- Uploads to same GitHub Release
build-linux (runs-on: ubuntu-latest)
- Builds for Linux
- Generates AppImage
- Uploads to same GitHub Release
All jobs upload to the same release draft, ensuring single unified release with all platforms.
Script: scripts/generate-release.js
Reads CHANGELOG.md and generates release-v{VERSION}.md with:
- Changelog content from new version section
- Download links for all platforms (Windows/macOS/Linux)
- System requirements
- Documentation links
- Release date
CHANGELOG.md Format Requirements:
## [0.9.6] - 2025-11-23
### Fixed
- **Bug title**: Description
### Added
- **Feature title**: Description
### Improved
- **Item**: Technical detailsFormat must match exactly for parser to extract correct section.
1. PREPARATION
├─ Update CHANGELOG.md with new version section
├─ Test locally (npm run dev, npm test)
└─ Verify version consistency
2. TRIGGER RELEASE
└─ npm run release-workflow 0.9.6
(or npm run auto-release 0.9.6 to skip manual testing)
├─ Updates package.json and ARCHITECTURE.md
├─ Generates release-v0.9.6.md
├─ Creates git commit
├─ Creates git tag v0.9.6
└─ Pushes to origin (main + tag)
3. GITHUB ACTIONS (publish.yml)
├─ Windows build (creates release draft)
├─ macOS build (parallel, uploads to draft)
└─ Linux build (parallel, uploads to draft)
4. FINALIZATION
├─ Release published (removes draft flag)
├─ All downloads available on GitHub
└─ Auto-updater detects new version
Configuration:
- Provider: GitHub
- Delta updates: Uses
.blockmapfiles for efficient downloads - Settings: Users can disable auto-check in app preferences
Update Flow:
- App checks for updates 3 seconds after startup
- If new version available, shows dialog with:
- Version number
- Changelog preview (400 chars)
- Link to full release notes
- User can: Download Now, Download Later, or Skip Version
- Download proceeds in background
- After download: Restart app to install
Skip Version Handling:
- Users can skip individual versions
- Skipped versions stored in memory (session-based)
- Next manual check or app restart resets
Windows:
- NSIS installer:
ImageMetaHub-Setup-{version}.exe - Portable ZIP:
ImageMetaHub-{version}.zip - Auto-update manifest:
latest.yml
macOS:
- DMG installer:
ImageMetaHub-{version}.dmg - Auto-update manifest:
latest-mac.yml - Notarization: Configured in
electron-builder.json
Linux:
- AppImage:
ImageMetaHub-{version}.AppImage - Auto-update manifest:
latest-linux.yml - One-file deployment (no dependencies)
All artifacts uploaded to GitHub Releases automatically by CI/CD.
electron-builder.json
- Target platforms and formats
- GitHub publish configuration
- Code signing settings
- Update manifest generation
.github/workflows/publish.yml
- Triggers on
v*tags - Build matrix for Windows, macOS, Linux
- Release creation and artifact upload
- Runs: Node.js v22 (fixed version)
electron.mjs
- Auto-updater initialization
- Update checking and download logic
- Dialog handling with user preferences
- Release notes extraction and formatting
Parser Version:
const PARSER_VERSION = 3;Located in electron.mjs, used for metadata cache invalidation:
- Increment when parser logic changes significantly
- Forces reprocessing of all cached metadata
- Current version affects ComfyUI parser improvements
GitHub Actions fails to build:
- Verify tag is pushed to origin (not just created locally)
- Check that
PARSER_VERSIONin electron.mjs is valid - Ensure
package.jsonversion matches tag (withoutvprefix)
Auto-updater not detecting new version:
- Check that GitHub Release is published (not draft)
- Verify latest.yml was created in Release assets
- Clear IndexedDB cache if testing locally
Release notes look wrong:
- Verify CHANGELOG.md uses exact format:
## [VERSION] - YYYY-MM-DD - Check section headers:
### Fixed,### Added,### Improved - Run
npm run generate-release VERSIONto test
Version mismatch across files:
- Always use
npm run release-workflow(updates both files) - Or manually sync:
package.json,ARCHITECTURE.md, git tag - Check with:
npm run buildbefore final push
See RELEASE-GUIDE.md and .github/workflows/publish.yml for additional details.
PolyForm Perimeter License 1.0.1 for current first-party code, including the AVIF contribution (with its author's written permission) and packages/metadata-engine. Previously published MPL 2.0 and Apache 2.0 versions retain their earlier rights. See LICENSING.md for details.
- GitHub Issues: https://github.com/LuqP2/image-metahub/issues
- Ko-fi: https://ko-fi.com/lucaspierri
When working on this codebase:
- Always read existing code before modifying
- Maintain backward compatibility with cached data
- Test with multiple AI generator formats
- Consider performance impact on large collections
- Keep privacy-first approach (no external connections)
- Follow TypeScript best practices
- Write tests for new functionality