Skip to content

Commit 9a08396

Browse files
committed
Merge feat/wave1-quality-contract: collections example + sync github fix
Two features shipped on this branch: 1. feat(collections): wire example to collection engine - Framework: 4 new components (CollectionPage, CollectionIndex, CollectionTeaser, CollectionRow) + recordsToCollectionEntries + 3 view-models + loadCollections, all re-exported from @grove-dev/astro/server. - Reference pages: /collections/[slug]/ fixed (was passing empty entries), /collections/index/ new. - Example: thin wiring in apps/example only — no new UI built in the example. 13 unit tests in collections.test.ts. 2. fix(sync): preserve curator fields and extract more GitHub metadata - buildGithubSyncPatch() merges into existing repository block instead of replacing it (curator-added fields survive). - fetchGithubMetadata 13 → 25 fields (homepage, html_url, description, created_at, watchers, size, visibility, fork, private, etc. — schema-aligned). - 4 dropped fields (updated_at, disabled, default_branch, latestReleaseAt) now written; latestReleaseAt lives flat at github.latestReleaseAt. - 5 new tests in github.test.ts. Verification: 193/193 unit tests pass; grove check 0 errors; all 4 example routes return 200. Plan: apps/docs/superpowers/plans/2026-07-25-collections-example.md (not committed per author request).
2 parents 684165b + b60452d commit 9a08396

66 files changed

Lines changed: 5373 additions & 127 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Lighthouse Audit
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "packages/core/**"
8+
- "packages/cli/**"
9+
- "packages/astro/**"
10+
- "apps/example/**"
11+
- ".github/workflows/lighthouse-audit.yml"
12+
pull_request:
13+
paths:
14+
- "packages/core/**"
15+
- "packages/cli/**"
16+
- "packages/astro/**"
17+
- "apps/example/**"
18+
- ".github/workflows/lighthouse-audit.yml"
19+
schedule:
20+
- cron: "37 6 * * 1"
21+
workflow_dispatch:
22+
23+
concurrency:
24+
group: lighthouse-audit-${{ github.ref }}
25+
cancel-in-progress: true
26+
27+
permissions:
28+
contents: read
29+
30+
jobs:
31+
audit:
32+
name: grove audit
33+
runs-on: ubuntu-latest
34+
timeout-minutes: 30
35+
steps:
36+
- uses: actions/checkout@v7
37+
- uses: actions/setup-node@v7
38+
with:
39+
node-version: 24
40+
cache: pnpm
41+
- run: corepack enable && corepack prepare pnpm@10.12.1 --activate
42+
- name: Install Chromium
43+
run: |
44+
sudo apt-get update
45+
sudo apt-get install -y chromium-browser
46+
echo "CHROME_PATH=$(which chromium-browser)" >> $GITHUB_ENV
47+
- run: pnpm install --frozen-lockfile
48+
- run: pnpm --filter @grove-dev/core --filter @grove-dev/cli --filter @grove-dev/astro -r build
49+
- run: pnpm --dir apps/example build
50+
- name: Run grove audit
51+
run: node ../../packages/cli/dist/index.js audit --runs 1 --json lighthouse-report.json --junit lighthouse-junit.xml
52+
working-directory: apps/example
53+
- uses: actions/upload-artifact@v5
54+
if: always()
55+
with:
56+
name: lighthouse-report
57+
path: |
58+
apps/example/lighthouse-report.json
59+
apps/example/lighthouse-junit.xml
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
slug: top-ai-agents
2+
kind: curated
3+
title: Top Open Source AI Agents
4+
description: Python-based agent frameworks and orchestration tools.
5+
query:
6+
stacks: [python]
7+
excludeStatuses: [archived]
8+
ranking:
9+
preset: quality
10+
editorial:
11+
selectionNote: Ranked by curation score and recent activity.
12+
seo:
13+
index: true
14+
title: Top Open Source AI Agents in 2026
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
slug: top-flutter
2+
kind: curated
3+
title: Top Open Source Flutter Apps
4+
description: Production-scale Flutter applications with public source code.
5+
query:
6+
stacks: [flutter]
7+
excludeStatuses: [archived]
8+
ranking:
9+
preset: quality
10+
seo:
11+
index: true
12+
title: Top Open Source Flutter Apps in 2026

apps/example/grove.config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export default defineConfig({
1616
nav: [
1717
{ label: "Home", href: "/" },
1818
{ label: "Browse", href: "/projects" },
19+
{ label: "Collections", href: "/collections" },
1920
{ label: "About", href: "/about" },
2021
],
2122

@@ -73,4 +74,17 @@ export default defineConfig({
7374
density: "comfortable",
7475
containerWidth: "72rem",
7576
},
77+
78+
audit: {
79+
baseUrl: "http://127.0.0.1:4321",
80+
pages: [
81+
{ path: "/", type: "home", label: "Homepage" },
82+
{ path: "/projects/", type: "directory", label: "Directory index" },
83+
{ path: "/collections/top-flutter/", type: "collection", label: "Top Flutter collection" },
84+
{ path: "/projects/open-webui/", type: "record", label: "Record detail" },
85+
{ path: "/about/", type: "content", label: "About page" },
86+
{ path: "/empty/", type: "empty", label: "Empty state" },
87+
{ path: "/this-page-does-not-exist/", type: "404", label: "404 page" },
88+
],
89+
},
7690
});

apps/example/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"start": "astro dev",
1616
"preview": "astro preview",
1717
"astro": "astro",
18-
"check": "astro check"
18+
"check": "astro check",
19+
"audit": "grove audit --runs 1"
1920
},
2021
"dependencies": {
2122
"@astrojs/check": "^0.9.9",
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
interface Props {
3+
title: string;
4+
description: string;
5+
}
6+
const { title, description } = Astro.props;
7+
const favicon =
8+
"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%2318181b'/%3E%3C/svg%3E";
9+
---
10+
<!doctype html>
11+
<html lang="en">
12+
<head>
13+
<meta charset="utf-8" />
14+
<meta name="viewport" content="width=device-width, initial-scale=1" />
15+
<title>{title}</title>
16+
<meta name="description" content={description} />
17+
<link rel="icon" href={favicon} />
18+
</head>
19+
<body>
20+
<slot />
21+
</body>
22+
</html>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
import CollectionPage from "@grove-dev/astro/components/CollectionPage.astro";
3+
import BaseLayout from "@grove-dev/astro/layouts/BaseLayout.astro";
4+
import {
5+
getCollectionPageModel,
6+
recordsToCollectionEntries,
7+
loadCollections,
8+
} from "@grove-dev/astro/server";
9+
import records from "@grove/generated/records.json";
10+
import siteConfig from "@grove/generated/site-config.json";
11+
12+
export async function getStaticPaths() {
13+
const collections = await loadCollections(process.cwd());
14+
return collections.map((c) => ({ params: { slug: c.slug } }));
15+
}
16+
17+
const { slug } = Astro.params;
18+
const collections = await loadCollections(process.cwd());
19+
const collection = collections.find((c) => c.slug === slug);
20+
if (!collection) return Astro.redirect("/404");
21+
22+
const entries = recordsToCollectionEntries(
23+
(records as { records: unknown[] }).records as never,
24+
siteConfig,
25+
);
26+
const model = getCollectionPageModel(collection, entries, collections);
27+
---
28+
<BaseLayout
29+
title={model.collection.title}
30+
description={model.collection.description}
31+
site={siteConfig}
32+
>
33+
<CollectionPage
34+
model={model}
35+
eyebrow={model.collection.kind === "curated" ? "Curated collection" : "Generated collection"}
36+
/>
37+
</BaseLayout>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
import CollectionIndex from "@grove-dev/astro/components/CollectionIndex.astro";
3+
import BaseLayout from "@grove-dev/astro/layouts/BaseLayout.astro";
4+
import {
5+
getCollectionIndexModel,
6+
recordsToCollectionEntries,
7+
loadCollections,
8+
} from "@grove-dev/astro/server";
9+
import records from "@grove/generated/records.json";
10+
import siteConfig from "@grove/generated/site-config.json";
11+
12+
const collections = await loadCollections(process.cwd());
13+
const entries = recordsToCollectionEntries(
14+
(records as { records: unknown[] }).records as never,
15+
siteConfig,
16+
);
17+
const model = getCollectionIndexModel(collections, entries);
18+
---
19+
<BaseLayout
20+
title="Collections"
21+
description="Curated and generated directories of related items."
22+
site={siteConfig}
23+
>
24+
<CollectionIndex model={model} />
25+
</BaseLayout>

apps/example/src/pages/empty.astro

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
import Base from "../layouts/Base.astro";
3+
---
4+
<Base title="No results" description="Empty state fixture.">
5+
<h1>No results</h1>
6+
<p>There are no items to show right now.</p>
7+
</Base>

apps/example/src/pages/index.astro

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,15 @@ import CategoryGrid from "@grove-dev/astro/components/CategoryGrid.astro";
2020
import ContributorsGrid from "@grove-dev/astro/components/ContributorsGrid.astro";
2121
import OriginalCollection from "@grove-dev/astro/components/OriginalCollection.astro";
2222
import FinalCta from "@grove-dev/astro/components/FinalCta.astro";
23+
import CollectionTeaser from "@grove-dev/astro/components/CollectionTeaser.astro";
2324
24-
import { getHomePageModel } from "@grove-dev/astro/server";
25+
import {
26+
getHomePageModel,
27+
getCollectionTeaserModel,
28+
recordsToCollectionEntries,
29+
loadCollections,
30+
} from "@grove-dev/astro/server";
31+
import records from "@grove/generated/records.json";
2532
import siteConfig from "@grove/generated/site-config.json";
2633
2734
const home = getHomePageModel(siteConfig);
@@ -35,6 +42,14 @@ const matureList = home.established;
3542
const { stacks, categories } = home;
3643
3744
const { contributors, stats, title, description } = home;
45+
46+
// ── Featured collections teaser ─────────────────────────────────
47+
const allCollections = await loadCollections(process.cwd());
48+
const teaserEntries = recordsToCollectionEntries(
49+
(records as { records: unknown[] }).records as never,
50+
siteConfig,
51+
);
52+
const collectionTeaser = getCollectionTeaserModel(allCollections, teaserEntries, 3);
3853
---
3954

4055
<BaseLayout title={title} description={description} site={siteConfig}>
@@ -103,6 +118,7 @@ const { contributors, stats, title, description } = home;
103118
/>
104119

105120
<StackGrid stacks={stacks} pathPrefix={`/${slug}`} itemLabelPlural={itemPlural} />
121+
<CollectionTeaser model={collectionTeaser} />
106122
<CategoryGrid categories={categories} pathPrefix={`/${slug}`} itemLabelPlural={itemPlural} />
107123

108124
<ContributorsGrid

0 commit comments

Comments
 (0)