diff --git a/convex/featuredPublicOrder.runtime.test.ts b/convex/featuredPublicOrder.runtime.test.ts
new file mode 100644
index 0000000000..4f9d2b73d2
--- /dev/null
+++ b/convex/featuredPublicOrder.runtime.test.ts
@@ -0,0 +1,165 @@
+///
+/* @vitest-environment edge-runtime */
+import { convexTest } from "convex-test";
+import { expect, it } from "vitest";
+import { api } from "./_generated/api";
+import { syncPackageSearchDigestForPackageId } from "./functions";
+import schema from "./schema";
+
+const modules = import.meta.glob("./**/*.ts");
+it("public Featured reads follow approved identity order and omit unbadged reservations", async () => {
+ const t = convexTest(schema, modules);
+ const data = await t.run(async (ctx) => {
+ const ownerUserId = await ctx.db.insert("users", { handle: "curator", role: "moderator" });
+ const publisher = await ctx.db.insert("publishers", {
+ kind: "user",
+ displayName: "Curator",
+ handle: "curator",
+ linkedUserId: ownerUserId,
+ createdAt: 1,
+ updatedAt: 1,
+ });
+ await ctx.db.patch(ownerUserId, { personalPublisherId: publisher });
+ const items = [];
+ for (const name of ["telemetry", "editorial", "manual"]) {
+ const packageId = await ctx.db.insert("packages", {
+ name,
+ normalizedName: name,
+ displayName: name,
+ family: "code-plugin",
+ ownerUserId,
+ ownerPublisherId: publisher,
+ channel: "community",
+ isOfficial: false,
+ categories: ["developer-tools"],
+ tags: {},
+ stats: { downloads: 1, installs: 1, stars: 0, versions: 1 },
+ createdAt: 1,
+ updatedAt: 1,
+ });
+ const skillId = await ctx.db.insert("skills", {
+ slug: name,
+ displayName: name,
+ ownerUserId,
+ ownerPublisherId: publisher,
+ moderationStatus: "active",
+ tags: {},
+ badges: {},
+ stats: { comments: 0, downloads: 1, stars: 0, versions: 1 },
+ createdAt: 1,
+ updatedAt: 1,
+ });
+ const storageId = await ctx.storage.store(new Blob(["x"]));
+ const file = { path: "SKILL.md", size: 1, storageId, sha256: "a".repeat(64) };
+ const releaseId = await ctx.db.insert("packageReleases", {
+ packageId,
+ version: "1.0.0",
+ changelog: "Fixture",
+ distTags: ["latest"],
+ files: [{ ...file, path: "index.js" }],
+ integritySha256: "a".repeat(64),
+ verification: { tier: "structural", scope: "artifact-only", scanStatus: "clean" },
+ createdBy: ownerUserId,
+ createdAt: 1,
+ });
+ await ctx.db.patch(packageId, {
+ latestReleaseId: releaseId,
+ scanStatus: "clean",
+ tags: { latest: releaseId },
+ });
+ await syncPackageSearchDigestForPackageId(ctx, packageId);
+ const versionId = await ctx.db.insert("skillVersions", {
+ skillId,
+ version: "1.0.0",
+ changelog: "Fixture",
+ files: [file],
+ parsed: { frontmatter: {} },
+ createdBy: ownerUserId,
+ createdAt: 1,
+ llmAnalysis: { status: "clean", checkedAt: 1 },
+ });
+ await ctx.db.patch(skillId, { latestVersionId: versionId, tags: { latest: versionId } });
+ items.push({ name, packageId, skillId });
+ }
+ return { ownerUserId, items };
+ });
+ const staff = t.withIdentity({ subject: `${data.ownerUserId}|session` });
+ for (const item of data.items) {
+ await staff.mutation(api.packages.setBatch, {
+ packageId: item.packageId,
+ batch: "highlighted",
+ });
+ await staff.mutation(api.skills.setBatch, { skillId: item.skillId, batch: "highlighted" });
+ }
+ await t.run(async (ctx) => {
+ for (const artifactKind of ["plugin", "skill"] as const) {
+ await ctx.db.insert("featuredSelections", {
+ artifactKind,
+ revision: 1,
+ editorial:
+ artifactKind === "plugin"
+ ? [
+ {
+ id: "plugin:pending",
+ name: "pending",
+ displayName: "Pending",
+ reason: "Awaiting publication",
+ },
+ ]
+ : [],
+ updatedAt: 1,
+ updatedBy: data.ownerUserId,
+ published: {
+ reportId: "retained-report-fixture",
+ reportHash: "retained-evidence-hash",
+ at: 1,
+ byUserId: data.ownerUserId,
+ periodStart: 0,
+ periodEnd: 1,
+ items: [data.items[1], data.items[0]].map((item) => ({
+ id: artifactKind === "plugin" ? `plugin:${item.name}` : `clawhub:${item.skillId}`,
+ version: "1.0.0",
+ selectionBasis: "telemetry" as const,
+ reason: "Reviewed",
+ })),
+ },
+ });
+ }
+ });
+ const expected = ["editorial", "telemetry", "manual"];
+ const plugins = await t.query(api.packages.listPublicPage, {
+ highlightedOnly: true,
+ paginationOpts: { cursor: null, numItems: 16 },
+ });
+ expect(plugins.page.map((item) => item.name)).toEqual(expected);
+ const skills = await t.query(api.skills.listPublicPageV4, {
+ highlightedOnly: true,
+ numItems: 16,
+ });
+ expect(skills.page.map((item) => item.skill.slug)).toEqual(expected);
+ expect(
+ (await t.query(api.skills.listHighlightedPublic, { limit: 16 })).map((item) => item.skill.slug),
+ ).toEqual(expected);
+ expect(
+ (await t.query(api.skills.listWithLatest, { batch: "highlighted", limit: 16 })).map(
+ (item) => item.skill.slug,
+ ),
+ ).toEqual(expected);
+ const skillPackages = await t.query(api.skills.listPackageCatalogPage, {
+ highlightedOnly: true,
+ paginationOpts: { cursor: null, numItems: 16 },
+ });
+ expect(skillPackages.page.map((item) => item.name)).toEqual(expected);
+ let cursor: string | null = null;
+ const paginated: string[] = [];
+ for (let index = 0; index < expected.length; index++) {
+ const page: typeof skillPackages = await t.query(api.skills.listPackageCatalogPage, {
+ highlightedOnly: true,
+ paginationOpts: { cursor, numItems: 1 },
+ });
+ paginated.push(...page.page.map((item) => item.name));
+ expect(page.isDone).toBe(index === expected.length - 1);
+ cursor = page.continueCursor;
+ }
+ expect(paginated).toEqual(expected);
+});
diff --git a/convex/lib/featuredSelections.ts b/convex/lib/featuredSelections.ts
index e4b2d76426..e234f14b25 100644
--- a/convex/lib/featuredSelections.ts
+++ b/convex/lib/featuredSelections.ts
@@ -62,3 +62,17 @@ export async function readPublishedFeaturedOrder(
.unique();
return selection?.published?.items.map((item) => item.id) ?? [];
}
+
+// The approved snapshot owns order; legacy/manual badges absent from it retain
+// their existing stable order after the published selection.
+export function orderPublishedFeatured(
+ items: readonly T[],
+ publishedIds: readonly string[],
+ identity: (item: T) => string,
+): T[] {
+ const ranks = new Map(publishedIds.map((id, index) => [id, index]));
+ return [...items].sort(
+ (left, right) =>
+ (ranks.get(identity(left)) ?? Infinity) - (ranks.get(identity(right)) ?? Infinity),
+ );
+}
diff --git a/convex/packages.public.test.ts b/convex/packages.public.test.ts
index bc3d5a780a..abe6577d20 100644
--- a/convex/packages.public.test.ts
+++ b/convex/packages.public.test.ts
@@ -1626,6 +1626,8 @@ function makeDigestCtx(options: {
return null;
}),
query: vi.fn((table: string) => {
+ if (table === "featuredSelections")
+ return { withIndex: vi.fn(() => ({ unique: vi.fn().mockResolvedValue(null) })) };
if (table === "packageBadges") {
return {
withIndex: vi.fn(() => ({
diff --git a/convex/packages.ts b/convex/packages.ts
index dac4f24b99..4e917fd1fe 100644
--- a/convex/packages.ts
+++ b/convex/packages.ts
@@ -70,6 +70,7 @@ import {
} from "./lib/emails";
import { experimentalClawsEnabled, isClawFamilyPubliclyVisible } from "./lib/experimentalClaws";
import { assertFeaturedCapacity } from "./lib/featuredPolicy";
+import { orderPublishedFeatured, readPublishedFeaturedOrder } from "./lib/featuredSelections";
import { requireGitHubAccountAge } from "./lib/githubAccount";
import { normalizeGitHubRepository } from "./lib/githubActionsOidc";
import { readGlobalPublicPluginsCount } from "./lib/globalStats";
@@ -2872,14 +2873,17 @@ async function fetchHighlightedPackagePage(
numItems: number;
},
) {
- const entries = await fetchHighlightedPackageEntries(ctx, args);
+ const entries = orderPublishedFeatured(
+ await fetchHighlightedPackageEntries(ctx, args),
+ await readPublishedFeaturedOrder(ctx, "plugin"),
+ ({ digest }) => `plugin:${digest.name}`,
+ );
const items = await Promise.all(
entries.map(
async ({ digest, featuredAt }) => await toPublicPackageListItem(ctx, digest, featuredAt),
),
);
- // fetchHighlightedPackageEntries follows the badge timestamp index newest-first.
- // Preserve that editorial order instead of re-ranking Featured by popularity.
+ // The published selection supplies order independently from badge timestamps.
if (!args.officialFirst) {
return items.slice(0, args.numItems);
}
diff --git a/convex/skills.listPublicPageV4.test.ts b/convex/skills.listPublicPageV4.test.ts
index c2b12170cf..5a728f87a8 100644
--- a/convex/skills.listPublicPageV4.test.ts
+++ b/convex/skills.listPublicPageV4.test.ts
@@ -105,7 +105,7 @@ describe("skills.listPublicPageV4", () => {
});
});
- it("keeps highlighted results in newest-featured order", async () => {
+ it("keeps legacy highlighted results in newest-featured order without a published snapshot", async () => {
const result = await listPublicPageV4Handler(
makeHighlightedCtx([
makeDigest({
@@ -220,6 +220,9 @@ function makeHighlightedCtx(digests: Array>) {
return {
db: {
query: vi.fn((table: string) => {
+ if (table === "featuredSelections") {
+ return { withIndex: vi.fn(() => ({ unique: vi.fn().mockResolvedValue(null) })) };
+ }
if (table === "skillBadges") {
return {
withIndex: vi.fn((_indexName: string, build: (q: EqBuilder) => unknown) => {
diff --git a/convex/skills.ts b/convex/skills.ts
index 16ac1b0259..47b0e5c49f 100644
--- a/convex/skills.ts
+++ b/convex/skills.ts
@@ -53,6 +53,7 @@ import {
} from "./lib/downloadTrend";
import { embeddingVisibilityFor } from "./lib/embeddingVisibility";
import { assertFeaturedCapacity } from "./lib/featuredPolicy";
+import { orderPublishedFeatured, readPublishedFeaturedOrder } from "./lib/featuredSelections";
import {
canHealSkillOwnershipByGitHubProviderAccountId,
getGitHubProviderAccountId,
@@ -2366,8 +2367,13 @@ async function loadHighlightedSkills(ctx: QueryCtx, limit: number) {
.order("desc")
.take(MAX_LIST_TAKE);
+ const ordered = orderPublishedFeatured(
+ entries,
+ await readPublishedFeaturedOrder(ctx, "skill"),
+ (badge) => `clawhub:${badge.skillId}`,
+ );
const skills: Doc<"skills">[] = [];
- for (const badge of entries) {
+ for (const badge of ordered) {
const skill = await ctx.db.get(badge.skillId);
if (!skill || skill.softDeletedAt) continue;
skills.push(skill);
@@ -3938,13 +3944,7 @@ export const listWithLatest = query({
entries.filter((skill) => !skill.softDeletedAt),
);
const withBadges = await attachBadgesToSkills(ctx, filtered);
- const ordered =
- args.batch === "highlighted"
- ? [...withBadges].sort(
- (a, b) => (b.badges?.highlighted?.at ?? 0) - (a.badges?.highlighted?.at ?? 0),
- )
- : withBadges;
- const limited = ordered.slice(0, limit);
+ const limited = withBadges.slice(0, limit);
const items = await Promise.all(
limited.map(async (skill) => {
const latestVersion = await loadPublicLatestVersionForSkill(ctx, skill);
@@ -6714,6 +6714,32 @@ export const listPackageCatalogPage = query({
if (args.topic !== undefined && !topic) {
return { page: [], isDone: true, continueCursor: "" };
}
+ if (args.highlightedOnly) {
+ const skills = await loadHighlightedSkills(ctx, MAX_LIST_TAKE);
+ const page: PublicSkillCatalogItem[] = [];
+ for (const skill of skills) {
+ const digest = await ctx.db
+ .query("skillSearchDigest")
+ .withIndex("by_skill", (q) => q.eq("skillId", skill._id))
+ .unique();
+ if (!digest || !skillCatalogMatchesFilters(digest, { ...args, topic })) continue;
+ const item = await toPublicSkillCatalogItem(ctx, digest);
+ if (item) page.push(item);
+ }
+ const { offset } = decodeSkillCatalogCursor(args.paginationOpts.cursor);
+ const end = offset + args.paginationOpts.numItems;
+ const isDone = end >= page.length;
+ return {
+ page: page.slice(offset, end),
+ isDone,
+ continueCursor: encodeSkillCatalogCursor({
+ cursor: null,
+ offset: isDone ? 0 : end,
+ pageSize: null,
+ done: isDone,
+ }),
+ };
+ }
if (topic) {
return await listSkillPackageCatalogTopicPage(ctx, {
...args,
@@ -7481,7 +7507,7 @@ async function listOfficialFirstSkillCategoryPage(
};
}
-/** Fetch highlighted skills newest-first via the skillBadges timestamp index. */
+/** Resolve current highlighted membership in its approved publication order. */
async function fetchHighlightedPage(
ctx: QueryCtx,
opts: {
@@ -7524,7 +7550,11 @@ async function fetchHighlightedPage(
digests.push(digest);
}
- const trimmed = digests.slice(0, opts.numItems);
+ const trimmed = orderPublishedFeatured(
+ digests,
+ await readPublishedFeaturedOrder(ctx, "skill"),
+ (digest) => `clawhub:${digest.skillId}`,
+ ).slice(0, opts.numItems);
const items: PublicSkillEntry[] = [];
for (const digest of trimmed) {
diff --git a/convex/skills.versions.public.test.ts b/convex/skills.versions.public.test.ts
index 7f282462f4..37cf1a09f6 100644
--- a/convex/skills.versions.public.test.ts
+++ b/convex/skills.versions.public.test.ts
@@ -957,7 +957,7 @@ describe("public skill version queries", () => {
const ctx = {
db: {
query: vi.fn((table: string) => {
- if (table === "officialPublishers") {
+ if (table === "officialPublishers" || table === "featuredSelections") {
return {
withIndex: vi.fn(() => ({
unique: vi.fn().mockResolvedValue(null),
diff --git a/src/__tests__/home-listing-section.claw591.test.tsx b/src/__tests__/home-listing-section.claw591.test.tsx
index bfa1832161..90687cb200 100644
--- a/src/__tests__/home-listing-section.claw591.test.tsx
+++ b/src/__tests__/home-listing-section.claw591.test.tsx
@@ -385,7 +385,7 @@ describe("HomeListingSection", () => {
await waitFor(() => {
expect(convexQueryMock).toHaveBeenCalledWith(
"skills:listPublicPageV4",
- expect.objectContaining({ highlightedOnly: true, numItems: 40 }),
+ expect.objectContaining({ highlightedOnly: true, numItems: 16 }),
);
});
diff --git a/src/lib/homeListingData.claw591.test.ts b/src/lib/homeListingData.claw591.test.ts
index 9c308d500a..d93bbae85d 100644
--- a/src/lib/homeListingData.claw591.test.ts
+++ b/src/lib/homeListingData.claw591.test.ts
@@ -219,7 +219,7 @@ describe("homeListingData", () => {
]);
expect(convexQueryMock).toHaveBeenCalledWith(
"skills:listPublicPageV4",
- expect.objectContaining({ highlightedOnly: true, numItems: 40 }),
+ expect.objectContaining({ highlightedOnly: true, numItems: 16 }),
);
});
@@ -260,11 +260,11 @@ describe("homeListingData", () => {
);
expect(fetchPluginCatalogMock).toHaveBeenNthCalledWith(
1,
- expect.objectContaining({ featured: true, isOfficial: undefined }),
+ expect.objectContaining({ featured: true, limit: 16 }),
);
expect(fetchPluginCatalogMock).toHaveBeenNthCalledWith(
2,
- expect.objectContaining({ featured: undefined, isOfficial: true }),
+ expect.objectContaining({ isOfficial: true }),
);
});
diff --git a/src/lib/homeListingData.test.ts b/src/lib/homeListingData.test.ts
index 47fb67e3a5..fc79122e0f 100644
--- a/src/lib/homeListingData.test.ts
+++ b/src/lib/homeListingData.test.ts
@@ -76,111 +76,73 @@ describe("homeListingData", () => {
"skills:listPublicPageV4",
expect.objectContaining({
highlightedOnly: true,
- numItems: 40,
- sort: "updated",
+ numItems: 16,
}),
);
});
- it("sorts filtered Featured skills newest-first by featuredAt", async () => {
- convexQueryMock
- .mockResolvedValueOnce({
- page: [
- {
- skill: {
- _id: "skills:older",
- slug: "older",
- displayName: "Older Featured",
- categories: ["development"],
- badges: { highlighted: { at: 100 } },
- stats: { downloads: 10_000 },
- },
+ it("preserves the published Featured skill order while filtering across categories", async () => {
+ convexQueryMock.mockResolvedValue({
+ page: [
+ {
+ skill: {
+ _id: "skills:editorial",
+ slug: "editorial",
+ categories: ["development"],
+ badges: { highlighted: { at: 100 } },
+ stats: { downloads: 1 },
},
- ],
- hasMore: false,
- nextCursor: null,
- })
- .mockResolvedValueOnce({
- page: [
- {
- skill: {
- _id: "skills:newest",
- slug: "newest",
- displayName: "Newest Featured",
- categories: ["integrations"],
- badges: { highlighted: { at: 200 } },
- stats: { downloads: 1 },
- },
+ },
+ {
+ skill: {
+ _id: "skills:excluded",
+ slug: "excluded",
+ categories: ["writing"],
+ badges: { highlighted: { at: 500 } },
+ stats: { downloads: 1000 },
},
- ],
- hasMore: false,
- nextCursor: null,
- });
-
+ },
+ {
+ skill: {
+ _id: "skills:telemetry",
+ slug: "telemetry",
+ categories: ["integrations"],
+ badges: { highlighted: { at: 200 } },
+ stats: { downloads: 10000 },
+ },
+ },
+ ],
+ hasMore: false,
+ nextCursor: null,
+ });
const result = await fetchHomeSkillListing(
"featured",
["development", "integrations"],
HOME_LISTING_PAGE_SIZE,
);
-
expect(
result.page.map((entry) => ("skill" in entry ? entry.skill.slug : entry.trending.slug)),
- ).toEqual(["newest", "older"]);
- expect(convexQueryMock).toHaveBeenCalledTimes(2);
- expect(convexQueryMock).toHaveBeenNthCalledWith(
- 1,
- "skills:listPublicPageV4",
- expect.objectContaining({ categorySlug: "development" }),
- );
- expect(convexQueryMock).toHaveBeenNthCalledWith(
- 2,
- "skills:listPublicPageV4",
- expect.objectContaining({ categorySlug: "integrations" }),
- );
+ ).toEqual(["editorial", "telemetry"]);
+ expect(result.hasMore).toBe(false);
+ expect(convexQueryMock).toHaveBeenCalledTimes(1);
});
- it("sorts filtered Featured plugins newest-first by featuredAt", async () => {
- fetchPluginCatalogMock
- .mockResolvedValueOnce({
- items: [
- {
- ...featuredPlugin,
- name: "older",
- categories: ["tools"],
- featuredAt: 100,
- stats: { downloads: 10_000 },
- },
- ],
- nextCursor: null,
- })
- .mockResolvedValueOnce({
- items: [
- {
- ...featuredPlugin,
- name: "newest",
- categories: ["gateway"],
- featuredAt: 200,
- stats: { downloads: 1 },
- },
- ],
- nextCursor: null,
- });
-
+ it("preserves the published Featured plugin order while filtering across categories", async () => {
+ fetchPluginCatalogMock.mockResolvedValue({
+ items: [
+ { ...featuredPlugin, name: "editorial", categories: ["tools"], featuredAt: 100 },
+ { ...featuredPlugin, name: "excluded", categories: ["other"], featuredAt: 500 },
+ { ...featuredPlugin, name: "telemetry", categories: ["gateway"], featuredAt: 200 },
+ ],
+ nextCursor: null,
+ });
const result = await fetchHomePluginListing(
"featured",
["tools", "gateway"],
HOME_LISTING_PAGE_SIZE,
);
-
- expect(result.items.map((item) => item.name)).toEqual(["newest", "older"]);
- expect(fetchPluginCatalogMock).toHaveBeenCalledTimes(2);
- expect(fetchPluginCatalogMock).toHaveBeenNthCalledWith(
- 1,
- expect.objectContaining({ category: "tools" }),
- );
- expect(fetchPluginCatalogMock).toHaveBeenNthCalledWith(
- 2,
- expect.objectContaining({ category: "gateway" }),
- );
+ expect(result.items.map((item) => item.name)).toEqual(["editorial", "telemetry"]);
+ expect(result.hasMore).toBe(false);
+ expect(fetchPluginCatalogMock).toHaveBeenCalledTimes(1);
});
});
diff --git a/src/lib/homeListingData.ts b/src/lib/homeListingData.ts
index fee9eddd5f..cf74ac37ed 100644
--- a/src/lib/homeListingData.ts
+++ b/src/lib/homeListingData.ts
@@ -1,9 +1,10 @@
import { api } from "../../convex/_generated/api";
+import { FEATURED_CATALOG_SIZE } from "../../convex/lib/featuredPolicy";
import { convexHttp } from "../convex/client";
import { fetchCatalogDiscoveryCapabilities } from "./catalogDiscoveryCapabilities";
import { getSkillCategoriesForSkill } from "./categories";
import { fetchPluginCatalog, type PackageListItem } from "./packageApi";
-import type { PublicSkill, PublicUser } from "./publicUser";
+import type { PublicPublisher, PublicSkill, PublicUser } from "./publicUser";
import {
fetchCanonicalTrendingPage,
type CanonicalTrendingItem,
@@ -17,7 +18,7 @@ export type { TrendingFeedState } from "./trendingApi";
export type HomeNativeSkillListingEntry = {
skill: PublicSkill;
ownerHandle?: string | null;
- owner?: PublicUser | null;
+ owner?: PublicUser | PublicPublisher | null;
};
type HomeTrendingSkillListingEntry = {
@@ -66,8 +67,6 @@ import { DISCOVERY_RECENT_WINDOW_MS as HOME_NEW_WINDOW_MS } from "../../convex/l
const PLUGIN_CATALOG_PAGE_LIMIT = 100;
const LEGACY_NEW_PLUGIN_MAX_REQUESTS = 10;
const TRENDING_SEARCH_PAGE_LIMIT = 100;
-// Featured is intentionally a finite editorial feed: the latest 40 badge-history rows.
-const FEATURED_SKILL_LIMIT = 40;
export function homeListingCacheKey({
kind,
@@ -235,12 +234,21 @@ export async function fetchHomeSkillListing(
};
}
- // highlightedOnly is a dedicated backend path ordered by skillBadges.by_kind_at;
- // the nominal sort below is ignored for Featured and never chooses its candidate set.
+ if (tab === "featured") {
+ // Filter the finite published selection once, preserving order across categories.
+ const result = await convexHttp.query(api.skills.listPublicPageV4, {
+ numItems: FEATURED_CATALOG_SIZE,
+ highlightedOnly: true,
+ });
+ const items = result.page.filter((entry) =>
+ skillMatchesAnyHomeCategory(entry.skill, categorySlugs),
+ );
+ return { page: items.slice(0, numItems), hasMore: items.length > numItems };
+ }
const capabilities =
tab === "new" ? await fetchCatalogDiscoveryCapabilities() : { apiVersion: 1 as const };
const newestCutoff = Date.now() - HOME_NEW_WINDOW_MS;
- const requestLimit = tab === "featured" ? FEATURED_SKILL_LIMIT : numItems;
+ const requestLimit = numItems;
const categoriesToFetch = categorySlugs.length > 0 ? categorySlugs : [null];
const results = await Promise.all(
categoriesToFetch.map(async (categorySlug) => {
@@ -254,7 +262,6 @@ export async function fetchHomeSkillListing(
numItems: requestLimit - page.length,
sort: tab === "new" || tab === "official" ? "newest" : "updated",
dir: "desc",
- highlightedOnly: tab === "featured" ? true : undefined,
officialOnly: tab === "official" ? true : undefined,
...(tab === "new" && capabilities.apiVersion >= 1 ? { createdAfter: newestCutoff } : {}),
categorySlug: categorySlug ?? undefined,
@@ -288,11 +295,6 @@ export async function fetchHomeSkillListing(
);
const items = uniqueHomeSkillEntries(results.flatMap((result) => result.page)).sort(
(left, right) => {
- if (tab === "featured") {
- return (
- (right.skill.badges?.highlighted?.at ?? 0) - (left.skill.badges?.highlighted?.at ?? 0)
- );
- }
if (tab === "new" || tab === "official") {
return right.skill.createdAt - left.skill.createdAt;
}
@@ -301,11 +303,7 @@ export async function fetchHomeSkillListing(
);
return {
page: items.slice(0, numItems),
- hasMore:
- tab === "featured"
- ? numItems < FEATURED_SKILL_LIMIT &&
- (items.length > numItems || results.some((result) => result.hasMore))
- : items.length > numItems || results.some((result) => result.hasMore),
+ hasMore: items.length > numItems || results.some((result) => result.hasMore),
};
}
@@ -315,6 +313,15 @@ export async function fetchHomePluginListing(
limit: number,
signal?: AbortSignal,
) {
+ if (tab === "featured") {
+ const result = await fetchPluginCatalog({
+ featured: true,
+ limit: FEATURED_CATALOG_SIZE,
+ signal,
+ });
+ const items = result.items.filter((item) => itemMatchesAnyHomeCategory(item, categorySlugs));
+ return { items: items.slice(0, limit), hasMore: items.length > limit };
+ }
const categoriesToFetch = categorySlugs.length > 0 ? categorySlugs : [null];
const newestCutoff = Date.now() - HOME_NEW_WINDOW_MS;
if (tab === "new") {
@@ -417,7 +424,6 @@ export async function fetchHomePluginListing(
const result = await fetchPluginCatalog({
category: categorySlug ?? undefined,
cursor: cursor ?? undefined,
- featured: tab === "featured" ? true : undefined,
isOfficial: tab === "official" ? true : undefined,
sort: "updated",
limit: Math.min(limit - items.length, PLUGIN_CATALOG_PAGE_LIMIT),
@@ -433,7 +439,6 @@ export async function fetchHomePluginListing(
}),
);
const items = uniqueHomePlugins(results.flatMap((result) => result.items)).sort((left, right) => {
- if (tab === "featured") return (right.featuredAt ?? 0) - (left.featuredAt ?? 0);
return right.updatedAt - left.updatedAt;
});
return {
diff --git a/src/styles.css b/src/styles.css
index a7f1547d02..6207e6ccd1 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -23463,6 +23463,9 @@ a.search-empty-action {
}
.home-v2-listing-row-by {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
font-family: var(--font-mono), ui-monospace, monospace;
font-size: 12px;
font-weight: 500;