Files
kikootwo 5d9a764151 Controlled pagination pill with lock & fit-scroll
Make the floating pagination pill a controlled component and add lock/fit-aware scroll behavior. UnifiedPagination now accepts activeIndex and onDominantSectionChange, reports observer-determined dominant section (parent may ignore when locked) and only shows/hides based on footer visibility. HomePage implements controlled state (activeIndex, lockedTo) with Prev/Next/jump locking, release on wheel/touch/key or 30s safety timeout, and dot clicks that always navigate and release locks. Extracted scroll math to src/lib/utils/paginationScroll.ts (decideScrollForPageChange) so paging avoids scrolling when a section fits below the sticky header and clamps targets; added unit tests and updated component tests and docs to reflect the new behavior. Removed now-unused onPageChange prop from HomeSection.
2026-05-18 13:21:06 -04:00

4.2 KiB

Home Page Sections (Per-User Configurable)

Status: Implemented | Per-user home page with configurable sections (popular, new releases, Audible categories)

Overview

Users customize their home page by adding/removing/reordering sections. Each section displays audiobooks from a specific source: built-in Popular, New Releases, or scraped Audible categories.

Data Models

UserHomeSection (user_home_sections):

  • id, userId (FK User), sectionType ('popular'|'new_releases'|'category'), categoryId (nullable), categoryName (nullable), sortOrder (int)
  • Unique: (userId, sectionType, categoryId)
  • Default: Popular (0) + New Releases (1) created on first access

AudibleCacheCategory (audible_cache_categories):

  • id, asin, categoryId, rank, lastSyncedAt
  • Unique: (asin, categoryId), Indexes: categoryId, (categoryId, rank)

API Endpoints

Method Path Auth Description
GET /api/user/home-sections user Returns sections + nextRefresh
PUT /api/user/home-sections user Save full config (delete-recreate), max 10
GET /api/audible/categories user Live scrape top-level categories
GET /api/audiobooks/category/[categoryId] public Paginated category books from cache

Refresh Processor (Unified Storage)

  • All section data stored in AudibleCacheCategory with reserved IDs: __popular__ and __new_releases__ for built-in sections
  • Popular/new-releases use same wipe-and-populate pattern as user categories
  • After built-in sections, queries DISTINCT categoryIds from UserHomeSection
  • Per section: wipe AudibleCacheCategory rows, scrape, upsert AudibleCache metadata, insert ranked category entries
  • Batch cooldown between sections (10-20s random)
  • Constants exported from audible-refresh.processor.ts: POPULAR_CATEGORY_ID, NEW_RELEASES_CATEGORY_ID

AudibleService Methods

  • getCategories(): Scrapes {baseUrl}/categories, returns {id, name}[]
  • getCategoryBooks(categoryId, limit): Scrapes /search?node={id}&pageSize=50&sort=popularity-rank, up to 200 results

Frontend

  • Hooks: useHomeSections(), useCategoryAudiobooks(), useAudibleCategories() in src/lib/hooks/useHomeSections.ts
  • Config Modal: src/components/home/HomeSectionConfigModal.tsx — drag-and-drop (desktop), up/down arrows (mobile), auto-save with debounce
  • Section Component: src/components/home/HomeSection.tsx — renders individual section with color-coded header
  • Home Page: src/app/page.tsx — dynamic sections from user config, gear icon for customize
  • Pagination: src/components/ui/UnifiedPagination.tsx — controlled by HomePage for activeIndex; observer reports dominant section but parent gates updates via lockedTo state. Lock set on Prev/Next/jump; released on user scroll input (wheel / touchstart / Arrow / Page / Home / End keys) or any dot click. Fit-aware scroll via src/lib/utils/paginationScroll.ts — no scroll when section fits viewport, otherwise snaps top under sticky header with clamps that structurally prevent scrolling the section out of view. Pill is shown anywhere on main content; only the footer hides it.

Key Decisions

  • 10 section limit per user (total)
  • Category picker scraped live (no categories table)
  • Top-level categories only (v1)
  • Wipe-and-re-scrape per category during refresh
  • Deduplication of categories across users before scraping
  • If category disappears, user sees empty section
  • 10-color palette assigned by sort order

Files

  • Schema: prisma/schema.prisma (UserHomeSection, AudibleCacheCategory)
  • Migration: prisma/migrations/20260306000000_add_home_sections/migration.sql
  • Service: src/lib/integrations/audible.service.ts (getCategories, getCategoryBooks)
  • Processor: src/lib/processors/audible-refresh.processor.ts
  • API Routes: src/app/api/user/home-sections/route.ts, src/app/api/audible/categories/route.ts, src/app/api/audiobooks/category/[categoryId]/route.ts
  • Hooks: src/lib/hooks/useHomeSections.ts
  • Components: src/components/home/HomeSectionConfigModal.tsx, src/components/home/HomeSection.tsx
  • Tests: tests/api/home-sections.routes.test.ts, tests/processors/audible-refresh.processor.test.ts