Files
bookhoard/docs/FRONTEND_INTEGRATION.md
john-okeefe 4d321528b2 docs: update comprehensive API documentation and project guides
This commit updates all documentation files throughout the project:

- Updated IMPLEMENTATION_PLAN.md with new implementation details
- Updated PROJECT_GUIDELINES.md with coding standards and practices
- Updated README.md with current project information
- Updated SCREENSHOT_AUTOMATION.md with new automation details
- Added TEST_DATA.md with test fixtures data
- Updated cover_image_serving_plan.md with static URL patterns

Documentation API updates:
- Updated API reference documentation for all endpoints including:
  - Authentication (login, logout, register, refresh_token)
  - Book matching (auto_link, bulk_link, link_book, search)
  - Collections (CRUD operations, shelf mappings, auto-assign rules)
  - Conflicts (bulk operations, resolve/dismiss)
  - Devices (registration, approval, shelf management)
  - Highlights (create, update, delete, get)
  - Kobo sync (bookmark, markup, initialization, sync)
  - KOReader sync (library, metadata, bookmarks, progress)
  - Libraries (CRUD, folders, media items, stats)
  - Media items (bulk operations, CRUD)
  - Notes (CRUD operations)
  - OPDS (acquisition, feeds, publication)
  - Progress (reading progress tracking)
  - Queue (device queue management)
  - Ratings (star ratings)
  - Scanner (watch mode, scan operations)
  - Sync protocols (Kobo, KOReader)
  - Users (profile, password, admin operations)
  - WebSocket protocols

- Updated user guides (admin, dashboard, settings, sync)
- Updated device setup guides (Kobo, KOReader)
- Updated developer guides (testing, contributing, operations)
- Updated scripts/README.md
2026-02-27 17:06:22 -05:00

6.2 KiB

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:

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

Response (after normalization):

{
  "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
  • 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"

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

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

Search Query Behavior

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

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

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

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

Search search field

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

Don't normalize user search input

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

// 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"]