From 6293bcceb53f0000d74c74aba81f7bd479042d61 Mon Sep 17 00:00:00 2001 From: dawn <90008@gaze.systems> Date: Sat, 30 May 2026 23:07:45 +0300 Subject: [PATCH] add preloading to more places, add one part of the repo preloading --- src/components/common.tsx | 1 + src/components/repo.tsx | 6 ++- src/lib/api/keys.ts | 5 +++ src/lib/api/repos.ts | 7 +++- src/lib/preloading.tsx | 38 +++++++++++++++++ src/pages/home.tsx | 85 +++++++++++++++++++++++++++++---------- src/pages/profile.tsx | 1 + src/pages/repo/shared.tsx | 7 +--- src/pages/search.tsx | 10 +++-- 9 files changed, 127 insertions(+), 33 deletions(-) create mode 100644 src/lib/api/keys.ts create mode 100644 src/lib/preloading.tsx diff --git a/src/components/common.tsx b/src/components/common.tsx index e6d8884..b0b0562 100644 --- a/src/components/common.tsx +++ b/src/components/common.tsx @@ -13,6 +13,7 @@ import { import { A } from '@solidjs/router'; import { Match, Show, Switch, createEffect, createMemo, createSignal, type Component } from 'solid-js'; import { createQuery, useQueryClient } from '@tanstack/solid-query'; +export { useQueryClient }; import { resolveAvatarUrl } from '../lib/api/identity'; export const ToggleButton: Component<{ diff --git a/src/components/repo.tsx b/src/components/repo.tsx index b9ddcba..29e7479 100644 --- a/src/components/repo.tsx +++ b/src/components/repo.tsx @@ -32,7 +32,8 @@ import { getRepoIssueCount } from '../lib/api/issues'; import { getRepoPullCount } from '../lib/api/pulls'; import { formatRelativeTime, languageColor, encodePath, formatLanguagePercent, blobHref, treeHref } from '../lib/repo-utils'; import { getTangledAppviewService } from '../lib/settings'; -import { Avatar, SkeletonBlock, StateBadge, PlaceholderAvatar, buttonStyles, cardStyles } from './common'; +import { Avatar, SkeletonBlock, StateBadge, PlaceholderAvatar, buttonStyles, cardStyles, usePreloader } from './common'; +import { repoQueryKey } from '../pages/repo/shared'; marked.setOptions({ gfm: true, @@ -1087,6 +1088,7 @@ export interface RepoCardProps { href?: string; noBorder?: boolean; showOwner?: boolean; + repoDid?: string; } export interface RepoStatsListProps { @@ -1233,6 +1235,7 @@ export const RepoStatsList: Component = (props) => { export const RepoCard: Component = (props) => { const linkHref = () => props.href || `/${props.owner}/${props.name}`; + const preload = usePreloader(); return (
= (props) => { 'truncate min-w-0 no-underline hover:underline text-black dark:text-white font-bold', props.compact && 'focus:outline-none' )} + {...preload(repoQueryKey(props.owner, props.name), () => getRepo(props.owner, props.name))} > {props.owner}/{props.name} diff --git a/src/lib/api/keys.ts b/src/lib/api/keys.ts new file mode 100644 index 0000000..51be7f8 --- /dev/null +++ b/src/lib/api/keys.ts @@ -0,0 +1,5 @@ +export const repoQueryKey = (owner: string, repo: string) => ['repo', owner, repo] as const; +export const issuesQueryKey = (repoDid: string) => ['issues', repoDid] as const; +export const issueQueryKey = (repoDid: string, issueRef: string) => ['issue', repoDid, issueRef] as const; +export const pullsQueryKey = (repoDid: string) => ['pulls', repoDid] as const; +export const pullQueryKey = (repoDid: string, pullRef: string) => ['pull', repoDid, pullRef] as const; diff --git a/src/lib/api/repos.ts b/src/lib/api/repos.ts index bff5e3e..291fde3 100644 --- a/src/lib/api/repos.ts +++ b/src/lib/api/repos.ts @@ -316,15 +316,18 @@ export const getRepo = async (ownerIdentifier: string, repoSlug: string): Promis const owner = await resolveActor(ownerIdentifier); const all = await listRepoRecords(owner); const wanted = repoSlug.trim().toLowerCase(); - const match = all.find((record) => { + const matches = all.filter((record) => { const name = record.value.name?.trim().toLowerCase(); return name === wanted || record.rkey.toLowerCase() === wanted; }); - if (!match) { + if (matches.length === 0) { throw new Error(`Repository not found: ${ownerIdentifier}/${repoSlug}`); } + // Prefer a record that doesn't use a localhost knot if multiple match + const match = matches.find((r) => !r.value.knot?.includes('localhost')) || matches[0]; + if (!match.value.repoDid) { throw new Error(`Repository is missing repoDid metadata`); } diff --git a/src/lib/preloading.tsx b/src/lib/preloading.tsx new file mode 100644 index 0000000..ae6cf6a --- /dev/null +++ b/src/lib/preloading.tsx @@ -0,0 +1,38 @@ +import { useQueryClient } from '@tanstack/solid-query'; +import { usePreloader } from '../components/common'; +import { getRepo } from './api/repos'; +import { getIssue } from './api/issues'; +import { getPull } from './api/pulls'; +import { repoQueryKey, issueQueryKey, pullQueryKey } from './api/keys'; + +export const useIssuePreloader = () => { + const queryClient = useQueryClient(); + const preload = usePreloader(); + + return (repoLabel: string, kind: 'issue' | 'pull', uri: string, number?: number) => { + const [owner, slug] = repoLabel.split('/'); + const issueRef = String(number || uri.split('/').pop()); + + return preload( + [kind, uri, 'prefetch'], + async () => { + const repo = await queryClient.fetchQuery({ + queryKey: repoQueryKey(owner, slug), + queryFn: () => getRepo(owner, slug), + }); + + if (kind === 'issue') { + await queryClient.prefetchQuery({ + queryKey: issueQueryKey(repo.repoDid, issueRef), + queryFn: () => getIssue(repo, issueRef), + }); + } else { + await queryClient.prefetchQuery({ + queryKey: pullQueryKey(repo.repoDid, issueRef), + queryFn: () => getPull(repo, issueRef), + }); + } + }, + ); + }; +}; diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 13e13e0..7ded2f3 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -17,14 +17,16 @@ import { A } from '@solidjs/router'; import { createQuery } from '@tanstack/solid-query'; import { For, Show, createEffect, createMemo, createSignal, onCleanup, type Component, type JSX } from 'solid-js'; -import { Avatar, ErrorState, LoadingState, StateBadge } from '../components/common'; +import { Avatar, ErrorState, LoadingState, StateBadge, usePreloader } from '../components/common'; import { RepoCard } from '../components/repo'; import { listFollowRecords } from '../lib/api/graph'; import { resolveActor } from '../lib/api/identity'; -import { listRepoRecords, type RepoRecord } from '../lib/api/repos'; +import { getRepo, listRepoRecords, type RepoRecord } from '../lib/api/repos'; import { useAuth } from '../lib/auth'; import { useLiveEvents, type LiveEvent } from '../lib/live-events'; +import { useIssuePreloader } from '../lib/preloading'; import { formatRelativeTime, getErrorMessage, loadRecentRepos, loadRecentIssuesPulls } from '../lib/repo-utils'; +import { repoQueryKey } from '../lib/api/keys'; const SAMPLE_REPOS: Array<{ owner: string; @@ -213,6 +215,7 @@ type HomeActivityItem = { title: string; repoLabel: string; href: string; + uri?: string; at?: string; body?: string; actorDid?: string; @@ -320,6 +323,7 @@ const fetchFollowingHomeData = async (viewerDid: string): Promise { const auth = useAuth(); const live = useLiveEvents(); + const issuePreloader = useIssuePreloader(); const recentRepos = createMemo(loadRecentRepos); const recentIssuesPulls = createMemo(loadRecentIssuesPulls); @@ -435,6 +439,7 @@ export const HomePage: Component = () => { ...label, repoLabel: repo.title, href: eventActivityHref(repo, event), + uri: event.sourceRecord, at: event.receivedAt, actorDid: currentUserDid(), actorHandle: currentUserHandle(), @@ -535,18 +540,26 @@ export const HomePage: Component = () => { >
- {(item) => ( - -
- {item.title}{' '} - #{item.uri.split('/').pop()} -
-
- - {item.author} · {item.repoLabel} -
-
- )} + {(item) => { + const preloadProps = issuePreloader(item.repoLabel, item.kind, item.uri, item.number); + + return ( + +
+ {item.title}{' '} + + #{item.uri.split('/').pop()} + +
+
+ + + {item.author} · {item.repoLabel} + +
+
+ ); + }}
@@ -733,7 +746,9 @@ const HomeFeedLoading: Component = () => ( const ActivityRow: Component<{ item: HomeActivityItem; index: number }> = (props) => { const repoUrl = () => `/${props.item.repoLabel}`; - const actorUrl = () => props.item.actorHandle ? `/${props.item.actorHandle}` : '#'; + const actorUrl = () => (props.item.actorHandle ? `/${props.item.actorHandle}` : '#'); + const preload = usePreloader(); + const issuePreloader = useIssuePreloader(); const actionText = () => { if (props.item.kind === 'repo') return 'published repo'; @@ -742,6 +757,20 @@ const ActivityRow: Component<{ item: HomeActivityItem; index: number }> = (props return 'updated'; }; + const preloadProps = () => { + if (props.item.kind === 'issue' || props.item.kind === 'pull') { + if (props.item.uri) { + return issuePreloader(props.item.repoLabel, props.item.kind, props.item.uri); + } + } + return {}; + }; + + const preloadRepoProps = () => { + const [owner, slug] = props.item.repoLabel.split('/'); + return preload(repoQueryKey(owner, slug), () => getRepo(owner, slug)); + }; + return (
0}> @@ -753,11 +782,18 @@ const ActivityRow: Component<{ item: HomeActivityItem; index: number }> = (props - + {props.item.actorHandle || 'user'} {actionText()} - + {props.item.repoLabel} @@ -771,17 +807,22 @@ const ActivityRow: Component<{ item: HomeActivityItem; index: number }> = (props fallback={
-
- {props.item.body} -
+
{props.item.body}
} diff --git a/src/pages/profile.tsx b/src/pages/profile.tsx index df10a86..6389da3 100644 --- a/src/pages/profile.tsx +++ b/src/pages/profile.tsx @@ -1511,6 +1511,7 @@ export const ProfilePage: Component = () => { ['repo', owner, repo] as const; -export const issuesQueryKey = (repoDid: string) => ['issues', repoDid] as const; -export const issueQueryKey = (repoDid: string, issueRef: string) => ['issue', repoDid, issueRef] as const; -export const pullsQueryKey = (repoDid: string) => ['pulls', repoDid] as const; -export const pullQueryKey = (repoDid: string, pullRef: string) => ['pull', repoDid, pullRef] as const; +export { repoQueryKey, issuesQueryKey, issueQueryKey, pullsQueryKey, pullQueryKey }; type OptimisticStarState = { state: 'starred'; rkey?: string } | { state: 'unstarred'; rkey: string }; diff --git a/src/pages/search.tsx b/src/pages/search.tsx index 70956ea..0ea377b 100644 --- a/src/pages/search.tsx +++ b/src/pages/search.tsx @@ -21,9 +21,9 @@ import { ErrorState, LoadingState, PaginationControls, usePreloader } from '../c import { RepoStatsList } from '../components/repo'; import { getIssueRecord, getIssue } from '../lib/api/issues'; import { getPullRecord, getPull } from '../lib/api/pulls'; -import { getRepoByDid } from '../lib/api/repos'; +import { getRepoByDid, getRepo } from '../lib/api/repos'; import { getString } from '../lib/api/strings'; -import { issueQueryKey, pullQueryKey } from './repo/shared'; +import { issueQueryKey, pullQueryKey, repoQueryKey } from './repo/shared'; import { ISSUE_COLLECTION, } from '../lib/api/constants'; @@ -340,7 +340,11 @@ const ResultTitle: Component<{ hit: SearchHit }> = (props) => { {title()}
}> - + getRepo(props.hit.author.handle, repoName()))} + > {props.hit.author.handle}/{repoName()} -- 2.51.2