Retro Bulletin Board Systems on atproto. Web app and TUI. lazy mirror of alyraffauf/atbbs atbbs.xyz
forums python tui atproto bbs
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

web/Board: don't hardcode constellation lag

+64 -10
+64 -10
web/src/pages/Board.tsx
··· 5 5 useMutation, 6 6 useSuspenseInfiniteQuery, 7 7 useSuspenseQuery, 8 + type InfiniteData, 9 + type QueryKey, 8 10 } from "@tanstack/react-query"; 9 11 import { useAuth } from "../lib/auth"; 10 12 import { useBreadcrumb } from "../hooks/useBreadcrumb"; 11 13 import { usePageTitle } from "../hooks/usePageTitle"; 12 - import { makeAtUri, parseAtUri, relativeDate } from "../lib/util"; 14 + import { makeAtUri, nowIso, parseAtUri, relativeDate } from "../lib/util"; 13 15 import { BOARD } from "../lib/lexicon"; 14 16 import { createPost, uploadAttachments } from "../lib/writes"; 15 17 import * as limits from "../lib/limits"; ··· 19 21 boardThreadsInfiniteQuery, 20 22 } from "../lib/queries"; 21 23 import { queryClient } from "../lib/queryClient"; 24 + import type { ThreadItem, ThreadPageResult } from "../lib/boardThreads"; 22 25 import ThreadLink, { ThreadListHeader } from "../components/nav/ThreadLink"; 23 26 import ComposeForm from "../components/form/ComposeForm"; 27 + 28 + // Constellation indexes PDS writes asynchronously — usually within a second, 29 + // occasionally longer. After creating a thread we refetch with backoff until 30 + // the board's server data includes the new URI, so the optimistic prepend is 31 + // replaced by authoritative data rather than being wiped by a premature 32 + // refetch-on-mount returning stale results. 33 + async function refetchUntilIndexed(boardKey: QueryKey, threadUri: string) { 34 + const delays = [500, 800, 1300, 2100, 3400]; 35 + for (const delay of delays) { 36 + await new Promise((resolve) => setTimeout(resolve, delay)); 37 + try { 38 + await queryClient.refetchQueries({ queryKey: boardKey }); 39 + } catch { 40 + continue; 41 + } 42 + const data = 43 + queryClient.getQueryData<InfiniteData<ThreadPageResult>>(boardKey); 44 + if (data?.pages.some((p) => p.threads.some((t) => t.uri === threadUri))) { 45 + return; 46 + } 47 + } 48 + } 24 49 25 50 export default function BoardPage() { 26 51 const { handle, slug } = useParams(); ··· 80 105 }); 81 106 return resp; 82 107 }, 83 - onSuccess: (resp) => { 84 - // Constellation lags a few seconds behind the PDS write. Wait 85 - // before invalidating or we'll refetch before the index is fresh. 86 - setTimeout(() => { 87 - queryClient.invalidateQueries( 88 - boardThreadsInfiniteQuery(bbs.identity.did, board.slug), 89 - ); 90 - }, 1500); 108 + onSuccess: (resp, input) => { 109 + if (!user) return; 110 + const { did, rkey } = parseAtUri(resp.data.uri); 111 + const now = nowIso(); 112 + const newThread: ThreadItem = { 113 + uri: resp.data.uri, 114 + did, 115 + rkey, 116 + handle: user.handle, 117 + title: input.title, 118 + body: input.body, 119 + createdAt: now, 120 + lastActivityAt: now, 121 + replyCount: 0, 122 + participants: [{ did, handle: user.handle }], 123 + }; 124 + const boardKey = boardThreadsInfiniteQuery( 125 + bbs.identity.did, 126 + board.slug, 127 + ).queryKey; 128 + queryClient.setQueryData<InfiniteData<ThreadPageResult>>( 129 + boardKey, 130 + (prev) => { 131 + if (!prev || !prev.pages.length) return prev; 132 + const [firstPage, ...rest] = prev.pages; 133 + return { 134 + ...prev, 135 + pages: [ 136 + { 137 + ...firstPage, 138 + threads: [newThread, ...firstPage.threads], 139 + }, 140 + ...rest, 141 + ], 142 + }; 143 + }, 144 + ); 145 + refetchUntilIndexed(boardKey, resp.data.uri); 91 146 setTitle(""); 92 147 setBody(""); 93 148 setFiles([]); 94 - const { did, rkey } = parseAtUri(resp.data.uri); 95 149 navigate(`/bbs/${handle}/thread/${did}/${rkey}`); 96 150 }, 97 151 onError: (error) => {