fix: default unavailable trending to featured (#3319)

This commit is contained in:
Patrick Erichsen
2026-07-30 12:49:52 -07:00
committed by GitHub
parent 5498e3ce83
commit 21078e6e0d
4 changed files with 41 additions and 15 deletions
@@ -117,7 +117,7 @@ describe("HomeListingSection", () => {
expect(screen.queryByText("skills.sh")).toBeNull();
});
it("hides unavailable Trending and falls back to the native New feed", async () => {
it("hides unavailable Trending and falls back to the Featured feed", async () => {
render(<HomeListingSection initialListing={initialTrending([], false, "unavailable")} />);
expect(screen.queryByRole("tab", { name: "Trending" })).toBeNull();
@@ -126,12 +126,14 @@ describe("HomeListingSection", () => {
"Official",
"New",
]);
expect(screen.getByRole("tab", { name: "New" }).getAttribute("aria-selected")).toBe("true");
expect(screen.getByRole("tab", { name: "Featured" }).getAttribute("aria-selected")).toBe(
"true",
);
expect(screen.queryByText("24-hour Trending unavailable")).toBeNull();
await waitFor(() =>
expect(convexQueryMock).toHaveBeenCalledWith(
"skills:listPublicPageV4",
expect.objectContaining({ sort: "newest", createdAfter: expect.any(Number) }),
expect.objectContaining({ highlightedOnly: true }),
),
);
});
@@ -150,12 +152,26 @@ describe("HomeListingSection", () => {
"Official",
"New",
]);
expect(screen.getByRole("tab", { name: "New" }).getAttribute("aria-selected")).toBe("true");
expect(screen.getByRole("tab", { name: "Featured" }).getAttribute("aria-selected")).toBe(
"true",
);
expect(screen.queryByText("24-hour Trending unavailable")).toBeNull();
expect(fetchCanonicalTrendingPageMock).not.toHaveBeenCalled();
expect(convexQueryMock).toHaveBeenCalledWith(
"skills:listPublicPageV4",
expect.objectContaining({ sort: "newest", createdAfter: expect.any(Number) }),
expect.objectContaining({ highlightedOnly: true }),
);
});
it("returns from Plugins to Featured when Trending is unavailable", async () => {
render(<HomeListingSection initialListing={initialTrending([], false, "unavailable")} />);
fireEvent.click(screen.getByRole("button", { name: "Plugins" }));
expect(screen.getByRole("tab", { name: "New" }).getAttribute("aria-selected")).toBe("true");
fireEvent.click(screen.getByRole("button", { name: "Skills" }));
expect(screen.getByRole("tab", { name: "Featured" }).getAttribute("aria-selected")).toBe(
"true",
);
});
+13 -5
View File
@@ -134,7 +134,7 @@ describe("SkillsIndex", () => {
expect(screen.queryByText(/Not scanned by ClawHub/i)).toBeNull();
});
it("hides disabled Trending and falls back to the native New feed", async () => {
it("hides disabled Trending and falls back to the Featured feed", async () => {
fetchCatalogDiscoveryCapabilitiesMock.mockResolvedValue({
apiVersion: 0,
canonicalTrendingEnabled: false,
@@ -144,10 +144,14 @@ describe("SkillsIndex", () => {
await waitFor(() => expect(screen.queryByRole("radio", { name: "Trending" })).toBeNull());
expect(tabLabels()).toEqual(["Featured", "Official", "New"]);
expect(screen.getByRole("radio", { name: "New" }).getAttribute("aria-checked")).toBe("true");
expect(screen.getByRole("radio", { name: "Featured" }).getAttribute("aria-checked")).toBe(
"true",
);
expect(screen.queryByText("24-hour Trending unavailable")).toBeNull();
expect(fetchCanonicalTrendingPageMock).not.toHaveBeenCalled();
expect(convexHttpMock.query).toHaveBeenCalled();
await waitFor(() =>
expect(getLastListPageArgs()).toEqual(expect.objectContaining({ highlightedOnly: true })),
);
});
it("hides stale Trending and falls back without a legacy retry", async () => {
@@ -157,9 +161,13 @@ describe("SkillsIndex", () => {
await waitFor(() => expect(screen.queryByRole("radio", { name: "Trending" })).toBeNull());
expect(tabLabels()).toEqual(["Featured", "Official", "New"]);
expect(screen.getByRole("radio", { name: "New" }).getAttribute("aria-checked")).toBe("true");
expect(screen.getByRole("radio", { name: "Featured" }).getAttribute("aria-checked")).toBe(
"true",
);
expect(screen.queryByText("24-hour Trending unavailable")).toBeNull();
expect(convexHttpMock.query).toHaveBeenCalled();
await waitFor(() =>
expect(getLastListPageArgs()).toEqual(expect.objectContaining({ highlightedOnly: true })),
);
});
it("labels an empty canonical 24-hour window honestly", async () => {
+6 -4
View File
@@ -471,7 +471,7 @@ export function HomeListingSection({ initialListing = null }: HomeListingSection
const [kind, setKind] = useState<ListingKind>(initialListing?.kind ?? "skills");
const initialTab =
initialListing?.kind === "skills" && initialListing.trendingState === "unavailable"
? "new"
? "featured"
: (initialListing?.tab ?? "trending");
const [tab, setTab] = useState<ListingTab>(initialTab);
const [view, setView] = useState<ListingView>("list");
@@ -577,7 +577,7 @@ export function HomeListingSection({ initialListing = null }: HomeListingSection
if (tab === "trending") {
const unavailable = cached.trendingState === "unavailable";
setCanonicalTrendingUnavailable(unavailable);
if (unavailable) setTab("new");
if (unavailable) setTab("featured");
}
} else {
setPlugins(cached.items);
@@ -616,7 +616,7 @@ export function HomeListingSection({ initialListing = null }: HomeListingSection
if (tab === "trending") {
const unavailable = result.trendingState === "unavailable";
setCanonicalTrendingUnavailable(unavailable);
if (unavailable) setTab("new");
if (unavailable) setTab("featured");
}
setListingHasMore(result.hasMore);
setStatus("idle");
@@ -795,7 +795,9 @@ export function HomeListingSection({ initialListing = null }: HomeListingSection
if (nextKind === kind) return;
setKind(nextKind);
setCategorySlugs([]);
setTab(nextKind === "skills" && !canonicalTrendingUnavailable ? "trending" : "new");
setTab(
nextKind === "skills" ? (canonicalTrendingUnavailable ? "featured" : "trending") : "new",
);
};
const removeCategory = (slug: string) => {
+1 -1
View File
@@ -130,7 +130,7 @@ export function useSkillsBrowseModel({
const requestedCatalogTab = normalizeSkillsCatalogTab(search.tab, search);
const catalogTab =
requestedCatalogTab === "trending" && canonicalTrendingUnavailable
? "new"
? "featured"
: requestedCatalogTab;
const requestedSort = hasQuery
? search.sort === "default"