diff options
| author | Adam Soltys <[email protected]> | 2026-09-04 18:37:53 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-09-04 18:37:53 -0700 |
| commit | 4431198900b2d1a5cfb73bd02ddfd1e377d9acb9 (patch) | |
| tree | b285d431f7d17d6ceee8f630dad4b8a043c628f5 /src/features/messages.js | |
| parent | ff5c5d2ac5c017c7c134f3969416306cd8742d1c (diff) | |
| download | coinosv3-4431198900b2d1a5cfb73bd02ddfd1e377d9acb9.tar.gz coinosv3-4431198900b2d1a5cfb73bd02ddfd1e377d9acb9.zip | |
Hats no longer pop in a beat after page load. The hats feature is a deferred chunk, so every boot painted bare heads until it arrived — but the core bundle can't carry the hat art. Instead, wrapAvatar now leaves a STAMP per head in localStorage (art + percent geometry, size-independent, capped at 40, cleared when a head goes bare so removals can't ghost), and the core avatar factory replays stamps until hatsReady answers — from then on a null from wrapAvatar genuinely means bare. Verified headless: a stamped hat is on the header avatar 15ms after DOM-ready, and once the chunk lands with a bare-head answer the stamp is cleared with no double-wrap
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
Diffstat (limited to 'src/features/messages.js')
| -rw-r--r-- | src/features/messages.js | 18 |
1 files changed, 17 insertions, 1 deletions
diff --git a/src/features/messages.js b/src/features/messages.js index d6eacc9..0aa7291 100644 --- a/src/features/messages.js +++ b/src/features/messages.js @@ -1592,9 +1592,25 @@ export function messagesFeature(ctx) { node.classList.add('clickable'); node.addEventListener('click', (e) => { e.stopPropagation(); openProfile(pk); }); } - return hook('wrapAvatar', pk, node) || node; + return hook('wrapAvatar', pk, node) || hatStamp(pk, node) || node; }; + // The hats feature is a deferred chunk — until it lands, replay last + // session's boot stamps (hat art + geometry it persisted) so a hat sits on + // the head from the FIRST frame instead of popping in a beat later. Once + // hatsReady answers, the stamps stand down: a null from wrapAvatar then + // genuinely means "bare head". + let _hatStamps = null; + function hatStamp(pk, node) { + if (hook('hatsReady')) return null; + if (_hatStamps === null) { + try { _hatStamps = JSON.parse(localStorage.getItem('hat-stamps') || '{}'); } catch { _hatStamps = {}; } + } + const s = _hatStamps[pk]; + if (!s || !s.a) return null; + return h('span', { class: 'hat-wrap' }, node, h('span', { class: 'ava-hat', style: s.s, html: s.a })); + } + // ---- profiles: view + own kind-0 editor --------------------------------- const fullProfiles = new Map(); // pk -> { raw kind0 content object, fetched_at } |
