From 8db67bd1a50c04e81909ff4f80dfe6f5ea86dd60 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Fri, 17 Apr 2026 19:06:58 +0200 Subject: [PATCH] =?UTF-8?q?feat(home):=20SvelteKit=20snapshot=20f=C3=BCr?= =?UTF-8?q?=20echte=20State-Preservation=20beim=20Back?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vorher (Commit 1055a67): Query in URL gespiegelt, Back triggerte einen frischen Request — zwar schnell, aber Scroll-Position + Ergebnisreihen- folge nicht garantiert konstant. Jetzt: SvelteKit's snapshot-API speichert query + hits + webHits + searchedFor + webError in der History-Entry. Beim Zurück-Navigieren aus /preview oder /recipes/[id] stellt restore() den exakten UI-Zustand wieder her — ohne Fetch. Scroll-Position wird von SvelteKit ohnehin auto- restored. Der Debounce-Effekt hat jetzt einen skipNextSearch-Flag, der beim Restore true gesetzt wird, damit das Setzen von query keinen neuen Search-Request auslöst. Erstmaliges Tippen nach dem Restore arbeitet wieder ganz normal mit Debounce. Die URL-Synchronisation (?q=…) bleibt bestehen — für Bookmarks und Share-Links. Snapshot überlagert den URL-Load in der Ordnung: restore → onMount sieht query bereits korrekt gesetzt → kein Double-Work. --- src/routes/+page.svelte | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 7dd93ee..1390357 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -2,6 +2,7 @@ import { onMount } from 'svelte'; import { page } from '$app/stores'; import { CookingPot, Globe, X } from 'lucide-svelte'; + import type { Snapshot } from './$types'; import type { SearchHit } from '$lib/server/recipes/search-local'; import type { WebHit } from '$lib/server/search/searxng'; import { randomQuote } from '$lib/quotes'; @@ -18,6 +19,27 @@ let webSearching = $state(false); let webError = $state(null); let searchedFor = $state(null); + let skipNextSearch = false; + + type SearchSnapshot = { + query: string; + hits: SearchHit[]; + webHits: WebHit[]; + searchedFor: string | null; + webError: string | null; + }; + + export const snapshot: Snapshot = { + capture: () => ({ query, hits, webHits, searchedFor, webError }), + restore: (v) => { + query = v.query; + hits = v.hits; + webHits = v.webHits; + searchedFor = v.searchedFor; + webError = v.webError; + skipNextSearch = true; + } + }; async function loadRecent() { const res = await fetch('/api/recipes/search'); @@ -100,6 +122,12 @@ $effect(() => { const q = query.trim(); if (debounceTimer) clearTimeout(debounceTimer); + if (skipNextSearch) { + // Snapshot-Restore hat hits/webHits/searchedFor wiederhergestellt — + // nicht erneut fetchen. + skipNextSearch = false; + return; + } if (q.length <= 3) { hits = []; webHits = [];