From 2ab27d048a5d94f1fe4c2891f275759631976374 Mon Sep 17 00:00:00 2001 From: Yusuf Suleman Date: Wed, 1 Apr 2026 22:42:56 -0500 Subject: [PATCH] fix: mobile tags pills show all active tags regardless of item count Co-Authored-By: Claude Opus 4.6 (1M context) --- .../lib/pages/brain/AtelierBrainPage.svelte | 3 +- .../lib/pages/reader/AtelierReaderPage.svelte | 64 ++++++++++++++----- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/frontend-v2/src/lib/pages/brain/AtelierBrainPage.svelte b/frontend-v2/src/lib/pages/brain/AtelierBrainPage.svelte index aebb485..8e1703f 100644 --- a/frontend-v2/src/lib/pages/brain/AtelierBrainPage.svelte +++ b/frontend-v2/src/lib/pages/brain/AtelierBrainPage.svelte @@ -452,10 +452,9 @@ {/each} - {#each sidebarTags.filter(t => t.is_active && t.item_count > 0) as tag} + {#each sidebarTags.filter(t => t.is_active) as tag} {/each} diff --git a/frontend-v2/src/lib/pages/reader/AtelierReaderPage.svelte b/frontend-v2/src/lib/pages/reader/AtelierReaderPage.svelte index 3d7b8d1..2f097ce 100644 --- a/frontend-v2/src/lib/pages/reader/AtelierReaderPage.svelte +++ b/frontend-v2/src/lib/pages/reader/AtelierReaderPage.svelte @@ -27,6 +27,7 @@ let autoScrollSpeed = $state(1.5); let articleListEl: HTMLDivElement; let scrollRAF: number | null = null; + let lastScrollTs = 0; let loading = $state(true); let loadingMore = $state(false); let hasMore = $state(true); @@ -315,22 +316,31 @@ if (scrollRAF) cancelAnimationFrame(scrollRAF); if (!usesPageScroll() && !articleListEl) return; autoScrollActive = true; - function step() { + lastScrollTs = 0; + function step(timestamp: number) { if (!autoScrollActive) return; + const dt = lastScrollTs ? Math.min(34, timestamp - lastScrollTs) : 16; + lastScrollTs = timestamp; + const pxPerSecond = 28 * autoScrollSpeed; + const delta = pxPerSecond * (dt / 1000); if (usesPageScroll()) { - const nextY = window.scrollY + autoScrollSpeed; - const maxY = Math.max(0, document.documentElement.scrollHeight - window.innerHeight); - window.scrollTo({ top: Math.min(nextY, maxY), behavior: 'auto' }); - if (nextY >= maxY) { + const scroller = document.scrollingElement; + if (!scroller) return; + const nextY = scroller.scrollTop + delta; + const maxY = Math.max(0, scroller.scrollHeight - window.innerHeight); + scroller.scrollTop = Math.min(nextY, maxY); + checkScrolledCards(); + if (nextY >= maxY - 1) { stopAutoScroll(); return; } } else { if (!articleListEl) return; - articleListEl.scrollTop += autoScrollSpeed; + articleListEl.scrollTop += delta; const maxScroll = articleListEl.scrollHeight - articleListEl.clientHeight; - if (articleListEl.scrollTop >= maxScroll) { + checkScrolledCards(); + if (articleListEl.scrollTop >= maxScroll - 1) { stopAutoScroll(); return; } @@ -342,6 +352,7 @@ } function stopAutoScroll() { autoScrollActive = false; + lastScrollTs = 0; if (scrollRAF) { cancelAnimationFrame(scrollRAF); scrollRAF = null; } } function toggleAutoScroll() { @@ -366,6 +377,7 @@ let scrollCheckTimer: ReturnType | null = null; function handleListScroll() { + if (usesPageScroll()) return; // Throttle: only check every 400ms if (scrollCheckTimer) return; scrollCheckTimer = setTimeout(() => { @@ -374,21 +386,40 @@ }, 400); } + function handleViewportScroll() { + if (!usesPageScroll()) return; + if (scrollCheckTimer) return; + scrollCheckTimer = setTimeout(() => { + scrollCheckTimer = null; + checkScrolledCards(); + }, 240); + } + function checkScrolledCards() { if (!articleListEl) return; - // Infinite scroll — load more when near bottom - const { scrollTop, scrollHeight, clientHeight } = articleListEl; - if (hasMore && !loadingMore && scrollHeight - scrollTop - clientHeight < 300) { - loadEntries(true); + const cards = articleListEl.querySelectorAll('[data-entry-id]'); + if (usesPageScroll()) { + const pageBottom = window.scrollY + window.innerHeight; + const loadThreshold = document.documentElement.scrollHeight - 500; + if (hasMore && !loadingMore && pageBottom >= loadThreshold) { + loadEntries(true); + } + } else { + const { scrollTop, scrollHeight, clientHeight } = articleListEl; + if (hasMore && !loadingMore && scrollHeight - scrollTop - clientHeight < 300) { + loadEntries(true); + } } - const listTop = articleListEl.getBoundingClientRect().top; - const cards = articleListEl.querySelectorAll('[data-entry-id]'); let newlyRead = 0; cards.forEach(card => { - if (card.getBoundingClientRect().bottom < listTop + 20) { + const rect = card.getBoundingClientRect(); + const passedThreshold = usesPageScroll() + ? rect.bottom < 84 + : rect.bottom < articleListEl.getBoundingClientRect().top + 20; + if (passedThreshold) { const id = Number(card.getAttribute('data-entry-id')); if (!id) return; const article = articles.find(a => a.id === id); @@ -422,16 +453,17 @@ } // ── Init ── - onMount(() => { +onMount(() => { loadSidebar(); loadEntries(); return () => { if (flushTimer) clearTimeout(flushTimer); + if (scrollCheckTimer) clearTimeout(scrollCheckTimer); }; }); - +