From 5b90667b4b9b248888b349ec812c90b2a8340a4e Mon Sep 17 00:00:00 2001 From: root Date: Mon, 13 Apr 2026 21:14:14 +0500 Subject: [PATCH] fix: replace {#await} IIFE trick with $effect for streamed data on discover page The {#await ... then} + {@const} IIFE pattern for assigning to $state variables stopped working reliably in Svelte 5.53+. Replaced with a proper $effect that awaits both streamed promises and assigns to state, which correctly triggers reactivity. Also: switch library page selection mode entry from long-press to a 'Select' button in the page header. --- ui/src/routes/books/+page.svelte | 50 ++++++----------------------- ui/src/routes/discover/+page.svelte | 15 +++++---- 2 files changed, 17 insertions(+), 48 deletions(-) diff --git a/ui/src/routes/books/+page.svelte b/ui/src/routes/books/+page.svelte index b0a6d5c..892d970 100644 --- a/ui/src/routes/books/+page.svelte +++ b/ui/src/routes/books/+page.svelte @@ -53,11 +53,6 @@ filteredBooks.length > 0 && filteredBooks.every((b) => selected.has(b.slug)) ); - function enterSelectMode(slug: string) { - selectMode = true; - selected = new Set([slug]); - } - function exitSelectMode() { selectMode = false; selected = new Set(); @@ -81,43 +76,11 @@ } } - // Long-press support (pointer events, works on desktop + mobile) - let longPressTimer: ReturnType | null = null; - let longPressFired = false; - - function onPointerDown(slug: string) { - if (selectMode) return; - longPressFired = false; - longPressTimer = setTimeout(() => { - longPressFired = true; - enterSelectMode(slug); - }, 500); - } - - function onPointerUp() { - if (longPressTimer) { - clearTimeout(longPressTimer); - longPressTimer = null; - } - } - - function onPointerCancel() { - if (longPressTimer) { - clearTimeout(longPressTimer); - longPressTimer = null; - } - } - - // Prevent navigation click if long-press just fired + // Card click: in selection mode, toggle selection; otherwise navigate normally function onCardClick(e: MouseEvent, slug: string) { if (selectMode) { e.preventDefault(); toggleSelect(slug); - return; - } - if (longPressFired) { - e.preventDefault(); - longPressFired = false; } } @@ -175,6 +138,14 @@ > Cancel + {:else if data.books?.length} + {/if} @@ -228,9 +199,6 @@ onCardClick(e, book.slug)} - onpointerdown={() => onPointerDown(book.slug)} - onpointerup={onPointerUp} - onpointercancel={onPointerCancel} draggable="false" class="group relative flex flex-col rounded-lg overflow-hidden bg-(--color-surface-2) border transition-colors select-none {isSelected diff --git a/ui/src/routes/discover/+page.svelte b/ui/src/routes/discover/+page.svelte index c344c95..2dd9e87 100644 --- a/ui/src/routes/discover/+page.svelte +++ b/ui/src/routes/discover/+page.svelte @@ -60,10 +60,17 @@ try { return JSON.parse(genres) as string[]; } catch { return []; } } - // Resolved books from streamed promise (populated in {#await} block via binding trick) + // Resolved books from streamed promises — populated via $effect once promises settle let resolvedBooks = $state([]); let resolvedVotedBooks = $state([]); + $effect(() => { + Promise.all([data.streamed.books, data.streamed.votedBooks]).then(([books, vb]) => { + resolvedBooks = books as Book[]; + resolvedVotedBooks = vb as VotedBook[]; + }); + }); + let deck = $derived.by(() => { let books = resolvedBooks; if (prefs.onboarded && prefs.genres.length > 0) { @@ -479,12 +486,6 @@ {/if} - -{#await Promise.all([data.streamed.books, data.streamed.votedBooks]) then [books, vb]} - - {@const _ = (() => { resolvedBooks = books as Book[]; resolvedVotedBooks = vb as VotedBook[]; return ''; })()} -{/await} -