Files
ComfyUI_frontend/src/stores
Maanil Verma 01cbfa6a23 feat(templates): replace template search with MiniSearch and usage ranking (#13386)
## Summary

The template picker's search now surfaces the right template for how
people actually type — abbreviations, typos, multi-word intent, and
non-Latin (CJK) titles — and orders results by real popularity instead
of a fuzzy-match score that was being thrown away. Search and ranking
now behave the same here as they do on the workflow hub.

## Changes

**What**

- Searching for the way people phrase things now works: `t2v`, `i2v`,
`cn` expand to their full modality terms, `img2img`/`v2v` expand to
editing (matching how the catalog tags image/video edit templates),
`flux upscale` and `sdxl lora` match across title/model/tag fields
together, prefixes like `vid` match `video`, and typos like `contorlnet`
still find ControlNet. Versioned names tokenize sensibly, so `wan 2.2`
and `wan2.2` both hit, while `2.5` never blurs into `3.5`.
- CJK titles are searchable. Unspaced Han/Hiragana/Katakana runs are
tokenized into character unigrams and bigrams, so a substring a user
types (`放大` inside `图像放大`, or the single trailing `大`) lands on a match.
Korean and other spaced scripts fall to the normal word tokenizer,
unchanged.
- Fuzzy matching is tighter: a term now tolerates edits up to 20% of its
length (down from a flat threshold), so `contorlnet` still finds
ControlNet but `upscale` no longer fuzzy-matches the shorter, unrelated
`scale`. Short (≤3-char) and digit-bearing terms stay exact.
- Results lead with text relevance. Previously the fuzzy match score was
computed and then discarded, and any active sort re-ordered results by
usage — so the best textual match rarely landed on top. Now relevance is
the authoritative order while a query is active, and when two results
match about equally well, the more-used template wins the tie (dampened
so one runaway-popular template can't dominate).
- The ranking is a stable total order. Scores are bucketed before usage
breaks ties, so a cluster of near-equally-relevant results always sorts
the same way — a naive per-pair "within X%" comparison is intransitive
and makes the order depend on internal input order (it can even shuffle
as you type another character).
- "Popular" ranks by raw usage, matching what the hub and the search
index show. It previously blended in a freshness term that pushed newer,
less-used templates above genuinely popular ones.
- The sort dropdown works during search again: it defaults to
"Relevance" but you can switch to Popular/Newest/etc. to re-order the
results, and your browse sort is restored (and never overwritten by a
search-time choice) when you clear the query.
- Alphabetical sort reads correctly: it sorts by the title shown on the
card, trims stray leading whitespace that used to jump templates to the
top, and groups number-prefixed titles after the letters instead of
ahead of them.
- Filter telemetry now reports the sort the user is actually seeing
(relevance while searching) rather than the persisted browse sort, so
analytics reflect the visible ordering.
- Removed the old runtime Fuse-options override path, which is obsolete
under the new engine.

**Breaking**

None. Existing filters (Model / Use Case / Runs On / distribution),
pagination, and persisted sort settings are unchanged; the relevance
mode is search-only and never persisted.

## Review Focus

- The ranking crux is `rankByRelevanceThenUsage` in
`templateSearchConfig.ts`: relevance is primary, usage only re-orders
results in the same score bucket, and bucketing keeps it a stable total
order. That's the one function to review for correctness.
- The CJK tokenizer (`cjkGrams` / `tokenize` in
`templateSearchConfig.ts`): script-matched so only unspaced scripts are
grammed, and a pure-CJK run relies on its grams (no whole-word token).
Splitting by code point is safe here (these scripts are BMP-only; emoji
are excluded by the run regex).
- Deliberately not touched: the "Recommended" sort keeps its curated
blend (usage + editorial rank + freshness) so it stays distinct from
"Popular"; `vram-low-to-high` remains unimplemented exactly as on main.

## Tradeoffs / notes

- Adds `minisearch` (~18 kB gzip). The template selector is where it's
used; accepted for the search-quality gain (a later change could
lazy-load it if bundle size becomes a concern).
- Bucketing means two results just across a bucket boundary don't
tie-break on usage even when their scores are close — the accepted cost
of a transitive, predictable order (this mirrors how the search index
quantizes relevance).
- `img2img` expands to editing (not literal "image to image") because
the catalog labels those templates "Image Edit" — verified against the
real data.
- CJK bigrams roughly double the token count for a pure-CJK title;
negligible at catalog scale (~550 templates, short titles).

## Testing

Behavioral coverage over the real search paths, not the mocks — the
ranking and tokenizer run against actual MiniSearch output; only the
ranking-store math is mocked. Also verified against the full
~550-template catalog end to end (all query types stable, zero
input-order-dependent orderings).

### Behavior matrix (verified on the real catalog)

| Input / action | Now | Previously |
| --- | --- | --- |
| `img2img` | Image-editing templates (Qwen Image Edit, …) | Matched
every "image" template — intent lost |
| `flux upscale` | Flux upscale templates (matches both terms across
fields) | **No results** (single-field fuzzy couldn't span title + tag)
|
| `sdxl lora` | SDXL templates | **No results** |
| `t2v` / `i2v` / `cn` | Expand to text→video / image→video / controlnet
| Only partial slug hits, if any |
| `vid` (prefix) | Matches `video` templates | Unreliable |
| `contorlnet` (typo) | Finds ControlNet | Often dropped by the strict
threshold |
| `upscale` | Matches upscale titles only | Fuzzy-matched the unrelated
substring `scale` |
| `放大` / `大` (CJK) | Matches `图像放大` and other titles containing the run
| No match — CJK titles were unsearchable by substring |
| `wan 2.2` and `wan2.2` | Both match; `2.5` never matches `3.5` | Space
vs no-space degraded the match |
| Near-tied cluster (e.g. `upscale`) | Stable order every time |
Reordered depending on input order (could shuffle as you type) |
| Query active, "Popular" selected | Best textual match still leads;
usage breaks near-ties | Sort re-ordered by usage, burying the best
match |
| Change sort while searching | Re-orders the search results; relevance
is the default | Sort was locked; dropdown had no effect |
| Clear the search | Restores the browse sort you had before | — |
| "Popular" sort | Orders by raw usage (matches hub / index) | Freshness
blend pushed newer low-usage templates up |
| A–Z sort | Letters first (`ACE…`), number-prefixed titles last
(`3x3…`, `360…`); leading whitespace ignored | Leading-space titles
jumped to the top; numbers sorted before letters |

### Unit tests (81 total, all passing)

- `templateSearchConfig.test.ts` (34) — tokenizer identifier/version
splits, CJK unigram/bigram gramming (and Korean left as a spaced word),
per-term fuzziness (`upscale` ≠ `scale`), abbreviation expansion (incl.
`img2img`→edit intent), prefix + typo matching, AND-then-OR,
literal-before-expansion ordering, relevance>tag>description ranking,
and `rankByRelevanceThenUsage` giving a stable order on an intransitive
cluster.
- `useTemplateFiltering.test.ts` (35) — the `img2img` / `flux upscale` /
`sdxl lora` regressions, relevance-default-on-search,
override-sort-while-searching, browse-sort restore on clear, ephemeral
mid-search sort, telemetry reporting the visible sort, Runs-On filter,
empty-result guard, filters preserving relevance order, and alphabetical
trimming + numbers-after-letters.
- `templateRankingStore.test.ts` (12) — freshness and default-score
(recommended) math.

Gate: `pnpm typecheck`, `pnpm lint`, `pnpm knip` clean.

## Screen Recording (if applicable)



https://github.com/user-attachments/assets/6748a3f7-e69d-44ac-826c-71990c8dce90
2026-07-11 01:16:40 +00:00
..
2026-06-26 22:54:04 +00:00
2026-06-26 22:54:04 +00:00
2025-10-01 18:34:58 -07:00
2026-06-29 22:15:54 +00:00
2026-06-26 22:54:04 +00:00
2025-10-15 05:27:19 +01:00

Stores

This directory contains Pinia stores for the ComfyUI frontend application. Stores provide centralized state management for the application.

Table of Contents

Overview

Stores in ComfyUI use Pinia, Vue's official state management library. Each store is responsible for managing a specific domain of the application state, such as user data, workflow information, graph state, and UI configuration.

Stores provide a way to maintain global application state that can be accessed from any component, regardless of where those components are in the component hierarchy. This solves the problem of "prop drilling" (passing data down through multiple levels of components) and allows components that aren't directly related to share and modify the same state.

For example, without global state:

                  App
                   │
        ┌──────────┴──────────┐
        │                     │
    HeaderBar               Canvas
        │                     │
        │                     │
    UserMenu            NodeProperties

In this structure, if the UserMenu component needs to update something that affects NodeProperties, the data would need to be passed up to App and then down again, through all intermediate components.

With Pinia stores, components can directly access and update the shared state:

    ┌─────────────────┐
    │                 │
    │  Pinia Stores   │
    │                 │
    └───────┬─────────┘
            │
            │ Accessed by
            ▼
┌──────────────────────────┐
│                          │
│       Components         │
│                          │
└──────────────────────────┘

Store Architecture

The store architecture in ComfyUI follows these principles:

  1. Domain-driven: Each store focuses on a specific domain of the application
  2. Single source of truth: Stores serve as the definitive source for specific data
  3. Composition: Stores can interact with other stores when needed
  4. Actions for logic: Business logic is encapsulated in store actions
  5. Getters for derived state: Computed values are exposed via getters

The following diagram illustrates the store architecture and data flow:

┌─────────────────────────────────────────────────────────┐
│                    Vue Components                        │
│                                                         │
│   ┌───────────────┐            ┌───────────────┐        │
│   │  Component A  │            │  Component B  │        │
│   └───────┬───────┘            └───────┬───────┘        │
│           │                            │                │
└───────────┼────────────────────────────┼────────────────┘
            │                            │
            │     ┌───────────────┐      │
            └────►│  Composables  │◄─────┘
                  └───────┬───────┘
                          │
┌─────────────────────────┼─────────────────────────────┐
│         Pinia Stores    │                             │
│                         │                             │
│     ┌───────────────────▼───────────────────────┐     │
│     │                 Actions                    │     │
│     └───────────────────┬───────────────────────┘     │
│                         │                             │
│     ┌───────────────────▼───────────────────────┐     │
│     │                  State                     │     │
│     └───────────────────┬───────────────────────┘     │
│                         │                             │
│     ┌───────────────────▼───────────────────────┐     │
│     │                 Getters                    │     │
│     └───────────────────┬───────────────────────┘     │
│                         │                             │
└─────────────────────────┼─────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                   External Services                      │
│       (API, localStorage, WebSocket, etc.)              │
└─────────────────────────────────────────────────────────┘

Core Stores

The following table lists ALL 46 store instances in the system as of 2026-01-29:

Main Stores

File Store Description Category
aboutPanelStore.ts useAboutPanelStore Manages the About panel state and badges UI
apiKeyAuthStore.ts useApiKeyAuthStore Handles API key authentication Auth
comfyManagerStore.ts useComfyManagerStore Manages ComfyUI application state Core
comfyRegistryStore.ts useComfyRegistryStore Handles extensions registry Registry
commandStore.ts useCommandStore Manages commands and command execution Core
dialogStore.ts useDialogStore Controls dialog/modal display and state UI
domWidgetStore.ts useDomWidgetStore Manages DOM widget state Widgets
electronDownloadStore.ts useElectronDownloadStore Handles Electron-specific download operations Platform
executionStore.ts useExecutionStore Tracks workflow execution state Execution
extensionStore.ts useExtensionStore Manages extension registration and state Extensions
firebaseAuthStore.ts useFirebaseAuthStore Handles Firebase authentication Auth
graphStore.ts useTitleEditorStore Manages title editing for nodes and groups UI
graphStore.ts useCanvasStore Manages the graph canvas state and interactions Core
helpCenterStore.ts useHelpCenterStore Manages help center visibility and state UI
nodeOutputStore.ts useNodeOutputStore Manages node outputs and execution results Media
maintenanceTaskStore.ts useMaintenanceTaskStore Handles system maintenance tasks System
menuItemStore.ts useMenuItemStore Handles menu items and their state UI
modelStore.ts useModelStore Manages AI models information Models
modelToNodeStore.ts useModelToNodeStore Maps models to compatible nodes Models
nodeBookmarkStore.ts useNodeBookmarkStore Manages node bookmarks and favorites Nodes
nodeDefStore.ts useNodeDefStore Manages node definitions and schemas Nodes
nodeDefStore.ts useNodeFrequencyStore Tracks node usage frequency Nodes
queueStore.ts useQueueStore Manages execution queue and task history Execution
queueStore.ts useQueuePendingTaskCountStore Tracks pending task counts Execution
queueStore.ts useQueueSettingsStore Manages queue execution settings Execution
releaseStore.ts useReleaseStore Manages application release information System
serverConfigStore.ts useServerConfigStore Handles server configuration Config
settingStore.ts useSettingStore Manages application settings Config
subgraphNavigationStore.ts useSubgraphNavigationStore Handles subgraph navigation state Navigation
systemStatsStore.ts useSystemStatsStore Tracks system performance statistics System
toastStore.ts useToastStore Manages toast notifications UI
userFileStore.ts useUserFileStore Manages user file operations Files
userStore.ts useUserStore Manages user data and preferences User
versionCompatibilityStore.ts useVersionCompatibilityStore Manages frontend/backend version compatibility warnings Core
widgetStore.ts useWidgetStore Manages widget configurations Widgets
workflowStore.ts useWorkflowStore Handles workflow data and operations Workflows
workflowStore.ts useWorkflowBookmarkStore Manages workflow bookmarks and favorites Workflows
workflowTemplatesStore.ts useWorkflowTemplatesStore Manages workflow templates Workflows
workspaceStore.ts useWorkspaceStore Manages overall workspace state Workspace

Workspace Stores

Located in stores/workspace/:

File Store Description Category
bottomPanelStore.ts useBottomPanelStore Controls bottom panel visibility and state UI
colorPaletteStore.ts useColorPaletteStore Manages color palette configurations UI
nodeHelpStore.ts useNodeHelpStore Handles node help and documentation display UI
searchBoxStore.ts useSearchBoxStore Manages search box functionality UI
sidebarTabStore.ts useSidebarTabStore Controls sidebar tab states and navigation UI

Store Development Guidelines

When developing or modifying stores, follow these best practices:

  1. Define clear purpose: Each store should have a specific responsibility
  2. Use actions for async operations: Encapsulate asynchronous logic in actions
  3. Keep stores focused: Each store should manage related state
  4. Document public API: Add comments for state properties, actions, and getters
  5. Use getters for derived state: Compute derived values using getters
  6. Test store functionality: Write unit tests for stores

Store Template

Here's a template for creating a new Pinia store, following the setup style used in ComfyUI:

import { defineStore } from 'pinia'
import { computed, ref } from 'vue'

export const useExampleStore = defineStore('example', () => {
  // State
  const items = ref([])
  const isLoading = ref(false)
  const error = ref(null)

  // Getters
  const itemCount = computed(() => items.value.length)
  const hasError = computed(() => error.value !== null)

  // Actions
  function addItem(item) {
    items.value.push(item)
  }

  async function fetchItems() {
    isLoading.value = true
    error.value = null

    try {
      const response = await fetch('/api/items')
      const data = await response.json()
      items.value = data
    } catch (err) {
      error.value = err.message
    } finally {
      isLoading.value = false
    }
  }

  // Expose state, getters, and actions
  return {
    // State
    items,
    isLoading,
    error,

    // Getters
    itemCount,
    hasError,

    // Actions
    addItem,
    fetchItems
  }
})

Common Patterns

Stores in ComfyUI frequently use these patterns:

API Integration

import { defineStore } from 'pinia'
import { ref } from 'vue'
import { api } from '@/scripts/api'

export const useDataStore = defineStore('data', () => {
  const data = ref([])
  const loading = ref(false)
  const error = ref(null)

  async function fetchData() {
    loading.value = true
    try {
      const result = await api.getExtensions()
      data.value = result
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }

  return {
    data,
    loading,
    error,
    fetchData
  }
})

Store Composition

import { defineStore, storeToRefs } from 'pinia'
import { computed, ref, watch } from 'vue'
import { useOtherStore } from './otherStore'

export const useComposedStore = defineStore('composed', () => {
  const otherStore = useOtherStore()
  const { someData } = storeToRefs(otherStore)

  // Local state
  const localState = ref(0)

  // Computed value based on other store
  const derivedValue = computed(() => {
    return computeFromOtherData(someData.value, localState.value)
  })

  // Action that uses another store
  async function complexAction() {
    await otherStore.someAction()
    localState.value += 1
  }

  return {
    localState,
    derivedValue,
    complexAction
  }
})

Persistent State

import { defineStore } from 'pinia'
import { ref, watch } from 'vue'

export const usePreferencesStore = defineStore('preferences', () => {
  // Load from localStorage if available
  const theme = ref(localStorage.getItem('theme') || 'light')
  const fontSize = ref(parseInt(localStorage.getItem('fontSize') || '14'))

  // Save to localStorage when changed
  watch(theme, (newTheme) => {
    localStorage.setItem('theme', newTheme)
  })

  watch(fontSize, (newSize) => {
    localStorage.setItem('fontSize', newSize.toString())
  })

  function setTheme(newTheme) {
    theme.value = newTheme
  }

  return {
    theme,
    fontSize,
    setTheme
  }
})

Testing Stores

Stores should be tested to ensure they behave as expected. Here's an example of how to test a store:

import { createPinia, setActivePinia } from 'pinia'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { nextTick } from 'vue'

import { api } from '@/scripts/api'
import { useExampleStore } from '@/stores/exampleStore'

// Mock API dependencies
vi.mock('@/scripts/api', () => ({
  api: {
    getData: vi.fn()
  }
}))

describe('useExampleStore', () => {
  let store: ReturnType<typeof useExampleStore>

  beforeEach(() => {
    // Create a fresh pinia instance and make it active
    setActivePinia(createPinia())
    store = useExampleStore()

    // Clear all mocks
    vi.clearAllMocks()
  })

  it('should initialize with default state', () => {
    expect(store.items).toEqual([])
    expect(store.isLoading).toBe(false)
    expect(store.error).toBeNull()
  })

  it('should add an item', () => {
    store.addItem('test')
    expect(store.items).toEqual(['test'])
    expect(store.itemCount).toBe(1)
  })

  it('should fetch items', async () => {
    // Setup mock response
    vi.mocked(api.getData).mockResolvedValue(['item1', 'item2'])

    // Call the action
    await store.fetchItems()

    // Verify state changes
    expect(store.isLoading).toBe(false)
    expect(store.items).toEqual(['item1', 'item2'])
    expect(store.error).toBeNull()
  })
})

For more information on Pinia, refer to the Pinia documentation.