# 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`