refactor: rewrite virtual lists to use TanStack Virtual
28 unmodified lines2930313233343536374 unmodified lines4243444546474849505152535455565733 unmodified lines91929394959697989910010110210310410510610716 unmodified lines28 unmodified lines
Shell commands like `cp`, `mv`, and `rm` may be aliased to include `-i` (interactive) mode on some systems, causing the agent to hang indefinitely waiting for y/n input.
**Use these forms instead:**
```bash# Force overwrite without promptingcp -f source dest # NOT: cp source destmv -f source dest # NOT: mv source dest4 unmodified linescp -rf source dest # NOT: cp -r source dest```
**Other commands that may prompt:**
- `scp` - use `-o BatchMode=yes` for non-interactive- `ssh` - use `-o BatchMode=yes` to fail instead of prompting- `apt-get` - use `-y` flag- `brew` - use `HOMEBREW_NO_AUTO_UPDATE=1` env var
<!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:7510c1e2 -->
## Beads Issue Tracker
This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands.
33 unmodified lines6. **Verify** - All changes committed AND pushed7. **Hand off** - Provide context for next session
**CRITICAL RULES:**
- Work is NOT complete until `git push` succeeds- NEVER stop before pushing - that leaves work stranded locally- NEVER say "ready to push when you are" - YOU must push- If push fails, resolve and retry until it succeeds<!-- END BEADS INTEGRATION -->
<!-- BEGIN BEADS CODEX SETUP: generated by bd setup codex -->
## Beads Issue Tracker
Use Beads (`bd`) for durable task tracking in repositories that include it. Use the `beads` skill at `.agents/skills/beads/SKILL.md` (project install) or `~/.agents/skills/beads/SKILL.md` (global install) for Beads workflow guidance, then use the `bd` CLI for issue operations.
16 unmodified lines1 unmodified line234567891033 unmodified lines4445464748495051525354535556575813 unmodified lines1 unmodified line
This file provides instructions and context for AI coding agents working on this project.
<!-- BEGIN BEADS INTEGRATION v:1 profile:minimal hash:7510c1e2 -->
## Beads Issue Tracker
This project uses **bd (beads)** for issue tracking. Run `bd prime` to see full workflow context and commands.
33 unmodified lines6. **Verify** - All changes committed AND pushed7. **Hand off** - Provide context for next session
**CRITICAL RULES:**
- Work is NOT complete until `git push` succeeds- NEVER stop before pushing - that leaves work stranded locally- NEVER say "ready to push when you are" - YOU must push- If push fails, resolve and retry until it succeeds<!-- END BEADS INTEGRATION -->
## Build & Test
_Add your build and test commands here_
13 unmodified lines20 unmodified lines21222324252526272829706 unmodified linesExpand all7367377387397407417407417427437447457467477487497507517527531112 unmodified linesExpand all186618671868186918661870187118721873187423 unmodified lines20 unmodified lines "npm:@sveltejs/kit@^2.49.2": "2.49.2_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.46.1___acorn@8.15.0__vite@7.3.0___@types+node@25.0.3___picomatch@4.0.3__@types+node@25.0.3_svelte@5.46.1__acorn@8.15.0_vite@7.3.0__@types+node@25.0.3__picomatch@4.0.3_acorn@8.15.0_@types+node@25.0.3", "npm:@sveltejs/vite-plugin-svelte@^6.2.1": "6.2.1_svelte@5.46.1__acorn@8.15.0_vite@7.3.0__@types+node@25.0.3__picomatch@4.0.3_@types+node@25.0.3", "npm:@tailwindcss/forms@~0.5.11": "0.5.11_tailwindcss@4.1.18", "npm:@tailwindcss/vite@^4.1.18": "4.1.18_vite@7.3.0__@types+node@25.0.3__picomatch@4.0.3_@types+node@25.0.3", "npm:@tutorlatin/svelte-tiny-virtual-list@^3.0.18": "3.0.18_svelte@5.46.1__acorn@8.15.0", "npm:@tanstack/svelte-virtual@^3.13.30": "3.13.30_svelte@5.46.1__acorn@8.15.0", "npm:@types/node@^25.0.3": "25.0.3", "npm:@wora/cache-persist@^2.2.1": "2.2.1", "npm:async-cache-dedupe@^3.4.0": "3.4.0", "npm:eslint-config-prettier@^10.1.8": "10.1.8_eslint@9.39.2",706 unmodified linesExpand all "tailwindcss", "vite" ] }, "@tutorlatin/svelte-tiny-virtual-list@3.0.18_svelte@5.46.1__acorn@8.15.0": { "integrity": "sha512-In7ASkVkLhg0ClWEVA50J/hWrGovwkw7dfHYlUyQJz4rPvh1TGpfo0lN1mXWERj2bkWY8AYITSbBZnVz34tcfA==", "@tanstack/svelte-virtual@3.13.30_svelte@5.46.1__acorn@8.15.0": { "integrity": "sha512-Yj6FOrUhYZ48kaSrc/CqOWM57wAJivoyW+L3V9zgL7vtDOvFU02aOnvmcmd/9Qypl/V29+ySwEzKA7f1Rpv0Xw==", "dependencies": [ "@tanstack/virtual-core", "svelte" ] }, "@tanstack/virtual-core@3.17.2": { "integrity": "sha512-w43MvWvmShpb6kIC9MOoLyUkLmRTLPjt61bHWs+X29hACSpX+n8DvgZ3qM7cUfflKlRRcHR9KVJE6TmcqnQvcA==" }, "@types/cookie@0.6.0": { "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" }, "@types/estree@1.0.8": {1112 unmodified linesExpand all "npm:@sveltejs/kit@^2.49.2", "npm:@sveltejs/vite-plugin-svelte@^6.2.1", "npm:@tailwindcss/forms@~0.5.11", "npm:@tailwindcss/vite@^4.1.18", "npm:@tutorlatin/svelte-tiny-virtual-list@^3.0.18", "npm:@tanstack/svelte-virtual@^3.13.30", "npm:@types/node@^25.0.3", "npm:@wora/cache-persist@^2.2.1", "npm:async-cache-dedupe@^3.4.0", "npm:eslint-config-prettier@^10.1.8",23 unmodified lines25 unmodified lines2627282930303132333431 unmodified lines25 unmodified lines "@atcute/oauth-browser-client": "^2.0.3", "@atcute/tid": "^1.1.1", "@floating-ui/dom": "^1.7.4", "@soffinal/websocket": "^0.2.1", "@tutorlatin/svelte-tiny-virtual-list": "^3.0.18", "@tanstack/svelte-virtual": "^3.13.30", "@wora/cache-persist": "^2.2.1", "async-cache-dedupe": "^3.4.0", "hash-wasm": "^4.12.0", "lru-cache": "^11.2.4",31 unmodified lines31 unmodified lines32333435363738394041424344454647484950515253545556575859606119 unmodified lines81828384818531 unmodified lines
### Working with Issues
Issues in Beads are:
- **Git-native**: Stored in Dolt database with version control and branching- **AI-friendly**: CLI-first design works perfectly with AI coding agents- **Branch-aware**: Issues can follow your branch workflow- **Always in sync**: Auto-syncs with your commits
## Why Beads?
✨ **AI-Native Design**
- Built specifically for AI-assisted development workflows- CLI-first interface works seamlessly with AI coding agents- No context switching to web UIs
🚀 **Developer Focused**
- Issues live in your repo, right next to your code- Works offline, syncs when you push- Fast, lightweight, and stays out of your way
🔧 **Git Integration**
- Automatic sync with git commits- Branch-aware issue tracking- Dolt-native three-way merge resolution
19 unmodified lines- **Examples**: [github.com/steveyegge/beads/examples](https://github.com/steveyegge/beads/tree/main/examples)
---
*Beads: Issue tracking that moves at the speed of thought* ⚡_Beads: Issue tracking that moves at the speed of thought_ ⚡12345678{"id":"int-aaf30fe3","kind":"field_change","created_at":"2026-06-29T14:29:06.229359693Z","actor":"dawn","issue_id":"nucleus-n16","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"installed @tanstack/svelte-virtual"}}{"id":"int-ec7a0de4","kind":"field_change","created_at":"2026-06-29T14:29:49.557989683Z","actor":"dawn","issue_id":"nucleus-xnz","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"refactored SettingsFeedsTab.svelte to use TanStack Virtual"}}{"id":"int-4f6810f8","kind":"field_change","created_at":"2026-06-29T14:30:13.834214558Z","actor":"dawn","issue_id":"nucleus-3w3","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"refactored SettingsModerationTab.svelte to use TanStack Virtual"}}{"id":"int-5a6cee4a","kind":"field_change","created_at":"2026-06-29T14:30:37.276554381Z","actor":"dawn","issue_id":"nucleus-fcu","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"refactored FollowingView.svelte to use TanStack Virtual"}}{"id":"int-2699475f","kind":"field_change","created_at":"2026-06-29T14:32:54.755171197Z","actor":"dawn","issue_id":"nucleus-ib8","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"refactored FeedTimelineView.svelte to use TanStack Virtual with dynamic height"}}{"id":"int-453baacc","kind":"field_change","created_at":"2026-06-29T14:33:25.416212006Z","actor":"dawn","issue_id":"nucleus-ehl","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"refactored GenericTimelineView.svelte to use TanStack Virtual with dynamic height"}}{"id":"int-ef0bb2b7","kind":"field_change","created_at":"2026-06-29T14:35:47.990869363Z","actor":"dawn","issue_id":"nucleus-9ot","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Removed old package and verified typescript / prettier formatting"}}{"id":"int-a4d53813","kind":"field_change","created_at":"2026-06-29T14:35:55.325922806Z","actor":"dawn","issue_id":"nucleus-bkx","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Epic complete: all list virtualizations rewritten to use TanStack Virtual."}}12345678{"_type":"issue","id":"nucleus-9ot","title":"Clean up @tutorlatin/svelte-tiny-virtual-list and verify build/lints","description":"Uninstall unused dependency, run linter and build to make sure everything works perfectly","status":"closed","priority":2,"issue_type":"task","assignee":"dawn","owner":"90008@klbr.net","created_at":"2026-06-29T14:27:01Z","created_by":"dawn","updated_at":"2026-06-29T14:35:48Z","started_at":"2026-06-29T14:33:32Z","closed_at":"2026-06-29T14:35:48Z","close_reason":"Removed old package and verified typescript / prettier formatting","dependencies":[{"issue_id":"nucleus-9ot","depends_on_id":"nucleus-3w3","type":"blocks","created_at":"2026-06-29T17:28:10Z","created_by":"dawn","metadata":"{}"},{"issue_id":"nucleus-9ot","depends_on_id":"nucleus-ehl","type":"blocks","created_at":"2026-06-29T17:28:23Z","created_by":"dawn","metadata":"{}"},{"issue_id":"nucleus-9ot","depends_on_id":"nucleus-fcu","type":"blocks","created_at":"2026-06-29T17:28:15Z","created_by":"dawn","metadata":"{}"},{"issue_id":"nucleus-9ot","depends_on_id":"nucleus-ib8","type":"blocks","created_at":"2026-06-29T17:28:19Z","created_by":"dawn","metadata":"{}"},{"issue_id":"nucleus-9ot","depends_on_id":"nucleus-n16","type":"blocks","created_at":"2026-06-29T17:28:02Z","created_by":"dawn","metadata":"{}"},{"issue_id":"nucleus-9ot","depends_on_id":"nucleus-xnz","type":"blocks","created_at":"2026-06-29T17:28:06Z","created_by":"dawn","metadata":"{}"}],"dependency_count":6,"dependent_count":1,"comment_count":0}{"_type":"issue","id":"nucleus-ehl","title":"Refactor GenericTimelineView to use TanStack Virtual","description":"Replace VirtualList in GenericTimelineView.svelte with TanStack Virtual using dynamic height measurement (measureElement)","status":"closed","priority":2,"issue_type":"task","assignee":"dawn","owner":"90008@klbr.net","created_at":"2026-06-29T14:26:53Z","created_by":"dawn","updated_at":"2026-06-29T14:33:25Z","started_at":"2026-06-29T14:33:01Z","closed_at":"2026-06-29T14:33:25Z","close_reason":"refactored GenericTimelineView.svelte to use TanStack Virtual with dynamic height","dependencies":[{"issue_id":"nucleus-ehl","depends_on_id":"nucleus-n16","type":"blocks","created_at":"2026-06-29T17:27:57Z","created_by":"dawn","metadata":"{}"}],"dependency_count":1,"dependent_count":2,"comment_count":0}{"_type":"issue","id":"nucleus-ib8","title":"Refactor FeedTimelineView to use TanStack Virtual","description":"Replace VirtualList in FeedTimelineView.svelte with TanStack Virtual using dynamic height measurement (measureElement)","status":"closed","priority":2,"issue_type":"task","assignee":"dawn","owner":"90008@klbr.net","created_at":"2026-06-29T14:26:45Z","created_by":"dawn","updated_at":"2026-06-29T14:32:55Z","started_at":"2026-06-29T14:30:44Z","closed_at":"2026-06-29T14:32:55Z","close_reason":"refactored FeedTimelineView.svelte to use TanStack Virtual with dynamic height","dependencies":[{"issue_id":"nucleus-ib8","depends_on_id":"nucleus-n16","type":"blocks","created_at":"2026-06-29T17:27:53Z","created_by":"dawn","metadata":"{}"}],"dependency_count":1,"dependent_count":2,"comment_count":0}{"_type":"issue","id":"nucleus-fcu","title":"Refactor FollowingView to use TanStack Virtual","description":"Replace VirtualList in FollowingView.svelte with TanStack Virtual","status":"closed","priority":2,"issue_type":"task","assignee":"dawn","owner":"90008@klbr.net","created_at":"2026-06-29T14:26:39Z","created_by":"dawn","updated_at":"2026-06-29T14:30:37Z","started_at":"2026-06-29T14:30:20Z","closed_at":"2026-06-29T14:30:37Z","close_reason":"refactored FollowingView.svelte to use TanStack Virtual","dependencies":[{"issue_id":"nucleus-fcu","depends_on_id":"nucleus-n16","type":"blocks","created_at":"2026-06-29T17:27:49Z","created_by":"dawn","metadata":"{}"}],"dependency_count":1,"dependent_count":2,"comment_count":0}{"_type":"issue","id":"nucleus-3w3","title":"Refactor SettingsModerationTab to use TanStack Virtual","description":"Replace VirtualList in SettingsModerationTab.svelte with TanStack Virtual","status":"closed","priority":2,"issue_type":"task","assignee":"dawn","owner":"90008@klbr.net","created_at":"2026-06-29T14:26:33Z","created_by":"dawn","updated_at":"2026-06-29T14:30:14Z","started_at":"2026-06-29T14:29:56Z","closed_at":"2026-06-29T14:30:14Z","close_reason":"refactored SettingsModerationTab.svelte to use TanStack Virtual","dependencies":[{"issue_id":"nucleus-3w3","depends_on_id":"nucleus-n16","type":"blocks","created_at":"2026-06-29T17:27:44Z","created_by":"dawn","metadata":"{}"}],"dependency_count":1,"dependent_count":2,"comment_count":0}{"_type":"issue","id":"nucleus-xnz","title":"Refactor SettingsFeedsTab to use TanStack Virtual","description":"Replace VirtualList in SettingsFeedsTab.svelte with TanStack Virtual","status":"closed","priority":2,"issue_type":"task","assignee":"dawn","owner":"90008@klbr.net","created_at":"2026-06-29T14:26:24Z","created_by":"dawn","updated_at":"2026-06-29T14:29:50Z","started_at":"2026-06-29T14:29:19Z","closed_at":"2026-06-29T14:29:50Z","close_reason":"refactored SettingsFeedsTab.svelte to use TanStack Virtual","dependencies":[{"issue_id":"nucleus-xnz","depends_on_id":"nucleus-n16","type":"blocks","created_at":"2026-06-29T17:27:40Z","created_by":"dawn","metadata":"{}"}],"dependency_count":1,"dependent_count":2,"comment_count":0}{"_type":"issue","id":"nucleus-n16","title":"Install @tanstack/svelte-virtual","description":"Add @tanstack/svelte-virtual package using deno install and update package.json","status":"closed","priority":2,"issue_type":"task","assignee":"dawn","owner":"90008@klbr.net","created_at":"2026-06-29T14:26:18Z","created_by":"dawn","updated_at":"2026-06-29T14:29:06Z","started_at":"2026-06-29T14:28:39Z","closed_at":"2026-06-29T14:29:06Z","close_reason":"installed @tanstack/svelte-virtual","dependency_count":0,"dependent_count":7,"comment_count":0}{"_type":"issue","id":"nucleus-bkx","title":"Epic: Rewrite nucleus to use TanStack Virtual","description":"Replace @tutorlatin/svelte-tiny-virtual-list with @tanstack/svelte-virtual, getting rid of custom height measurement hacks and improving scroll/list performance.","status":"closed","priority":2,"issue_type":"feature","owner":"90008@klbr.net","created_at":"2026-06-29T14:26:11Z","created_by":"dawn","updated_at":"2026-06-29T14:35:55Z","closed_at":"2026-06-29T14:35:55Z","close_reason":"Epic complete: all list virtualizations rewritten to use TanStack Virtual.","dependencies":[{"issue_id":"nucleus-bkx","depends_on_id":"nucleus-3w3","type":"blocks","created_at":"2026-06-29T17:27:16Z","created_by":"dawn","metadata":"{}"},{"issue_id":"nucleus-bkx","depends_on_id":"nucleus-9ot","type":"blocks","created_at":"2026-06-29T17:27:33Z","created_by":"dawn","metadata":"{}"},{"issue_id":"nucleus-bkx","depends_on_id":"nucleus-ehl","type":"blocks","created_at":"2026-06-29T17:27:29Z","created_by":"dawn","metadata":"{}"},{"issue_id":"nucleus-bkx","depends_on_id":"nucleus-fcu","type":"blocks","created_at":"2026-06-29T17:27:20Z","created_by":"dawn","metadata":"{}"},{"issue_id":"nucleus-bkx","depends_on_id":"nucleus-ib8","type":"blocks","created_at":"2026-06-29T17:27:24Z","created_by":"dawn","metadata":"{}"},{"issue_id":"nucleus-bkx","depends_on_id":"nucleus-n16","type":"blocks","created_at":"2026-06-29T17:27:07Z","created_by":"dawn","metadata":"{}"},{"issue_id":"nucleus-bkx","depends_on_id":"nucleus-xnz","type":"blocks","created_at":"2026-06-29T17:27:12Z","created_by":"dawn","metadata":"{}"}],"dependency_count":7,"dependent_count":0,"comment_count":0}1234567234567{ "database": "dolt", "backend": "dolt", "dolt_mode": "embedded", "dolt_database": "nucleus", "project_id": "bf0b94c2-7ce1-4a31-87a1-b40a40ae1e2f"}No newline at end of file "database": "dolt", "backend": "dolt", "dolt_mode": "embedded", "dolt_database": "nucleus", "project_id": "bf0b94c2-7ce1-4a31-87a1-b40a40ae1e2f"}1234567891011121314151617181920212223242526234567891011121314151617181920212223242526{ "hooks": { "PreCompact": [ { "hooks": [ { "command": "bd prime", "type": "command" } ], "matcher": "" } ], "SessionStart": [ { "hooks": [ { "command": "bd prime", "type": "command" } ], "matcher": "" } ] }}No newline at end of file "hooks": { "PreCompact": [ { "hooks": [ { "command": "bd prime", "type": "command" } ], "matcher": "" } ], "SessionStart": [ { "hooks": [ { "command": "bd prime", "type": "command" } ], "matcher": "" } ] }}106 unmodified linesExpand all10710810911011111111211311448 unmodified lines163164165166168167106 unmodified linesExpand all animation: pulse-highlight 0.6s ease-in-out 3;}
@keyframes pulse-highlight {
0%, 100% { box-shadow: 0 0 0 0 var(--nucleus-selected-post); }48 unmodified lines}
.settings-desc { @apply mb-2 text-sm text-(--nucleus-fg)/80;}No newline at end of file}5 unmodified lines67891010111213141415161724 unmodified lines424344454746474849505351525354575556575819 unmodified lines78798081858283848586878893899091927 unmodified lines1001011021031091101111121131141151041051061071081091101111121181191201211131141151161171181191201211221231241251261271281291301311321331341351367 unmodified lines14414514614713914814915014314415115215315430 unmodified lines1851861871881831841851861871881891891901911921931911921931941951961971981992002012022032042051941951961972071981992002012022032042092102112122132142152162172182192202212222232242052062072082092102112122132142152162172182192202212222262272232242252292302312322332342352362372382392402412422432442262272282292302312322332342352362372382392402452412422432441 unmodified line5 unmodified lines
import { accounts } from '$lib/accounts'; import type { Did, RecordKey } from '@atcute/lexicons/syntax'; import { InfiniteLoader, LoaderState } from 'svelte-infinite'; import VirtualList from '@tutorlatin/svelte-tiny-virtual-list'; import { createVirtualizer } from '@tanstack/svelte-virtual'; import { allPosts, viewClient, accountPreferences, feedTimelines, feedCursors, fetchFeed, resetFeed,24 unmodified lines const userDid = $derived(client?.user?.did);
let feedServiceDid = $state<string | null>(null); let newPostsAvailable = $state(false); let virtualList = $state<VirtualList | null>(null); let scrollToIndex = $state<number | undefined>(undefined);
const viewKey = $derived(`${userDid ?? 'anon'}-${selectedFeed}`);
$effect(() => { viewKey; // dependency void viewKey; // dependency feedServiceDid = null; newPostsAvailable = false; displayCount = 15; measuredHeights = []; loaderState.reset(); scrollToIndex = undefined;
fetchFeedGenerator(client ?? viewClient, selectedFeed).then((meta) => {19 unmodified lines let loading = $state(false); let loadError = $state('');
let fetchingInteractions = $state(false); let scheduledFetchInteractions = $state(false);
export const clearFeed = () => { if (!userDid) return; scrollToIndex = 0; setTimeout(() => (scrollToIndex = undefined), 100); newPostsAvailable = false; displayCount = 15; measuredHeights = []; resetFeed(userDid, selectedFeed); loaderState.reset(); loadMore(); };7 unmodified lines .map((uri) => allPosts.get(uri)) .filter((p): p is NonNullable<typeof p> => p !== undefined); });
let measuredHeights: number[] = $state([]); const itemHeights = $derived.by(() => { const heights = measuredHeights.slice(0, feedPosts.length); while (heights.length < feedPosts.length) { heights.push(estimatePostHeight(feedPosts[heights.length])); } return heights; let parentRef = $state<HTMLDivElement | null>(null); const virtualizer = createVirtualizer({ count: 0, getScrollElement: () => parentRef, estimateSize: (index) => estimatePostHeight(feedPosts[index]), overscan: 5, getItemKey: (index) => feedPosts[index]?.uri ?? index });
const averageHeight = $derived.by(() => { if (measuredHeights.length === 0) return 150; const sum = measuredHeights.reduce((a, b) => a + b, 0); return sum / measuredHeights.length; $effect(() => { $virtualizer.setOptions({ count: feedPosts.length, getScrollElement: () => parentRef, estimateSize: (index) => estimatePostHeight(feedPosts[index]), overscan: 5, getItemKey: (index) => feedPosts[index]?.uri ?? index }); });
$effect(() => { if (scrollToIndex !== undefined) { $virtualizer.scrollToIndex(scrollToIndex); } });
const measureElement = (node: HTMLElement) => { $virtualizer.measureElement(node); };
const loadMore = async () => { if (loading || !client || !userDid || !feedServiceDid) return;
loading = true;7 unmodified lines if (bufferSize < 5 && !cursor?.end) { const result = await fetchFeed(client, selectedFeed, feedServiceDid); if (client.user && userDid) { if (!fetchingInteractions) { scheduledFetchInteractions = false; fetchingInteractions = true; await fetchInteractionsToFeedTimelineEnd(client, userDid, selectedFeed); fetchingInteractions = false; } else { scheduledFetchInteractions = true; } } console.log('feed loaded', result?.end); if (result?.end) loaderState.complete();30 unmodified lines<div class="h-full [scrollbar-color:var(--nucleus-accent)_transparent] {className}"> <LoadNewPosts visible={newPostsAvailable} onclick={clearFeed} /> {#if userDid || $accounts.length > 0} {#key viewKey} <VirtualList bind:this={virtualList} height="100%" itemCount={feedPosts.length} itemSize={itemHeights} estimatedItemSize={averageHeight} scrollToIndex={feedPosts.length > 0 ? scrollToIndex : undefined} <div bind:this={parentRef} class="h-full overflow-x-hidden overflow-y-auto" style="position: relative;" > {#snippet item({ index, style }: { index: number; style: string })} {@const { post, postDid, postRkey } = renderItem(index)} <div style="{style} height: auto;" bind:clientHeight={ () => { // we need to return this so the bind works return measuredHeights[index] ?? estimatePostHeight(post); }, (h) => { // update the height if (measuredHeights[index] !== h) measuredHeights[index] = h; } } > <div style="height: {$virtualizer.getTotalSize()}px; width: 100%; position: relative;"> {#each $virtualizer.getVirtualItems() as virtualItem (virtualItem.key)} {@const { post, postDid, postRkey } = renderItem(virtualItem.index)} <div class="mx-2 mb-1.5 border-b border-dashed border-[color-mix(in_srgb,var(--nucleus-accent)_30%,transparent)] pb-3 last:border-0" data-index={virtualItem.index} use:measureElement style="position: absolute; top: 0; left: 0; width: 100%; transform: translateY({virtualItem.start}px);" > <div class="mx-2 mb-1.5 border-b border-dashed border-[color-mix(in_srgb,var(--nucleus-accent)_30%,transparent)] pb-3 last:border-0" > {#if post && postDid && postRkey} <BskyPost client={client!} did={postDid} rkey={postRkey} data={post} onQuote={(p) => { postComposerState.focus = 'focused'; postComposerState.quoting = p; }} onReply={(p) => { postComposerState.focus = 'focused'; postComposerState.replying = p; }} /> {/if} {#if post && postDid && postRkey} <BskyPost client={client!} did={postDid} rkey={postRkey} data={post} onQuote={(p) => { postComposerState.focus = 'focused'; postComposerState.quoting = p; }} onReply={(p) => { postComposerState.focus = 'focused'; postComposerState.replying = p; }} /> {/if} </div> </div> </div> {/snippet} {/each} </div>
{#snippet footer()} <div class="pb-20"> <InfiniteLoader {loaderState} triggerLoad={loadMore} loopDetectionTimeout={0}> <div class="h-px w-px opacity-0"></div> {#snippet noData()} <EndOfList /> {/snippet} {#snippet loading()} <LoadingSpinner /> {/snippet} {#snippet error()} <LoadError error={loadError} onRetry={loadMore} /> {/snippet} </InfiniteLoader> </div> {/snippet} <div class="pb-20"> <InfiniteLoader {loaderState} triggerLoad={loadMore} loopDetectionTimeout={0}> <div class="h-px w-px opacity-0"></div> {#snippet noData()} <EndOfList /> {/snippet} {#snippet loading()} <LoadingSpinner /> {/snippet} {#snippet error()} <LoadError error={loadError} onRetry={loadMore} /> {/snippet} </InfiniteLoader> </div> </div> </VirtualList> {/key} {:else} <NotLoggedIn /> {/if}1 unmodified line7 unmodified lines891011121213141516103 unmodified linesExpand all12012112212312412512612712812913013113213313413513613713813914014114214314428 unmodified lines1731741751761771781791801601611621631641651661671681691701711721811821831841851861871881891901911921931941951961977 unmodified lines replyIndex } from '$lib/state.svelte'; import type { Did } from '@atcute/lexicons'; import { type AtpClient } from '$lib/at/client.svelte'; import VirtualList from '@tutorlatin/svelte-tiny-virtual-list'; import { createVirtualizer } from '@tanstack/svelte-virtual'; import { calculateFollowedUserStats, calculateInteractionScores, sortFollowedUser,103 unmodified linesExpand all observer.observe(document.body); return () => observer.disconnect(); } });
let parentRef = $state<HTMLDivElement | null>(null); const virtualizer = createVirtualizer({ count: 0, getScrollElement: () => parentRef, estimateSize: () => 76, overscan: 5 });
$effect(() => { $virtualizer.setOptions({ count: sortedFollowing.length, getScrollElement: () => parentRef, estimateSize: () => 76, overscan: 5 }); });</script>
<div class="flex h-full flex-col p-2"> <div class="mb-4 flex items-center justify-between gap-2 p-2 px-2 md:gap-4">28 unmodified lines style="border-color: var(--nucleus-accent) var(--nucleus-accent) var(--nucleus-accent) transparent;" ></div> </div> {:else if listHeight > 0} <div bind:this={parentRef} style="height: {listHeight}px; overflow-y: auto; overflow-x: hidden; position: relative;" > <VirtualList height={listHeight} itemCount={sortedFollowing.length} itemSize={76}> {#snippet item({ index, style }: { index: number; style: string })} {@const user = sortedFollowing[index]} <FollowingItem {style} did={user.did} stats={user.data!} {client} sort={followingSort} {currentTime} /> {/snippet} </VirtualList> <div style="height: {$virtualizer.getTotalSize()}px; width: 100%; position: relative;"> {#each $virtualizer.getVirtualItems() as virtualItem (virtualItem.key)} {@const user = sortedFollowing[virtualItem.index]} <FollowingItem style="position: absolute; top: 0; left: 0; width: 100%; height: {virtualItem.size}px; transform: translateY({virtualItem.start}px);" did={user.did} stats={user.data!} {client} sort={followingSort} {currentTime} /> {/each} </div> </div> {/if} </div></div>