diff --git a/plans/005-limit-home-continue-watching-carousel-query.md b/plans/005-limit-home-continue-watching-carousel-query.md new file mode 100644 index 00000000..f09db656 --- /dev/null +++ b/plans/005-limit-home-continue-watching-carousel-query.md @@ -0,0 +1,35 @@ +--- +finding: "Limit home continue-watching carousel query" +catalog: "Performance" +impact: "A large local library can make the home page fetch and render every continue-watching row for a carousel." +base_commit: "0d31d46" +--- + +## Effort + +S - add a bounded query and route it through the catalog section path. + +## Risk + +LOW - the home carousel can be limited without changing full watchlist behavior. + +## Confidence + +HIGH - the current query is unbounded and the template renders every returned row. + +## Evidence + +- `templates/index.gohtml:5` loads `/api/catalog/continue` on page load. +- `internal/anime/service.go:56-60` calls `GetContinueWatchingEntries` for the Continue section. +- `internal/db/queries.sql:100-118` defines `GetContinueWatchingEntries` without `LIMIT`. +- `templates/components/continue_watching.gohtml:7-43` ranges over every returned row. + +## Resolution Approach + +Add a separate limited query for the home-page continue-watching carousel, ordered by `updated_at DESC`. A limit around 20-30 entries is reasonable for a horizontal carousel; choose a named constant in Go rather than scattering a magic number. + +Do not change any full watchlist or management views unless they intentionally share this carousel endpoint. Keep the existing unbounded query only if another path truly needs the full set; otherwise rename queries to make their purpose clear. + +Update service tests or add repository/service tests that seed more rows than the limit and assert the Continue catalog section returns only the most recent entries in order. If generated SQL files are maintained manually in this repo, update generated query code consistently with existing patterns; if generated by sqlc, use the project’s existing generation workflow only if documented and non-destructive. + +Verify with `go test ./internal/anime ./internal/db ./...`. diff --git a/plans/006-move-top-picks-recommendation-expansion-off-request-path.md b/plans/006-move-top-picks-recommendation-expansion-off-request-path.md new file mode 100644 index 00000000..26b95616 --- /dev/null +++ b/plans/006-move-top-picks-recommendation-expansion-off-request-path.md @@ -0,0 +1,36 @@ +--- +finding: "Move top-picks recommendation expansion off request path" +catalog: "Performance" +impact: "Top-picks requests can fan out into many Jikan calls, competing with rate limits and making the page slow on cache misses." +base_commit: "0d31d46" +--- + +## Effort + +M - requires cache/materialization design plus tests around freshness and fallback behavior. + +## Risk + +MED - caching changes can alter recommendation freshness, ordering, and perceived personalization. + +## Confidence + +HIGH - recommendation handlers currently call the expansion engine during requests. + +## Evidence + +- `internal/anime/catalog_handler.go:43` calls `GetTopPickForYou` during the home HTMX request. +- `internal/anime/catalog_handler.go:86` calls `GetTopPicksForYou` during the full top-picks page request. +- `internal/anime/recommendations/engine.go:83-107` fetches seed anime and recommendations from Jikan. +- `internal/anime/recommendations/engine.go:209-215` may fetch individual candidate anime while scoring. +- `internal/anime/recommendations/constants.go:6-12` bounds the work, but the bounds still allow many provider calls per request. + +## Resolution Approach + +Introduce a cached or materialized recommendation result per user. The request path should first read a bounded cached result and render quickly. A refresh path can reuse the current recommendation engine to recompute candidates in the background or on explicit cache miss with a short timeout. + +Preserve existing ranking logic initially. This plan should not redesign recommendation scoring; it should move expensive expansion away from synchronous rendering. Prefer stale-while-refresh behavior if the app already has enough data to show previous recommendations. + +Define freshness explicitly, for example a short TTL for active users and invalidation when watchlist entries change. Add tests around cache hit, cache miss, stale fallback, and provider failure. Include observability logs for refresh failures without breaking the page when stale data is available. + +Verify with `go test ./internal/anime/... ./integrations/jikan/...` and the existing full `go test ./...` suite.