From 39d682f35391b9a1746e9dafe7420a760fb314a7 Mon Sep 17 00:00:00 2001 From: jay Date: Fri, 12 Jun 2026 12:21:43 -0400 Subject: [PATCH] Polish: plain unfound word-search chips + no signed-out flash on refresh - Word Search: unfound words were tinted (accent-soft background) like found ones, so the remaining words were hard to spot as the board filled. Unfound chips are now plain (transparent + a light outline); found words keep their grid colour. Easy to see what's left. - Auth: a refresh briefly flashed the signed-out header until /api/auth/me returned. Now the last-known user is cached and hydrated immediately, so the signed-in UI paints at once; the session is still revalidated every load (a stale/expired one corrects within a beat) and the cache is cleared on logout. Co-Authored-By: Claude Opus 4.8 --- frontend/src/lib/auth.svelte.js | 19 ++++++++++++++++++- .../src/lib/components/WordSearchGame.svelte | 6 +++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/frontend/src/lib/auth.svelte.js b/frontend/src/lib/auth.svelte.js index 918cd3e..37dae34 100644 --- a/frontend/src/lib/auth.svelte.js +++ b/frontend/src/lib/auth.svelte.js @@ -2,7 +2,22 @@ import { SvelteSet } from 'svelte/reactivity'; import { getJSON, postJSON, delJSON } from './api.js'; -export const auth = $state({ user: null, ready: false }); +// Cache the last-known user so a refresh paints the signed-in UI immediately +// instead of flashing "signed out" for the /api/auth/me round-trip. The session +// is still revalidated on every load (see refresh) — a stale/expired one just +// corrects within a beat instead of greeting you logged-out every time. +const AUTH_CACHE = 'goodnews:auth_user'; +function _loadUser() { + try { return JSON.parse(localStorage.getItem(AUTH_CACHE) || 'null'); } catch { return null; } +} +function _saveUser(u) { + try { + if (u) localStorage.setItem(AUTH_CACHE, JSON.stringify(u)); + else localStorage.removeItem(AUTH_CACHE); + } catch { /* ignore */ } +} + +export const auth = $state({ user: _loadUser(), ready: false }); export const savedIds = new SvelteSet(); // reactive set of saved article ids export const followKeys = new SvelteSet(); // reactive set of "kind:value" follows @@ -16,6 +31,7 @@ export async function refresh() { auth.user = null; } finally { auth.ready = true; + _saveUser(auth.user); } if (auth.user) { await loadSaved(); await loadFollows(); } else { savedIds.clear(); followKeys.clear(); } @@ -99,6 +115,7 @@ export async function logout() { // e.g. after delete-account or sign-out-everywhere). export function clearLocal() { auth.user = null; + _saveUser(null); savedIds.clear(); followKeys.clear(); } diff --git a/frontend/src/lib/components/WordSearchGame.svelte b/frontend/src/lib/components/WordSearchGame.svelte index 5a4a515..aab6307 100644 --- a/frontend/src/lib/components/WordSearchGame.svelte +++ b/frontend/src/lib/components/WordSearchGame.svelte @@ -221,8 +221,12 @@ .plabel { text-transform: uppercase; letter-spacing: 0.12em; font-size: 0.62rem; font-family: var(--label); color: var(--muted); text-align: center; margin: 0 0 10px; } .words { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; padding: 0; margin: 0; } + /* Unfound words are plain (transparent + outline) so it's easy to see what's + left as the board fills; found words get their grid colour via inline style. */ .words li { font-family: var(--label); font-size: 0.82rem; letter-spacing: 0.04em; color: var(--ink); - padding: 4px 11px; border-radius: 999px; background: var(--accent-soft); transition: background 0.2s ease; } + padding: 4px 11px; border-radius: 999px; background: transparent; + border: 1px solid var(--line); transition: background 0.2s ease, border-color 0.2s ease; } + .words li.got { border-color: transparent; } .hint { text-align: center; color: var(--muted); font-size: 0.84rem; margin: 0; } .result { text-align: center; } .rmark { font-family: var(--serif); font-style: italic; color: var(--accent-deep); font-size: 1.2rem; margin: 0 0 12px; }