From 854f06401f1484e2b7a9e7a2a9c1a8bf9cee7627 Mon Sep 17 00:00:00 2001 From: jay Date: Fri, 12 Jun 2026 09:48:01 -0400 Subject: [PATCH] Home: paint the saved brief instantly, refresh behind it (Codex) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex's point: edge-caching /api/brief helps anonymous visitors, but a logged-in reader with prefs/dismissals makes it a personalized (private, origin-bound) request — so it won't fix Jay's own "Gathering the good news…" delay. The real fix isn't more CDN tuning, it's not blocking the first paint on the network. The brief is already saved locally (BRIEF_VIEW_KEY). Now on the Today view we render that saved brief immediately and refresh /api/brief in the background; "Gathering…" only shows on a true first visit with nothing cached. A failed background refresh stays invisible (loadToday returns if a brief is already painted; onMount won't blank painted content) so a slow/offline origin never wipes a good view. Co-Authored-By: Claude Opus 4.8 --- frontend/src/routes/+page.svelte | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 3fa1797..579f7fb 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -220,7 +220,13 @@ async function loadToday(fresh) { const q = P.param(prefs.data); const ex = Array.from(dismissed).join(','); - const fetched = await getJSON(`/api/brief?limit=7${q ? '&' + q : ''}${ex ? '&exclude=' + ex : ''}`); + let fetched; + try { + fetched = await getJSON(`/api/brief?limit=7${q ? '&' + q : ''}${ex ? '&exclude=' + ex : ''}`); + } catch (e) { + if (brief) return; // already showing a saved brief — a failed background refresh stays invisible + throw e; // true first load with nothing painted → let the caller surface the error + } const view = P.loadJSON(BRIEF_VIEW_KEY, null); const sameServerBrief = view && view.generated_at && fetched.generated_at && view.generated_at === fetched.generated_at; @@ -429,6 +435,18 @@ dismissed = new Set(P.loadJSON(DISMISSED_KEY, [])); refreshAuth(); trackVisit(); + // Instant paint: render the last saved Today brief immediately and refresh + // it behind the scenes, so the first view never blocks on a (personalized, + // origin-bound) /api/brief request. "Gathering the good news…" then only + // shows on a genuine first visit with nothing cached yet. + if (selected === 'today') { + const cached = P.loadJSON(BRIEF_VIEW_KEY, null); + if (cached && Array.isArray(cached.items) && cached.items.length) { + brief = cached; + heroIdx = 0; + loading = false; + } + } // Critical path: moods + categories (needed to resolve a mood/topic view) // load in PARALLEL, then the requested view. Every getJSON times out, and // loading ALWAYS clears in finally, so the page can never get stuck on @@ -441,7 +459,7 @@ moods = m; topics = c.topics; await loadView(selected); } catch (e) { - error = 'Could not reach Upbeat Bytes.'; + if (!brief) error = 'Could not reach Upbeat Bytes.'; // keep painted content if a refresh failed } finally { loading = false; }