Skip to content

fix(provider-polyhaven): rank by match quality and popularity; cache the asset list - #42

Merged
MyPrototypeWhat merged 3 commits into
mainfrom
fix/polyhaven-relevance
Oct 26, 2026
Merged

MyPrototypeWhat merged 3 commits into
mainfrom
fix/polyhaven-relevance

Conversation

@MyPrototypeWhat

Copy link
Copy Markdown
Collaborator

Poly Haven results were effectively alphabetical, and every search downloaded the whole asset list.

Why

The list endpoint has no search, so the provider filtered client-side. It counted how many query words occurred as a substring anywhere in an asset and kept the API's alphabetical order on ties. It then capped results at maxAssets before core reranked them.

Take "sunset HDRI", checked live on 997 HDRIs. hdri matches no asset. sunset substring-matches 225 assets through the sunrise-sunset category. All 225 tie, so the top 12 came back as aarfontein_dawn_2, aarfontein_dusk, aarfontein_pan and so on.

Separately, every search re-downloaded the list: about 1 MB for HDRIs and 0.86 MB for textures. Both sources are keyless and on by default in hosts.

Change

  • Ranking. Each query word scores its best match:

    • 3: a word of the name or id;
    • 2: a word of a tag or category;
    • 1: a substring;
    • 0: no match.

    Assets are sorted by total score, then download_count, then list order. Ranking happens before the page window. An empty query lists the most-downloaded first.

  • Unicode tokens. Words in any script are kept whole (crème, 日落, सूर्यास्त). A non-Latin or punctuation-only query that matches nothing now returns []. Before, it fell into the "no words" path.

  • Asset list cache. Each provider instance downloads the list once and reuses it for an hour. The new listTtlMs option controls this, and 0 turns the cache off.

    • Concurrent searches share one in-flight download.
    • The TTL counts from when the download completes.
    • A failure, a non-OK status or a malformed body (null, array, or an object with no assets) is dropped and never cached.
    • The shared download does not take one search's abort signal. Each search races its own signal, and the abort listener is removed afterwards.
    • A search that is already aborted starts no download.
    • The shared download fails after 30 s, so a stalled request cannot hold every search until its own deadline.
  • raw is a structuredClone of the cached asset, so a consumer mutating it cannot change later results.

Live, before → after

query before (top 4) after (top 4)
"sunset HDRI" aarfontein_dawn_2, aarfontein_dusk, aarfontein_pan, aarfontein_sunrise belfast_sunset_puresky, venice_sunset, industrial_sunset_puresky, sunset_jhbcentral
"forest rock" brown_mud_rocks_01, forest_ground_04, forest_ground_05, forest_ground_06 forest_ground_04, rocky_trail, forest_ground_06, stony_dirt_path

A second search on the same instance makes no list request.

Testing

  • 23 new and 3 updated tests, all written first, each seen failing.
  • Mutations of each cache and abort rule were checked against the tests. Every one is caught.
  • Two review rounds. The final review found no Critical or Important issues, and all six of its Minors are fixed here.
  • Gate: pnpm typecheck, pnpm lint, pnpm build, pnpm test:run (686 passed, 25 skipped) and pnpm smoke:artifact. REFKIT_LIVE=1 conformance passes for polyhaven, polyhaven-hdri and ambientcg.

The changeset is a patch for @refkit/provider-polyhaven. It is the first release under trusted publishing that touches only one package.

@MyPrototypeWhat
MyPrototypeWhat merged commit b986bc4 into main Oct 26, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant