From 585c03a11e38ef1ac6cdbcbeff7257835d3ba035 Mon Sep 17 00:00:00 2001 From: Alexander Bocken Date: Mon, 4 May 2026 22:21:16 +0200 Subject: [PATCH] feat(offline): hoist sync UI to homepage, slow auto-sync to weekly Move OfflineSyncIndicator (logo pip) and OfflineSyncBanner from the [recipeLang] layout/page to (main)/+layout.svelte and (main)/+page.svelte. Sync is an app-wide concern, not recipe-specific, and surfacing it on the homepage gives the entry point users actually see when they install the PWA. Indicator pulls language from languageStore since (main) doesn't have data.lang from a recipe-scoped load. Drop the now-unused .banner-wrap CSS and OfflineSyncIndicator/Banner imports from the recipe routes. Auto-sync cadence: - AUTO_SYNC_INTERVAL 30 min -> 1 week. Recipes don't change often enough to justify a half-hourly background download (the user explicitly wanted this dialed back). - Internal poll tick 5 min -> 1 hour. Polling 12x an hour for a weekly event is wasted work; hourly is fine and still responsive when the weekly window opens. Bump 1.65.3 -> 1.66.0. --- package.json | 2 +- src/lib/stores/pwa.svelte.ts | 9 +++++---- src/routes/(main)/+layout.svelte | 18 +++++++++++++++++ src/routes/(main)/+page.svelte | 3 +++ .../[recipeLang=recipeLang]/+layout.svelte | 16 --------------- .../[recipeLang=recipeLang]/+page.svelte | 20 ------------------- 6 files changed, 27 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index 0fac4b7b..47f4870e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homepage", - "version": "1.65.3", + "version": "1.66.0", "private": true, "type": "module", "scripts": { diff --git a/src/lib/stores/pwa.svelte.ts b/src/lib/stores/pwa.svelte.ts index 8ad929b9..56a2064e 100644 --- a/src/lib/stores/pwa.svelte.ts +++ b/src/lib/stores/pwa.svelte.ts @@ -2,7 +2,7 @@ import { browser } from '$app/environment'; import { isOfflineDataAvailable, getLastSync, clearOfflineData } from '$lib/offline/db'; import { downloadAllRecipes, type SyncResult, type SyncProgress } from '$lib/offline/sync'; -const AUTO_SYNC_INTERVAL = 30 * 60 * 1000; // 30 minutes +const AUTO_SYNC_INTERVAL = 7 * 24 * 60 * 60 * 1000; // 1 week const LAST_SYNC_KEY = 'bocken-last-sync-time'; type PWAState = { @@ -152,12 +152,13 @@ function createPWAStore() { startAutoSync() { if (autoSyncInterval) return; // Already running - // Check every 5 minutes if we should sync + // Check hourly if we should sync — actual sync only fires once + // AUTO_SYNC_INTERVAL has elapsed since the last sync. autoSyncInterval = setInterval(() => { autoSync(); - }, 5 * 60 * 1000); // Check every 5 minutes + }, 60 * 60 * 1000); - console.log('[PWA] Auto-sync enabled (every 30 minutes)'); + console.log('[PWA] Auto-sync enabled (weekly)'); }, stopAutoSync() { diff --git a/src/routes/(main)/+layout.svelte b/src/routes/(main)/+layout.svelte index 75df9da5..7440646a 100644 --- a/src/routes/(main)/+layout.svelte +++ b/src/routes/(main)/+layout.svelte @@ -2,6 +2,8 @@ import Header from '$lib/components/Header.svelte' import UserHeader from '$lib/components/UserHeader.svelte'; import LanguageSelector from '$lib/components/LanguageSelector.svelte'; +import OfflineSyncIndicator from '$lib/components/OfflineSyncIndicator.svelte'; +import { languageStore } from '$lib/stores/language.svelte'; let { data, children } = $props(); let user = $derived(data.session?.user); @@ -16,9 +18,25 @@ let user = $derived(data.session?.user); {/snippet} + {#snippet logo_overlay()} +
+ +
+ {/snippet} + {#snippet right_side()} {/snippet} {@render children()} + + diff --git a/src/routes/(main)/+page.svelte b/src/routes/(main)/+page.svelte index db3fe101..ab489364 100644 --- a/src/routes/(main)/+page.svelte +++ b/src/routes/(main)/+page.svelte @@ -2,6 +2,7 @@ import { resolve } from '$app/paths'; import LinksGrid from "$lib/components/LinksGrid.svelte"; import Seo from '$lib/components/Seo.svelte'; + import OfflineSyncBanner from '$lib/components/OfflineSyncBanner.svelte'; import { onMount } from 'svelte'; let { data } = $props(); @@ -143,6 +144,8 @@ section h2{ {/if} + +

{labels.pages}

diff --git a/src/routes/[recipeLang=recipeLang]/+layout.svelte b/src/routes/[recipeLang=recipeLang]/+layout.svelte index f8ec5093..dc0dd94f 100644 --- a/src/routes/[recipeLang=recipeLang]/+layout.svelte +++ b/src/routes/[recipeLang=recipeLang]/+layout.svelte @@ -45,7 +45,6 @@ onNavigate((navigation) => { }); import UserHeader from '$lib/components/UserHeader.svelte'; import LanguageSelector from '$lib/components/LanguageSelector.svelte'; -import OfflineSyncIndicator from '$lib/components/OfflineSyncIndicator.svelte'; import BookOpen from '@lucide/svelte/icons/book-open'; import Heart from '@lucide/svelte/icons/heart'; import Leaf from '@lucide/svelte/icons/leaf'; @@ -134,12 +133,6 @@ const recipeCanonicalPath = $derived(recipeAltPath(page.url.pathname, /** @type {/snippet} - {#snippet logo_overlay()} -
- -
- {/snippet} - {#snippet right_side()} {/snippet} @@ -147,12 +140,3 @@ const recipeCanonicalPath = $derived(recipeAltPath(page.url.pathname, /** @type {@render children()} - diff --git a/src/routes/[recipeLang=recipeLang]/+page.svelte b/src/routes/[recipeLang=recipeLang]/+page.svelte index 2a568cfe..b26338fe 100644 --- a/src/routes/[recipeLang=recipeLang]/+page.svelte +++ b/src/routes/[recipeLang=recipeLang]/+page.svelte @@ -4,7 +4,6 @@ import AddButton from '$lib/components/AddButton.svelte'; import Seo from '$lib/components/Seo.svelte'; import CompactCard from '$lib/components/recipes/CompactCard.svelte'; - import OfflineSyncBanner from '$lib/components/OfflineSyncBanner.svelte'; import Search from '$lib/components/recipes/Search.svelte'; import { getCategories } from '$lib/js/categories'; import { m, type RecipesLang } from '$lib/js/recipesI18n'; @@ -315,19 +314,6 @@ z-index: 10; } -/* ─── Offline sync banner — between search and recipe grid ─── */ -.banner-wrap { - max-width: 1200px; - margin: 0 auto; - padding: 0 2em; - position: relative; - z-index: 9; -} -.banner-wrap.fallback { - padding: 0 2em; - margin: 0 auto; -} - .sentinel { height: 1px; } @@ -442,9 +428,6 @@
-
{#each visibleRecipes as recipe, i (recipe._id)} {labels.title}

{labels.subheading}

-