You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Continues the /feed Firefox-profiler investigation behind #308 (sustained +10 MB/s heap growth during interaction, flat when idle). Two pieces already landed: #308 cached the two profiled allocation hotspots (Intl formatters), and the per-snapshot <Show keyed> re-mount churn was fixed upstream in solid-relay#68 (bumped to beta.26 on main). What's left of the interaction-time DOM + heap growth is mount cost that never comes back down — primitives that mount and stay mounted. This issue tracks that.
This issue is for improving web-next's performance and prevent OOM.
Items
Window / evict long lists (the root).PersonalTimeline / PublicTimeline / NotificationList defer first mount (LazyMount + createChunkedVisibleCount, lib/deferredRender.ts:49) but never unmount scrolled-past cards — visibleCount/total only climb (PersonalTimeline.tsx:296). Why it matters: over a session every PostCard ever scrolled stays in the DOM, each holding its Relay fragment subscriptions and a self-rescheduling per-card <time> timer (Timestamp.tsx). DOM nodes and heap grow monotonically on the busiest route — unbounded growth, not a constant factor. Long feed sessions degrade: scroll and input get heavier as the DOM balloons, with OOM risk on lower-memory devices (mobile first). (The codebase already ships a clientMemoryWatchdog for exactly this growth — it detects it, but can't fix it.) Fix: window to a viewport band (mount only a visible slice), or cap retained edges and drop off-screen ones. This also collapses the per-card <time> timers for free.
Lazy-mount Tooltips in PostEngagementBar.ReplyControl + SplitControl (×3) each wrap their button in a Kobalte <Tooltip> → ~100 mounted on a 25-card timeline, each carrying FloatingUI + Portal + Presence + DismissableLayer + a hover-delay timer. Why it matters: the tooltip text just duplicates the button's existing aria-label, so it's ~100 always-mounted primitives whose only added value is a desktop hover label. Fix: gate <Tooltip> behind a hovered() signal flipped on onPointerEnter / onFocusIn (the pattern the emoji popover already uses via emojiPickerMounted), or drop it for native title="". Same shape in VisibilityTag.tsx, ProfileCard.tsx.
Lazy-mount ActorHoverCard. Wraps every author avatar (×25) plus every @mention in post bodies, keeping a Kobalte <HoverCard> mounted — even though the loader inside it is already correctly <Show when={open()}>-gated, so only the primitive shell is wasted. Fix: wrap the children in a span that flips a hovered() signal on pointer/focus enter, and render the <HoverCard> inside <Show when={hovered()}> with the same children as fallback.
Ordering
Item 1 (windowing) is the root — it collapses the per-card <time> timers and is what the watchdog reports. Items 2–3 reduce per-card mount cost and are worth a re-profile after solid-relay#68, which already removed the per-tick re-mount churn that made these primitives expensive.
Verification
Rerun the profiler scenario (30 s on /feed, one composer open/close, one reaction toggle): target heap growth < ~1 MB/s during interaction (was +10 MB/s pre-#308), DOM node count bounded across a scroll session, and clientMemoryWatchdog quiet.
About this issue
Drafted by Shiro (Claude Opus 4.8), working with @nyanrus, continuing the #308 profiler notes. (The lazy-mount count reductions here were sanity-checked with a small Lean + Deno bundle — background, happy to share.) Correct me on anything I misread.
Context
Continues the
/feedFirefox-profiler investigation behind #308 (sustained +10 MB/s heap growth during interaction, flat when idle). Two pieces already landed: #308 cached the two profiled allocation hotspots (Intlformatters), and the per-snapshot<Show keyed>re-mount churn was fixed upstream in solid-relay#68 (bumped tobeta.26onmain). What's left of the interaction-time DOM + heap growth is mount cost that never comes back down — primitives that mount and stay mounted. This issue tracks that.This issue is for improving web-next's performance and prevent OOM.
Items
Window / evict long lists (the root).
PersonalTimeline/PublicTimeline/NotificationListdefer first mount (LazyMount+createChunkedVisibleCount,lib/deferredRender.ts:49) but never unmount scrolled-past cards —visibleCount/totalonly climb (PersonalTimeline.tsx:296).Why it matters: over a session every
PostCardever scrolled stays in the DOM, each holding its Relay fragment subscriptions and a self-rescheduling per-card<time>timer (Timestamp.tsx). DOM nodes and heap grow monotonically on the busiest route — unbounded growth, not a constant factor. Long feed sessions degrade: scroll and input get heavier as the DOM balloons, with OOM risk on lower-memory devices (mobile first). (The codebase already ships aclientMemoryWatchdogfor exactly this growth — it detects it, but can't fix it.)Fix: window to a viewport band (mount only a visible slice), or cap retained edges and drop off-screen ones. This also collapses the per-card
<time>timers for free.Lazy-mount Tooltips in
PostEngagementBar.ReplyControl+SplitControl(×3) each wrap their button in a Kobalte<Tooltip>→ ~100 mounted on a 25-card timeline, each carrying FloatingUI + Portal + Presence + DismissableLayer + a hover-delay timer.Why it matters: the tooltip text just duplicates the button's existing
aria-label, so it's ~100 always-mounted primitives whose only added value is a desktop hover label.Fix: gate
<Tooltip>behind ahovered()signal flipped ononPointerEnter/onFocusIn(the pattern the emoji popover already uses viaemojiPickerMounted), or drop it for nativetitle="". Same shape inVisibilityTag.tsx,ProfileCard.tsx.Lazy-mount
ActorHoverCard. Wraps every author avatar (×25) plus every@mentionin post bodies, keeping a Kobalte<HoverCard>mounted — even though the loader inside it is already correctly<Show when={open()}>-gated, so only the primitive shell is wasted.Fix: wrap the children in a span that flips a
hovered()signal on pointer/focus enter, and render the<HoverCard>inside<Show when={hovered()}>with the same children as fallback.Ordering
Item 1 (windowing) is the root — it collapses the per-card
<time>timers and is what the watchdog reports. Items 2–3 reduce per-card mount cost and are worth a re-profile after solid-relay#68, which already removed the per-tick re-mount churn that made these primitives expensive.Verification
Rerun the profiler scenario (30 s on
/feed, one composer open/close, one reaction toggle): target heap growth < ~1 MB/s during interaction (was +10 MB/s pre-#308), DOM node count bounded across a scroll session, andclientMemoryWatchdogquiet.About this issue
Drafted by Shiro (Claude Opus 4.8), working with @nyanrus, continuing the #308 profiler notes. (The lazy-mount count reductions here were sanity-checked with a small Lean + Deno bundle — background, happy to share.) Correct me on anything I misread.