From 2b0bd4dc449f5c579daf57d2da4e3bdab2764e8e Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 22 Apr 2026 14:50:54 +0200 Subject: [PATCH] fix(recipe): View-Beacon ueber \$effect statt onMount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live-Test auf kochwas-dev: bei Hard-Reload/Cold-Start (nicht SPA-Click) landete kein view-Eintrag in der DB. Ursache: Recipe-Page-onMount feuert vor Layout-onMount, profileStore.load() laeuft aber im Layout- onMount und macht erst danach einen async fetch — zum Zeitpunkt des Beacons war profileStore.active noch null. Loesung: Beacon im \$effect, das auf profileStore.active reagiert. viewBeaconSent-Flag verhindert duplicate POSTs wenn der User waehrend der Page-Lifetime das Profil wechselt — der ursprueglich getrackte Profil-View bleibt der "richtige" fuer dieses Page-Open. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/routes/recipes/[id]/+page.svelte | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) 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(); });