diff --git a/src/routes/recipes/[id]/+page.svelte b/src/routes/recipes/[id]/+page.svelte index f84f10e..8e7db08 100644 --- a/src/routes/recipes/[id]/+page.svelte +++ b/src/routes/recipes/[id]/+page.svelte @@ -356,19 +356,27 @@ }; document.addEventListener('visibilitychange', onVisibility); - // Track view per active profile (fire-and-forget). Skipped when no - // profile is active — we'd just be writing rows nobody can sort against later. - if (profileStore.active) { - void fetch(`/api/recipes/${data.recipe.id}/view`, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify({ profile_id: profileStore.active.id }) - }); - } - return () => document.removeEventListener('visibilitychange', onVisibility); }); + // Track view per active profile (fire-and-forget). Lives in $effect, not + // onMount, because profileStore.load() runs from layout's onMount and the + // child onMount fires first — at mount time profileStore.active is still + // null on cold loads. The effect re-runs once active populates, the + // viewBeaconSent flag prevents duplicate POSTs on subsequent profile + // switches within the same page instance. + let viewBeaconSent = $state(false); + $effect(() => { + if (viewBeaconSent) return; + if (!profileStore.active) return; + viewBeaconSent = true; + void fetch(`/api/recipes/${data.recipe.id}/view`, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ profile_id: profileStore.active.id }) + }); + }); + onDestroy(() => { void releaseWakeLock(); });