On initial load, two separate $effects in bindings.svelte.ts — one for
filters, one for sort — each issued its own setOptions with a different
queryKey on the first flush, producing an orphaned
`/fonts?limit=50&offset=0` request immediately followed by the real
`/fonts?limit=50&sort=popularity&offset=0`. Hardcoding the default sort
on the singleton would have papered over the symptom while leaving the
sortStore default and the catalog-store default coupled by hand.
Make bindings the sole emitter of query params:
- features/.../bindings: merge filter + sort effects into one. The effect
reads both stores, builds the merged param object, and issues a single
setParams. No more interleaved setOptions on mount.
- entities/.../fontCatalogStore: gate the observer with `enabled: false`
on construction. The first setParams flips `#enabled` on and triggers
exactly one fetch with the correct queryKey. Removes the need for a
hardcoded default sort on the singleton.
- isEmpty is also gated on `#enabled` so the brief pre-config window
doesn't render "no results" before bindings configures the query.
- The constructor seeds #result from observer.getCurrentResult() because
subscribe may not fire synchronously when the observer is disabled.
The proxy returned `{fonts: null, total: 0}` for empty results, which
fetchProxyFonts surfaced as a generic Error. fontCatalogStore wrapped it
as FontNetworkError, and TanStack retried 3× with exponential backoff —
pinning the loading skeleton for ~7s before settling on an empty list.
Schema mismatches are deterministic; retrying only delays surfacing the
contract violation.
- shared/api/queryClient: introduce NonRetryableError marker class.
The default retry handler short-circuits when it sees this so any
store using the shared client gets fail-fast behavior for free.
- entities/Font/lib/errors: FontResponseError extends NonRetryableError.
- entities/Font/api/proxy/proxyFonts: throw FontResponseError (was a
bare Error). Document that ProxyFontsResponse.fonts is always an array.
- entities/Font/.../fontCatalogStore.fetchPage: preserve a
FontResponseError raised lower in the stack instead of re-wrapping
it as FontNetworkError.
- features/FilterAndSortFonts/api/filters: throw NonRetryableError on
invalid filters payloads and document the array-never-null contract.
Adds an `empty` snippet prop to FontVirtualList and supplies it from the
sidebar FontList. Settled queries with zero results now render a centered
"No typefaces found" label instead of a blank list area.