@grove-dev/astro 0.1.5 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +4 -25
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +56 -22
  5. package/dist/index.js.map +1 -1
  6. package/dist/layouts/index.d.ts +25 -0
  7. package/dist/layouts/index.d.ts.map +1 -0
  8. package/dist/layouts/index.js +25 -0
  9. package/dist/layouts/index.js.map +1 -0
  10. package/dist/lib/display.d.ts +33 -0
  11. package/dist/lib/display.d.ts.map +1 -0
  12. package/dist/lib/display.js +104 -0
  13. package/dist/lib/display.js.map +1 -0
  14. package/dist/lib/format.d.ts +30 -0
  15. package/dist/lib/format.d.ts.map +1 -0
  16. package/dist/lib/format.js +73 -0
  17. package/dist/lib/format.js.map +1 -0
  18. package/dist/lib/index.d.ts +26 -0
  19. package/dist/lib/index.d.ts.map +1 -0
  20. package/dist/lib/index.js +26 -0
  21. package/dist/lib/index.js.map +1 -0
  22. package/dist/lib/lenses.d.ts +96 -0
  23. package/dist/lib/lenses.d.ts.map +1 -0
  24. package/dist/lib/lenses.js +181 -0
  25. package/dist/lib/lenses.js.map +1 -0
  26. package/dist/lib/repo.d.ts +30 -0
  27. package/dist/lib/repo.d.ts.map +1 -0
  28. package/dist/lib/repo.js +44 -0
  29. package/dist/lib/repo.js.map +1 -0
  30. package/dist/lib/scores.d.ts +38 -0
  31. package/dist/lib/scores.d.ts.map +1 -0
  32. package/dist/lib/scores.js +83 -0
  33. package/dist/lib/scores.js.map +1 -0
  34. package/dist/lib/search.d.ts +199 -0
  35. package/dist/lib/search.d.ts.map +1 -0
  36. package/dist/lib/search.js +357 -0
  37. package/dist/lib/search.js.map +1 -0
  38. package/dist/lib/taxonomy-counts.d.ts +25 -0
  39. package/dist/lib/taxonomy-counts.d.ts.map +1 -0
  40. package/dist/lib/taxonomy-counts.js +41 -0
  41. package/dist/lib/taxonomy-counts.js.map +1 -0
  42. package/package.json +7 -7
  43. package/src/components/AppsIndexRow.astro +204 -0
  44. package/src/components/AppsPagination.astro +126 -0
  45. package/src/components/CategoryGrid.astro +86 -17
  46. package/src/components/ContributorsGrid.astro +149 -0
  47. package/src/components/DecisionRow.astro +89 -0
  48. package/src/components/ExploreByCategory.astro +67 -0
  49. package/src/components/ExploreByStack.astro +72 -0
  50. package/src/components/Hero.astro +246 -0
  51. package/src/components/Icon.astro +223 -0
  52. package/src/components/ItemCard.astro +306 -30
  53. package/src/components/MinimalAbout.astro +82 -0
  54. package/src/components/OriginalCollection.astro +116 -0
  55. package/src/components/RecordSection.astro +88 -0
  56. package/src/components/RefinePanel.astro +197 -0
  57. package/src/components/ScoreBars.astro +92 -22
  58. package/src/components/SmartLensTabs.astro +71 -0
  59. package/src/components/StackGrid.astro +132 -0
  60. package/src/components/WhyThisExists.astro +116 -0
  61. package/src/index.ts +61 -14
  62. package/src/layouts/BaseLayout.astro +227 -15
  63. package/src/layouts/Container.astro +57 -0
  64. package/src/layouts/Footer.astro +235 -0
  65. package/src/layouts/Header.astro +159 -0
  66. package/src/layouts/SectionHeader.astro +58 -0
  67. package/{templates/default/src/components/layout → src/layouts}/Seo.astro +62 -38
  68. package/src/layouts/ThemeToggle.astro +110 -0
  69. package/src/layouts/astro-shim.d.ts +29 -0
  70. package/src/layouts/index.ts +24 -0
  71. package/src/lib/display.ts +118 -0
  72. package/src/lib/format.ts +65 -0
  73. package/src/lib/index.ts +26 -0
  74. package/src/lib/lenses.ts +241 -0
  75. package/src/lib/repo.ts +44 -0
  76. package/src/lib/scores.ts +94 -0
  77. package/src/lib/search.ts +481 -0
  78. package/src/lib/taxonomy-counts.ts +60 -0
  79. package/src/styles.css +366 -638
  80. package/templates/default/astro.config.mjs +8 -6
  81. package/templates/default/content/pages/about.md +66 -0
  82. package/templates/default/content/pages/methodology.md +103 -0
  83. package/templates/default/data/generated/records.full.json +8 -0
  84. package/templates/default/data/generated/records.index.json +7 -0
  85. package/templates/default/data/generated/records.json +8 -0
  86. package/templates/default/data/generated/site-config.json +31 -0
  87. package/templates/default/data/records/.gitkeep +0 -0
  88. package/templates/default/grove.config.ts +36 -0
  89. package/templates/default/package.json +6 -5
  90. package/templates/default/public/llms-full.txt +14 -0
  91. package/templates/default/public/llms.txt +11 -0
  92. package/templates/default/src/components/layout/BaseLayout.astro +10 -57
  93. package/templates/default/src/components/layout/Footer.astro +16 -134
  94. package/templates/default/src/components/layout/Header.astro +25 -126
  95. package/templates/default/src/data/records.ts +193 -0
  96. package/templates/default/src/lib/markdown.ts +171 -0
  97. package/templates/default/src/pages/about.astro +95 -429
  98. package/templates/default/src/pages/index.astro +263 -61
  99. package/templates/default/src/pages/projects/[slug].astro +480 -0
  100. package/templates/default/src/pages/projects/index.astro +193 -0
  101. package/templates/default/src/pages/sitemap.xml.ts +73 -61
  102. package/templates/default/src/pages/submit.astro +122 -411
  103. package/templates/default/src/styles/global.css +42 -68
  104. package/src/components/ActiveFilterChips.astro +0 -20
  105. package/src/components/DirectoryFilters.astro +0 -100
  106. package/src/components/DirectoryHero.astro +0 -32
  107. package/src/components/HealthBadge.astro +0 -12
  108. package/src/components/LensTabs.astro +0 -26
  109. package/src/components/MethodologyPanel.astro +0 -20
  110. package/src/components/Pagination.astro +0 -24
  111. package/src/components/ProjectDetail.astro +0 -166
  112. package/src/components/SubmitDraft.astro +0 -137
  113. package/templates/default/src/components/AppCard.astro +0 -248
  114. package/templates/default/src/components/AppSection.astro +0 -58
  115. package/templates/default/src/components/AppsIndexRow.astro +0 -230
  116. package/templates/default/src/components/AppsPagination.astro +0 -147
  117. package/templates/default/src/components/CategoryGrid.astro +0 -80
  118. package/templates/default/src/components/ContributorsGrid.astro +0 -105
  119. package/templates/default/src/components/DecisionRow.astro +0 -219
  120. package/templates/default/src/components/ExploreByCategory.astro +0 -74
  121. package/templates/default/src/components/ExploreByStack.astro +0 -102
  122. package/templates/default/src/components/Hero.astro +0 -176
  123. package/templates/default/src/components/MinimalAbout.astro +0 -57
  124. package/templates/default/src/components/OriginalCollection.astro +0 -144
  125. package/templates/default/src/components/RefinePanel.astro +0 -272
  126. package/templates/default/src/components/ScoreBars.astro +0 -72
  127. package/templates/default/src/components/SmartLensTabs.astro +0 -65
  128. package/templates/default/src/components/StackGrid.astro +0 -95
  129. package/templates/default/src/components/WhyThisExists.astro +0 -81
  130. package/templates/default/src/components/icons/Icon.astro +0 -148
  131. package/templates/default/src/components/layout/SectionHeader.astro +0 -42
  132. package/templates/default/src/components/layout/ThemeToggle.astro +0 -97
  133. package/templates/default/src/data/apps.ts +0 -149
  134. package/templates/default/src/data/categories.ts +0 -27
  135. package/templates/default/src/data/config.ts +0 -28
  136. package/templates/default/src/data/contributors.ts +0 -96
  137. package/templates/default/src/data/stacks.ts +0 -47
  138. package/templates/default/src/data/stats.ts +0 -65
  139. package/templates/default/src/data/types.ts +0 -248
  140. package/templates/default/src/lib/apps-search.ts +0 -228
  141. package/templates/default/src/lib/format.ts +0 -36
  142. package/templates/default/src/lib/lenses.ts +0 -98
  143. package/templates/default/src/lib/repo.ts +0 -12
  144. package/templates/default/src/lib/scores.ts +0 -51
  145. package/templates/default/src/lib/taxonomy-counts.ts +0 -31
  146. package/templates/default/src/pages/apps/[slug].astro +0 -709
  147. package/templates/default/src/pages/apps/index.astro +0 -669
  148. package/templates/default/src/pages/contributors.astro +0 -142
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lenses.d.ts","sourceRoot":"","sources":["../../src/lib/lenses.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACd,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,MAAM,GACd,KAAK,GACL,KAAK,GACL,KAAK,GACL,QAAQ,GACR,UAAU,GACV,cAAc,CAAC;AAEnB,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,KAAK,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;CACrC;AAID;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACjC,MAAM,CAAC,EAAE;QACP,MAAM,CAAC,EAAE,YAAY,CAAC;QACtB,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,cAAc,GAAG,QAAQ,CAAC;QACxD,gBAAgB,CAAC,EAAE,OAAO,CAAC;KAC5B,CAAC;IACF,MAAM,CAAC,EAAE;QACP,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC1B,CAAC;CACH;AA4BD,eAAO,MAAM,MAAM,EAAE,OAAO,EAmD3B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,EAOlC,CAAC;AAEF,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAG3E;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,EAAE,IAAI,MAAM,CAGzE;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,eAAe,GACtB,MAAM,GAAG,EAAE,CAKb;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAK7E;AAED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,eAAe,EACvB,UAAU,GAAE,MAAY,GACvB,MAAM,CAWR;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,EAAE,GAAG,CAAC,EAAE,CAKnF;AAID,YAAY,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Curated lenses for the list / discovery page.
3
+ *
4
+ * A lens is a single-select view that translates into a (possibly
5
+ * empty) URL search-param filter. The 6 lenses below cover the
6
+ * "what should I look at next?" mental shortcut:
7
+ *
8
+ * - "all" — no filter
9
+ * - "hot" — recent attention: stars ≥ 1000 OR pushed within 30d
10
+ * - "new" — fresh entries: pushed within 7d OR curated "new" label
11
+ * - "mature" — long-running projects: curated tier AND pushed > 365d ago
12
+ * - "featured" — curated "featured" label
13
+ * - "needs-review" — cleanup candidate (stale / archived / etc.)
14
+ *
15
+ * The lens id is what shows up in the URL as `?lens=...`. Filter
16
+ * resolution happens in `lib/search.ts` (`filterApps`).
17
+ *
18
+ * Keep this list in sync with `lib/search.ts` and any UI tabs.
19
+ */
20
+ const DAY_MS = 86_400_000;
21
+ function daysSince(iso) {
22
+ if (!iso)
23
+ return Infinity;
24
+ const d = new Date(iso);
25
+ if (Number.isNaN(d.valueOf()))
26
+ return Infinity;
27
+ return (Date.now() - d.valueOf()) / DAY_MS;
28
+ }
29
+ function hasLabel(record, label) {
30
+ return record.curation?.labels?.includes(label) ?? false;
31
+ }
32
+ const CLEANUP_STATUSES = [
33
+ "stale",
34
+ "inactive",
35
+ "archived",
36
+ "unavailable",
37
+ "needs_review",
38
+ ];
39
+ function isCleanupCandidate(record) {
40
+ if (record.health?.cleanupCandidate)
41
+ return true;
42
+ const status = record.health?.status;
43
+ if (!status)
44
+ return false;
45
+ return CLEANUP_STATUSES.includes(status);
46
+ }
47
+ export const LENSES = [
48
+ {
49
+ id: "all",
50
+ label: "All",
51
+ description: "Every record in the directory.",
52
+ match: () => true,
53
+ },
54
+ {
55
+ id: "hot",
56
+ label: "Trending",
57
+ description: "Records with recent attention — stars ≥ 1000 or pushed within 30 days.",
58
+ match: (r) => {
59
+ const stars = r.github?.stars ?? 0;
60
+ const pushed = daysSince(r.github?.pushedAt);
61
+ return stars >= 1000 || pushed <= 30;
62
+ },
63
+ },
64
+ {
65
+ id: "new",
66
+ label: "Recently added",
67
+ description: "Fresh entries — pushed within 7 days or curated with the “new” label.",
68
+ match: (r) => {
69
+ const pushed = daysSince(r.github?.pushedAt);
70
+ return pushed <= 7 || hasLabel(r, "new");
71
+ },
72
+ },
73
+ {
74
+ id: "mature",
75
+ label: "Established",
76
+ description: "Long-running projects — curated tier and last push > 365 days ago.",
77
+ match: (r) => {
78
+ const tier = r.health?.tier;
79
+ const pushed = daysSince(r.github?.pushedAt);
80
+ return tier === "curated" && pushed > 365;
81
+ },
82
+ },
83
+ {
84
+ id: "featured",
85
+ label: "Featured",
86
+ description: "Records curated with the “featured” label.",
87
+ match: (r) => hasLabel(r, "featured"),
88
+ },
89
+ {
90
+ id: "needs-review",
91
+ label: "Needs review",
92
+ description: "Cleanup candidates — stale, archived, or unmaintained.",
93
+ match: (r) => isCleanupCandidate(r),
94
+ },
95
+ ];
96
+ /**
97
+ * The lenses rendered as top-row tabs on the list page. Order
98
+ * matters — left to right. Keep this in sync with what the design
99
+ * calls for; URL deep-linking accepts any `LensId` even if it's not
100
+ * in the primary row.
101
+ */
102
+ export const PRIMARY_LENSES = [
103
+ "all",
104
+ "hot",
105
+ "new",
106
+ "mature",
107
+ "featured",
108
+ "needs-review",
109
+ ];
110
+ export function lensById(id) {
111
+ if (!id)
112
+ return undefined;
113
+ return LENSES.find((l) => l.id === id);
114
+ }
115
+ export function isPrimaryLens(id) {
116
+ if (!id)
117
+ return false;
118
+ return PRIMARY_LENSES.includes(id);
119
+ }
120
+ /**
121
+ * Resolve the active lens from URL search params. Returns the empty
122
+ * string when no `lens=...` is set, so callers can treat both
123
+ * "absent" and "unknown" identically.
124
+ */
125
+ export function lensFromSearchParams(params) {
126
+ const v = params.get("lens");
127
+ if (!v)
128
+ return "";
129
+ if (LENSES.some((l) => l.id === v))
130
+ return v;
131
+ return "";
132
+ }
133
+ /**
134
+ * `true` when the URL search params currently reflect this lens's
135
+ * filter shape. The "all" lens is the implicit "no filter" state —
136
+ * it's active only when no other lens-shaped params are present.
137
+ */
138
+ export function isLensActive(lensId, params) {
139
+ if (lensId === "all") {
140
+ return lensFromSearchParams(params) === "" && !params.get("label");
141
+ }
142
+ return lensFromSearchParams(params) === lensId;
143
+ }
144
+ /**
145
+ * Build a `href` for a lens tab. Selecting a lens resets the other
146
+ * "view" params (label, page) and applies only the chosen lens's
147
+ * filter. Other filters (q, stack, platform, category, sort) are
148
+ * preserved.
149
+ *
150
+ * `pathPrefix` is the URL root for the list page — typically
151
+ * `/projects` for the project-directory blueprint, `/resources` for
152
+ * resource-hub, `/entities` for ecosystem-map. Defaults to `/`.
153
+ */
154
+ export function hrefForLens(lensId, params, pathPrefix = "/") {
155
+ const sp = new URLSearchParams(params);
156
+ sp.delete("lens");
157
+ sp.delete("label");
158
+ sp.delete("page");
159
+ if (lensId && lensId !== "all") {
160
+ sp.set("lens", lensId);
161
+ }
162
+ const qs = sp.toString();
163
+ const base = pathPrefix.replace(/\/$/, "");
164
+ return qs ? `${base}?${qs}` : base || "/";
165
+ }
166
+ /**
167
+ * Apply the lens filter to a record array. When `lensId` is
168
+ * `""` (no lens) or `"all"`, returns the input unchanged.
169
+ *
170
+ * Type-parameterized so the same helper works on `ProjectRecord[]`
171
+ * and on the lighter index payload that list pages ship.
172
+ */
173
+ export function applyLens(records, lensId) {
174
+ if (!lensId || lensId === "all")
175
+ return records;
176
+ const def = lensById(lensId);
177
+ if (!def)
178
+ return records;
179
+ return records.filter((r) => def.match(r));
180
+ }
181
+ //# sourceMappingURL=lenses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lenses.js","sourceRoot":"","sources":["../../src/lib/lenses.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Parse a GitHub repo URL into its owner and repo components.
3
+ *
4
+ * Returns `null` (not `{ owner: null, repo: null }`) if the URL
5
+ * doesn't match the github.com path shape — this lets callers
6
+ * distinguish "no URL" from "URL without owner" with a single
7
+ * null check.
8
+ *
9
+ * Used in:
10
+ * - ItemCard / AppCard avatar fallback (owner avatar via GitHub
11
+ * avatars API).
12
+ * - Detail page (showing owner + repo name in breadcrumb / header).
13
+ * - Anywhere we need to display "@owner / repo" or link to the owner.
14
+ */
15
+ export declare function getOwnerAndRepoFromRepoUrl(repoUrl: string | null | undefined): {
16
+ owner: string;
17
+ repo: string;
18
+ } | null;
19
+ /**
20
+ * Build a GitHub avatar URL for a given owner. Defaults to size 80
21
+ * which is the smallest "high quality" size in GitHub's avatar CDN.
22
+ *
23
+ * Returns a string (not nullable) so callers can plug the result
24
+ * straight into an `src` attribute. The avatar endpoint always
25
+ * returns a placeholder image for unknown owners, so the only
26
+ * reason to check `null` upstream is when you specifically want
27
+ * to render a different fallback (e.g. initials).
28
+ */
29
+ export declare function getOwnerAvatarUrl(owner: string, size?: number): string;
30
+ //# sourceMappingURL=repo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/lib/repo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACjC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAQxC;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,MAAM,CAOlE"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Parse a GitHub repo URL into its owner and repo components.
3
+ *
4
+ * Returns `null` (not `{ owner: null, repo: null }`) if the URL
5
+ * doesn't match the github.com path shape — this lets callers
6
+ * distinguish "no URL" from "URL without owner" with a single
7
+ * null check.
8
+ *
9
+ * Used in:
10
+ * - ItemCard / AppCard avatar fallback (owner avatar via GitHub
11
+ * avatars API).
12
+ * - Detail page (showing owner + repo name in breadcrumb / header).
13
+ * - Anywhere we need to display "@owner / repo" or link to the owner.
14
+ */
15
+ export function getOwnerAndRepoFromRepoUrl(repoUrl) {
16
+ if (!repoUrl || !repoUrl.includes("github.com/"))
17
+ return null;
18
+ const m = repoUrl.match(/github\.com\/([^/]+)\/([^/?#]+)/);
19
+ if (!m)
20
+ return null;
21
+ return {
22
+ owner: m[1],
23
+ repo: m[2].replace(/\.git$/, ""),
24
+ };
25
+ }
26
+ /**
27
+ * Build a GitHub avatar URL for a given owner. Defaults to size 80
28
+ * which is the smallest "high quality" size in GitHub's avatar CDN.
29
+ *
30
+ * Returns a string (not nullable) so callers can plug the result
31
+ * straight into an `src` attribute. The avatar endpoint always
32
+ * returns a placeholder image for unknown owners, so the only
33
+ * reason to check `null` upstream is when you specifically want
34
+ * to render a different fallback (e.g. initials).
35
+ */
36
+ export function getOwnerAvatarUrl(owner, size = 80) {
37
+ if (!owner) {
38
+ // Defensive: callers may pass a possibly-empty string. Return the
39
+ // generic GitHub identicon so an `<img>` doesn't 404.
40
+ return `https://avatars.githubusercontent.com/identicon?v=4&s=${size}`;
41
+ }
42
+ return `https://avatars.githubusercontent.com/${owner}?v=4&s=${size}`;
43
+ }
44
+ //# sourceMappingURL=repo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo.js","sourceRoot":"","sources":["../../src/lib/repo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,0BAA0B,CACxC,OAAkC;IAElC,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9D,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;IAC3D,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,OAAO;QACL,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QACX,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;KACjC,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,IAAI,GAAG,EAAE;IACxD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,kEAAkE;QAClE,sDAAsD;QACtD,OAAO,yDAAyD,IAAI,EAAE,CAAC;IACzE,CAAC;IACD,OAAO,yCAAyC,KAAK,UAAU,IAAI,EAAE,CAAC;AACxE,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Score-dimension utilities.
3
+ *
4
+ * Operates on the `scores` field of a `ProjectRecord` (or any
5
+ * object that has the same shape — defined as `Score` in
6
+ * `@grove-dev/core`). All helpers are dependency-free.
7
+ */
8
+ import type { Score } from "@grove-dev/core";
9
+ /**
10
+ * Format a single score (0-100) into a 0-5 "tier" used by the UI:
11
+ *
12
+ * 0-19 → 0 (very low)
13
+ * 20-39 → 1 (low)
14
+ * 40-59 → 2 (medium)
15
+ * 60-79 → 3 (high)
16
+ * 80-100 → 4 (very high)
17
+ *
18
+ * The list page renders each score as a 4-cell bar; this gives
19
+ * the visual a stable shape regardless of the underlying number.
20
+ */
21
+ export declare function scoreTier(n: number): 0 | 1 | 2 | 3 | 4;
22
+ /** Compact textual label for a score tier (used in tooltips). */
23
+ export declare function scoreTierLabel(n: number): string;
24
+ /** Short numeric label used inline next to a score bar, e.g. "82". */
25
+ export declare function scoreLabel(n: number | undefined): string;
26
+ /** Order of score dimensions, used by both list and detail. */
27
+ export declare const SCORE_DIMENSIONS: (keyof Score)[];
28
+ /** Human-readable label for a score dimension. */
29
+ export declare const SCORE_LABELS: Record<keyof Score, string>;
30
+ /**
31
+ * Reasoning copy per dimension, surfaced in the detail page so the
32
+ * numbers don't feel magical. These are intentionally short — the
33
+ * curation notes on each record carry the longer story.
34
+ */
35
+ export declare const SCORE_REASONING: Record<keyof Score, string>;
36
+ /** Re-export the `Score` type for callers that only need the lib API. */
37
+ export type { Score };
38
+ //# sourceMappingURL=scores.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scores.d.ts","sourceRoot":"","sources":["../../src/lib/scores.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAMtD;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAahD;AAED,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAGxD;AAED,+DAA+D;AAC/D,eAAO,MAAM,gBAAgB,EAAE,CAAC,MAAM,KAAK,CAAC,EAO3C,CAAC;AAEF,kDAAkD;AAClD,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,KAAK,EAAE,MAAM,CAOpD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,KAAK,EAAE,MAAM,CAavD,CAAC;AAEF,yEAAyE;AACzE,YAAY,EAAE,KAAK,EAAE,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Score-dimension utilities.
3
+ *
4
+ * Operates on the `scores` field of a `ProjectRecord` (or any
5
+ * object that has the same shape — defined as `Score` in
6
+ * `@grove-dev/core`). All helpers are dependency-free.
7
+ */
8
+ /**
9
+ * Format a single score (0-100) into a 0-5 "tier" used by the UI:
10
+ *
11
+ * 0-19 → 0 (very low)
12
+ * 20-39 → 1 (low)
13
+ * 40-59 → 2 (medium)
14
+ * 60-79 → 3 (high)
15
+ * 80-100 → 4 (very high)
16
+ *
17
+ * The list page renders each score as a 4-cell bar; this gives
18
+ * the visual a stable shape regardless of the underlying number.
19
+ */
20
+ export function scoreTier(n) {
21
+ if (n < 20)
22
+ return 0;
23
+ if (n < 40)
24
+ return 1;
25
+ if (n < 60)
26
+ return 2;
27
+ if (n < 80)
28
+ return 3;
29
+ return 4;
30
+ }
31
+ /** Compact textual label for a score tier (used in tooltips). */
32
+ export function scoreTierLabel(n) {
33
+ switch (scoreTier(n)) {
34
+ case 0:
35
+ return "Very low";
36
+ case 1:
37
+ return "Low";
38
+ case 2:
39
+ return "Medium";
40
+ case 3:
41
+ return "High";
42
+ case 4:
43
+ return "Very high";
44
+ }
45
+ }
46
+ /** Short numeric label used inline next to a score bar, e.g. "82". */
47
+ export function scoreLabel(n) {
48
+ if (typeof n !== "number")
49
+ return "—";
50
+ return Math.round(n).toString();
51
+ }
52
+ /** Order of score dimensions, used by both list and detail. */
53
+ export const SCORE_DIMENSIONS = [
54
+ "activity",
55
+ "maturity",
56
+ "learning",
57
+ "contribution",
58
+ "docs",
59
+ "overall",
60
+ ];
61
+ /** Human-readable label for a score dimension. */
62
+ export const SCORE_LABELS = {
63
+ activity: "Activity",
64
+ maturity: "Maturity",
65
+ learning: "Learning",
66
+ contribution: "Contribution",
67
+ docs: "Docs",
68
+ overall: "Overall",
69
+ };
70
+ /**
71
+ * Reasoning copy per dimension, surfaced in the detail page so the
72
+ * numbers don't feel magical. These are intentionally short — the
73
+ * curation notes on each record carry the longer story.
74
+ */
75
+ export const SCORE_REASONING = {
76
+ activity: "Recent commits, open and merged PRs, issue response time, release cadence.",
77
+ maturity: "Repo age, stable structure, real-world production usage, contributor stability.",
78
+ learning: "Codebase readability, useful architecture patterns, comment quality, examples.",
79
+ contribution: "Open issues, recent merged PRs from new contributors, maintainer responsiveness, contribution guide.",
80
+ docs: "README quality, architecture docs, contributing guide, examples, code comments.",
81
+ overall: "Composite judgment — how likely this record is to satisfy a curious developer.",
82
+ };
83
+ //# sourceMappingURL=scores.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scores.js","sourceRoot":"","sources":["../../src/lib/scores.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO,CAAC,CAAC;IACrB,OAAO,CAAC,CAAC;AACX,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,QAAQ,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QACrB,KAAK,CAAC;YACJ,OAAO,UAAU,CAAC;QACpB,KAAK,CAAC;YACJ,OAAO,KAAK,CAAC;QACf,KAAK,CAAC;YACJ,OAAO,QAAQ,CAAC;QAClB,KAAK,CAAC;YACJ,OAAO,MAAM,CAAC;QAChB,KAAK,CAAC;YACJ,OAAO,WAAW,CAAC;IACvB,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,UAAU,CAAC,CAAqB;IAC9C,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IACtC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;AAClC,CAAC;AAED,+DAA+D;AAC/D,MAAM,CAAC,MAAM,gBAAgB,GAAoB;IAC/C,UAAU;IACV,UAAU;IACV,UAAU;IACV,cAAc;IACd,MAAM;IACN,SAAS;CACV,CAAC;AAEF,kDAAkD;AAClD,MAAM,CAAC,MAAM,YAAY,GAAgC;IACvD,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;IACpB,YAAY,EAAE,cAAc;IAC5B,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;CACnB,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAgC;IAC1D,QAAQ,EACN,4EAA4E;IAC9E,QAAQ,EACN,iFAAiF;IACnF,QAAQ,EACN,gFAAgF;IAClF,YAAY,EACV,sGAAsG;IACxG,IAAI,EACF,iFAAiF;IACnF,OAAO,EACL,gFAAgF;CACnF,CAAC"}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * URL-driven search/filter/sort state for the directory list page.
3
+ *
4
+ * Multi-value fields use repeated keys: `?stack=Flutter&stack=RN`.
5
+ * Single-value fields (q, sort, page, lens) are scalar. Empty
6
+ * / undefined fields mean "no filter on this dimension".
7
+ *
8
+ * The functions here are generic over a record-like shape that
9
+ * carries the index payload (see `toIndexRecord` in
10
+ * `@grove-dev/core`). The list page only needs `name`, `slug`,
11
+ * `description`, `category`, `tags`, `stack`, `stacks`, `platforms`,
12
+ * `repoUrl`, `logoUrl`, `health`, `curation`, and `github.{stars,
13
+ * pushedAt}` — all of which are present in the index projection.
14
+ */
15
+ import type { HealthStatus, ProjectRecord } from "@grove-dev/core";
16
+ import { type LensId } from "./lenses.js";
17
+ /**
18
+ * Subset of `ProjectRecord` the search lib actually reads. Defined
19
+ * inline so the lib works on both full records and the lighter
20
+ * index payload. Use `ProjectRecord` from core when you have it;
21
+ * pass any object that satisfies `Searchable` otherwise.
22
+ *
23
+ * `Searchable` is a superset of `AppLike` (the lens-matching shape
24
+ * in `lib/lenses.ts`) — both interfaces share the same optional
25
+ * field shape, so `Searchable` records can be passed to
26
+ * `applyLens()` and `filterApps()` interchangeably.
27
+ */
28
+ export interface Searchable {
29
+ slug?: string;
30
+ name: string;
31
+ description?: string;
32
+ category?: string;
33
+ tags?: string[];
34
+ stack?: string;
35
+ stacks?: string[];
36
+ platforms?: string[];
37
+ /** Canonical repo URL — used for owner extraction in text search. */
38
+ repoUrl?: string;
39
+ logoUrl?: string;
40
+ /** Curated labels (new, hot, mature, featured). */
41
+ curation?: {
42
+ labels?: string[];
43
+ };
44
+ /** Health block — drives status filter + tier fallback. */
45
+ health?: {
46
+ status?: HealthStatus;
47
+ tier?: "curated" | "listed" | "experimental" | "hidden";
48
+ visibility?: "highlight" | "keep" | "needs_review" | "hide" | "remove" | "historical";
49
+ cleanupCandidate?: boolean;
50
+ };
51
+ /** GitHub metadata used for sort + text search. */
52
+ github?: {
53
+ stars?: number;
54
+ forks?: number;
55
+ pushedAt?: string | null;
56
+ license?: string | null;
57
+ };
58
+ /** Score block — used for "best-overall" sort. */
59
+ scores?: {
60
+ activity?: number;
61
+ maturity?: number;
62
+ learning?: number;
63
+ contribution?: number;
64
+ docs?: number;
65
+ overall?: number;
66
+ };
67
+ }
68
+ export type AppsFilters = {
69
+ q?: string;
70
+ /** Stack slugs, repeated URL key `stack=`. Matches against `stack` AND `stacks[]`. */
71
+ stacks?: string[];
72
+ /** Platform slugs, repeated URL key `platform=`. Matches `platforms[]`. */
73
+ platforms?: string[];
74
+ /** Category names, repeated URL key `category=`. */
75
+ categories?: string[];
76
+ /** Curated label ids (new/hot/mature/featured), repeated URL key `label=`. */
77
+ labels?: string[];
78
+ /** SPDX license ids, repeated URL key `license=`. */
79
+ licenses?: string[];
80
+ /** Health status values, comma-separated repeated URL key `status=`. */
81
+ statuses?: string[];
82
+ /** A curated lens (single-select view). */
83
+ lens?: LensId;
84
+ /** Sort order. Single value, defaults to "recently-updated". */
85
+ sort?: AppsSort;
86
+ /** 1-based page number. */
87
+ page?: number;
88
+ };
89
+ /** Available sort orders, in UI order. */
90
+ export declare const SORT_OPTIONS: readonly [{
91
+ readonly value: "recently-updated";
92
+ readonly label: "Recently updated";
93
+ }, {
94
+ readonly value: "most-starred";
95
+ readonly label: "Most starred";
96
+ }, {
97
+ readonly value: "recently-added";
98
+ readonly label: "Recently added";
99
+ }, {
100
+ readonly value: "best-overall";
101
+ readonly label: "Best overall";
102
+ }, {
103
+ readonly value: "alphabetical";
104
+ readonly label: "Alphabetical";
105
+ }];
106
+ export type AppsSort = (typeof SORT_OPTIONS)[number]["value"];
107
+ /**
108
+ * How many records per page. Constant for now; future versions may
109
+ * vary per blueprint.
110
+ */
111
+ export declare const PAGE_SIZE = 24;
112
+ /**
113
+ * Read filters from a URLSearchParams (or anything URLSearchParams-
114
+ * shaped, e.g. the result of `new URL(req.url).searchParams`).
115
+ */
116
+ export declare function filtersFromSearchParams(sp: URLSearchParams): AppsFilters;
117
+ /**
118
+ * Serialize a filters object back to URLSearchParams. Undefined /
119
+ * empty values are dropped. Defaults (sort, page 1) are also dropped
120
+ * to keep URLs short and canonical.
121
+ */
122
+ export declare function searchParamsFromFilters(f: AppsFilters): URLSearchParams;
123
+ /**
124
+ * True if any filter is active (i.e. the result list isn't "all
125
+ * records, default sort").
126
+ */
127
+ export declare function hasAnyFilter(f: AppsFilters): boolean;
128
+ /**
129
+ * Return the subset of records that match all active filters (AND
130
+ * across dimensions, OR within a dimension).
131
+ *
132
+ * `q` is a case-insensitive substring search across name, owner,
133
+ * description, category, stack/stacks/platforms, tags, and license.
134
+ * Other dimensions are exact match.
135
+ */
136
+ export declare function filterApps<T extends Searchable>(records: T[], f: AppsFilters): T[];
137
+ /**
138
+ * Apply lens → filter → sort pipeline. Lens is applied first because
139
+ * it's a single-select "view" that may be broader or narrower than
140
+ * the regular filter dimensions.
141
+ */
142
+ export declare function pipeline<T extends Searchable>(records: T[], filters: AppsFilters): T[];
143
+ /**
144
+ * Sort records in-place-free: returns a new array. Stable for ties.
145
+ * Records missing the sort key (e.g. no stars) sink to the bottom
146
+ * rather than vanishing.
147
+ */
148
+ export declare function applySort<T extends Searchable>(records: T[], sort: AppsSort): T[];
149
+ /** Slice an array for a given page (1-based). */
150
+ export declare function paginate<T>(items: T[], page: number, pageSize?: number): T[];
151
+ /** Total number of pages given an item count. Always >= 1. */
152
+ export declare function totalPages(itemCount: number, pageSize?: number): number;
153
+ /** Resolve the effective sort from a filters object, applying default. */
154
+ export declare function effectiveSort(f: AppsFilters): AppsSort;
155
+ /** Resolve the effective page from a filters object, applying default. */
156
+ export declare function effectivePage(f: AppsFilters): number;
157
+ /**
158
+ * Return a flat list of active-filter chips for display + removal.
159
+ * Used by the "x" button on each chip in the UI.
160
+ */
161
+ export declare function activeFilterChips(f: AppsFilters): {
162
+ key: keyof AppsFilters;
163
+ value: string;
164
+ label: string;
165
+ }[];
166
+ /**
167
+ * Build the full list of facet values from the record array,
168
+ * preserving a sensible order (frequency desc, then alphabetical
169
+ * for ties).
170
+ */
171
+ export declare function buildFacets<T extends Searchable>(records: T[]): {
172
+ stacks: {
173
+ value: string;
174
+ count: number;
175
+ }[];
176
+ platforms: {
177
+ value: string;
178
+ count: number;
179
+ }[];
180
+ categories: {
181
+ value: string;
182
+ count: number;
183
+ }[];
184
+ labels: {
185
+ value: string;
186
+ count: number;
187
+ }[];
188
+ licenses: {
189
+ value: string;
190
+ count: number;
191
+ }[];
192
+ };
193
+ /**
194
+ * Re-export the `ProjectRecord` type so consumers of the search lib
195
+ * can write `import type { ProjectRecord } from "@grove-dev/astro/lib"`
196
+ * without a second import.
197
+ */
198
+ export type { ProjectRecord };
199
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/lib/search.ts"],"names":[],"mappings":"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"}