Skip to content

Latest commit

 

History

History
1139 lines (853 loc) · 47.9 KB

AGENTS.md

File metadata and controls

1139 lines (853 loc) · 47.9 KB

This document provides guidance for AI assistants working on the Image MetaHub codebase.

Project Overview

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

Project Structure

/
├── 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

Important Documentation

  • 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

Development Workflow

Running the Project

# 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

Code Style

  • 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

ComfyUI Parser Architecture

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:

  1. Graph Construction (comfyUIParser.ts)

    • Merges workflow (UI data with widgets_values) and prompt (execution data)
    • Handles NaN sanitization and incomplete exports
    • Overlays workflow nodes onto prompt data for complete graph representation
  2. 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: boolean flag on param rules instead of hardcoded logic
    • Structured Output: resolveFacts() returns type-safe WorkflowFacts object with prompts, model, loras, sampling, and dimensions
  3. 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.md for complete reference
  4. Reusable Extractors (extractors.ts)

    • Composable extraction functions for common patterns:
      • concatTextExtractor: Concatenate multiple text inputs
      • extractLorasFromText: Extract LoRA tags from <lora:name> syntax
      • removeLoraTagsFromText: Strip LoRA tags from prompts
      • cleanWildcardText: Remove unresolved wildcard artifacts
      • extractLorasFromStack: Parse LoRA Stack widget arrays
      • getWildcardOrPopulatedText: Prioritize populated over template text
    • Reduces code duplication by 80-90% across node definitions

Adding New ComfyUI Nodes:

  1. 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
}
  1. Use Extractors When Possible: Check extractors.ts for reusable functions before writing custom extractors. Common patterns like text concatenation, LoRA extraction, and wildcard cleaning are already implemented.

  2. Accumulate Flag: For parameters that should collect values from multiple nodes in the graph (like LoRAs), add accumulate: true to the param mapping rule. The traversal engine will automatically collect values from all nodes instead of stopping at the first match.

  3. widget_order is CRITICAL: The array must match the exact sequence in embedded PNG widgets_values data. Mismatches cause value swapping bugs (e.g., steps=0, cfg=28 instead of steps=28, cfg=3).

  4. Add tests in __tests__/comfyui/ with real workflow fixtures

  5. Verify with actual ComfyUI PNG exports

Common Issues:

  • Value Swapping: Missing __unknown__ placeholders in widget_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.png

For detailed documentation, see services/parsers/comfyui/DEVELOPMENT.md.

Metadata Parsing

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

Key Features to Maintain

  1. Privacy: All processing is local, no external connections (except auto-updater and A1111 integration)
  2. Performance: Optimized for 18,000+ images with smart caching
  3. Metadata Search: Full-text search across all metadata fields
  4. Multi-Format Support: Handle various AI generator formats
  5. File Operations: Rename, delete, export metadata (desktop only)
  6. A1111 Integration: Send images back to Automatic1111 for editing or regeneration
  7. Video & GIF Support: Indexing, playback, and thumbnail support for MP4, WEBM, and GIF files
  8. Audio Support: Indexing, metadata extraction, filtering, and playback for common audio formats
  9. Shadow Metadata: View original metadata and revert changes (non-destructive editing)
  10. Subfolder Management: Ability to exclude specific subfolders from indexing
  11. Image Adjustments: Brightness, contrast, saturation, and hue adjustments with metadata-preserving PNG Save As / Overwrite desktop workflows
  12. ComfyUI Workspace: Embedded ComfyUI browser in Electron with image context, library thumbnails, workflow metadata tabs, and direct grid/table/viewer entry points
  13. External ComfyUI Queue Monitoring: Optional detection of ComfyUI jobs started outside Image MetaHub in the shared generation queue
  14. 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.

Smart Library & Auto-Tags

  • 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)

Local Visual Search

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 id SEMANTIC_CACHE_ID = 'imh-visual-search' (not per-directory, since the store flattens every directory into one images[]).
  • 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 or shader-f16 load failure.
  • Model files: served to the worker over a dedicated imh-model:// protocol from <userData>/models/, registered in electron.mjs alongside 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 (a Map, never on IndexedImage) 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_VERSION bump 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.

A1111 Integration

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:

  1. 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
  2. Metadata Formatter (utils/a1111Formatter.ts)

    • Converts BaseMetadata to 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)
  3. 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
  4. 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:

  1. 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
  2. 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: Default http://127.0.0.1:7860
  • a1111LastConnectionStatus: 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:5173

Common Issues:

  • CORS errors: Missing --cors-allow-origins flag
  • Connection timeout: A1111 not running or wrong port
  • Generation timeout: Increase timeout in a1111ApiClient.ts if 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 correctly

ComfyUI Integration

The 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:

  1. 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
  2. Workflow Builder (services/comfyUIWorkflowBuilder.ts / ComfyUI API helpers)

    • Converts BaseMetadata to 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_ms and steps_per_second metrics
    • 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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.txt

Tags and Notes Integration:

When images are generated with the MetaHub Save Node and contain tags or notes:

  1. Tags Automatic Import:

    • During indexing, tags from the imagemetahub_data metadata 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
  2. 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_version
    • python_version

Optional Metrics (Not Required):

  • generation_time_ms: Only present if MetaHub Timer node in workflow
  • steps_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:

  1. 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
  2. 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
  3. 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: Default http://127.0.0.1:8188
  • comfyUIConnectionStatus: Connection state tracking ('connected' | 'disconnected' | 'checking')
  • comfyUIQueueMonitoringEnabled: Enables external ComfyUI queue detection
  • comfyUIWorkspaceLastUrl, comfyUIWorkspacePanelWidth, comfyUIWorkspaceAutoOpenSelectedImage: Workspace persistence and behavior
  • Connection test button in Settings modal (tests /system_stats endpoint)

ComfyUI Setup Requirements:

User must:

  1. Install ComfyUI locally
  2. Install MetaHub Save Node custom node
  3. Install MetaHub Timer node (included with Save Node package)
  4. ComfyUI API runs by default on http://127.0.0.1:8188 (no flags needed)
  5. 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:8188 should load web interface
    • Verify port in Settings → ComfyUI Integration
  • "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
  • 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
  • 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 images

MetaHub 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_ms and steps_per_second are populated
  • Required for complete verified telemetry (VRAM + GPU device + timing + software versions)

Workflow Generation vs Original Workflow:

Image MetaHub supports two ComfyUI generation paths:

  1. 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
  2. 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:

  1. Open the image in ComfyUI Workspace or use "Copy to ComfyUI"
  2. Load or continue editing inside ComfyUI
  3. Add advanced nodes (ControlNet, upscaler, custom pipelines, etc.)
  4. Save custom workflow templates for reuse

Common Tasks

Adding Smart Library Features

  1. Update clustering or auto-tagging logic in services/ or utils/
  2. Update UI in components/SmartLibrary.tsx or stack components
  3. Extend store state in store/useImageStore.ts if needed
  4. Add/adjust worker logic in services/workers/ for background tasks
  5. Add tests in __tests__/ when parser or tokenizer changes

Adding New Metadata Format Support

  1. Add parser in services/ or utils/
  2. Update type definitions in types.ts
  3. Add tests in __tests__/
  4. Update CLI parser if applicable
  5. Document in CHANGELOG.md

Adding New UI Features

  1. Create component in components/
  2. Add state management in store/ if needed
  3. Update App.tsx to integrate
  4. Consider Electron/browser compatibility
  5. Test with large collections

Working on Image Adjustments

  1. Keep adjustment logic in services/imageEditingService.ts
  2. Use components/ImageAdjustmentPanel.tsx for viewer controls
  3. Preserve metadata when exporting adjusted PNGs
  4. Treat Overwrite as desktop-only and PNG-only unless the save pipeline explicitly changes
  5. Reindex or refresh the saved image path after desktop save/overwrite operations

Fixing Performance Issues

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".

  1. Instrument the suspect path with recordPerformanceDuration / beginPerformanceFlow (utils/performanceDiagnostics.ts). Enable Performance Diagnostics in Settings, or use window.__IMH_PERF__ — printSummary(), getEvents(), setConsoleLogging(true). A long-task observer is already attached.
  2. 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 await in 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 a setTimeout(0) alongside the call — if it fires late, the renderer was starved and the main process is innocent.
  3. 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.
  4. 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.
  5. Profile with large collections, and prefer lazy loading and background work.

Testing Strategy

  • 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

Git Workflow

  • Main branch: main
  • Always commit with descriptive messages
  • Push to feature branches, not main
  • Follow conventional commit style

Browser vs Desktop Considerations

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.

Performance Tips

  • 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.json and 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-write makes you say so) and read utils/cacheTombstones.mjs first: 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 — mediaSourceCache hands back a URL string without reading a byte — so neighbours are decoded ahead of time through services/mediaDecodeCache.ts, and ImageModal reads that warmth during render to swap in a single commit. Assigning src twice (thumbnail, then full) costs a second decode and a visible resolution pop, so it is worth avoiding whenever the full source is already warm

Common Pitfalls

  1. Metadata Parsing: Different generators use different formats and field names
  2. File System: Path handling differs between Windows/macOS/Linux
  3. Memory: Large images can cause memory issues if not handled properly
  4. Caching: Invalid cache can cause stale data - include cache versioning
  5. Electron IPC: Properly handle async communication between main and renderer

Release Process

This is an Electron desktop app with a robust multi-platform release pipeline combining local scripts and GitHub Actions.

Versionioning

Pattern: Semantic Versioning (SemVer) - MAJOR.MINOR.PATCH[-PRERELEASE]

Files to sync:

  • package.json - version field
  • ARCHITECTURE.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

Release Scripts

Three main scripts available in package.json:

1. npm run auto-release <version> (Fully Automated - RECOMMENDED)

npm run auto-release 0.9.6

Executes complete pipeline:

  • Runs npm run build (compile + test)
  • Updates package.json version
  • Updates ARCHITECTURE.md version
  • 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.6

Same 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.6

Git Tags and Triggering Builds

Tag Creation:

git tag v0.9.6           # Create locally
git push origin v0.9.6   # Push to GitHub

GitHub Actions Trigger:

  • .github/workflows/publish.yml automatically triggers on any tag matching v*
  • 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 v prefix
  • Match version in package.json exactly
  • Never push tags directly to main branch; push separately

GitHub Actions Workflow (publish.yml)

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.

Release Notes Generation

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 details

Format must match exactly for parser to extract correct section.

Complete Release Workflow

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

Auto-Updater (electron-updater)

Configuration:

  • Provider: GitHub
  • Delta updates: Uses .blockmap files for efficient downloads
  • Settings: Users can disable auto-check in app preferences

Update Flow:

  1. App checks for updates 3 seconds after startup
  2. If new version available, shows dialog with:
    • Version number
    • Changelog preview (400 chars)
    • Link to full release notes
  3. User can: Download Now, Download Later, or Skip Version
  4. Download proceeds in background
  5. 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

Multi-Platform Distribution

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.

Configuration Files

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

Cache Invalidation

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

Release Troubleshooting

GitHub Actions fails to build:

  • Verify tag is pushed to origin (not just created locally)
  • Check that PARSER_VERSION in electron.mjs is valid
  • Ensure package.json version matches tag (without v prefix)

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 VERSION to 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 build before final push

See RELEASE-GUIDE.md and .github/workflows/publish.yml for additional details.

License

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.

Support


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