fix(comparisonStore): preserve stored selection on cold load

The seed-defaults effect fired whenever fontA/fontB were still
undefined, including the window between constructor reading storage
and the per-id batch resolving. On a slow batch or fast catalog the
effect clobbered storage with catalog[0]/catalog[N-1], losing the
user's pick on reload.

Now bails when storage already holds IDs, and reads storage via
untrack so per-font selection writes don't re-trigger the effect.

Adds a deterministic regression test that controls catalog/batch
ordering via mockImplementation timing.
This commit is contained in:
Ilia Mashkov
2026-05-28 14:58:18 +03:00
parent 7a9422b574
commit b9e21a66d3
2 changed files with 66 additions and 12 deletions
@@ -102,7 +102,7 @@ export class ComparisonStore {
this.#fontsByIdsStore = new FontsByIdsStore(fontAId && fontBId ? [fontAId, fontBId] : []);
$effect.root(() => {
// Effect 1: Sync batch results → fontA / fontB
// Sync batch results → fontA / fontB
$effect(() => {
const fonts = this.#fontsByIdsStore.fonts;
if (fonts.length === 0) {
@@ -124,7 +124,7 @@ export class ComparisonStore {
}
});
// Effect 2: Trigger font loading whenever selection or weight changes
// Trigger font loading whenever selection or weight changes
$effect(() => {
const fa = this.#fontA;
const fb = this.#fontB;
@@ -154,24 +154,38 @@ export class ComparisonStore {
}
});
// Effect 3: Set default fonts when storage is empty
// Set default fonts when storage is empty
$effect(() => {
if (this.#fontA && this.#fontB) {
return;
}
const fonts = fontCatalogStore.fonts;
if (fonts.length >= 2) {
untrack(() => {
const id1 = fonts[0].id;
const id2 = fonts[fonts.length - 1].id;
storage.value = { fontAId: id1, fontBId: id2 };
this.#fontsByIdsStore.setIds([id1, id2]);
});
// Don't clobber a pending rehydration - only seed when storage is empty.
// Untracked: only the catalog load should drive this effect, not the
// user's storage writes that happen as a result of normal selection.
const hasStoredSelection = untrack(() => {
return storage.value.fontAId !== null || storage.value.fontBId !== null;
});
if (hasStoredSelection) {
return;
}
const fonts = fontCatalogStore.fonts;
if (fonts.length < 2) {
return;
}
untrack(() => {
const id1 = fonts[0].id;
const id2 = fonts[fonts.length - 1].id;
storage.value = { fontAId: id1, fontBId: id2 };
this.#fontsByIdsStore.setIds([id1, id2]);
});
});
// Effect 4: Pin fontA/fontB so eviction never removes on-screen fonts
// Pin fontA/fontB so eviction never removes on-screen fonts
$effect(() => {
const fa = this.#fontA;
const fb = this.#fontB;