From 10ca2e906f3e238be4e53a062787ed7e8158a170 Mon Sep 17 00:00:00 2001
From: dawn <90008@gaze.systems>
Date: Fri, 26 Dec 2025 21:35:31 +0000
Subject: [PATCH] virtual list
---
deno.lock | 8 ++++++++
package.json | 1 +
src/app.html | 3 ++-
src/components/FollowingView.svelte | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------
src/components/SettingsView.svelte | 4 ++--
src/lib/settings.ts | 21 +++++++++++++++++----
src/routes/+page.svelte | 6 ++++--
7 file(s) changed, 89 insertion(s)(+), 65 deletion(s)(-)
diff --git a/deno.lock b/deno.lock
--- a/deno.lock
+++ b/deno.lock
@@ -22,6 +22,7 @@
"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.17": "3.0.17_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",
@@ -714,6 +715,12 @@
"@tailwindcss/oxide",
"tailwindcss",
"vite"
+ ]
+ },
+ "@tutorlatin/svelte-tiny-virtual-list@3.0.17_svelte@5.46.1__acorn@8.15.0": {
+ "integrity": "sha512-OvFRITfbWdsFk7VR2FKVJiBMPlgbyc81hqbFORXdEcBXcT91XRdLXfhSbS8o14ntUBMFPWVv19fti+Ez50q45g==",
+ "dependencies": [
+ "svelte"
]
},
"@types/cookie@0.6.0": {
@@ -1814,6 +1821,7 @@
"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.17",
"npm:@types/node@^25.0.3",
"npm:@wora/cache-persist@^2.2.1",
"npm:async-cache-dedupe@^3.4.0",
diff --git a/package.json b/package.json
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"@atcute/tid": "^1.0.3",
"@floating-ui/dom": "^1.7.4",
"@soffinal/websocket": "^0.2.1",
+ "@tutorlatin/svelte-tiny-virtual-list": "^3.0.17",
"@wora/cache-persist": "^2.2.1",
"async-cache-dedupe": "^3.4.0",
"hash-wasm": "^4.12.0",
diff --git a/src/app.html b/src/app.html
--- a/src/app.html
+++ b/src/app.html
@@ -2,7 +2,8 @@
-
+
+
%sveltekit.head%
diff --git a/src/components/FollowingView.svelte b/src/components/FollowingView.svelte
--- a/src/components/FollowingView.svelte
+++ b/src/components/FollowingView.svelte
@@ -6,8 +6,7 @@
import { getRelativeTime } from '$lib/date';
import { generateColorForDid } from '$lib/accounts';
import { type AtprotoDid } from '@atcute/lexicons/syntax';
- import { flip } from 'svelte/animate';
- import { cubicOut } from 'svelte/easing';
+ import VirtualList from '@tutorlatin/svelte-tiny-virtual-list';
interface Props {
selectedDid: Did;
@@ -194,64 +193,16 @@
);
-{#snippet followingItems()}
- {#each sortedFollowing as user (user.did)}
- {@const stats = user.data!}
- {@const lastPostAt = stats.lastPostAt}
- {@const relTime = getRelativeTime(lastPostAt, currentTime)}
- {@const color = generateColorForDid(user.did)}
-
-
-
-
-
- {#await Promise.all([user.profile, user.handle]) then [displayName, handle]}
- {displayName || handle}
- @{handle}
- {/await}
-
-
-
- posted {relTime}
- {relTime !== 'now' ? 'ago' : ''}
-
- {#if stats.recentPostCount > 0}
-
- {stats.recentPostCount} posts / 6h
-
- {/if}
- {#if followingSort === 'conversational' && stats.conversationalScore > 0}
-
- ★ {stats.conversationalScore.toFixed(1)}
-
- {/if}
-
-
-
-
- {/each}
-{/snippet}
-
-
-
+
+
-
+
{#each ['recent', 'active', 'conversational'] as type (type)}
diff --git a/src/components/SettingsView.svelte b/src/components/SettingsView.svelte
--- a/src/components/SettingsView.svelte
+++ b/src/components/SettingsView.svelte
@@ -155,9 +155,9 @@
{
- const stored = localStorage.getItem('settings');
+ // Prevent SSR crash if localStorage is missing
+ const stored = typeof localStorage !== 'undefined' ? localStorage.getItem('settings') : null;
const initial: Partial = stored ? JSON.parse(stored) : defaultSettings;
initial.endpoints = { ...defaultSettings.endpoints, ...initial.endpoints };
@@ -35,28 +36,40 @@
const { subscribe, set, update } = writable(initial as Settings);
subscribe((settings) => {
+ if (typeof document === 'undefined') return;
const theme = settings.theme;
document.documentElement.style.setProperty('--nucleus-bg', theme.bg);
document.documentElement.style.setProperty('--nucleus-fg', theme.fg);
document.documentElement.style.setProperty('--nucleus-accent', theme.accent);
document.documentElement.style.setProperty('--nucleus-accent2', theme.accent2);
+
+ const oldMeta = document.querySelector('meta[name="theme-color"]');
+ if (oldMeta) oldMeta.remove();
+
+ const metaThemeColor = document.createElement('meta');
+ metaThemeColor.setAttribute('name', 'theme-color');
+ metaThemeColor.setAttribute('content', theme.bg);
+ document.head.appendChild(metaThemeColor);
});
return {
subscribe,
set: (value: Settings) => {
- localStorage.setItem('settings', JSON.stringify(value));
+ if (typeof localStorage !== 'undefined')
+ localStorage.setItem('settings', JSON.stringify(value));
set(value);
},
update: (fn: (value: Settings) => Settings) => {
update((value) => {
const newValue = fn(value);
- localStorage.setItem('settings', JSON.stringify(newValue));
+ if (typeof localStorage !== 'undefined')
+ localStorage.setItem('settings', JSON.stringify(newValue));
return newValue;
});
},
reset: () => {
- localStorage.setItem('settings', JSON.stringify(defaultSettings));
+ if (typeof localStorage !== 'undefined')
+ localStorage.setItem('settings', JSON.stringify(defaultSettings));
set(defaultSettings);
}
};
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -352,8 +352,8 @@
@@ -389,6 +389,8 @@
+
+