From c9ad6407c4fc05b80b91d701ee9a5b4450c4efc6 Mon Sep 17 00:00:00 2001 From: artheru Date: Wed, 10 Jun 2026 07:36:03 +0800 Subject: [PATCH] browse: loading skeletons, fix empty-state flash (0.51.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AgentBrowse tracked no loaded state, so on cold load it briefly flashed 'No agents here yet — be the first' before data arrived - add a loaded flag: show 3 shimmer skeleton cards while fetching; gate the empty-state and 'publish your own' card on loaded - skeleton shimmer respects prefers-reduced-motion - also verified (after a stale-Chrome scare) there is NO auth bug: cookieless /api/auth/me returns null, fresh logged-out load shows Login (no admin nav), and the public-agent Use flow works without login - verified live: skeletons during load, no flash, real card+add-card after --- index.html | 2 +- package.json | 2 +- src/AgentPlatform.jsx | 16 ++++++++++++---- src/Scene2D.jsx | 2 +- src/styles.css | 13 +++++++++++++ 5 files changed, 28 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index c5eb0e7..2d693ec 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - + diff --git a/package.json b/package.json index 00c8069..adbeba6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amerc-site", - "version": "0.50.0-hero", + "version": "0.51.0-skel", "private": true, "type": "module", "scripts": { diff --git a/src/AgentPlatform.jsx b/src/AgentPlatform.jsx index aeb91eb..061e49c 100644 --- a/src/AgentPlatform.jsx +++ b/src/AgentPlatform.jsx @@ -15,6 +15,7 @@ export function AgentBrowse({ auth, setRoute }) { const [q, setQ] = useState(''); const [open, setOpen] = useState(null); const [err, setErr] = useState(''); + const [loaded, setLoaded] = useState(false); const load = useCallback(async () => { setErr(''); @@ -22,7 +23,7 @@ export function AgentBrowse({ auth, setRoute }) { const [c, s] = await Promise.all([api(`/agents/classes${tag ? `?tag=${encodeURIComponent(tag)}` : ''}`), api('/agents/stats')]); setClasses((c.classes || []).filter((x) => !q || (x.name + x.tags.join() + x.description).toLowerCase().includes(q.toLowerCase()))); setTags(s.tags || {}); - } catch (e) { setErr(e.message); } + } catch (e) { setErr(e.message); } finally { setLoaded(true); } }, [tag, q]); useEffect(() => { load(); }, [load]); @@ -43,7 +44,14 @@ export function AgentBrowse({ auth, setRoute }) { {err &&

{err}

}
- {classes.map((c) => ( + {!loaded && Array.from({ length: 3 }).map((_, i) => ( + + ))} + {loaded && classes.map((c) => ( ))} - {classes.length > 0 && ( + {loaded && classes.length > 0 && ( )} - {!classes.length && ( + {loaded && !classes.length && (

No agents here yet — be the first.

    diff --git a/src/Scene2D.jsx b/src/Scene2D.jsx index 44d804a..df100ee 100644 --- a/src/Scene2D.jsx +++ b/src/Scene2D.jsx @@ -7,7 +7,7 @@ import Account from './Account.jsx'; import TavernGame from './TavernGame.jsx'; import { api } from './api.js'; -const RELEASE = '0.50.0-hero'; +const RELEASE = '0.51.0-skel'; const NAV = [ { id: 'home', label: 'Tavern' }, diff --git a/src/styles.css b/src/styles.css index 683631e..f2f95dd 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1356,3 +1356,16 @@ button { .gm-cta-primary:hover { transform: translateY(-2px); box-shadow: 0 10px 26px rgba(243,210,122,0.42); } .gm-cta-secondary { background: rgba(18,12,28,0.66); color: #ecdfc6; box-shadow: inset 0 0 0 1.5px rgba(243,210,122,0.5); backdrop-filter: blur(2px); } .gm-cta-secondary:hover { background: rgba(42,30,58,0.85); color: #fff; transform: translateY(-2px); } + +/* browse loading skeletons (0.51) */ +.ap-skel { pointer-events: none; } +.ap-skel-head { display: flex; align-items: center; gap: 11px; } +.sk-av, .sk-tt i, .sk-p, .ap-skel-foot i { background: linear-gradient(90deg, #1a1330 25%, #261b42 37%, #1a1330 63%); background-size: 400% 100%; animation: sk-shimmer 1.4s ease infinite; border-radius: 6px; display: block; } +.sk-av { width: 38px; height: 38px; border-radius: 10px; flex: 0 0 auto; } +.sk-tt { display: flex; flex-direction: column; gap: 6px; flex: 1; } +.sk-tt i { height: 11px; width: 60%; } .sk-tt i:last-child { width: 35%; height: 9px; } +.sk-p { height: 9px; width: 100%; margin-top: 12px; } .sk-p.sk-short { width: 70%; margin-top: 7px; } +.ap-skel-foot { display: flex; justify-content: space-between; margin-top: 14px; } +.ap-skel-foot i { height: 9px; width: 30%; } +@keyframes sk-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } } +@media (prefers-reduced-motion: reduce) { .sk-av, .sk-tt i, .sk-p, .ap-skel-foot i { animation: none; } }