refactor(filters): mount-scope store bindings and fix effect-update loop

Replace the side-effect-on-import $effect.root in bindings with an explicit
startFilterBindings() started from an AppBindings provider in onMount, so the
filters/sort -> font-catalog bridge has a lifecycle tied to the app tree and a
returned cleanup. bindings now consumes getFontCatalog().

Fix the effect-update loop this surfaced: setGroups populated the reactive
groups array in place via `groups.length = 0; groups.push(...)`. push reads the
array's length signal, so the populating effect both read and wrote
groups.length each run and re-triggered itself forever
(effect_update_depth_exceeded). setGroups now reassigns the array (groups is
`let`), which does not read length.

Extract mapFilterMetadataToGroups to own the metadata -> group-config mapping,
including sorting a copy of options (the source is TanStack-cached data; an
in-place sort corrupts the cache and writes into the effect's read dependency).
This commit is contained in:
Ilia Mashkov
2026-06-01 17:25:26 +03:00
parent 1ad015aed6
commit 9780ff9358
9 changed files with 191 additions and 58 deletions
+24
View File
@@ -0,0 +1,24 @@
<!--
Component: AppBindings
Provider that starts app-wide store bindings (filters → sort → font catalog)
for its subtree. Mount-scoped so the bindings' lifetime tracks the app tree.
-->
<script lang="ts">
import { startFilterBindings } from '$features/FilterAndSortFonts';
import { onMount } from 'svelte';
import type { Snippet } from 'svelte';
interface Props {
/**
* Content snippet
*/
children?: Snippet;
}
let { children }: Props = $props();
// startFilterBindings returns its $effect.root cleanup; onMount runs it on unmount.
onMount(() => startFilterBindings());
</script>
{@render children?.()}