fix(recipe): View-Beacon ueber \$effect statt onMount
All checks were successful
Build & Publish Docker Image / build-and-push (push) Successful in 33s

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) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-22 14:50:54 +02:00
parent e7318164cb
commit 2b0bd4dc44

View File

@@ -356,19 +356,27 @@
}; };
document.addEventListener('visibilitychange', onVisibility); 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); 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(() => { onDestroy(() => {
void releaseWakeLock(); void releaseWakeLock();
}); });