From d45b8829a35e49bdc01512659d2f70fc32d82e8b Mon Sep 17 00:00:00 2001 From: dawn <90008@gaze.systems> Date: Thu, 21 May 2026 11:12:21 +0300 Subject: [PATCH] use search in repo wide issue / pull search --- src/lib/api.ts | 2 + src/lib/api/core.ts | 328 +++++++++++++++++++++++++++++++++++--- src/lib/api/issues.ts | 1 + src/lib/api/pulls.ts | 1 + src/lib/repo-utils.ts | 6 +- src/pages/repo/issues.tsx | 18 +-- src/pages/repo/pulls.tsx | 54 +++---- src/pages/repo/shared.tsx | 14 +- src/views/search.tsx | 50 +++--- 9 files changed, 390 insertions(+), 84 deletions(-) diff --git a/src/lib/api.ts b/src/lib/api.ts index 3090113..cdae8c0 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -43,6 +43,7 @@ export { createIssueComment, getIssue, getIssueRecord, + getRepoIssueCount, listIssues, listIssuesPage, setIssueState, @@ -53,6 +54,7 @@ export { fetchPullRoundPatch, getPull, getPullRecord, + getRepoPullCount, listPulls, listPullsPage, setPullStatus, diff --git a/src/lib/api/core.ts b/src/lib/api/core.ts index c61791e..4f4d71b 100644 --- a/src/lib/api/core.ts +++ b/src/lib/api/core.ts @@ -398,6 +398,10 @@ interface AppviewListResponse { cursor?: string | null; } +interface AppviewBulkResponse { + items: Array>; +} + interface AppviewStatefulRecordView extends AppviewRecordView { state?: string; stateUpdatedAt?: string; @@ -440,6 +444,7 @@ interface AppviewCursorCheckpoint { } const APPVIEW_PAGE_BATCH_LIMIT = 100; +const SEARCH_PAGE_BATCH_LIMIT = 100; const APPVIEW_CURSOR_CACHE_LIMIT = 64; const appviewPageCursorCache = new Map>(); @@ -477,6 +482,30 @@ const timestampFromTid = (rkey?: string): number => { const maxTimestamp = (...values: number[]): number => values.reduce((latest, value) => Math.max(latest, value), 0); +const getAppviewRecordSortTimestamp = ( + record: HydratedRecord, +): number => + maxTimestamp( + timestampFromDate(record.value.createdAt), + timestampFromTid(record.rkey), + ); + +const sortStatefulRecordsByNewestRecord = ( + items: Array>, +): Array> => + [...items].sort((left, right) => { + const timestampDiff = getAppviewRecordSortTimestamp(right) - getAppviewRecordSortTimestamp(left); + if (timestampDiff !== 0) { + return timestampDiff; + } + + if (left.number !== right.number) { + return right.number - left.number; + } + + return right.rkey.localeCompare(left.rkey); + }); + const sortStatefulRecordsByUpdatedAt = async ( items: Array>, getUpdatedAt: (item: StatefulHydratedRecord) => Promise, @@ -642,6 +671,37 @@ const appviewJson = async ( return (await response.json()) as T; }; +const appviewJsonUrl = async (url: URL, signal?: AbortSignal): Promise => { + let response: Response; + try { + response = await fetch(url, { + headers: { + accept: 'application/json', + }, + signal, + }); + } catch (cause) { + throw new AppviewUnavailableError(cause); + } + + if (!response.ok) { + let message = `${response.status} ${response.statusText}`.trim(); + try { + const body = (await response.json()) as { message?: unknown; error?: unknown }; + const bodyMessage = typeof body.message === 'string' ? body.message : body.error; + if (typeof bodyMessage === 'string') { + message = bodyMessage; + } + } catch { + // Keep the HTTP status message when the appview does not return JSON. + } + + throw new AppviewResponseError(response.status, `Appview ${url.pathname.slice('/xrpc/'.length)} failed (${response.status}): ${message}`); + } + + return (await response.json()) as T; +}; + const appviewFallback = async (primary: () => Promise, fallback: () => Promise): Promise => { try { return await primary(); @@ -676,6 +736,29 @@ const listAllAppviewRecords = async ( return records; }; +const appviewBulkRecords = async ( + nsid: string, + paramName: string, + uris: ResourceUri[], +): Promise>> => { + if (uris.length === 0) { + return []; + } + + const records: Array> = []; + for (let index = 0; index < uris.length; index += 50) { + const url = new URL(`/xrpc/${nsid}`, normalizeServiceUrl(getTangledAppviewService())); + for (const uri of uris.slice(index, index + 50)) { + url.searchParams.append(paramName, uri); + } + + const page = await appviewJsonUrl>(url); + records.push(...page.items); + } + + return records; +}; + const appviewRecordToHydrated = async (record: AppviewRecordView): Promise> => { const parsed = parseAtUri(record.uri); const author = await resolveActorForDisplay(parsed.did); @@ -914,6 +997,70 @@ const listAppviewStatefulRecordsPage = async ( + nsid: SearchCollection, + repo: RepoContext, + options: StatefulPageOptions, + getState: (uri: ResourceUri) => Promise, +): Promise>> => { + const query = options.query?.trim(); + if (!query) { + return { items: [], totalCount: 0, hasNext: false }; + } + + const offset = Math.max(options.offset, 0); + const limit = Math.max(options.limit, 1); + const requestedEnd = offset + limit; + const items: Array> = []; + let cursor: string | undefined; + let matchingSeen = 0; + + while (true) { + const page = await appviewJson>('sh.tangled.search.query', { + q: query, + nsid, + author: options.author, + repo: repo.repoDid, + cursor, + limit: SEARCH_PAGE_BATCH_LIMIT, + }); + const hydrated = await Promise.all(page.hits.map((hit) => appviewRecordToHydrated(hit))); + const states = await Promise.all(hydrated.map((record) => getState(record.uri))); + + for (const [index, record] of hydrated.entries()) { + const state = states[index]; + if (state !== options.state) { + continue; + } + + if (matchingSeen >= offset && matchingSeen < requestedEnd) { + items.push({ + ...record, + number: 0, + state, + }); + } + + matchingSeen += 1; + if (matchingSeen > requestedEnd) { + return { + items: items.slice(0, limit), + hasNext: true, + }; + } + } + + cursor = page.cursor ?? undefined; + if (!cursor || page.hits.length === 0) { + return { + items, + totalCount: matchingSeen, + hasNext: false, + }; + } + } +}; + const getRepoViaAppview = async ( nsid: string, repo: RepoContext, @@ -1059,6 +1206,11 @@ const getRepoStarCountFromAppview = async (repo: RepoContext): Promise = return count.distinctAuthors ?? count.distinct_authors ?? count.count; }; +const getAppviewRecordCount = async (nsid: string, subject: string): Promise => { + const count = await appviewJson(nsid, { subject }); + return count.count; +}; + const findCurrentUserStarFromAppview = async ( repo: RepoContext, viewerDid: Did, @@ -1567,6 +1719,40 @@ const fetchRecordValue = async ( const hydrateBacklinks = async (refs: BacklinkRecordRef[]): Promise>> => Promise.all(refs.map((ref) => hydrateRecord(ref))); +const backlinkRefToUri = (ref: BacklinkRecordRef): ResourceUri => + `at://${ref.did}/${ref.collection}/${ref.rkey}` as ResourceUri; + +const hydrateBacklinksFromAppview = async ( + refs: BacklinkRecordRef[], + nsid: string, + paramName: string, +): Promise>> => + appviewFallback( + async () => { + const records = await appviewBulkRecords( + nsid, + paramName, + refs.map(backlinkRefToUri), + ); + return Promise.all(records.map((record) => appviewRecordToHydrated(record))); + }, + () => hydrateBacklinks(refs), + ); + +const hydrateIssueBacklinks = (refs: BacklinkRecordRef[]): Promise>> => + hydrateBacklinksFromAppview( + refs, + 'sh.tangled.repo.getIssues', + 'issues', + ); + +const hydratePullBacklinks = (refs: BacklinkRecordRef[]): Promise>> => + hydrateBacklinksFromAppview( + refs, + 'sh.tangled.repo.getPulls', + 'pulls', + ); + const getOptionalRecord = async ( actor: ResolvedActor, collection: Nsid, @@ -1665,9 +1851,10 @@ const listBacklinkedRecordsPage = async , getState: (uri: ResourceUri) => Promise, getUpdatedAt?: (record: StatefulHydratedRecord) => Promise, + hydrateRefs: (refs: BacklinkRecordRef[]) => Promise>> = hydrateBacklinks, ): Promise>> => { if (getUpdatedAt) { - const hydrated = await hydrateBacklinks(await getUniqueBacklinkRefs(subject, source)); + const hydrated = await hydrateRefs(await getUniqueBacklinkRefs(subject, source)); const numbers = toNumberMap(hydrated); const states = await Promise.all(hydrated.map((record) => getState(record.uri))); const items = hydrated.map((record, index) => ({ @@ -1734,7 +1921,7 @@ const listBacklinkedRecordsPage = async (refs); + const hydrated = await hydrateRefs(refs); hydrated.sort(sortByCreatedAt).reverse(); const states = await Promise.all(hydrated.map((record) => getState(record.uri))); @@ -1824,7 +2011,7 @@ const listIssuesFromBacklinks = async (repo: RepoContext): Promise(refs); + const issues = await hydrateIssueBacklinks(refs); const numbers = toNumberMap(issues); const states = await Promise.all(issues.map((issue) => getLatestIssueState(issue.uri))); @@ -1848,8 +2035,15 @@ const listIssuesPageFromBacklinks = async ( options, getLatestIssueState, (issue) => getIssueUpdatedAt(issue), + hydrateIssueBacklinks, ); +const getIssueCountFromBacklinks = async (repo: RepoContext): Promise => + (await getUniqueBacklinkRefs(repo.repoDid, [ + 'sh.tangled.repo.issue:repoDid', + 'sh.tangled.repo.issue:repo', + ])).length; + const getIssueFromBacklinks = async (repo: RepoContext, issueRef: string): Promise => { const issues = await listIssuesFromBacklinks(repo); const issue = findNumberedRecord(issues, issueRef); @@ -1869,7 +2063,7 @@ const getIssueFromBacklinks = async (repo: RepoContext, issueRef: string): Promi const listPullsFromBacklinks = async (repo: RepoContext): Promise => { const refs = await getBacklinks(repo.repoDid, 'sh.tangled.repo.pull:target.repo'); - const pulls = await hydrateBacklinks(refs); + const pulls = await hydratePullBacklinks(refs); const numbers = toNumberMap(pulls); const states = await Promise.all(pulls.map((pull) => getLatestPullStatus(pull.uri))); @@ -1893,8 +2087,13 @@ const listPullsPageFromBacklinks = async ( options, getLatestPullStatus, (pull) => getPullUpdatedAt(pull), + hydratePullBacklinks, ); +const getPullCountFromBacklinks = async (repo: RepoContext): Promise => + (await getBacklinksPage(repo.repoDid, 'sh.tangled.repo.pull:target.repo', 1)).total ?? + (await getBacklinks(repo.repoDid, 'sh.tangled.repo.pull:target.repo')).length; + const getPullFromBacklinks = async (repo: RepoContext, pullRef: string): Promise => { const pulls = await listPullsFromBacklinks(repo); const pull = findNumberedRecord(pulls, pullRef); @@ -1939,7 +2138,6 @@ const listIssuesFromAppview = async (repo: RepoContext): Promise ) as Array>; const issues = await Promise.all(records.map((record) => appviewRecordToHydrated(record))); const numbers = toNumberMap(issues); - const stateUpdatedAtByUri = new Map(records.map((record) => [record.uri, record.stateUpdatedAt])); const summaries = issues .map((issue, index) => ({ @@ -1948,26 +2146,56 @@ const listIssuesFromAppview = async (repo: RepoContext): Promise state: normalizeIssueState(records[index].state), })); - return sortStatefulRecordsByUpdatedAt( - summaries, - (issue) => getIssueUpdatedAt(issue, stateUpdatedAtByUri.get(issue.uri)), - ); + return sortStatefulRecordsByNewestRecord(summaries); }; const listIssuesPageFromAppview = async ( repo: RepoContext, options: StatefulPageOptions<'open' | 'closed'>, -): Promise> => - listAppviewStatefulRecordsPage( +): Promise> => { + if (options.query?.trim()) { + // Appview search can scope to repo/author/nsid. State is still derived + // from separate state records, so filter state after each search hit. + return listSearchedStatefulRecordsPage( + 'sh.tangled.repo.issue', + repo, + options, + getLatestIssueState, + ); + } + + // Temporary until Bobbin exposes newest-first/reverse repo issue pagination. + // This loads all appview issue records, but sorts without per-item backlinks. + return listAppviewStatefulRecordsPage( 'sh.tangled.repo.listIssues', repo.repoDid, options, normalizeIssueState, options.author ? { author: options.author } : {}, - (issue, record) => getIssueUpdatedAt(issue, record.stateUpdatedAt), + (issue) => Promise.resolve(getAppviewRecordSortTimestamp(issue)), ); +}; const getIssueFromAppview = async (repo: RepoContext, issueRef: string): Promise => { + const directUri = parseDirectRecordRef(issueRef, ISSUE_COLLECTION); + if (directUri) { + const issueRecord = await getIssueRecord(directUri); + if (issueRecord.value.repo !== repo.repoDid) { + throw new Error(`Issue not found`); + } + + const issue: IssueSummary = { + ...issueRecord, + number: 0, + state: await getLatestIssueState(issueRecord.uri), + }; + + return { + issue, + comments: await listIssueCommentsFromAppview(issue.uri), + }; + } + const issues = await listIssuesFromAppview(repo); const issue = findNumberedRecord(issues, issueRef); if (!issue) { @@ -1987,7 +2215,6 @@ const listPullsFromAppview = async (repo: RepoContext): Promise = ) as Array>; const pulls = await Promise.all(records.map((record) => appviewRecordToHydrated(record))); const numbers = toNumberMap(pulls); - const stateUpdatedAtByUri = new Map(records.map((record) => [record.uri, record.stateUpdatedAt])); const summaries = pulls .map((pull, index) => ({ @@ -1996,26 +2223,56 @@ const listPullsFromAppview = async (repo: RepoContext): Promise = state: normalizePullStatus(records[index].state), })); - return sortStatefulRecordsByUpdatedAt( - summaries, - (pull) => getPullUpdatedAt(pull, stateUpdatedAtByUri.get(pull.uri)), - ); + return sortStatefulRecordsByNewestRecord(summaries); }; const listPullsPageFromAppview = async ( repo: RepoContext, options: StatefulPageOptions<'open' | 'closed' | 'merged'>, -): Promise> => - listAppviewStatefulRecordsPage( +): Promise> => { + if (options.query?.trim()) { + // Appview search can scope to repo/author/nsid. State is still derived + // from separate status records, so filter state after each search hit. + return listSearchedStatefulRecordsPage( + 'sh.tangled.repo.pull', + repo, + options, + getLatestPullStatus, + ); + } + + // Temporary until Bobbin exposes newest-first/reverse repo pull pagination. + // This loads all appview pull records, but sorts without per-item backlinks. + return listAppviewStatefulRecordsPage( 'sh.tangled.repo.listPulls', repo.repoDid, options, normalizePullStatus, options.author ? { author: options.author } : {}, - (pull, record) => getPullUpdatedAt(pull, record.stateUpdatedAt), + (pull) => Promise.resolve(getAppviewRecordSortTimestamp(pull)), ); +}; const getPullFromAppview = async (repo: RepoContext, pullRef: string): Promise => { + const directUri = parseDirectRecordRef(pullRef, PULL_COLLECTION); + if (directUri) { + const pullRecord = await getPullRecord(directUri); + if (pullRecord.value.target.repo !== repo.repoDid) { + throw new Error(`Pull request not found`); + } + + const pull: PullSummary = { + ...pullRecord, + number: 0, + state: await getLatestPullStatus(pullRecord.uri), + }; + + return { + pull, + comments: await listPullCommentsFromAppview(pull.uri), + }; + } + const pulls = await listPullsFromAppview(repo); const pull = findNumberedRecord(pulls, pullRef); if (!pull) { @@ -2043,6 +2300,12 @@ export const listIssuesPage = async ( () => listIssuesPageFromBacklinks(repo, options), ); +export const getRepoIssueCount = async (repo: RepoContext): Promise => + appviewFallback( + () => getAppviewRecordCount('sh.tangled.repo.countIssues', repo.repoDid), + () => getIssueCountFromBacklinks(repo), + ); + export const getIssue = async (repo: RepoContext, issueRef: string): Promise => appviewFallback( () => getIssueFromAppview(repo, issueRef), @@ -2064,6 +2327,12 @@ export const listPullsPage = async ( () => listPullsPageFromBacklinks(repo, options), ); +export const getRepoPullCount = async (repo: RepoContext): Promise => + appviewFallback( + () => getAppviewRecordCount('sh.tangled.repo.countPulls', repo.repoDid), + () => getPullCountFromBacklinks(repo), + ); + export const getPull = async (repo: RepoContext, pullRef: string): Promise => appviewFallback( () => getPullFromAppview(repo, pullRef), @@ -2397,6 +2666,25 @@ export const parseAtUri = (uri: string): { did: Did; collection: Nsid; rkey: str }; }; +const parseDirectRecordRef = (ref: string, collection: Nsid): ResourceUri | null => { + let normalized = ref.trim(); + try { + normalized = decodeURIComponent(normalized); + } catch { + // Keep the original route param when it is not percent-encoded. + } + + if (!normalized.startsWith('at://')) { + return null; + } + + try { + return parseAtUri(normalized).collection === collection ? (normalized as ResourceUri) : null; + } catch { + return null; + } +}; + export const extractBlobCid = (blob: unknown): string | null => { if (!blob || typeof blob !== 'object') { return null; diff --git a/src/lib/api/issues.ts b/src/lib/api/issues.ts index 5dfd6c1..a0fa4d9 100644 --- a/src/lib/api/issues.ts +++ b/src/lib/api/issues.ts @@ -3,6 +3,7 @@ export { createIssueComment, getIssue, getIssueRecord, + getRepoIssueCount, listIssues, listIssuesPage, setIssueState, diff --git a/src/lib/api/pulls.ts b/src/lib/api/pulls.ts index 44a872c..182128c 100644 --- a/src/lib/api/pulls.ts +++ b/src/lib/api/pulls.ts @@ -4,6 +4,7 @@ export { fetchPullRoundPatch, getPull, getPullRecord, + getRepoPullCount, listPulls, listPullsPage, setPullStatus, diff --git a/src/lib/repo-utils.ts b/src/lib/repo-utils.ts index 774dcce..a2ac481 100644 --- a/src/lib/repo-utils.ts +++ b/src/lib/repo-utils.ts @@ -91,11 +91,13 @@ export const treeHref = (repo: RepoContext, ref: string, path = ''): string => export const blobHref = (repo: RepoContext, ref: string, path: string): string => `/${repo.owner.handle}/${repo.slug}/blob/${encodeURIComponent(ref)}/${encodePath(path)}`; +const encodeRouteParam = (value: string | number): string => encodeURIComponent(String(value)); + export const issueHref = (repo: RepoContext, issueRef: string | number): string => - `/${repo.owner.handle}/${repo.slug}/issues/${issueRef}`; + `/${repo.owner.handle}/${repo.slug}/issues/${encodeRouteParam(issueRef)}`; export const pullHref = (repo: RepoContext, pullRef: string | number): string => - `/${repo.owner.handle}/${repo.slug}/pulls/${pullRef}`; + `/${repo.owner.handle}/${repo.slug}/pulls/${encodeRouteParam(pullRef)}`; export const encodePath = (path: string): string => path diff --git a/src/pages/repo/issues.tsx b/src/pages/repo/issues.tsx index 65663ac..8c5800a 100644 --- a/src/pages/repo/issues.tsx +++ b/src/pages/repo/issues.tsx @@ -154,16 +154,16 @@ export const IssuesPage: Component = () => { >
- - {(issue) => ( - + + {(issue) => ( +
{issue.value.title}{' '} - #{issue.number} + #{issue.number || issue.rkey}
@@ -227,7 +227,7 @@ export const NewIssuePage: Component = () => { try { const created = await createIssue(agent, repo, { title: title(), body: body() }); await client.invalidateQueries({ queryKey: issuesQueryKey(repo.repoDid) }); - navigate(issueHref(repo, created.rkey)); + navigate(issueHref(repo, created.uri)); } catch (cause) { setError(cause instanceof Error ? cause.message : 'Failed to create issue'); } finally { @@ -396,7 +396,7 @@ export const IssuePage: Component = () => {

{detail().issue.value.title}{' '} - #{detail().issue.number} + #{detail().issue.number || detail().issue.rkey}

diff --git a/src/pages/repo/pulls.tsx b/src/pages/repo/pulls.tsx index 764c0e0..4740fbd 100644 --- a/src/pages/repo/pulls.tsx +++ b/src/pages/repo/pulls.tsx @@ -173,16 +173,16 @@ export const PullsPage: Component = () => { >
- - {(pull) => ( - + + {(pull) => ( +
{pull.value.title}{' '} - #{pull.number} + #{pull.number || pull.rkey}
@@ -337,22 +337,22 @@ export const NewPullPage: Component = () => { setSubmitting(true); setError(null); - try { - const created = await createPull(agent, repo, { - title: title(), - body: body(), - sourceBranch: mode === 'patch' ? targetBranch() : sourceBranch(), - targetBranch: targetBranch(), - patch, - }); - await client.invalidateQueries({ queryKey: pullsQueryKey(repo.repoDid) }); - navigate(pullHref(repo, created.rkey)); - } catch (cause) { - setError(cause instanceof Error ? cause.message : 'Failed to create pull request'); - } finally { - setSubmitting(false); - } - }; + try { + const created = await createPull(agent, repo, { + title: title(), + body: body(), + sourceBranch: mode === 'patch' ? targetBranch() : sourceBranch(), + targetBranch: targetBranch(), + patch, + }); + await client.invalidateQueries({ queryKey: pullsQueryKey(repo.repoDid) }); + navigate(pullHref(repo, created.uri)); + } catch (cause) { + setError(cause instanceof Error ? cause.message : 'Failed to create pull request'); + } finally { + setSubmitting(false); + } + }; return ( @@ -883,10 +883,10 @@ export const PullPage: Component = () => {
-

- {detail().pull.value.title}{' '} - #{detail().pull.number} -

+

+ {detail().pull.value.title}{' '} + #{detail().pull.number || detail().pull.rkey} +

diff --git a/src/pages/repo/shared.tsx b/src/pages/repo/shared.tsx index 904939a..3ee3e8c 100644 --- a/src/pages/repo/shared.tsx +++ b/src/pages/repo/shared.tsx @@ -2,7 +2,7 @@ import { CircleDot, GitPullRequest, Globe, LoaderCircle, Rss, SquareChartGantt, import { A, type RouteSectionProps, useParams } from '@solidjs/router'; import { createQuery } from '@tanstack/solid-query'; import { For, Match, Show, Switch, createContext, createEffect, createMemo, createSignal, onCleanup, useContext, type Component, type JSX } from 'solid-js'; -import { createRepoStar, deleteRepoStar, getRepo, getRepoStarSummary } from '../../lib/api'; +import { createRepoStar, deleteRepoStar, getRepo, getRepoIssueCount, getRepoPullCount, getRepoStarSummary } from '../../lib/api'; import { useAuth } from '../../lib/auth'; import { useLiveEvents } from '../../lib/live-events'; import { Avatar, ErrorState, cardStyles } from '../../components/common'; @@ -63,6 +63,16 @@ export const RepoFrame: Component<{ enabled: Boolean(repoQuery.data), queryFn: async () => getRepoStarSummary(repoQuery.data!, auth.currentDid()), })); + const issueCountQuery = createQuery(() => ({ + queryKey: [...issuesQueryKey(repoQuery.data?.repoDid ?? ''), 'count'], + enabled: Boolean(repoQuery.data), + queryFn: async () => getRepoIssueCount(repoQuery.data!), + })); + const pullCountQuery = createQuery(() => ({ + queryKey: [...pullsQueryKey(repoQuery.data?.repoDid ?? ''), 'count'], + enabled: Boolean(repoQuery.data), + queryFn: async () => getRepoPullCount(repoQuery.data!), + })); const starSummaryLoading = createMemo(() => !starSummaryQuery.data && (starSummaryQuery.isLoading || starSummaryQuery.isFetching)); const starBusy = createMemo(() => starWorking() || starSummaryLoading()); createEffect(() => { @@ -341,12 +351,14 @@ export const RepoFrame: Component<{ href={`/${repo().owner.handle}/${repo().slug}/issues`} icon={} label="issues" + meta={issueCountQuery.data === undefined ? undefined : String(issueCountQuery.data)} /> } label="pulls" + meta={pullCountQuery.data === undefined ? undefined : String(pullCountQuery.data)} />
diff --git a/src/views/search.tsx b/src/views/search.tsx index 952ba7c..32f04a9 100644 --- a/src/views/search.tsx +++ b/src/views/search.tsx @@ -218,16 +218,16 @@ const CommentParentLink: Component<{ hit: SearchHit }> = (props) => { return ( comment}> {(parent) => { - const repoDid = createMemo(() => parent().kind === 'issue' - ? (parent().record.value as { repo: Did }).repo - : (parent().record.value as { target: { repo: Did } }).target.repo); - return ( - - comment on {parent().kind} - - ); - }} - + const repoDid = createMemo(() => parent().kind === 'issue' + ? (parent().record.value as { repo: Did }).repo + : (parent().record.value as { target: { repo: Did } }).target.repo); + return ( + + comment on {parent().kind} + + ); + }} + ); }; @@ -290,21 +290,21 @@ const ResultTitle: Component<{ hit: SearchHit }> = (props) => {
{title()}}> - - - {props.hit.author.handle}/{repoName()} - - - - - {title()} - - - - - {title()} - - + + + {props.hit.author.handle}/{repoName()} + + + + + {title()} + + + + + {title()} + + -- 2.51.2