# Discover Pages This folder documents how each public discovery surface is assembled today. It is intentionally page-oriented rather than architecture-oriented. For the broader discovery engine, signal collection, and personalization background, also see `docs/discovery-personalization-engine.md`. ## Pages Covered - `Trending` → `GET /discover/trending` - `Rising` → `GET /discover/rising` - `Fresh` → `GET /discover/fresh` - `Top Rated` → `GET /discover/top-rated` - `Most Downloaded` → `GET /discover/most-downloaded` - `Today Downloads` → `GET /downloads/today` - `On This Day` → `GET /discover/on-this-day` - `For You` → `GET /discover/for-you` (auth only) ## Shared Request Pipeline Most Discover pages follow this pattern: 1. Route enters `App\Http\Controllers\Web\DiscoverController`. 2. The controller calls `App\Services\ArtworkSearchService`. 3. `ArtworkSearchService` queries the `artworks` Meilisearch index through Laravel Scout. 4. The search result usually contains only search/index fields. 5. `DiscoverController::hydrateDiscoverSearchResults()` then reloads full `Artwork` rows from MySQL with relations (`user`, `profile`, `categories`) and converts them into the view model used by Blade. 6. Some pages can still pass through additional presentation layers such as `GridFiller`, depending on the controller action. ## Shared Visibility Rules All search-backed pages use the same base visibility filter in `ArtworkSearchService`: ```text is_public = true AND is_approved = true ``` That means an artwork must be: - public - approved - present in the Meilisearch index If the database row is correct but search is stale, the page can still miss the artwork until indexing catches up. ## Shared Cache Behavior `ArtworkSearchService` uses application cache in front of Meilisearch. - Default TTL: 300 seconds - `Rising`: 120 seconds - Category/content-type sort pages use per-sort TTLs, but those are outside this folder's scope The page can therefore lag behind a real publish or stat change even when the underlying data is already correct. ## Shared Supporting Jobs These jobs are active in the current Laravel 11 runtime scheduler (`routes/console.php`): - `skinbase:flush-redis-stats` every 5 minutes - `skinbase:recalculate-trending --period=24h` every 30 minutes - `skinbase:recalculate-trending --period=7d --skip-index` every 30 minutes - `skinbase:reset-windowed-stats --period=24h` daily at 03:30 - `skinbase:reset-windowed-stats --period=7d` weekly (Monday) at 03:30 - `nova:recalculate-rankings --sync-rank-scores` every 30 minutes - `artworks:publish-scheduled` every minute - `analytics:aggregate-discovery-feedback` daily at 03:25 - `RecBuildItemPairsFromFavouritesJob` every 4 hours - `RecComputeSimilarByTagsJob` daily at 02:00 - `RecComputeSimilarByBehaviorJob` daily at 02:15 - `RecComputeSimilarHybridJob` daily at 02:30 ## Important Scheduler Caveat The codebase still contains some discovery-related schedules inside `app/Console/Kernel.php`, but the active Laravel 11 runtime schedule comes from `routes/console.php`. The Rising pipeline depends on these active runtime jobs: - `nova:metrics-snapshot-hourly` hourly - `nova:recalculate-heat` every 15 minutes If Rising stops moving while Trending changes, check `php artisan schedule:list` first and confirm both jobs are still active. ## File Map - `trending.md` - `rising.md` - `fresh.md` - `top-rated.md` - `most-downloaded.md` - `today-downloads.md` - `on-this-day.md` - `for-you.md`