# Search API Documentation

## Overview

Core search functionality using MiniSearch for fast, fuzzy-matching across templates and stacks. Unified index combining both searchable item types with intelligent field weighting.

## Module: useSearch()

Composable for search state management and query execution.

### Import

```typescript
import { useSearch, type SearchableItem } from "@/composables/useSearch";
```

### API Reference

#### Function: useSearch()

Returns search composable with state and methods.

**Returns:**

```typescript
{
  query: Ref<string>,           // Current search query
  results: Ref<SearchResult[]>, // Search results array
  isSearching: Ref<boolean>,    // Search in-progress flag
  performSearch: (q: string, templates: Template[], stacks?: Stack[]) => void,
  clearSearch: () => void,      // Reset query and results
  initializeIndex: (templates: Template[], stacks: Stack[]) => void
}
```

#### Method: performSearch(query, templates, stacks?)

Execute search query.

**Parameters:**

- `query: string` - Search query (min 2 chars, auto-trimmed)
- `templates: Template[]` - Array of templates to search
- `stacks?: Stack[]` - Optional array of stacks to search (default: [])

**Behavior:**

- Sets `isSearching` to true/false
- Initializes index on first call
- Combines templates + stacks into unified search space
- Returns up to 8 results sorted by relevance

**Example:**

```typescript
const { performSearch, results } = useSearch();

// Search templates
performSearch("docker", templates);

// Search templates + stacks
performSearch("production", templates, stacks);

// Access results
console.log(results.value); // SearchResult[]
```

#### Method: clearSearch()

Reset search state.

**Behavior:**

- Sets `query` to ""
- Sets `results` to []
- Does not affect index

**Example:**

```typescript
const { clearSearch } = useSearch();
clearSearch();
```

## Types

### SearchableItem

Union type combining Template/Stack with search metadata.

```typescript
type SearchableItem = (Template | Stack) & {
  itemType: "template" | "stack";
  id: string;
  tags?: string[];
  category?: string;
};
```

**Fields:**

- `itemType` - Discriminator for union type ("template" | "stack")
- `id` - Unique identifier (template-N or stack-N)
- `tags` - Array of tag strings (optional)
- `category` - Category name (optional)
- Plus all Template/Stack fields

### SearchResult

Individual search result with metadata.

```typescript
interface SearchResult {
  item: SearchableItem; // The matched item
  score: number; // Relevance score (higher = better)
  match: Record<string, string[]>; // Fields matched + highlighted terms
}
```

**Example:**

```typescript
{
  item: {
    id: "template-0",
    name: "docker-setup",
    type: "command",
    description: "Automated Docker environment configuration",
    itemType: "template",
    tags: ["docker", "devops"],
    category: "command"
  },
  score: 15.2,
  match: {
    name: ["docker"],
    tags: ["docker"]
  }
}
```

## Search Configuration

### Field Weights (Boost)

Relevance scoring weights for matching fields:

| Field       | Weight | Rationale                               |
| ----------- | ------ | --------------------------------------- |
| name        | 3x     | Exact name matches are highest priority |
| tags        | 2x     | Tags are curated, high-signal           |
| category    | 1.5x   | Type/category indicates purpose         |
| type        | 1.5x   | Template type is key classifier         |
| description | 1x     | Base priority, least specific           |

### Fuzzy Matching

Dynamic fuzzy tolerance based on query length:

- Query ≤ 3 chars: `fuzzy = 0.3` (more lenient for short queries)
- Query > 3 chars: `fuzzy = 0.2` (stricter for longer queries)

**Fuzzy Tolerance Meaning:**

- `0.2` = Allow 20% character differences (1-2 char changes in 5-char word)
- `0.3` = Allow 30% character differences (more forgiving for short queries)

### Result Limits

- Minimum query length: 2 characters (queries < 2 chars return [])
- Maximum results: 8 items
- Sorting: Descending by score (highest relevance first)

### Index Scope

Combined from:

- **Templates:** All template objects with type field
- **Stacks:** All stack objects with category = "stack"

Indexed fields: `name`, `description`, `tags`, `category`, `type`

## Usage Examples

### Basic Search

```typescript
<script setup lang="ts">
import { useSearch } from "@/composables/useSearch";
import type { Template } from "@/types/template";

const props = defineProps<{
  templates: Template[];
}>();

const { performSearch, results, clearSearch } = useSearch();

function handleSearch(query: string) {
  if (query.length >= 2) {
    performSearch(query, props.templates);
  }
}
</script>

<template>
  <div>
    <input @input="handleSearch($event.target.value)" />
    <ul>
      <li v-for="result in results" :key="result.item.id">
        {{ result.item.name }} (score: {{ result.score.toFixed(2) }})
      </li>
    </ul>
  </div>
</template>
```

### With Stacks

```typescript
const { performSearch, results } = useSearch();

// Search both templates and stacks
performSearch("nextjs", templates, stacks);

// Filter by type
const templateResults = results.value.filter(
  (r) => r.item.itemType === "template",
);
const stackResults = results.value.filter((r) => r.item.itemType === "stack");
```

### Debounced Search

```typescript
import { ref } from "vue";

const query = ref("");
let debounceTimer: ReturnType<typeof setTimeout>;

function handleInput(q: string) {
  query.value = q;
  clearTimeout(debounceTimer);
  debounceTimer = setTimeout(() => {
    performSearch(q, templates);
  }, 100);
}
```

### SearchBar Component Pattern

```vue
<script setup lang="ts">
import { ref, computed } from "vue";
import { useSearch } from "@/composables/useSearch";

const props = defineProps<{
  templates: Template[];
}>();

const query = ref("");
const { performSearch, results, clearSearch } = useSearch();

const suggestions = computed(() =>
  results.value.slice(0, 8).map((r) => r.item),
);

function handleSearch(q: string) {
  performSearch(q, props.templates);
}

function selectResult(item: SearchableItem) {
  query.value = "";
  clearSearch();
  // Handle selection (open modal, etc)
}
</script>

<template>
  <div class="search-container">
    <input
      v-model="query"
      @input="handleSearch(query)"
      placeholder="Search templates..."
    />
    <ul v-if="suggestions.length > 0">
      <li
        v-for="item in suggestions"
        :key="item.id"
        @click="selectResult(item)"
      >
        {{ item.name }}
      </li>
    </ul>
  </div>
</template>
```

## Performance Characteristics

### Indexing

| Dataset Size | Time    | Note                   |
| ------------ | ------- | ---------------------- |
| 50 items     | ~5ms    | Initial index creation |
| 200 items    | ~15ms   | Typical template set   |
| 500+ items   | 30-52ms | Large catalog          |

Indexing happens once on first search (lazy initialization).

### Search Execution

- **Typical query:** <1ms
- **Complex fuzzy query:** <5ms
- **Max results (8):** <1ms

All searches complete synchronously in <50ms.

### Memory

- MiniSearch overhead: ~20KB base
- Per item: ~100-200 bytes indexed

## Testing

Test files in `src/__tests__/`:

- `useSearch.test.ts` - 54 test cases covering all functionality
- `SearchBar.test.ts` - Component integration tests
- `performance.test.ts` - Performance benchmarks

Run tests:

```bash
npm test                    # Watch mode
npm run test:run           # Single run
npm run test:coverage      # Coverage report
npm run test:ui            # Vitest UI
```

Coverage target: >95% (currently 96.87%)

## Troubleshooting

### No results for valid query

Check:

1. Query length ≥ 2 characters
2. Templates/stacks provided to performSearch
3. Field names match (case-insensitive)
4. Try shorter/longer fuzzy tolerance in config

### Search too slow

- Check dataset size (>1000 items?)
- Simplify query (fewer special chars)
- Verify index is created (check `initializeIndex` call)
- Run performance benchmark: `npm run test`

### Results ranking unexpected

Review field boost weights in `useSearch.ts`:

- name: 3x
- tags: 2x
- category/type: 1.5x
- description: 1x

Adjust `boost` object in MiniSearch config to change weights.

## Integration Notes

**Breaking Changes from Fuse.js:**

- Function name: `search()` → `performSearch()`
- Type name: `SearchResult` (same interface)
- Bundle size: -4KB (MiniSearch vs Fuse.js)
- Default fuzzy tolerance: 0.2 (was 0.6 in Fuse)

**Migration Path:**

1. Replace `useSearch` import
2. Update `search()` calls to `performSearch()`
3. Adjust fuzzy tolerance if needed (0.2 is tighter)
4. Test with live search queries

## Next Steps (Phase 2)

- [ ] Implement useCart.ts for stack building
- [ ] Add search filters (by type, category, tags)
- [ ] Search history/recent queries
- [ ] Search analytics (popular queries)
- [ ] Advanced search syntax (field-specific: `type:agent`, `tag:security`)
