# Frontend Integration Notes

## Tag & Contributor Normalization

The backend implements dual-field normalization for searchability:

### Architecture

| Field Type                                              | Purpose        | Behavior                                            | Example         |
| ------------------------------------------------------- | -------------- | --------------------------------------------------- | --------------- |
| **Display Field** (`tags`, `contributors`)              | Show to users  | Preserves exact variant, punctuation, proper casing | `"ACME CORP."`  |
| **Search Field** (`tags_search`, `contributors_search`) | Search against | Lowercase, no punctuation, deduplicated             | `["acme corp"]` |

### Normalization Rules

#### Tags

1. Trim whitespace from each tag
2. Titlecase each tag (preserves hyphenation: "non-fiction" → "Non-Fiction")
3. Case-insensitive deduplication
4. Remove punctuation for search field only
5. Store both display and search versions

#### Contributors

1. Trim whitespace from each contributor
2. Preserve original casing (including CAPSLOCK companies)
3. Preserve original punctuation for display
4. Remove punctuation for search comparison only
5. Case-insensitive deduplication
6. Store both display and search versions

### API Request/Response

**Request:**

```json
{
  "tags": ["science-fiction", "ACME CORP.", "  O'Reilly Media"],
  "contributors": ["  Acme Corp  ", "acme corp"]
}
```

**Response (after normalization):**

```json
{
  "tags": ["Science-Fiction", "O'Reilly Media"],
  "tags_search": ["science fiction", "oreilly media"],
  "contributors": ["Acme Corp", "acme corp"],
  "contributors_search": ["acme corp"]
}
```

### Frontend Implementation Guidelines

#### Display

- Use `tags` and `contributors` fields
- These preserve exact user input (casing, punctuation)
- No transformation needed

#### Search

- Use search inputs against `tags_search` and `contributors_search`
- Normalize user search input:
  - Convert to lowercase
  - Remove punctuation (optional but recommended)
  - Search using `= ANY()` operator

#### User Typing "Science-Fiction"

```typescript
// User types exact value
const searchValue = "Science-Fiction";

// Backend normalizes to display and search versions
// Display: "Science-Fiction"
// Search: "science fiction"
```

#### Search Query Behavior

```typescript
// User searches: "ACME CORP."
// Backend normalizes search to: "acme corp"
// This matches contributors_search = ["acme corp"]
// Which finds display contributors = ["ACME CORP.", "Acme Corp", "acme corp"]
```

### Checkbox Filter Integration

When building frontend checkbox filters for contributors/tags:

#### Get Unique Values for Dropdown

```typescript
// Fetch distinct normalized values for filters
GET /api/contributors?distinct=true
Response: ["acme corp", "oreilly media", "penguin"]

// Render as checkboxes (using display names from another endpoint or mapping)
```

#### Filter Query

```typescript
// User selects checkbox
const filterValue = "acme corp";

// API request (filter by search field)
{
  "contributors_search": ["acme corp"]
}

// Backend matches contributors_search array using = ANY()
```

### Important Notes

1. **Display ≠ Search**: Always send search queries to search fields, not display fields
2. **Backend Normalization**: Backend normalizes input on CREATE/UPDATE, so always use search fields for filtering
3. **Case Sensitivity**: Search is case-insensitive, display is case-preserved
4. **Punctuation**: Display preserves it, search ignores it
5. **Deduplication**: Search fields are deduplicated, display fields are not

### Common Mistakes to Avoid

❌ **Searching display field directly**

```typescript
// WRONG - Will miss different casing/punctuation
WHERE 'ACME CORP.' = ANY(contributors)
```

✅ **Search search field**

```typescript
// CORRECT - Case-insensitive, punctuation-free
WHERE 'acme corp' = ANY(contributors_search)
```

❌ **Don't normalize user search input**

```typescript
// WRONG - If user types "ACME CORP" explicitly to find exact match
const search = "acme corp"; // Changes user's intent
```

✅ **Use exact user input for search**

```typescript
// CORRECT - Backend handles normalization
const search = "ACME CORP"; // Backend will match "acme corp" in search field
```

### Schema Reference

**Display Fields:**

- `tags TEXT[]` - Titlecase, original punctuation
- `contributors TEXT[]` - Original casing, original punctuation

**Search Fields:**

- `tags_search TEXT[]` - Lowercase, no punctuation, deduplicated
- `contributors_search TEXT[]` - Lowercase, no punctuation, deduplicated

**GIN Indexes:**

- `idx_media_items_tags_search` - Fast search on tags_search
- `idx_media_items_contributors_search` - Fast search on contributors_search
- `idx_media_items_tags_gin` - Display field (if needed)
- `idx_media_items_contributors_gin` - Display field (if needed)

### Example Flow

1. **User creates media item:**
   - Input: `tags: ["science-fiction", "ACME CORP."]`
   - Backend stores:
     - `tags`: `["Science-Fiction"]` (titlecased)
     - `tags_search`: `["science fiction"]` (lowercase, no punctuation)
     - `contributors`: `["ACME CORP."]` (preserved)
     - `contributors_search`: `["acme corp"]` (normalized)

2. **User searches "ACME CORP":**
   - Frontend sends: `q: "ACME CORP"`
   - Backend searches `tags_search` and `contributors_search`
   - Finds: `contributors_search = ["acme corp"]` → MATCH ✅
   - Returns: Media item with `contributors = ["ACME CORP."]`

3. **User searches "acme corp":**
   - Frontend sends: `q: "acme corp"`
   - Backend searches `tags_search` and `contributors_search`
   - Finds: `contributors_search = ["acme corp"]` → MATCH ✅
   - Returns: Media item with `contributors = ["ACME CORP."]`

4. **User searches "science-fiction":**
   - Frontend sends: `q: "science-fiction"`
   - Backend searches `tags_search`
   - Finds: `tags_search = ["science fiction"]` → NO MATCH (hyphen vs space)
   - Does NOT return (but fuzzy search might catch it)

5. **User searches "science fiction":**
   - Frontend sends: `q: "science fiction"`
   - Backend searches `tags_search`
   - Finds: `tags_search = ["science fiction"]` → MATCH ✅
   - Returns: Media item with `tags = ["Science-Fiction"]`
