feat(app-store): sort grid by stars and rotate featured weekly (#787)

Grid templates are now sorted by star count (descending) so popular
apps surface naturally rather than appearing in registry fetch order.

Featured hero rotates weekly among the top 5 starred apps instead of
always pinning the single highest-starred entry. Rotation is seeded
by week number so all nodes show the same featured app throughout a
given week. Registries with no star data gracefully skip the featured
hero and preserve their natural ordering.
This commit is contained in:
Anso
2026-04-26 18:56:09 -04:00
committed by GitHub
parent f94b2ce85c
commit dcf8794047
2 changed files with 13 additions and 11 deletions
+9 -9
View File
@@ -25,15 +25,15 @@ templatesRouter.get('/', authMiddleware, async (req: Request, res: Response) =>
const imageRefs = templates.map(t => t.image).filter((i): i is string => !!i);
const scanSummary = DatabaseService.getInstance().getLatestScanSummaryByImageRefs(req.nodeId, imageRefs);
let featuredIndex = -1;
let featuredStars = 0;
templates.forEach((t, i) => {
const s = t.stars ?? 0;
if (s > featuredStars) {
featuredStars = s;
featuredIndex = i;
}
});
const topCandidates = templates
.map((t, i) => ({ t, i }))
.filter(({ t }) => (t.stars ?? 0) > 0)
.sort((a, b) => (b.t.stars ?? 0) - (a.t.stars ?? 0))
.slice(0, 5);
const weekIndex = Math.floor(Date.now() / (7 * 86_400_000));
const featuredIndex = topCandidates.length > 0
? topCandidates[weekIndex % topCandidates.length].i
: -1;
const enriched = templates.map((t, i) => {
const summary = t.image ? scanSummary.get(t.image) : undefined;
+4 -2
View File
@@ -247,8 +247,10 @@ export function AppStoreView({ onDeploySuccess }: AppStoreViewProps) {
}, [filtered, searchQuery]);
const gridTemplates = useMemo(() => {
if (!featuredTemplate) return filtered;
return filtered.filter(t => t.title !== featuredTemplate.title);
const base = featuredTemplate
? filtered.filter(t => t.title !== featuredTemplate.title)
: filtered;
return [...base].sort((a, b) => (b.stars ?? 0) - (a.stars ?? 0));
}, [filtered, featuredTemplate]);
return (