From 4431198900b2d1a5cfb73bd02ddfd1e377d9acb9 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 4 Sep 2026 18:37:53 -0700 Subject: 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 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz --- src/features/messages.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) (limited to 'src/features/messages.js') 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 } -- cgit v1.2.3