diff --git a/desktop/package.json b/desktop/package.json
index e820dc38bb..6128f0a9e3 100644
--- a/desktop/package.json
+++ b/desktop/package.json
@@ -45,6 +45,7 @@
     "@radix-ui/react-tooltip": "^1.2.8",
     "@tanstack/react-query": "^5.90.21",
     "@tanstack/react-router": "^1.168.10",
+    "@tanstack/react-virtual": "^3.14.2",
     "@tauri-apps/api": "~2.11",
     "@tauri-apps/plugin-notification": "^2.3.3",
     "@tauri-apps/plugin-opener": "~2.5",
diff --git a/desktop/playwright.config.ts b/desktop/playwright.config.ts
index 5dd4263b74..8256c70c99 100644
--- a/desktop/playwright.config.ts
+++ b/desktop/playwright.config.ts
@@ -48,6 +48,7 @@ export default defineConfig({
         "**/thread-unread-screenshots.spec.ts",
         "**/animated-avatar-screenshots.spec.ts",
         "**/reminders-screenshots.spec.ts",
+        "**/virtualization-screenshots.spec.ts",
       ],
       use: {
         ...devices["Desktop Chrome"],
diff --git a/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx b/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx
index 9371f83115..3ae4051f05 100644
--- a/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx
+++ b/desktop/src/features/agents/ui/AgentSessionTranscriptList.tsx
@@ -42,7 +42,7 @@ export function AgentSessionTranscriptList({
       role="log"
     >
       {items.map((item) => (
-        <div className="mt-4 first:mt-0" key={item.id}>
+        <div className="mt-4 first:mt-0 content-visibility-auto" key={item.id}>
           <TranscriptItemView
             agentName={agentName}
             item={item}
diff --git a/desktop/src/features/channels/ui/ChannelCanvas.tsx b/desktop/src/features/channels/ui/ChannelCanvas.tsx
index 314d31ee11..2c38f8883e 100644
--- a/desktop/src/features/channels/ui/ChannelCanvas.tsx
+++ b/desktop/src/features/channels/ui/ChannelCanvas.tsx
@@ -36,6 +36,9 @@ export function ChannelCanvas({
   const [draft, setDraft] = React.useState("");
 
   const canvasContent = canvasQuery.data?.content ?? null;
+  // Defer the single large Markdown parse so opening the canvas commits the
+  // surrounding chrome immediately and the heavy render reconciles after.
+  const deferredCanvasContent = React.useDeferredValue(canvasContent);
 
   function handleStartEditing() {
     setDraft(canvasContent ?? "");
@@ -121,7 +124,10 @@ export function ChannelCanvas({
           className="rounded-2xl border border-border/70 bg-muted/20 px-4 py-3"
           data-testid="channel-canvas-content"
         >
-          <Markdown channelNames={channelNames} content={canvasContent} />
+          <Markdown
+            channelNames={channelNames}
+            content={deferredCanvasContent ?? ""}
+          />
         </div>
       ) : (
         <p className="text-sm text-muted-foreground">
diff --git a/desktop/src/features/channels/ui/MembersSidebar.tsx b/desktop/src/features/channels/ui/MembersSidebar.tsx
index 8202781abc..4f74c03fe5 100644
--- a/desktop/src/features/channels/ui/MembersSidebar.tsx
+++ b/desktop/src/features/channels/ui/MembersSidebar.tsx
@@ -432,46 +432,47 @@ export function MembersSidebar({
 
   function renderMemberCard(member: ChannelMember, memberIsBot: boolean) {
     return (
-      <MembersSidebarMemberCard
-        canChangeRole={canManageMembers && member.pubkey !== currentPubkey}
-        canRemoveMember={canRemoveMember(member)}
-        isActionPending={isActionPending || changeRoleMutation.isPending}
-        isArchived={isArchived}
-        key={member.pubkey}
-        managedAgent={
-          memberIsBot
-            ? managedAgentByPubkey.get(normalizePubkey(member.pubkey))
-            : undefined
-        }
-        member={member}
-        memberIsBot={memberIsBot}
-        memberAvatarLabel={member.displayName ?? formatPubkey(member.pubkey)}
-        memberLabel={formatMemberName(member, currentPubkey)}
-        onChangeRole={(m, role) => {
-          void changeRoleMutation.mutateAsync({ pubkey: m.pubkey, role });
-        }}
-        onEditRespondTo={memberIsBot ? setEditRespondToAgent : undefined}
-        onManagedAgentAction={(agent) => {
-          void handleAgentLifecycleAction(agent);
-        }}
-        onOpenProfile={handleOpenProfile}
-        onRemoveMember={handleRemoveMember}
-        onViewActivity={
-          onViewActivity
-            ? (pubkey: string) => {
-                onOpenChange(false);
-                onViewActivity(pubkey);
-              }
-            : undefined
-        }
-        presenceStatus={
-          memberPresenceQuery.data?.[member.pubkey.toLowerCase()] ?? null
-        }
-        profileAvatarUrl={
-          memberProfilesQuery.data?.profiles[member.pubkey.toLowerCase()]
-            ?.avatarUrl ?? null
-        }
-      />
+      <div className="content-visibility-auto" key={member.pubkey}>
+        <MembersSidebarMemberCard
+          canChangeRole={canManageMembers && member.pubkey !== currentPubkey}
+          canRemoveMember={canRemoveMember(member)}
+          isActionPending={isActionPending || changeRoleMutation.isPending}
+          isArchived={isArchived}
+          managedAgent={
+            memberIsBot
+              ? managedAgentByPubkey.get(normalizePubkey(member.pubkey))
+              : undefined
+          }
+          member={member}
+          memberIsBot={memberIsBot}
+          memberAvatarLabel={member.displayName ?? formatPubkey(member.pubkey)}
+          memberLabel={formatMemberName(member, currentPubkey)}
+          onChangeRole={(m, role) => {
+            void changeRoleMutation.mutateAsync({ pubkey: m.pubkey, role });
+          }}
+          onEditRespondTo={memberIsBot ? setEditRespondToAgent : undefined}
+          onManagedAgentAction={(agent) => {
+            void handleAgentLifecycleAction(agent);
+          }}
+          onOpenProfile={handleOpenProfile}
+          onRemoveMember={handleRemoveMember}
+          onViewActivity={
+            onViewActivity
+              ? (pubkey: string) => {
+                  onOpenChange(false);
+                  onViewActivity(pubkey);
+                }
+              : undefined
+          }
+          presenceStatus={
+            memberPresenceQuery.data?.[member.pubkey.toLowerCase()] ?? null
+          }
+          profileAvatarUrl={
+            memberProfilesQuery.data?.profiles[member.pubkey.toLowerCase()]
+              ?.avatarUrl ?? null
+          }
+        />
+      </div>
     );
   }
 
diff --git a/desktop/src/features/custom-emoji/ui/EmojiPicker.tsx b/desktop/src/features/custom-emoji/ui/EmojiPicker.tsx
index 423416bafc..573b94bfee 100644
--- a/desktop/src/features/custom-emoji/ui/EmojiPicker.tsx
+++ b/desktop/src/features/custom-emoji/ui/EmojiPicker.tsx
@@ -1,10 +1,33 @@
 import data from "@emoji-mart/data";
 import Picker from "@emoji-mart/react";
+import { init } from "emoji-mart";
 import * as React from "react";
 
 import { buildCustomEmojiCategory } from "@/features/custom-emoji/emojiMartCategory";
 import { useCustomEmoji } from "@/features/custom-emoji/hooks";
 
+// emoji-mart builds its searchable index synchronously inside `init`, which
+// `<Picker>` calls on mount — so the first reaction popover open paid the full
+// ~1.8k-emoji index build and froze the cursor. Warm `init({ data })` once at
+// idle so the index is prebuilt; `init` is a no-op after the first call (its
+// `Data` singleton guards the rebuild), so the Picker's mount-time `init` skips
+// the heavy work. Search still reads the prebuilt index — no first-keystroke
+// hitch. Module-level so it fires regardless of when a picker first mounts.
+let warmStarted = false;
+function warmEmojiIndex() {
+  if (warmStarted) {
+    return;
+  }
+  warmStarted = true;
+  const warm = () => void init({ data });
+  if (typeof window !== "undefined" && "requestIdleCallback" in window) {
+    window.requestIdleCallback(warm, { timeout: 1_500 });
+  } else {
+    globalThis.setTimeout(warm, 250);
+  }
+}
+warmEmojiIndex();
+
 /**
  * The one emoji picker for the whole app. Every place that lets a user choose
  * an emoji — composing a message, reacting to a regular or system message,
diff --git a/desktop/src/features/forum/ui/ForumThreadPanel.tsx b/desktop/src/features/forum/ui/ForumThreadPanel.tsx
index 2cdbaf7575..0ea16a8601 100644
--- a/desktop/src/features/forum/ui/ForumThreadPanel.tsx
+++ b/desktop/src/features/forum/ui/ForumThreadPanel.tsx
@@ -75,7 +75,10 @@ function ReplyRow({
   const replyMentionNames = resolveMentionNames(reply.tags, profiles);
 
   return (
-    <div className="group px-4 py-3" data-forum-event-id={reply.eventId}>
+    <div
+      className="group content-visibility-auto px-4 py-3"
+      data-forum-event-id={reply.eventId}
+    >
       <div className="flex items-center gap-2">
         <UserProfilePopover pubkey={reply.pubkey}>
           <button
diff --git a/desktop/src/features/forum/ui/ForumView.tsx b/desktop/src/features/forum/ui/ForumView.tsx
index fea00ebdbd..16366de47f 100644
--- a/desktop/src/features/forum/ui/ForumView.tsx
+++ b/desktop/src/features/forum/ui/ForumView.tsx
@@ -7,6 +7,7 @@ import type { Channel } from "@/shared/api/types";
 import { channelChrome } from "@/shared/layout/chromeLayout";
 import { cn } from "@/shared/lib/cn";
 import { Skeleton } from "@/shared/ui/skeleton";
+import { VirtualizedList } from "@/shared/ui/VirtualizedList";
 
 import {
   useCreateForumPostMutation,
@@ -47,6 +48,7 @@ export function ForumView({
   targetReplyId,
 }: ForumViewProps) {
   const [isComposerOpen, setIsComposerOpen] = React.useState(false);
+  const postsScrollRef = React.useRef<HTMLDivElement>(null);
 
   const profileQuery = useProfileQuery();
   const postsQuery = useForumPostsQuery(channel);
@@ -185,6 +187,7 @@ export function ForumView({
       <div
         className="flex-1 overflow-y-auto"
         data-scroll-restoration-id={`forum-list:${channel.id}`}
+        ref={postsScrollRef}
       >
         {postsQuery.isLoading ? (
           <div className="space-y-3 p-4">
@@ -205,26 +208,32 @@ export function ForumView({
             </div>
           </div>
         ) : (
-          <div className="space-y-3 p-4">
-            {posts.map((post) => (
-              <ForumPostCard
-                canDelete={canDelete(post.pubkey, effectiveCurrentPubkey)}
-                currentPubkey={effectiveCurrentPubkey}
-                isActive={selectedPostId === post.eventId}
-                isDeleting={
-                  deletePostMutation.isPending &&
-                  deletePostMutation.variables?.eventId === post.eventId
-                }
-                key={post.eventId}
-                onClick={() => onSelectPost(post.eventId)}
-                onDelete={(eventId) => {
-                  deletePostMutation.mutate({ eventId });
-                }}
-                post={post}
-                profiles={profiles}
-              />
-            ))}
-          </div>
+          <VirtualizedList
+            estimateSize={120}
+            getItemKey={(post) => post.eventId}
+            innerClassName="p-4"
+            items={posts}
+            renderItem={(post) => (
+              <div className="pb-3">
+                <ForumPostCard
+                  canDelete={canDelete(post.pubkey, effectiveCurrentPubkey)}
+                  currentPubkey={effectiveCurrentPubkey}
+                  isActive={selectedPostId === post.eventId}
+                  isDeleting={
+                    deletePostMutation.isPending &&
+                    deletePostMutation.variables?.eventId === post.eventId
+                  }
+                  onClick={() => onSelectPost(post.eventId)}
+                  onDelete={(eventId) => {
+                    deletePostMutation.mutate({ eventId });
+                  }}
+                  post={post}
+                  profiles={profiles}
+                />
+              </div>
+            )}
+            scrollRef={postsScrollRef}
+          />
         )}
       </div>
     </div>
diff --git a/desktop/src/features/home/ui/InboxListPane.tsx b/desktop/src/features/home/ui/InboxListPane.tsx
index fa51d46a80..c4131d8beb 100644
--- a/desktop/src/features/home/ui/InboxListPane.tsx
+++ b/desktop/src/features/home/ui/InboxListPane.tsx
@@ -1,4 +1,5 @@
 import { ChevronDown, Inbox } from "lucide-react";
+import * as React from "react";
 
 import {
   formatInboxTypeLabel,
@@ -18,6 +19,7 @@ import {
   DropdownMenuTrigger,
 } from "@/shared/ui/dropdown-menu";
 import { UserAvatar } from "@/shared/ui/UserAvatar";
+import { VirtualizedList } from "@/shared/ui/VirtualizedList";
 
 const FILTER_OPTIONS: Array<{ label: string; value: InboxFilter }> = [
   { value: "all", label: "All" },
@@ -47,6 +49,91 @@ export function InboxListPane({
   showRightDivider = false,
 }: InboxListPaneProps) {
   const activeFilter = FILTER_OPTIONS.find((option) => option.value === filter);
+  const scrollRef = React.useRef<HTMLDivElement>(null);
+
+  const renderItem = (item: InboxItem) => {
+    const isSelected = item.id === selectedId;
+    const isDone = doneSet.has(item.id);
+    const typeLabel = formatInboxTypeLabel(item);
+
+    return (
+      <button
+        className={cn(
+          "flex w-full items-start gap-2.5 border-l px-5 py-2 text-left transition-colors",
+          isSelected
+            ? "border-l-transparent bg-muted/30"
+            : "border-l-transparent hover:bg-muted/25 active:bg-muted/40",
+        )}
+        data-testid={`home-inbox-item-${item.id}`}
+        onClick={() => onSelect(item.id)}
+        type="button"
+      >
+        <div className="relative">
+          <UserAvatar
+            avatarUrl={item.avatarUrl}
+            className="h-8 w-8"
+            displayName={item.senderLabel}
+            size="md"
+          />
+          {!isDone ? (
+            <span className="absolute -right-1 -top-1 h-2.5 w-2.5 rounded-full border-2 border-background bg-primary" />
+          ) : null}
+        </div>
+
+        <div className="min-w-0 flex-1">
+          <div className="flex items-start gap-2">
+            <div className="min-w-0 flex-1">
+              <div className="flex items-center gap-2">
+                <p className="truncate text-sm font-semibold text-foreground">
+                  {item.senderLabel}
+                </p>
+                {item.isActionRequired ? (
+                  <span className="inline-flex shrink-0 items-center text-2xs font-semibold uppercase tracking-[0.14em] text-amber-600 dark:text-amber-300">
+                    Needs action
+                  </span>
+                ) : null}
+              </div>
+            </div>
+            <span
+              className={cn(
+                "shrink-0 text-xs text-muted-foreground",
+                isDone ? "font-normal" : "font-semibold",
+              )}
+            >
+              {item.timestampLabel}
+            </span>
+          </div>
+
+          <div
+            className={cn(
+              "mt-0.5 line-clamp-2 text-sm leading-5 **:inline [&_a]:font-medium [&_a]:text-current [&_br]:hidden [&_p]:inline",
+              isDone
+                ? "font-normal text-muted-foreground"
+                : "font-semibold text-foreground",
+            )}
+          >
+            <Markdown
+              className="inline max-w-full text-inherit"
+              content={item.preview}
+              interactive={false}
+              mentionNames={item.mentionNames}
+            />
+          </div>
+
+          <div className="mt-1 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
+            <span
+              className={cn(
+                "text-2xs text-muted-foreground",
+                isDone ? "font-normal" : "font-semibold",
+              )}
+            >
+              {typeLabel}
+            </span>
+          </div>
+        </div>
+      </button>
+    );
+  };
 
   return (
     <section
@@ -101,6 +188,7 @@ export function InboxListPane({
       <div
         className="min-h-0 flex-1 overflow-y-auto overscroll-contain"
         data-testid="home-inbox-list"
+        ref={scrollRef}
       >
         {items.length === 0 ? (
           <div className="flex h-full min-h-64 items-center justify-center px-6 text-center">
@@ -114,92 +202,13 @@ export function InboxListPane({
             </div>
           </div>
         ) : (
-          <div>
-            {items.map((item) => {
-              const isSelected = item.id === selectedId;
-              const isDone = doneSet.has(item.id);
-              const typeLabel = formatInboxTypeLabel(item);
-
-              return (
-                <button
-                  className={cn(
-                    "flex w-full items-start gap-2.5 border-l px-5 py-2 text-left transition-colors",
-                    isSelected
-                      ? "border-l-transparent bg-muted/30"
-                      : "border-l-transparent hover:bg-muted/25 active:bg-muted/40",
-                  )}
-                  data-testid={`home-inbox-item-${item.id}`}
-                  key={item.id}
-                  onClick={() => onSelect(item.id)}
-                  type="button"
-                >
-                  <div className="relative">
-                    <UserAvatar
-                      avatarUrl={item.avatarUrl}
-                      className="h-8 w-8"
-                      displayName={item.senderLabel}
-                      size="md"
-                    />
-                    {!isDone ? (
-                      <span className="absolute -right-1 -top-1 h-2.5 w-2.5 rounded-full border-2 border-background bg-primary" />
-                    ) : null}
-                  </div>
-
-                  <div className="min-w-0 flex-1">
-                    <div className="flex items-start gap-2">
-                      <div className="min-w-0 flex-1">
-                        <div className="flex items-center gap-2">
-                          <p className="truncate text-sm font-semibold text-foreground">
-                            {item.senderLabel}
-                          </p>
-                          {item.isActionRequired ? (
-                            <span className="inline-flex shrink-0 items-center text-2xs font-semibold uppercase tracking-[0.14em] text-amber-600 dark:text-amber-300">
-                              Needs action
-                            </span>
-                          ) : null}
-                        </div>
-                      </div>
-                      <span
-                        className={cn(
-                          "shrink-0 text-xs text-muted-foreground",
-                          isDone ? "font-normal" : "font-semibold",
-                        )}
-                      >
-                        {item.timestampLabel}
-                      </span>
-                    </div>
-
-                    <div
-                      className={cn(
-                        "mt-0.5 line-clamp-2 text-sm leading-5 **:inline [&_a]:font-medium [&_a]:text-current [&_br]:hidden [&_p]:inline",
-                        isDone
-                          ? "font-normal text-muted-foreground"
-                          : "font-semibold text-foreground",
-                      )}
-                    >
-                      <Markdown
-                        className="inline max-w-full text-inherit"
-                        content={item.preview}
-                        interactive={false}
-                        mentionNames={item.mentionNames}
-                      />
-                    </div>
-
-                    <div className="mt-1 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
-                      <span
-                        className={cn(
-                          "text-2xs text-muted-foreground",
-                          isDone ? "font-normal" : "font-semibold",
-                        )}
-                      >
-                        {typeLabel}
-                      </span>
-                    </div>
-                  </div>
-                </button>
-              );
-            })}
-          </div>
+          <VirtualizedList
+            estimateSize={76}
+            getItemKey={(item) => item.id}
+            items={items}
+            renderItem={renderItem}
+            scrollRef={scrollRef}
+          />
         )}
       </div>
     </section>
diff --git a/desktop/src/features/pulse/ui/PulseView.tsx b/desktop/src/features/pulse/ui/PulseView.tsx
index 82aefc14de..84351e3769 100644
--- a/desktop/src/features/pulse/ui/PulseView.tsx
+++ b/desktop/src/features/pulse/ui/PulseView.tsx
@@ -29,6 +29,7 @@ import type { ChannelMember, UserProfileSummary } from "@/shared/api/types";
 import { Input } from "@/shared/ui/input";
 import { Skeleton } from "@/shared/ui/skeleton";
 import { UserAvatar } from "@/shared/ui/UserAvatar";
+import { VirtualizedList } from "@/shared/ui/VirtualizedList";
 
 export type PulseTab =
   | "search"
@@ -73,6 +74,7 @@ function TimelineSkeleton() {
 export function PulseView({ currentPubkey }: PulseViewProps) {
   const [activeTab, setActiveTab] = React.useState<PulseTab>("everyone");
   const [searchQuery, setSearchQuery] = React.useState("");
+  const scrollRef = React.useRef<HTMLDivElement>(null);
   const contactListQuery = useContactListQuery(currentPubkey);
   const contacts = contactListQuery.data?.contacts ?? [];
   const contactPubkeys = React.useMemo(
@@ -268,43 +270,57 @@ export function PulseView({ currentPubkey }: PulseViewProps) {
       return agentNoteGroups.length === 0 ? (
         <EmptyState message={emptyMessages.agents} />
       ) : (
-        agentNoteGroups.map((group) => (
-          <AgentActivityCard
-            agentStatus={agentStatusMap[group.pubkey]}
-            group={group}
-            key={`${group.pubkey}-${group.latestAt}`}
-            profile={profiles[group.pubkey.toLowerCase()] ?? null}
-          />
-        ))
+        <VirtualizedList
+          estimateSize={160}
+          getItemKey={(group) => `${group.pubkey}-${group.latestAt}`}
+          items={agentNoteGroups}
+          renderItem={(group) => (
+            <div className="pb-4">
+              <AgentActivityCard
+                agentStatus={agentStatusMap[group.pubkey]}
+                group={group}
+                profile={profiles[group.pubkey.toLowerCase()] ?? null}
+              />
+            </div>
+          )}
+          scrollRef={scrollRef}
+        />
       );
     }
 
     return visibleNotes.length === 0 ? (
       <EmptyState message={emptyMessages[activeTab]} />
     ) : (
-      visibleNotes.map((note) => (
-        <NoteCard
-          actions={{
-            reply: noteActions.reply,
-            share: noteActions.share,
-            startDm: noteActions.startDm,
-            toggleUpvote: noteActions.toggleUpvote,
-          }}
-          composerProfiles={mentionProfiles}
-          currentUserDisplayName={currentDisplayName}
-          currentUserProfile={currentProfile}
-          isAgent={agentPubkeySet.has(note.pubkey)}
-          isOwnNote={note.pubkey === currentPubkey}
-          isReplySending={noteActions.isReplySending}
-          isUpvotePending={noteActions.isUpvotePending(note.id)}
-          isUpvoted={noteActions.isUpvoted(note.id)}
-          reactionCount={noteActions.reactionCount(note.id)}
-          key={note.id}
-          members={pulseMentionMembers}
-          note={note}
-          profile={profiles[note.pubkey.toLowerCase()] ?? null}
-        />
-      ))
+      <VirtualizedList
+        estimateSize={140}
+        getItemKey={(note) => note.id}
+        items={visibleNotes}
+        renderItem={(note) => (
+          <div className="pb-4">
+            <NoteCard
+              actions={{
+                reply: noteActions.reply,
+                share: noteActions.share,
+                startDm: noteActions.startDm,
+                toggleUpvote: noteActions.toggleUpvote,
+              }}
+              composerProfiles={mentionProfiles}
+              currentUserDisplayName={currentDisplayName}
+              currentUserProfile={currentProfile}
+              isAgent={agentPubkeySet.has(note.pubkey)}
+              isOwnNote={note.pubkey === currentPubkey}
+              isReplySending={noteActions.isReplySending}
+              isUpvotePending={noteActions.isUpvotePending(note.id)}
+              isUpvoted={noteActions.isUpvoted(note.id)}
+              reactionCount={noteActions.reactionCount(note.id)}
+              members={pulseMentionMembers}
+              note={note}
+              profile={profiles[note.pubkey.toLowerCase()] ?? null}
+            />
+          </div>
+        )}
+        scrollRef={scrollRef}
+      />
     );
   }
 
@@ -318,7 +334,7 @@ export function PulseView({ currentPubkey }: PulseViewProps) {
         relayAgents={relayAgents}
       />
 
-      <div className="mt-0 min-h-0 flex-1 overflow-y-auto">
+      <div className="mt-0 min-h-0 flex-1 overflow-y-auto" ref={scrollRef}>
         <div
           aria-labelledby={pulseTabId(activeTab)}
           className={`mx-auto flex w-full max-w-2xl flex-col px-4 pb-10 sm:px-6 ${
@@ -399,9 +415,7 @@ export function PulseView({ currentPubkey }: PulseViewProps) {
             </div>
           ) : null}
 
-          {activeTab !== "search" ? (
-            <div className="space-y-4">{renderTimeline()}</div>
-          ) : null}
+          {activeTab !== "search" ? <div>{renderTimeline()}</div> : null}
         </div>
       </div>
     </div>
diff --git a/desktop/src/features/relay-members/ui/RelayMembersSettingsCard.tsx b/desktop/src/features/relay-members/ui/RelayMembersSettingsCard.tsx
index c522e209b1..5a6f101cb5 100644
--- a/desktop/src/features/relay-members/ui/RelayMembersSettingsCard.tsx
+++ b/desktop/src/features/relay-members/ui/RelayMembersSettingsCard.tsx
@@ -36,6 +36,7 @@ import {
   DropdownMenuTrigger,
 } from "@/shared/ui/dropdown-menu";
 import { Input } from "@/shared/ui/input";
+import { VirtualizedList } from "@/shared/ui/VirtualizedList";
 
 type AssignableRelayRole = Exclude<RelayMemberRole, "owner">;
 
@@ -486,17 +487,22 @@ export function RelayMembersSettingsCard({
               No members match your search.
             </p>
           ) : (
-            <div className="max-h-[28rem] space-y-2 overflow-y-auto pr-1">
-              {filteredMembers.map((member) => (
-                <RelayMemberRow
-                  currentPubkey={currentPubkey}
-                  currentRole={currentRole}
-                  key={member.pubkey}
-                  member={member}
-                  profile={profiles?.[normalizePubkey(member.pubkey)]}
-                />
-              ))}
-            </div>
+            <VirtualizedList
+              className="max-h-[28rem] pr-1"
+              estimateSize={56}
+              getItemKey={(member) => member.pubkey}
+              items={filteredMembers}
+              renderItem={(member) => (
+                <div className="pb-2">
+                  <RelayMemberRow
+                    currentPubkey={currentPubkey}
+                    currentRole={currentRole}
+                    member={member}
+                    profile={profiles?.[normalizePubkey(member.pubkey)]}
+                  />
+                </div>
+              )}
+            />
           )}
         </div>
       </div>
diff --git a/desktop/src/features/sidebar/ui/CustomChannelSection.tsx b/desktop/src/features/sidebar/ui/CustomChannelSection.tsx
index 28bad42934..dfed45d66a 100644
--- a/desktop/src/features/sidebar/ui/CustomChannelSection.tsx
+++ b/desktop/src/features/sidebar/ui/CustomChannelSection.tsx
@@ -363,7 +363,7 @@ export function ChannelGroupSection({
         {items.map((channel) => (
           <ContextMenu key={channel.id}>
             <ContextMenuTrigger asChild>
-              <SidebarMenuItem>
+              <SidebarMenuItem className="content-visibility-auto-row">
                 {draggable ? (
                   <DraggableChannelRow channelId={channel.id}>
                     <ChannelMenuButton
diff --git a/desktop/src/shared/styles/globals.css b/desktop/src/shared/styles/globals.css
index 7d86ae831e..dbf37d3b4b 100644
--- a/desktop/src/shared/styles/globals.css
+++ b/desktop/src/shared/styles/globals.css
@@ -1768,6 +1768,23 @@
 }
 
 @layer utilities {
+  /*
+   * Skips rendering (layout, paint) for offscreen rows while keeping them in
+   * the DOM — unlike windowing, in-DOM state (open `<details>`, drag-and-drop,
+   * deep-link `querySelector`) survives. The intrinsic size is a height
+   * placeholder so the scrollbar stays stable before a row is first rendered.
+   */
+  .content-visibility-auto {
+    content-visibility: auto;
+    contain-intrinsic-size: auto 200px;
+  }
+
+  /* Compact variant for short single-line rows (sidebar channels, etc). */
+  .content-visibility-auto-row {
+    content-visibility: auto;
+    contain-intrinsic-size: auto 2rem;
+  }
+
   .buzz-huddle-tooltip {
     --primary: var(
       --huddle-tooltip-surface,
diff --git a/desktop/src/shared/ui/VirtualizedList.tsx b/desktop/src/shared/ui/VirtualizedList.tsx
new file mode 100644
index 0000000000..f31fcc8fdf
--- /dev/null
+++ b/desktop/src/shared/ui/VirtualizedList.tsx
@@ -0,0 +1,144 @@
+import { type Virtualizer, useVirtualizer } from "@tanstack/react-virtual";
+import * as React from "react";
+
+import { cn } from "@/shared/lib/cn";
+
+export type ListVirtualizer = Virtualizer<HTMLElement, Element>;
+
+/**
+ * A headless virtualized list primitive using @tanstack/react-virtual.
+ *
+ * Migration contract:
+ * - Rows must tolerate unmount/remount (no DOM-resident state that can't be
+ *   reconstructed from props/data). Surfaces with in-DOM row state (open
+ *   `<details>`, drag-and-drop) should use `content-visibility` instead.
+ * - Rows may have variable height — the library's `measureElement` handles
+ *   dynamic sizing automatically.
+ *
+ * Supports:
+ * (a) Optional non-virtualized sticky-header slot rendered above the virtual
+ *     rows inside the scroll container (for PulseView's sticky composer, etc).
+ * (b) Optional externally-owned scroll container — pass `scrollRef` when the
+ *     caller already owns the scrolling element (a surface that shares its
+ *     scroll region with non-row siblings). When omitted, VirtualizedList
+ *     renders its own `overflow-y-auto` container.
+ */
+type VirtualizedListProps<T> = {
+  /** The data items to virtualize. */
+  items: T[];
+  /** Stable key extractor for each item. */
+  getItemKey: (item: T, index: number) => string | number;
+  /** Render function for each row. Receives the item and its index. */
+  renderItem: (item: T, index: number) => React.ReactNode;
+  /** Estimated row height in px — used before measurement. */
+  estimateSize?: number;
+  /** Optional non-virtualized content rendered above the virtual rows (sticky headers, etc). */
+  stickyHeader?: React.ReactNode;
+  /**
+   * Externally-owned scroll container. When provided, no internal scroll
+   * container is rendered — the caller's element scrolls and is measured.
+   */
+  scrollRef?: React.RefObject<HTMLElement | null>;
+  /** Class name for the internal scroll container (ignored when scrollRef is provided). */
+  className?: string;
+  /** Class name for the inner spacer div that holds the virtual rows. */
+  innerClassName?: string;
+  /** Overscan — number of items to render outside the visible area. */
+  overscan?: number;
+  /** Receives the virtualizer instance (for `scrollToIndex`, etc). */
+  onVirtualizer?: (virtualizer: ListVirtualizer) => void;
+};
+
+export function VirtualizedList<T>({
+  items,
+  getItemKey,
+  renderItem,
+  estimateSize = 80,
+  stickyHeader,
+  scrollRef,
+  className,
+  innerClassName,
+  overscan = 5,
+  onVirtualizer,
+}: VirtualizedListProps<T>) {
+  const internalScrollRef = React.useRef<HTMLDivElement>(null);
+  const spacerRef = React.useRef<HTMLDivElement>(null);
+  const ownsScroll = scrollRef === undefined;
+  const resolvedScrollRef = scrollRef ?? internalScrollRef;
+  // Read the element lazily inside the callback so the virtualizer picks it up
+  // once the ref attaches — capturing `ref.current` at render time would freeze
+  // it at the first-render `null`.
+  const getScrollElement = React.useCallback(
+    () => resolvedScrollRef.current,
+    [resolvedScrollRef],
+  );
+
+  // When a sticky header (or any caller content) sits above the rows in the
+  // same scroll container, the row spacer no longer starts at scrollTop 0.
+  // Feed that offset to the virtualizer as `scrollMargin` so the visible-range
+  // math stays aligned; without it the wrong rows render near the top.
+  const [scrollMargin, setScrollMargin] = React.useState(0);
+  React.useLayoutEffect(() => {
+    const scrollEl = resolvedScrollRef.current;
+    const spacer = spacerRef.current;
+    if (!scrollEl || !spacer) {
+      return;
+    }
+    const offset =
+      spacer.getBoundingClientRect().top -
+      scrollEl.getBoundingClientRect().top +
+      scrollEl.scrollTop;
+    setScrollMargin((prev) => (prev === offset ? prev : offset));
+  });
+
+  const virtualizer = useVirtualizer({
+    count: items.length,
+    getScrollElement,
+    estimateSize: () => estimateSize,
+    getItemKey: (index) => getItemKey(items[index], index),
+    overscan,
+    scrollMargin,
+  });
+
+  React.useEffect(() => {
+    onVirtualizer?.(virtualizer);
+  }, [onVirtualizer, virtualizer]);
+
+  const content = (
+    <>
+      {stickyHeader}
+      <div
+        className={cn("relative w-full", innerClassName)}
+        ref={spacerRef}
+        style={{ height: `${virtualizer.getTotalSize()}px` }}
+      >
+        {virtualizer.getVirtualItems().map((virtualRow) => (
+          <div
+            data-index={virtualRow.index}
+            key={virtualRow.key}
+            ref={virtualizer.measureElement}
+            style={{
+              position: "absolute",
+              top: 0,
+              left: 0,
+              width: "100%",
+              transform: `translateY(${virtualRow.start - scrollMargin}px)`,
+            }}
+          >
+            {renderItem(items[virtualRow.index], virtualRow.index)}
+          </div>
+        ))}
+      </div>
+    </>
+  );
+
+  if (ownsScroll) {
+    return (
+      <div className={cn("overflow-y-auto", className)} ref={internalScrollRef}>
+        {content}
+      </div>
+    );
+  }
+
+  return content;
+}
diff --git a/desktop/src/testing/e2eBridge.ts b/desktop/src/testing/e2eBridge.ts
index eaed6b1fbd..197196508f 100644
--- a/desktop/src/testing/e2eBridge.ts
+++ b/desktop/src/testing/e2eBridge.ts
@@ -2211,6 +2211,31 @@ function getMockMessageStore(channelId: string): RelayEvent[] {
               content: "Looks good to me. We should ship it.",
               sig: "mocksig".repeat(20).slice(0, 128),
             },
+            // Filler replies so the thread overflows the panel viewport — the
+            // deep-link target (mock-forum-release-deeplink) sits below the fold
+            // at open, proving scrollIntoView lands an offscreen content-
+            // visibility row. Named IDs above are untouched.
+            ...Array.from({ length: 24 }, (_, index) => ({
+              id:
+                index === 23
+                  ? "mock-forum-release-deeplink"
+                  : `mock-forum-release-filler-${index}`,
+              pubkey: ALICE_PUBKEY,
+              created_at: Math.floor(Date.now() / 1000) - (79 - index) * 60,
+              kind: 45003,
+              tags: buildReplyMessageTags(
+                channelId,
+                ALICE_PUBKEY,
+                "mock-forum-release-thread",
+                "mock-forum-release-thread",
+                undefined,
+              ),
+              content:
+                index === 23
+                  ? "Deep-link target: confirmed the rollout plan end to end."
+                  : `Follow-up note #${index + 1} on the release checklist.`,
+              sig: "mocksig".repeat(20).slice(0, 128),
+            })),
           ]
         : channelId === "94a444a4-c0a3-5966-ab05-530c6ddc2301"
           ? [
@@ -2522,7 +2547,9 @@ function getMockUserNotes(pubkey: string): RawUserNote[] {
   const now = Math.floor(Date.now() / 1000);
 
   if (pubkey === DEFAULT_MOCK_IDENTITY.pubkey) {
-    return [
+    // Two named notes plus generated filler so the Pulse feed overflows the
+    // viewport — required to exercise windowed scroll + sticky-composer offset.
+    const named: RawUserNote[] = [
       {
         id: "mock-note-launch",
         pubkey,
@@ -2538,6 +2565,14 @@ function getMockUserNotes(pubkey: string): RawUserNote[] {
         tags: [],
       },
     ];
+    const filler: RawUserNote[] = Array.from({ length: 28 }, (_, index) => ({
+      id: `mock-note-filler-${index}`,
+      pubkey,
+      created_at: now - (4 + index) * 60 * 60,
+      content: `Pulse update #${index + 1}: tracking virtualization rollout across desktop surfaces.`,
+      tags: [],
+    }));
+    return [...named, ...filler];
   }
 
   if (pubkey === ALICE_PUBKEY) {
diff --git a/desktop/tests/e2e/virtualization-screenshots.spec.ts b/desktop/tests/e2e/virtualization-screenshots.spec.ts
new file mode 100644
index 0000000000..09a5e90dd8
--- /dev/null
+++ b/desktop/tests/e2e/virtualization-screenshots.spec.ts
@@ -0,0 +1,199 @@
+import { expect, test } from "@playwright/test";
+import type { Locator, Page } from "@playwright/test";
+
+import { installMockBridge } from "../helpers/bridge";
+
+// Screenshot capture for the desktop list-virtualization pass (PR #1089). Each
+// shot is gated by an assertion so a geometry regression fails the run rather
+// than silently producing a misleading image. The shots are the empirical gate
+// that closes the correctness residual flagged in review: the absolute-position
+// / scrollMargin geometry under live dynamic measurement, and the
+// content-visibility "rows stay committed" claim on the dnd-coupled surface.
+// Artifacts land in test-results/virtualization/.
+const SHOTS = "test-results/virtualization";
+
+const WATERCOOLER_CHANNEL_ID = "a27e1ee9-76a6-5bdf-a5d5-1d85610dad11";
+const FORUM_THREAD_ID = "mock-forum-release-thread";
+const FORUM_DEEPLINK_REPLY_ID = "mock-forum-release-deeplink";
+
+// Mock-mode current-user pubkey (DEFAULT_MOCK_IDENTITY). Custom channel
+// sections persist under buzz-channel-sections.v1:<pubkey>, so shot 6 seeds two
+// sections for this key before the app boots.
+const MOCK_PUBKEY = "deadbeef".repeat(8);
+const SECTION_TOP = { id: "sec-top", name: "Priority", order: 0 };
+const SECTION_BOTTOM = { id: "sec-bottom", name: "Archive", order: 1 };
+
+async function seedChannelSections(page: Page) {
+  await page.addInitScript(
+    ({ pubkey, sections }) => {
+      window.localStorage.setItem(
+        `buzz-channel-sections.v1:${pubkey}`,
+        JSON.stringify({ version: 1, sections, assignments: {} }),
+      );
+    },
+    { pubkey: MOCK_PUBKEY, sections: [SECTION_TOP, SECTION_BOTTOM] },
+  );
+}
+
+// dnd-kit's PointerSensor activates only after the pointer travels past its
+// 6px distance constraint, so a single move never starts a drag. This walks the
+// pointer down, past the activation threshold, onto the target, then releases —
+// the sequence dnd-kit needs to fire onDragEnd and commit the reorder.
+async function dragOver(page: Page, source: Locator, target: Locator) {
+  const from = await source.boundingBox();
+  const to = await target.boundingBox();
+  if (!from || !to) throw new Error("drag handles not laid out");
+  await page.mouse.move(from.x + from.width / 2, from.y + from.height / 2);
+  await page.mouse.down();
+  await page.mouse.move(from.x + from.width / 2, from.y + from.height / 2 + 10);
+  await page.mouse.move(to.x + to.width / 2, to.y + to.height / 2, {
+    steps: 10,
+  });
+  await page.mouse.up();
+}
+
+test.describe("list virtualization screenshots", () => {
+  test("01 — Pulse windowed feed with sticky composer pinned mid-scroll", async ({
+    page,
+  }) => {
+    await installMockBridge(page);
+    await page.goto("/");
+    await page.getByTestId("open-pulse-view").click();
+
+    // The seeded feed overflows the viewport (30 notes), so the windowed list
+    // renders a subset and the composer stays pinned. Wait for virtual rows.
+    const rows = page.locator("[data-index]");
+    await expect(rows.first()).toBeVisible();
+    const composer = page.locator(".pulse-composer");
+    await expect(composer).toBeVisible();
+
+    // Scroll the feed mid-list, then prove the sticky composer is still pinned
+    // at the top of its scroll container — this exercises the
+    // translateY(start - scrollMargin) offset under a non-zero scrollTop.
+    const scroller = composer.locator(
+      "xpath=ancestor::*[contains(@class,'overflow-y-auto')][1]",
+    );
+    await scroller.evaluate((el) => {
+      el.scrollTop = 600;
+    });
+    await expect
+      .poll(async () =>
+        composer.evaluate(
+          (el, scrollEl) => {
+            const composerTop = el.getBoundingClientRect().top;
+            const scrollTop = (scrollEl as HTMLElement).getBoundingClientRect()
+              .top;
+            return Math.abs(composerTop - scrollTop);
+          },
+          await scroller.elementHandle(),
+        ),
+      )
+      .toBeLessThan(80);
+
+    await page.screenshot({ path: `${SHOTS}/01-pulse-sticky-composer.png` });
+  });
+
+  test("02 — forum deep-link lands on an offscreen reply", async ({ page }) => {
+    await installMockBridge(page);
+    await page.goto(
+      `/#/channels/${WATERCOOLER_CHANNEL_ID}/posts/${FORUM_THREAD_ID}?replyId=${FORUM_DEEPLINK_REPLY_ID}`,
+    );
+
+    // The deep-link target is the last of 25 replies — offscreen at open. Under
+    // content-visibility the row stays queryable, so scrollIntoView lands it.
+    const target = page.locator(
+      `[data-forum-event-id="${FORUM_DEEPLINK_REPLY_ID}"]`,
+    );
+    await expect(target).toBeVisible();
+    await expect(target).toContainText("Deep-link target");
+    // Assert the row sits within the viewport vertically — proves the scroll
+    // actually moved to it rather than leaving it below the fold.
+    await expect
+      .poll(async () =>
+        target.evaluate((el) => {
+          const rect = el.getBoundingClientRect();
+          return rect.top >= 0 && rect.bottom <= window.innerHeight;
+        }),
+      )
+      .toBe(true);
+
+    await page.screenshot({ path: `${SHOTS}/02-forum-deeplink-offscreen.png` });
+  });
+
+  test("03 — members search shows both sticky titles under content-visibility", async ({
+    page,
+  }) => {
+    await installMockBridge(page);
+    await page.goto("/");
+    await page.getByTestId("channel-general").click();
+    await expect(page.getByTestId("chat-title")).toHaveText("general");
+    await page.getByTestId("channel-members-trigger").click();
+    await expect(page.getByTestId("members-sidebar")).toBeVisible();
+
+    // "a" matches member `alice` (Members section) and non-member `charlie`
+    // (Not in this channel section) — both heterogeneous lists + both sticky
+    // titles must stay alive under content-visibility.
+    await page.getByTestId("channel-management-search-users").fill("a");
+    await expect(page.getByText("Members", { exact: true })).toBeVisible();
+    await expect(
+      page.getByText("Not in this channel", { exact: true }),
+    ).toBeVisible();
+    // A member row and an add-search (non-member) row both rendered.
+    await expect(
+      page.getByTestId("members-sidebar-people").getByText("alice"),
+    ).toBeVisible();
+    await expect(
+      page.locator('[data-testid^="channel-user-search-result-"]').first(),
+    ).toBeVisible();
+
+    await page.screenshot({
+      path: `${SHOTS}/03-members-both-sticky-titles.png`,
+    });
+  });
+
+  test("06 — custom-section dnd reorder commits under content-visibility", async ({
+    page,
+  }) => {
+    await seedChannelSections(page);
+    await installMockBridge(page);
+    await page.goto("/");
+    await page.getByTestId("channel-general").click();
+    await expect(page.getByTestId("chat-title")).toHaveText("general");
+
+    // dnd-kit marks each section's wrapping row with role="button" +
+    // aria-roledescription="sortable" and spreads the drag listeners there, so
+    // the row itself is the handle. Scoping to that attribute reads the live
+    // section order and excludes the inner disclosure button and the (hidden)
+    // assign-to-section context-menu items that reuse the same names.
+    const headers = page.locator('[aria-roledescription="sortable"]');
+    const topHeader = headers.filter({ hasText: "Priority" });
+    const bottomHeader = headers.filter({ hasText: "Archive" });
+    await expect(topHeader).toBeVisible();
+    await expect(bottomHeader).toBeVisible();
+    await expect(headers).toHaveCount(2);
+
+    const sectionOrder = async () =>
+      headers.evaluateAll((rows) =>
+        rows.map((row) =>
+          row.textContent?.trim().startsWith("Priority")
+            ? "Priority"
+            : "Archive",
+        ),
+      );
+    expect(await sectionOrder()).toEqual(["Priority", "Archive"]);
+
+    await page.screenshot({ path: `${SHOTS}/06a-sections-before-reorder.png` });
+
+    // Drag "Priority" past "Archive" — onDragEnd commits arrayMove and persists
+    // the new order. The drop must land for the order to flip.
+    await dragOver(page, topHeader, bottomHeader);
+
+    // The drop landed: order flipped. A no-op drag would leave it unchanged.
+    await expect.poll(sectionOrder).toEqual(["Archive", "Priority"]);
+    // Both section rows stayed committed in the DOM across the reorder — the
+    // content-visibility invariant the divergence rests on (no unmount).
+    await expect(headers).toHaveCount(2);
+
+    await page.screenshot({ path: `${SHOTS}/06b-sections-after-reorder.png` });
+  });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f9a3c2bbc2..186b2cf222 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -90,6 +90,9 @@ importers:
       '@tanstack/react-router':
         specifier: ^1.168.10
         version: 1.170.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+      '@tanstack/react-virtual':
+        specifier: ^3.14.2
+        version: 3.14.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
       '@tauri-apps/api':
         specifier: ~2.11
         version: 2.11.0
@@ -452,14 +455,12 @@ packages:
     engines: {node: '>=14.21.3'}
     cpu: [x64]
     os: [linux]
-    libc: [musl]
 
   '@biomejs/cli-linux-x64@2.4.16':
     resolution: {integrity: sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==}
     engines: {node: '>=14.21.3'}
     cpu: [x64]
     os: [linux]
-    libc: [glibc]
 
   '@biomejs/cli-win32-arm64@2.4.16':
     resolution: {integrity: sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==}
@@ -1549,6 +1550,12 @@ packages:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
 
+  '@tanstack/react-virtual@3.14.2':
+    resolution: {integrity: sha512-IpWnmCLvuymRfeeLNVXIzNEYBFLpd3drVIS91sqV78VTZFyldlChkOocZRCPp1B+Wnk09bcLNme8WaMU/9/9bQ==}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
   '@tanstack/router-core@1.171.5':
     resolution: {integrity: sha512-BfilbQqqWiQwJn68cD8wmk1ajEWIO3IlEA1zVuWslWbiVc23CDn+6ACO5tfPAcc96ED37hxela5ij3VBvAtusw==}
     engines: {node: '>=20.19'}
@@ -1585,6 +1592,9 @@ packages:
   '@tanstack/store@0.9.3':
     resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==}
 
+  '@tanstack/virtual-core@3.17.0':
+    resolution: {integrity: sha512-gOxY/hFkPh/XQYhnThBHzkbkX3Ed+z/iushyz+R+JAr213aXxUDgQoTgTdrDpBSRsjFM73P/KfUyWmaF9WHMkQ==}
+
   '@tanstack/virtual-file-routes@1.162.0':
     resolution: {integrity: sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==}
     engines: {node: '>=20.19'}
@@ -1629,7 +1639,6 @@ packages:
     engines: {node: '>= 10'}
     cpu: [riscv64]
     os: [linux]
-    libc: [glibc]
 
   '@tauri-apps/cli-linux-x64-gnu@2.11.2':
     resolution: {integrity: sha512-Ru4gwJKPG0ctVGchRGpRup4Y4lW2SSfFnrbQcyHhCliKy4g8Qz97TrUgCur4CbWyAgKxvGh3SjrkA0LDYzDGiw==}
@@ -3051,7 +3060,7 @@ packages:
     engines: {node: '>= 0.4'}
 
   wrappy@1.0.2:
-    resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+    resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
 
   yallist@3.1.1:
     resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -4205,6 +4214,12 @@ snapshots:
       react-dom: 19.2.7(react@19.2.7)
       use-sync-external-store: 1.6.0(react@19.2.7)
 
+  '@tanstack/react-virtual@3.14.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
+    dependencies:
+      '@tanstack/virtual-core': 3.17.0
+      react: 19.2.7
+      react-dom: 19.2.7(react@19.2.7)
+
   '@tanstack/router-core@1.171.5':
     dependencies:
       '@tanstack/history': 1.162.0
@@ -4262,6 +4277,8 @@ snapshots:
 
   '@tanstack/store@0.9.3': {}
 
+  '@tanstack/virtual-core@3.17.0': {}
+
   '@tanstack/virtual-file-routes@1.162.0': {}
 
   '@tauri-apps/api@2.11.0': {}
