From 64a7400f6769ab433f06758e035ecb8c2ab6d1dd Mon Sep 17 00:00:00 2001 From: dawn Date: Mon, 3 Aug 2026 22:03:04 +0300 Subject: [PATCH] web/lib/components/profile: dedup repos with same repoKey Signed-off-by: dawn --- web/src/lib/components/profile/pages.test.ts | 48 ++++++++++++++++++++ web/src/lib/components/profile/pages.ts | 8 +++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/web/src/lib/components/profile/pages.test.ts b/web/src/lib/components/profile/pages.test.ts index 5bd83b96..988568a5 100644 --- a/web/src/lib/components/profile/pages.test.ts +++ b/web/src/lib/components/profile/pages.test.ts @@ -144,4 +144,52 @@ describe("fetchStarredPage", () => { ) ).toBe(true); }); + + it("deduplicates repos returned by fetchReposPage by repoKey", async () => { + const fetchMock = vi.fn().mockResolvedValue( + Response.json({ + output: { + items: [ + { + uri: "at://did:plc:alice/sh.tangled.repo/repo-one", + value: { + repoDid: "did:plc:repo-one", + name: "repo-one", + createdAt: "2026-08-01T00:00:00Z" + } + }, + { + uri: "at://did:plc:alice/sh.tangled.repo/repo-one-dup", + value: { + repoDid: "did:plc:repo-one", + name: "repo-one-dup", + createdAt: "2026-08-02T00:00:00Z" + } + }, + { + uri: "at://did:plc:alice/sh.tangled.repo/repo-two", + value: { + repoDid: "did:plc:repo-two", + name: "repo-two", + createdAt: "2026-08-03T00:00:00Z" + } + } + ] + }, + data: {} + }) + ); + const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock }); + + const result = await fetchReposPage(ctx, { + did: "did:plc:alice", + handle: "alice.test", + viewerDid: "did:plc:viewer" + }); + + expect(result.items).toHaveLength(2); + expect(result.items[0].repoDid).toBe("did:plc:repo-one"); + expect(result.items[0].name).toBe("repo-one-dup"); + expect(result.items[1].repoDid).toBe("did:plc:repo-two"); + }); }); diff --git a/web/src/lib/components/profile/pages.ts b/web/src/lib/components/profile/pages.ts index 50f6c31e..0ed9f395 100644 --- a/web/src/lib/components/profile/pages.ts +++ b/web/src/lib/components/profile/pages.ts @@ -26,6 +26,7 @@ import type * as ShTangledFeedStar from "$lib/api/lexicons/types/sh/tangled/feed import type * as ShTangledString from "$lib/api/lexicons/types/sh/tangled/string"; import type * as ShTangledGraphFollow from "$lib/api/lexicons/types/sh/tangled/graph/follow"; import type { RepoCardData, StringCardData, PersonData, VouchData, StarData } from "./types"; +import { repoKey } from "./types"; // same page size as the appview's lists export const PROFILE_PAGE_LIMIT = 30; @@ -277,8 +278,13 @@ export const fetchReposPage = async ( enrich: targetAll(descriptors, ["items[].value.repoDid"]), ...(viewerDid ? { viewer: viewerDid } : {}) }); + const cards = new Map(); + for (const item of enriched.output.items) { + const card = resolveRepoCard(item, handle, enriched.data); + cards.set(repoKey(card), card); + } return { - items: enriched.output.items.map((item) => resolveRepoCard(item, handle, enriched.data)), + items: Array.from(cards.values()), cursor: enriched.output.cursor }; } -- 2.51.2