From 24c6dc957bc116c72b45cc89cb6a577a9381a690 Mon Sep 17 00:00:00 2001
From: dawn <90008@gaze.systems>
Date: Tue, 6 Jan 2026 02:52:34 +0300
Subject: [PATCH] implement blocks sync
---
.zed/settings.json | 13 ++
src/components/FollowingItem.svelte | 52 ++-----
src/components/TimelineView.svelte | 20 +--
src/lib/at/client.ts | 136 +++++++-----------
src/lib/at/constellation.ts | 2 +-
src/lib/at/fetch.ts | 7 +-
src/lib/at/index.ts | 3 +
src/lib/index.ts | 2 +
src/lib/state.svelte.ts | 196 +++++++++++++++++---------
src/routes/[...catchall]/+page.svelte | 29 ++--
10 files changed, 236 insertions(+), 224 deletions(-)
create mode 100644 .zed/settings.json
diff --git a/.zed/settings.json b/.zed/settings.json
new file mode 100644
index 0000000..1ac3de3
--- /dev/null
+++ b/.zed/settings.json
@@ -0,0 +1,13 @@
+{
+ "lsp": {
+ "vtsls": {
+ "settings": {
+ "typescript": {
+ "suggestionActions": {
+ "enabled": false
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/components/FollowingItem.svelte b/src/components/FollowingItem.svelte
index 4f93032..237a228 100644
--- a/src/components/FollowingItem.svelte
+++ b/src/components/FollowingItem.svelte
@@ -1,5 +1,4 @@
@@ -8,11 +7,11 @@
import { getRelativeTime } from '$lib/date';
import { generateColorForDid } from '$lib/accounts';
import type { Did } from '@atcute/lexicons';
- import type { AtprotoDid } from '@atcute/lexicons/syntax';
import type { calculateFollowedUserStats, Sort } from '$lib/following';
- import type { AtpClient } from '$lib/at/client';
+ import { resolveDidDoc, type AtpClient } from '$lib/at/client';
import { SvelteMap } from 'svelte/reactivity';
- import { clients, getClient, router } from '$lib/state.svelte';
+ import { router } from '$lib/state.svelte';
+ import { map } from '$lib/result';
interface Props {
style: string;
@@ -35,48 +34,20 @@
const c = profileCache.get(targetDid)!;
displayName = c.displayName;
handle = c.handle;
- } else {
- const existingClient = clients.get(targetDid as AtprotoDid);
- if (existingClient?.user?.handle) {
- handle = existingClient.user.handle;
- } else {
- handle = 'handle.invalid';
- displayName = undefined;
- }
}
try {
- // Optimization: Check clients map first to avoid async overhead if possible
- // but we need to ensure we have the profile data, not just client existence.
- const userClient = await getClient(targetDid as AtprotoDid);
-
- // Check if the component has been recycled for a different user while we were awaiting
+ const [profileRes, handleRes] = await Promise.all([
+ client.getProfile(),
+ resolveDidDoc(targetDid).then((r) => map(r, (doc) => doc.handle))
+ ]);
if (did !== targetDid) return;
+ if (profileRes.ok) displayName = profileRes.value.displayName;
+ if (handleRes.ok) handle = handleRes.value;
- let newHandle = handle;
- let newDisplayName = displayName;
-
- if (userClient.user?.handle) {
- newHandle = userClient.user.handle;
- handle = newHandle;
- } else {
- newHandle = targetDid;
- handle = newHandle;
- }
-
- const profileRes = await userClient.getProfile();
-
- if (did !== targetDid) return;
-
- if (profileRes.ok) {
- newDisplayName = profileRes.value.displayName;
- displayName = newDisplayName;
- }
-
- // Update cache
profileCache.set(targetDid, {
- handle: newHandle,
- displayName: newDisplayName
+ handle,
+ displayName
});
} catch (e) {
if (did !== targetDid) return;
@@ -85,7 +56,6 @@
}
};
- // Re-run whenever `did` changes
$effect(() => {
loadProfile(did);
});
diff --git a/src/components/TimelineView.svelte b/src/components/TimelineView.svelte
index bc62583..1e19413 100644
--- a/src/components/TimelineView.svelte
+++ b/src/components/TimelineView.svelte
@@ -11,7 +11,7 @@
fetchTimeline,
allPosts,
timelines,
- fetchInteractionsUntil
+ fetchInteractionsToTimelineEnd
} from '$lib/state.svelte';
import Icon from '@iconify/svelte';
import { buildThreads, filterThreads, type ThreadPost } from '$lib/thread';
@@ -53,7 +53,6 @@
const loaderState = new LoaderState();
let scrollContainer = $state();
let loading = $state(false);
- let fetchMoreInteractions: boolean | undefined = $state(false);
let loadError = $state('');
const loadMore = async () => {
@@ -63,9 +62,9 @@
loaderState.status = 'LOADING';
try {
- await fetchTimeline(did as AtprotoDid, 7, showReplies);
- // interaction fetching is done lazily so we dont block loading posts
- fetchMoreInteractions = true;
+ await fetchTimeline(client, did as AtprotoDid, 7, showReplies);
+ // only fetch interactions if logged in (because if not who is the interactor)
+ if (client.user) await fetchInteractionsToTimelineEnd(client, did);
loaderState.loaded();
} catch (error) {
loadError = `${error}`;
@@ -87,11 +86,12 @@
const cursor = did ? postCursors.get(did as AtprotoDid) : undefined;
if (!cursor?.end) loadMore();
}
- if (client && did && fetchMoreInteractions) {
- // set to false so it doesnt attempt to fetch again while its already fetching
- fetchMoreInteractions = false;
- fetchInteractionsUntil(client, did).then(() => (fetchMoreInteractions = undefined));
- }
+ });
+ // we want to load interactions when changing logged in user on timelines
+ // only on timelines that arent logged in users, because those are already
+ // loaded by loadMore
+ $effect(() => {
+ if (client && did && client.user?.did !== did) fetchInteractionsToTimelineEnd(client, did);
});
diff --git a/src/lib/at/client.ts b/src/lib/at/client.ts
index 0612f50..0cbcfd6 100644
--- a/src/lib/at/client.ts
+++ b/src/lib/at/client.ts
@@ -8,7 +8,6 @@ import { Client as AtcuteClient, simpleFetchHandler } from '@atcute/client';
import { safeParse, type Blob as AtpBlob, type Handle, type InferOutput } from '@atcute/lexicons';
import {
isDid,
- parseCanonicalResourceUri,
parseResourceUri,
type ActorIdentifier,
type AtprotoDid,
@@ -36,7 +35,7 @@ import { WebSocket } from '@soffinal/websocket';
import type { Notification } from './stardust';
import type { OAuthUserAgent } from '@atcute/oauth-browser-client';
import { timestampFromCursor, toCanonicalUri, toResourceUri } from '$lib';
-import { constellationUrl, slingshotUrl, spacedustUrl } from '.';
+import { constellationUrl, httpToDidWeb, slingshotUrl, spacedustUrl } from '.';
export type RecordOutput