fix: show honest skills.sh Trending provenance (#3423)

* fix: show honest skills.sh trending provenance

* test: type mixed trending fixtures
This commit is contained in:
Vyctor H. Brzezowski
2026-08-06 15:38:38 -03:00
committed by GitHub
parent 29bc11f29d
commit 871e430ef6
4 changed files with 101 additions and 16 deletions
@@ -129,7 +129,7 @@ describe("HomeListingSection", () => {
expect(screen.queryByRole("button", { name: "Grid view" })).toBeNull();
});
it("identifies skills.sh rows by their source owner and upstream install count", async () => {
it("identifies skills.sh rows without presenting upstream installs as downloads", async () => {
const external = {
...makeTrending("reddit-automation", "reddit-automation", 0, 12_345, 0),
id: "skills-sh:doany-skills/skills/reddit-automation",
@@ -161,10 +161,11 @@ describe("HomeListingSection", () => {
expect(screen.getByText("@doany-skills")).toBeTruthy();
const sourceBadge = screen.getByText("skills.sh");
const downloads = screen.getByLabelText("Downloads");
expect(downloads.firstElementChild).toBe(sourceBadge);
const source = screen.getByLabelText("Source");
expect(source.firstElementChild).toBe(sourceBadge);
expect(sourceBadge.getAttribute("title")).toBeNull();
expect(downloads.textContent).toContain("12.3k");
expect(source.textContent).toBe("skills.sh");
expect(screen.queryByText("12.3k")).toBeNull();
fireEvent.pointerMove(sourceBadge);
expect((await screen.findByRole("tooltip")).textContent).toBe("Synced from skills.sh");
@@ -202,7 +203,7 @@ describe("HomeListingSection", () => {
const sourceBadge = screen.getByText("skills.sh");
expect(sourceBadge.getAttribute("title")).toBeNull();
expect(screen.getByLabelText("Downloads").textContent).toBe("skills.sh");
expect(screen.getByLabelText("Source").textContent).toBe("skills.sh");
});
it("hides unavailable Trending and falls back to the Featured feed", async () => {
+63 -1
View File
@@ -2,6 +2,8 @@
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
import type { ReactNode } from "react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { TooltipProvider } from "../components/ui/tooltip";
import type { CanonicalTrendingItem } from "../lib/trendingApi";
import { Route as SkillsRoute, SkillsIndex } from "../routes/skills/index";
import {
convexHttpMock,
@@ -153,6 +155,41 @@ describe("SkillsIndex", () => {
expect(screen.queryByLabelText("24-hour installs")).toBeNull();
});
it("shows skills.sh provenance without presenting lifetime installs as downloads", async () => {
fetchCanonicalTrendingPageMock.mockResolvedValue(
canonicalPage([makeSkillsShTrending("external", "External Skill", 12_345)]),
);
render(
<TooltipProvider delayDuration={0}>
<SkillsIndex />
</TooltipProvider>,
);
expect(await screen.findByText("External Skill")).toBeTruthy();
expect(screen.getByLabelText("Source").textContent).toBe("skills.sh");
expect(screen.queryByLabelText("24-hour downloads")).toBeNull();
expect(screen.queryByText("12.3k")).toBeNull();
});
it("keeps skills.sh provenance visible in the Trending grid", async () => {
searchMock = { view: "grid" };
fetchCanonicalTrendingPageMock.mockResolvedValue(
canonicalPage([makeSkillsShTrending("external", "External Skill", 12_345)]),
);
render(
<TooltipProvider delayDuration={0}>
<SkillsIndex />
</TooltipProvider>,
);
expect(await screen.findByText("External Skill")).toBeTruthy();
expect(screen.getByLabelText("Source").textContent).toBe("skills.sh");
expect(screen.queryByLabelText("24-hour downloads")).toBeNull();
expect(screen.queryByText("12.3k")).toBeNull();
});
it("hides disabled Trending and falls back to the Featured feed", async () => {
fetchCatalogDiscoveryCapabilitiesMock.mockResolvedValue({
apiVersion: 0,
@@ -448,7 +485,7 @@ function getLastListPageArgs() {
return (call?.[1] ?? {}) as Record<string, unknown>;
}
function canonicalPage(items: ReturnType<typeof makeTrending>[], nextCursor: string | null = null) {
function canonicalPage(items: CanonicalTrendingItem[], nextCursor: string | null = null) {
return {
kind: "skills" as const,
snapshotId: "snapshot-1",
@@ -496,6 +533,31 @@ function makeTrending(
};
}
function makeSkillsShTrending(slug: string, displayName: string, lifetime: number) {
return {
...makeTrending(slug, displayName, 0, lifetime, 0),
id: `skills-sh:example/skills/${slug}`,
source: "skills-sh" as const,
canonicalUrl: `/skills-sh/example/skills/${slug}`,
publisher: null,
sourceIdentity: {
id: `example/skills/${slug}`,
owner: "example",
repo: "skills",
host: null,
lifetimeInstalls: lifetime,
},
metrics: {
trending24hDownloads: null,
trending24hInstalls: null,
trending24hBookmarks: null,
lifetimeInstalls: lifetime,
lifetimeInstallsPeriod: "lifetime" as const,
updatedAt: 1,
},
};
}
function makeListResult(slug: string, displayName: string) {
return {
skill: {
+1 -7
View File
@@ -130,7 +130,6 @@ function HomeListingSkillRow({ entry }: { entry: SkillPageEntry }) {
const owner = isSkillsSh
? (item.sourceIdentity?.owner ?? item.sourceIdentity?.host)
: item.publisher?.handle;
const upstreamInstalls = item.sourceIdentity?.lifetimeInstalls ?? item.metrics.lifetimeInstalls;
return (
<Link to={item.canonicalUrl} className="home-v2-listing-row">
<div className="home-v2-listing-row-body">
@@ -145,7 +144,7 @@ function HomeListingSkillRow({ entry }: { entry: SkillPageEntry }) {
</p>
</div>
{isSkillsSh ? (
<div className="home-v2-listing-row-stats is-skills-sh" aria-label="Downloads">
<div className="home-v2-listing-row-stats is-skills-sh" aria-label="Source">
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="compact" size="sm">
@@ -156,11 +155,6 @@ function HomeListingSkillRow({ entry }: { entry: SkillPageEntry }) {
Synced from skills.sh
</TooltipContent>
</Tooltip>
{typeof upstreamInstalls === "number" ? (
<span title={`${upstreamInstalls.toLocaleString()} skills.sh installs`}>
{formatCompactStat(upstreamInstalls)}
</span>
) : null}
</div>
) : typeof item.metrics.trending24hDownloads === "number" ? (
<div className="home-v2-listing-row-stats" aria-label="Downloads">
+31 -3
View File
@@ -8,6 +8,7 @@ import { SkillListItem } from "../../components/SkillListItem";
import { SkillStatsTripletLine } from "../../components/SkillStats";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "../../components/ui/tooltip";
import { getSkillBadges } from "../../lib/badges";
import { formatCompactStat } from "../../lib/numberFormat";
import { timeAgo } from "../../lib/timeAgo";
@@ -58,8 +59,22 @@ function TrendingSkillListItem({ item }: { item: TrendingSkillListEntry }) {
<p className="skill-list-item-summary">{truncateText(trending.summary, 80)}</p>
) : null}
</div>
<div className="skill-list-item-meta" aria-label="24-hour downloads">
{typeof trending.metrics.trending24hDownloads === "number" ? (
<div
className="skill-list-item-meta"
aria-label={trending.source === "skills-sh" ? "Source" : "24-hour downloads"}
>
{trending.source === "skills-sh" ? (
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="compact" size="sm">
skills.sh
</Badge>
</TooltipTrigger>
<TooltipContent side="top" align="center">
Synced from skills.sh
</TooltipContent>
</Tooltip>
) : typeof trending.metrics.trending24hDownloads === "number" ? (
<span className="skill-list-item-meta-item">
<Download size={14} aria-hidden="true" />
{formatCompactStat(trending.metrics.trending24hDownloads)}
@@ -94,7 +109,20 @@ function TrendingSkillCard({ item }: { item: TrendingSkillListEntry }) {
{trending.summary}
</p>
) : null}
{typeof trending.metrics.trending24hDownloads === "number" ? (
{trending.source === "skills-sh" ? (
<div className="skill-card-grid-meta" aria-label="Source">
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="compact" size="sm">
skills.sh
</Badge>
</TooltipTrigger>
<TooltipContent side="top" align="center">
Synced from skills.sh
</TooltipContent>
</Tooltip>
</div>
) : typeof trending.metrics.trending24hDownloads === "number" ? (
<div className="skill-card-grid-meta" aria-label="24-hour downloads">
<span>
<Download size={14} aria-hidden="true" />