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
6.2 KiB
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
- Trim whitespace from each tag
- Titlecase each tag (preserves hyphenation: "non-fiction" → "Non-Fiction")
- Case-insensitive deduplication
- Remove punctuation for search field only
- Store both display and search versions
Contributors
- Trim whitespace from each contributor
- Preserve original casing (including CAPSLOCK companies)
- Preserve original punctuation for display
- Remove punctuation for search comparison only
- Case-insensitive deduplication
- 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
tagsandcontributorsfields - These preserve exact user input (casing, punctuation)
- No transformation needed
Search
- Use search inputs against
tags_searchandcontributors_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
- Display ≠ Search: Always send search queries to search fields, not display fields
- Backend Normalization: Backend normalizes input on CREATE/UPDATE, so always use search fields for filtering
- Case Sensitivity: Search is case-insensitive, display is case-preserved
- Punctuation: Display preserves it, search ignores it
- 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 punctuationcontributors TEXT[]- Original casing, original punctuation
Search Fields:
tags_search TEXT[]- Lowercase, no punctuation, deduplicatedcontributors_search TEXT[]- Lowercase, no punctuation, deduplicated
GIN Indexes:
idx_media_items_tags_search- Fast search on tags_searchidx_media_items_contributors_search- Fast search on contributors_searchidx_media_items_tags_gin- Display field (if needed)idx_media_items_contributors_gin- Display field (if needed)
Example Flow
-
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)
- Input:
-
User searches "ACME CORP":
- Frontend sends:
q: "ACME CORP" - Backend searches
tags_searchandcontributors_search - Finds:
contributors_search = ["acme corp"]→ MATCH ✅ - Returns: Media item with
contributors = ["ACME CORP."]
- Frontend sends:
-
User searches "acme corp":
- Frontend sends:
q: "acme corp" - Backend searches
tags_searchandcontributors_search - Finds:
contributors_search = ["acme corp"]→ MATCH ✅ - Returns: Media item with
contributors = ["ACME CORP."]
- Frontend sends:
-
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)
- Frontend sends:
-
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"]
- Frontend sends: