@grove-dev/astro 0.1.5 → 0.2.2

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 (246) hide show
  1. package/dist/index.d.ts +4 -25
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +56 -22
  4. package/dist/index.js.map +1 -1
  5. package/dist/layouts/index.d.ts +25 -0
  6. package/dist/layouts/index.d.ts.map +1 -0
  7. package/dist/layouts/index.js +25 -0
  8. package/dist/layouts/index.js.map +1 -0
  9. package/dist/lib/display.d.ts +33 -0
  10. package/dist/lib/display.d.ts.map +1 -0
  11. package/dist/lib/display.js +104 -0
  12. package/dist/lib/display.js.map +1 -0
  13. package/dist/lib/format.d.ts +30 -0
  14. package/dist/lib/format.d.ts.map +1 -0
  15. package/dist/lib/format.js +73 -0
  16. package/dist/lib/format.js.map +1 -0
  17. package/dist/lib/index.d.ts +26 -0
  18. package/dist/lib/index.d.ts.map +1 -0
  19. package/dist/lib/index.js +26 -0
  20. package/dist/lib/index.js.map +1 -0
  21. package/dist/lib/lenses.d.ts +96 -0
  22. package/dist/lib/lenses.d.ts.map +1 -0
  23. package/dist/lib/lenses.js +181 -0
  24. package/dist/lib/lenses.js.map +1 -0
  25. package/dist/lib/repo.d.ts +30 -0
  26. package/dist/lib/repo.d.ts.map +1 -0
  27. package/dist/lib/repo.js +44 -0
  28. package/dist/lib/repo.js.map +1 -0
  29. package/dist/lib/scores.d.ts +38 -0
  30. package/dist/lib/scores.d.ts.map +1 -0
  31. package/dist/lib/scores.js +83 -0
  32. package/dist/lib/scores.js.map +1 -0
  33. package/dist/lib/search.d.ts +199 -0
  34. package/dist/lib/search.d.ts.map +1 -0
  35. package/dist/lib/search.js +357 -0
  36. package/dist/lib/search.js.map +1 -0
  37. package/dist/lib/taxonomy-counts.d.ts +25 -0
  38. package/dist/lib/taxonomy-counts.d.ts.map +1 -0
  39. package/dist/lib/taxonomy-counts.js +41 -0
  40. package/dist/lib/taxonomy-counts.js.map +1 -0
  41. package/package.json +7 -7
  42. package/src/components/AppSection.astro +83 -0
  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 +289 -30
  53. package/src/components/MinimalAbout.astro +82 -0
  54. package/src/components/OriginalCollection.astro +116 -0
  55. package/src/components/RefinePanel.astro +197 -0
  56. package/src/components/ScoreBars.astro +92 -22
  57. package/src/components/SmartLensTabs.astro +71 -0
  58. package/src/components/StackGrid.astro +132 -0
  59. package/src/components/WhyThisExists.astro +116 -0
  60. package/src/index.ts +61 -14
  61. package/src/layouts/BaseLayout.astro +227 -15
  62. package/src/layouts/Container.astro +57 -0
  63. package/src/layouts/Footer.astro +235 -0
  64. package/src/layouts/Header.astro +159 -0
  65. package/src/layouts/SectionHeader.astro +58 -0
  66. package/{templates/default/src/components/layout → src/layouts}/Seo.astro +62 -38
  67. package/src/layouts/ThemeToggle.astro +110 -0
  68. package/src/layouts/astro-shim.d.ts +29 -0
  69. package/src/layouts/index.ts +24 -0
  70. package/src/lib/display.ts +118 -0
  71. package/src/lib/format.ts +65 -0
  72. package/src/lib/index.ts +26 -0
  73. package/src/lib/lenses.ts +241 -0
  74. package/src/lib/repo.ts +44 -0
  75. package/src/lib/scores.ts +94 -0
  76. package/src/lib/search.ts +481 -0
  77. package/src/lib/taxonomy-counts.ts +60 -0
  78. package/src/styles.css +366 -638
  79. package/templates/default/.astro/content.d.ts +155 -0
  80. package/templates/default/.astro/settings.json +5 -0
  81. package/templates/default/.astro/types.d.ts +2 -0
  82. package/templates/default/astro.config.mjs +8 -6
  83. package/templates/default/content/pages/about.md +66 -0
  84. package/templates/default/content/pages/methodology.md +103 -0
  85. package/templates/default/data/generated/records.full.json +8 -0
  86. package/templates/default/data/generated/records.index.json +7 -0
  87. package/templates/default/data/generated/records.json +8 -0
  88. package/templates/default/data/generated/site-config.json +31 -0
  89. package/templates/default/data/records/.gitkeep +0 -0
  90. package/templates/default/dist/_astro/BaseLayout.DfpwR3jc.css +1 -0
  91. package/templates/default/dist/about/index.html +165 -0
  92. package/templates/default/dist/icons/platforms/android.svg +1 -0
  93. package/templates/default/dist/icons/platforms/apple-dark.svg +1 -0
  94. package/templates/default/dist/icons/platforms/apple-light.svg +1 -0
  95. package/templates/default/dist/icons/platforms/chrome.svg +1 -0
  96. package/templates/default/dist/icons/platforms/chromeos.svg +5 -0
  97. package/templates/default/dist/icons/platforms/desktop.svg +4 -0
  98. package/templates/default/dist/icons/platforms/embedded.svg +5 -0
  99. package/templates/default/dist/icons/platforms/ios.svg +1 -0
  100. package/templates/default/dist/icons/platforms/linux.svg +1 -0
  101. package/templates/default/dist/icons/platforms/macos.svg +1 -0
  102. package/templates/default/dist/icons/platforms/ubuntu.svg +1 -0
  103. package/templates/default/dist/icons/platforms/web.svg +5 -0
  104. package/templates/default/dist/icons/platforms/windows.svg +6 -0
  105. package/templates/default/dist/icons/stacks/android.svg +1 -0
  106. package/templates/default/dist/icons/stacks/apple-dark.svg +1 -0
  107. package/templates/default/dist/icons/stacks/apple-light.svg +1 -0
  108. package/templates/default/dist/icons/stacks/capacitor.svg +14 -0
  109. package/templates/default/dist/icons/stacks/dart.svg +1 -0
  110. package/templates/default/dist/icons/stacks/firebase.svg +1 -0
  111. package/templates/default/dist/icons/stacks/flutter.svg +1 -0
  112. package/templates/default/dist/icons/stacks/graphql.svg +1 -0
  113. package/templates/default/dist/icons/stacks/ionic.svg +4 -0
  114. package/templates/default/dist/icons/stacks/java.svg +1 -0
  115. package/templates/default/dist/icons/stacks/javascript.svg +1 -0
  116. package/templates/default/dist/icons/stacks/kotlin.svg +1 -0
  117. package/templates/default/dist/icons/stacks/mongodb.svg +1 -0
  118. package/templates/default/dist/icons/stacks/nodejs.svg +1 -0
  119. package/templates/default/dist/icons/stacks/python.svg +1 -0
  120. package/templates/default/dist/icons/stacks/react-native.svg +1 -0
  121. package/templates/default/dist/icons/stacks/react.svg +1 -0
  122. package/templates/default/dist/icons/stacks/rust.svg +1 -0
  123. package/templates/default/dist/icons/stacks/solidity.svg +1 -0
  124. package/templates/default/dist/icons/stacks/swift.svg +1 -0
  125. package/templates/default/dist/icons/stacks/tensorflow.svg +1 -0
  126. package/templates/default/dist/icons/stacks/typescript.svg +1 -0
  127. package/templates/default/dist/index.html +55 -0
  128. package/templates/default/dist/llms-full.txt +14 -0
  129. package/templates/default/dist/llms.txt +11 -0
  130. package/templates/default/dist/og-image.svg +48 -0
  131. package/templates/default/dist/projects/index.html +61 -0
  132. package/templates/default/dist/robots.txt +53 -0
  133. package/templates/default/dist/sitemap.xml +21 -0
  134. package/templates/default/dist/submit/index.html +69 -0
  135. package/templates/default/grove.config.ts +36 -0
  136. package/templates/default/node_modules/.bin/astro +21 -0
  137. package/templates/default/node_modules/.bin/astro-check +21 -0
  138. package/templates/default/node_modules/.bin/grove +21 -0
  139. package/templates/default/node_modules/.bin/jiti +21 -0
  140. package/templates/default/node_modules/.bin/prettier +21 -0
  141. package/templates/default/node_modules/.bin/rollup +21 -0
  142. package/templates/default/node_modules/.bin/tsc +21 -0
  143. package/templates/default/node_modules/.bin/tsserver +21 -0
  144. package/templates/default/node_modules/.bin/tsx +21 -0
  145. package/templates/default/node_modules/.bin/vite +21 -0
  146. package/templates/default/node_modules/.bin/yaml +21 -0
  147. package/templates/default/node_modules/.vite/deps/_metadata.json +79 -0
  148. package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js +433 -0
  149. package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js.map +7 -0
  150. package/templates/default/node_modules/.vite/deps/astro___aria-query.js +5 -0
  151. package/templates/default/node_modules/.vite/deps/astro___aria-query.js.map +7 -0
  152. package/templates/default/node_modules/.vite/deps/astro___axobject-query.js +5 -0
  153. package/templates/default/node_modules/.vite/deps/astro___axobject-query.js.map +7 -0
  154. package/templates/default/node_modules/.vite/deps/astro___html-escaper.js +9 -0
  155. package/templates/default/node_modules/.vite/deps/astro___html-escaper.js.map +7 -0
  156. package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +241 -0
  157. package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +7 -0
  158. package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js +1592 -0
  159. package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js.map +7 -0
  160. package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js +30 -0
  161. package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +7 -0
  162. package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js +186 -0
  163. package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js.map +7 -0
  164. package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js +48 -0
  165. package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js.map +7 -0
  166. package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js +6779 -0
  167. package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js.map +7 -0
  168. package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js +33 -0
  169. package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js.map +7 -0
  170. package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js +88 -0
  171. package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js.map +7 -0
  172. package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js +72 -0
  173. package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js.map +7 -0
  174. package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js +3757 -0
  175. package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js.map +7 -0
  176. package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js +54 -0
  177. package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js.map +7 -0
  178. package/templates/default/node_modules/.vite/deps/package.json +3 -0
  179. package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js +213 -0
  180. package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js.map +7 -0
  181. package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js +525 -0
  182. package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js.map +7 -0
  183. package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js +1020 -0
  184. package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js.map +7 -0
  185. package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js +147 -0
  186. package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js.map +7 -0
  187. package/templates/default/package.json +6 -5
  188. package/templates/default/public/llms-full.txt +14 -0
  189. package/templates/default/public/llms.txt +11 -0
  190. package/templates/default/src/components/layout/BaseLayout.astro +10 -57
  191. package/templates/default/src/components/layout/Footer.astro +16 -134
  192. package/templates/default/src/components/layout/Header.astro +25 -126
  193. package/templates/default/src/data/records.ts +161 -0
  194. package/templates/default/src/lib/markdown.ts +171 -0
  195. package/templates/default/src/pages/about.astro +95 -429
  196. package/templates/default/src/pages/index.astro +258 -61
  197. package/templates/default/src/pages/projects/[slug].astro +480 -0
  198. package/templates/default/src/pages/projects/index.astro +193 -0
  199. package/templates/default/src/pages/sitemap.xml.ts +73 -61
  200. package/templates/default/src/pages/submit.astro +122 -411
  201. package/templates/default/src/styles/global.css +42 -68
  202. package/src/components/ActiveFilterChips.astro +0 -20
  203. package/src/components/DirectoryFilters.astro +0 -100
  204. package/src/components/DirectoryHero.astro +0 -32
  205. package/src/components/HealthBadge.astro +0 -12
  206. package/src/components/LensTabs.astro +0 -26
  207. package/src/components/MethodologyPanel.astro +0 -20
  208. package/src/components/Pagination.astro +0 -24
  209. package/src/components/ProjectDetail.astro +0 -166
  210. package/src/components/SubmitDraft.astro +0 -137
  211. package/templates/default/src/components/AppCard.astro +0 -248
  212. package/templates/default/src/components/AppSection.astro +0 -58
  213. package/templates/default/src/components/AppsIndexRow.astro +0 -230
  214. package/templates/default/src/components/AppsPagination.astro +0 -147
  215. package/templates/default/src/components/CategoryGrid.astro +0 -80
  216. package/templates/default/src/components/ContributorsGrid.astro +0 -105
  217. package/templates/default/src/components/DecisionRow.astro +0 -219
  218. package/templates/default/src/components/ExploreByCategory.astro +0 -74
  219. package/templates/default/src/components/ExploreByStack.astro +0 -102
  220. package/templates/default/src/components/Hero.astro +0 -176
  221. package/templates/default/src/components/MinimalAbout.astro +0 -57
  222. package/templates/default/src/components/OriginalCollection.astro +0 -144
  223. package/templates/default/src/components/RefinePanel.astro +0 -272
  224. package/templates/default/src/components/ScoreBars.astro +0 -72
  225. package/templates/default/src/components/SmartLensTabs.astro +0 -65
  226. package/templates/default/src/components/StackGrid.astro +0 -95
  227. package/templates/default/src/components/WhyThisExists.astro +0 -81
  228. package/templates/default/src/components/icons/Icon.astro +0 -148
  229. package/templates/default/src/components/layout/SectionHeader.astro +0 -42
  230. package/templates/default/src/components/layout/ThemeToggle.astro +0 -97
  231. package/templates/default/src/data/apps.ts +0 -149
  232. package/templates/default/src/data/categories.ts +0 -27
  233. package/templates/default/src/data/config.ts +0 -28
  234. package/templates/default/src/data/contributors.ts +0 -96
  235. package/templates/default/src/data/stacks.ts +0 -47
  236. package/templates/default/src/data/stats.ts +0 -65
  237. package/templates/default/src/data/types.ts +0 -248
  238. package/templates/default/src/lib/apps-search.ts +0 -228
  239. package/templates/default/src/lib/format.ts +0 -36
  240. package/templates/default/src/lib/lenses.ts +0 -98
  241. package/templates/default/src/lib/repo.ts +0 -12
  242. package/templates/default/src/lib/scores.ts +0 -51
  243. package/templates/default/src/lib/taxonomy-counts.ts +0 -31
  244. package/templates/default/src/pages/apps/[slug].astro +0 -709
  245. package/templates/default/src/pages/apps/index.astro +0 -669
  246. package/templates/default/src/pages/contributors.astro +0 -142
@@ -0,0 +1,155 @@
1
+ declare module 'astro:content' {
2
+ export interface RenderResult {
3
+ Content: import('astro/runtime/server/index.js').AstroComponentFactory;
4
+ headings: import('astro').MarkdownHeading[];
5
+ remarkPluginFrontmatter: Record<string, any>;
6
+ }
7
+ interface Render {
8
+ '.md': Promise<RenderResult>;
9
+ }
10
+
11
+ export interface RenderedContent {
12
+ html: string;
13
+ metadata?: {
14
+ imagePaths: Array<string>;
15
+ [key: string]: unknown;
16
+ };
17
+ }
18
+
19
+ type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
20
+
21
+ export type CollectionKey = keyof DataEntryMap;
22
+ export type CollectionEntry<C extends CollectionKey> = Flatten<DataEntryMap[C]>;
23
+
24
+ type AllValuesOf<T> = T extends any ? T[keyof T] : never;
25
+
26
+ export type ReferenceDataEntry<
27
+ C extends CollectionKey,
28
+ E extends keyof DataEntryMap[C] = string,
29
+ > = {
30
+ collection: C;
31
+ id: E;
32
+ };
33
+
34
+ export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
35
+ collection: C;
36
+ id: string;
37
+ };
38
+
39
+ export function getCollection<C extends keyof DataEntryMap, E extends CollectionEntry<C>>(
40
+ collection: C,
41
+ filter?: (entry: CollectionEntry<C>) => entry is E,
42
+ ): Promise<E[]>;
43
+ export function getCollection<C extends keyof DataEntryMap>(
44
+ collection: C,
45
+ filter?: (entry: CollectionEntry<C>) => unknown,
46
+ ): Promise<CollectionEntry<C>[]>;
47
+
48
+ export function getLiveCollection<C extends keyof LiveContentConfig['collections']>(
49
+ collection: C,
50
+ filter?: LiveLoaderCollectionFilterType<C>,
51
+ ): Promise<
52
+ import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
53
+ >;
54
+
55
+ export function getEntry<
56
+ C extends keyof DataEntryMap,
57
+ E extends keyof DataEntryMap[C] | (string & {}),
58
+ >(
59
+ entry: ReferenceDataEntry<C, E>,
60
+ ): E extends keyof DataEntryMap[C]
61
+ ? Promise<DataEntryMap[C][E]>
62
+ : Promise<CollectionEntry<C> | undefined>;
63
+ export function getEntry<
64
+ C extends keyof DataEntryMap,
65
+ E extends keyof DataEntryMap[C] | (string & {}),
66
+ >(
67
+ collection: C,
68
+ id: E,
69
+ ): E extends keyof DataEntryMap[C]
70
+ ? string extends keyof DataEntryMap[C]
71
+ ? Promise<DataEntryMap[C][E]> | undefined
72
+ : Promise<DataEntryMap[C][E]>
73
+ : Promise<CollectionEntry<C> | undefined>;
74
+ export function getLiveEntry<C extends keyof LiveContentConfig['collections']>(
75
+ collection: C,
76
+ filter: string | LiveLoaderEntryFilterType<C>,
77
+ ): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
78
+
79
+ /** Resolve an array of entry references from the same collection */
80
+ export function getEntries<C extends keyof DataEntryMap>(
81
+ entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
82
+ ): Promise<CollectionEntry<C>[]>;
83
+
84
+ export function render<C extends keyof DataEntryMap>(
85
+ entry: DataEntryMap[C][string],
86
+ ): Promise<RenderResult>;
87
+
88
+ export function reference<
89
+ C extends
90
+ | keyof DataEntryMap
91
+ // Allow generic `string` to avoid excessive type errors in the config
92
+ // if `dev` is not running to update as you edit.
93
+ // Invalid collection names will be caught at build time.
94
+ | (string & {}),
95
+ >(
96
+ collection: C,
97
+ ): import('astro/zod').ZodPipe<
98
+ import('astro/zod').ZodString,
99
+ import('astro/zod').ZodTransform<
100
+ C extends keyof DataEntryMap
101
+ ? {
102
+ collection: C;
103
+ id: string;
104
+ }
105
+ : never,
106
+ string
107
+ >
108
+ >;
109
+
110
+ type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
111
+ type InferEntrySchema<C extends keyof DataEntryMap> = import('astro/zod').infer<
112
+ ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
113
+ >;
114
+ type ExtractLoaderConfig<T> = T extends { loader: infer L } ? L : never;
115
+ type InferLoaderSchema<
116
+ C extends keyof DataEntryMap,
117
+ L = ExtractLoaderConfig<ContentConfig['collections'][C]>,
118
+ > = L extends { schema: import('astro/zod').ZodSchema }
119
+ ? import('astro/zod').infer<L['schema']>
120
+ : any;
121
+
122
+ type DataEntryMap = {
123
+
124
+ };
125
+
126
+ type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
127
+ infer TData,
128
+ infer TEntryFilter,
129
+ infer TCollectionFilter,
130
+ infer TError
131
+ >
132
+ ? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
133
+ : { data: never; entryFilter: never; collectionFilter: never; error: never };
134
+ type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
135
+ type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
136
+ type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
137
+ type ExtractDataType<T> = ExtractLoaderTypes<T>['data'];
138
+
139
+ type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
140
+ LiveContentConfig['collections'][C]['schema'] extends undefined
141
+ ? ExtractDataType<LiveContentConfig['collections'][C]['loader']>
142
+ : import('astro/zod').infer<
143
+ Exclude<LiveContentConfig['collections'][C]['schema'], undefined>
144
+ >;
145
+ type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> =
146
+ ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>;
147
+ type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> =
148
+ ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>;
149
+ type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType<
150
+ LiveContentConfig['collections'][C]['loader']
151
+ >;
152
+
153
+ export type ContentConfig = never;
154
+ export type LiveContentConfig = never;
155
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "_variables": {
3
+ "lastUpdateCheck": 1781103180236
4
+ }
5
+ }
@@ -0,0 +1,2 @@
1
+ /// <reference types="astro/client" />
2
+ /// <reference path="content.d.ts" />
@@ -2,18 +2,20 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  import { defineConfig } from 'astro/config';
4
4
  import tailwindcss from '@tailwindcss/vite';
5
+ import groveAstro from '@grove-dev/astro';
5
6
 
6
7
  // `site` is the canonical URL the build uses for absolute links
7
- // (sitemap, OpenGraph, canonical tags, JSON-LD). Set curated.config.ts
8
- // → `siteUrl` and run `grove build` — this file is read by
9
- // Astro directly.
8
+ // (sitemap, OpenGraph, canonical tags, JSON-LD). The CLI injects
9
+ // `grove.config.ts` → `site.url` at scaffold time, but it can also
10
+ // be overridden per-build via the `SITE_URL` env var.
10
11
  //
11
- // The sitemap itself is generated by a custom Astro endpoint at
12
- // `src/pages/sitemap.xml.ts` rather than `@astrojs/sitemap`, so the
13
- // public URL is exactly `/sitemap.xml` (no `-index.xml` suffix).
12
+ // The sitemap is generated by the CLI's `grove sitemap` command
13
+ // into `public/sitemap.xml`; the public URL is exactly `/sitemap.xml`
14
+ // (no `-index.xml` suffix).
14
15
  export default defineConfig({
15
16
  site: process.env.SITE_URL || 'https://example.com',
16
17
  trailingSlash: 'ignore',
18
+ integrations: [groveAstro()],
17
19
  vite: {
18
20
  plugins: [tailwindcss()],
19
21
  },
@@ -0,0 +1,66 @@
1
+ ---
2
+ title: "About"
3
+ description: "What this directory is, who it's for, and how it works."
4
+ ---
5
+
6
+ # About this directory
7
+
8
+ This site is a small, hand-curated index of useful open-source work
9
+ relevant to a specific community. The framing is deliberately
10
+ narrow: we list *one* project per record, with a hand-written
11
+ "why listed" note for each, and we surface the things that matter
12
+ in practice (recent activity, install friction, license, etc.)
13
+ rather than the things that look impressive on a GitHub profile.
14
+
15
+ ## How it works
16
+
17
+ Records live as YAML files in `data/records/`. Each record carries:
18
+
19
+ - A `kind` (`project`, `resource`, or `entity` — picked by the
20
+ blueprint in `grove.config.ts`).
21
+ - A `slug` (URL-safe identifier; becomes the path on
22
+ `/<kind>/<slug>`).
23
+ - A `name` and a one-line `description`.
24
+ - Cross-links (`links.github`, `links.website`, etc.) and free-form
25
+ tags.
26
+ - An optional `curation` block with `labels`, `reviewedAt`,
27
+ `bestFor`, `whyListed`, `caveats`, and `scores`.
28
+ - An optional `github` block with the live repository signals
29
+ (stars, forks, latest release, languages, pushed-at).
30
+
31
+ At build time:
32
+
33
+ 1. `grove validate` checks every record against the JSON schema.
34
+ 2. `grove generate` writes `data/generated/records.{full,index}.json`
35
+ and `data/generated/site-config.json`.
36
+ 3. `grove sitemap` writes `public/sitemap.xml`.
37
+ 4. `grove llms` writes `public/llms.txt` and `llms-full.txt`.
38
+ 5. `astro build` produces the static output.
39
+
40
+ The whole pipeline is reproducible. The records are the source of
41
+ truth; everything else is derived.
42
+
43
+ ## Why a static site
44
+
45
+ The directory is a *reference*, not an *app*. Static HTML is the
46
+ right tool:
47
+
48
+ - Every page is a single round-trip. No client-side data layer.
49
+ - The output is auditable: open the deployed HTML and you can see
50
+ the full markup, no hidden hydration.
51
+ - Hosting is cheap (or free, on GitHub Pages / Cloudflare Pages).
52
+ - AI crawlers can read the full content. The `llms.txt` /
53
+ `llms-full.txt` files give them a single fetch to land on.
54
+
55
+ ## Contributing
56
+
57
+ To add a record, open a submission issue. The maintainer will
58
+ review and, if accepted, open a PR that adds the new YAML file.
59
+ See `/submit` for the issue form and a minimal record template.
60
+
61
+ ## License
62
+
63
+ The site's source code is MIT. The records are released under CC-BY
64
+ 4.0 unless a record's `license` field says otherwise. A record's
65
+ `license` field is the license of the *project it describes*, not
66
+ the license of the directory entry.
@@ -0,0 +1,103 @@
1
+ ---
2
+ title: "Methodology"
3
+ description: "How records are picked, scored, and surfaced in this directory."
4
+ ---
5
+
6
+ # Methodology
7
+
8
+ This page describes how entries in the directory are chosen, how the
9
+ curation signals are computed, and what we do (and don't) promise about
10
+ the data. The intent is to make the directory's logic auditable — every
11
+ number on a detail page is reproducible from the record's YAML.
12
+
13
+ ## Selection
14
+
15
+ A record is included if it is:
16
+
17
+ 1. **Open source.** Source is public under an OSI-approved license
18
+ (or public-domain dedication).
19
+ 2. **Actively maintained.** A pushed commit within the last 18 months
20
+ OR a declared maintenance status of `maintained` / `passive`.
21
+ 3. **Useful to a curious developer.** A working build, a real user
22
+ base (or a credible path to one), and a clear description of what
23
+ the project does.
24
+
25
+ We exclude: abandoned experiments, vapourware, projects whose entire
26
+ purpose is to sell a hosted plan, and any record that has not been
27
+ hand-checked by a maintainer.
28
+
29
+ ## Curation labels
30
+
31
+ The `curation.labels` field is a small, hand-set list. The supported
32
+ labels are:
33
+
34
+ - `hot` — high recent activity or attention (≥ 1 000 stars or a
35
+ commit within the last 30 days).
36
+ - `new` — added to the directory in the last 7 days.
37
+ - `mature` — long-running curated project, pushed > 365 days ago,
38
+ but still maintained.
39
+ - `featured` — editor's pick for the front page.
40
+ - `needs-review` — open submission awaiting a second maintainer
41
+ look.
42
+
43
+ These labels are mutually additive, not exclusive. A `new` record
44
+ can also be `hot`; a `mature` record can also be `featured`.
45
+
46
+ ## Scoring
47
+
48
+ The detail page surfaces a five-dimension score block. The five
49
+ dimensions are:
50
+
51
+ - **Documentation** — is the README / docs site enough to onboard
52
+ without reading the source?
53
+ - **Onboarding** — install in < 5 min, first useful result in < 1 h.
54
+ - **Maintenance** — recent commits, issue close rate, release cadence.
55
+ - **Community** — issues answered, discussions active, contributors
56
+ per release.
57
+ - **Production-readiness** — CI, tests, security policy, releases
58
+ with semver.
59
+
60
+ Each dimension is scored 0-5. The overall score on the detail page
61
+ is the mean. Scores are stored on the record, not computed at build
62
+ time, so we can deliberately disagree with the raw signals (a 1-line
63
+ toy that happens to be wildly popular can still score low on
64
+ documentation).
65
+
66
+ ## Health signals
67
+
68
+ The `health` block on each record tracks four state machines:
69
+
70
+ - `status` — `healthy` / `slowing` / `stale` / `archived`.
71
+ - `tier` — `curated` / `community` / `experimental`.
72
+ - `issue_close_rate` — moving 90-day average.
73
+ - `last_release` — most recent semver tag.
74
+
75
+ A record's `status` transitions are time-boxed: a project with no
76
+ commits in 365 days flips from `slowing` to `stale`; 730 days flips
77
+ to `archived`. The transition is mechanical; the recovery is
78
+ manual (a maintainer sets `tier` back to `community` after review).
79
+
80
+ ## Lenses
81
+
82
+ The directory's home page surfaces three curated lenses:
83
+
84
+ - **Hot** — `curation.labels` includes `hot`, OR `github.stars` ≥
85
+ 1 000, OR `pushedAt` within 30 days.
86
+ - **New** — `pushedAt` within 7 days, OR `curation.labels` includes
87
+ `new`.
88
+ - **Mature** — `health.tier === "curated"` AND `pushedAt` > 365 days
89
+ ago.
90
+
91
+ Each lens caps at 6 items on the home page; the full filtered list
92
+ is reachable via the lens tab on `/projects`.
93
+
94
+ ## What we don't promise
95
+
96
+ - **Real-time data.** The site is rebuilt on a schedule, not on
97
+ every push. Counts and dates can lag by up to one build cycle.
98
+ - **Verdict-free scores.** A high overall score means "useful to a
99
+ curious developer", not "best in class" — judgement calls are
100
+ documented per record, not baked into the score.
101
+ - **Causal claims.** The "best for" / "why listed" / "caveats"
102
+ blocks are editorial notes, not derived signals. They reflect
103
+ what a maintainer thought at review time.
@@ -0,0 +1,8 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "blueprint": "project-directory",
4
+ "generatedAt": "2026-06-10T15:03:23.841Z",
5
+ "totalRecords": 0,
6
+ "visibleRecords": 0,
7
+ "records": []
8
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "blueprint": "project-directory",
4
+ "generatedAt": "2026-06-10T15:03:23.841Z",
5
+ "totalRecords": 0,
6
+ "records": []
7
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "blueprint": "project-directory",
4
+ "generatedAt": "2026-06-10T15:03:23.841Z",
5
+ "totalRecords": 0,
6
+ "visibleRecords": 0,
7
+ "records": []
8
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "blueprint": "project-directory",
3
+ "name": "Grove Directory",
4
+ "tagline": "A curated, health-aware developer directory.",
5
+ "description": "A curated, health-aware developer directory powered by Grove.",
6
+ "siteUrl": "https://example.com",
7
+ "repoUrl": "https://github.com/tortuvshin/grove",
8
+ "nav": [
9
+ {
10
+ "label": "Home",
11
+ "href": "/"
12
+ },
13
+ {
14
+ "label": "Browse",
15
+ "href": "/projects"
16
+ },
17
+ {
18
+ "label": "About",
19
+ "href": "/about"
20
+ }
21
+ ],
22
+ "theme": {
23
+ "primaryColor": "#16a34a",
24
+ "radius": "soft",
25
+ "density": "comfortable",
26
+ "containerWidth": "72rem"
27
+ },
28
+ "integrations": {
29
+ "github": false
30
+ }
31
+ }
File without changes
@@ -0,0 +1 @@
1
+ @theme{ --color-ink-50: #fafafa; --color-ink-100: #f4f4f5; --color-ink-200: #e4e4e7; --color-ink-300: #d4d4d8; --color-ink-400: #a1a1aa; --color-ink-500: #71717a; --color-ink-600: #52525b; --color-ink-700: #3f3f46; --color-ink-800: #27272a; --color-ink-900: #18181b; --color-ink-950: #09090b; --color-accent: #1f6feb; --color-accent-muted: #388bfd1a; --color-emerald-50: #ecfdf5; --color-emerald-300: #6ee7b7; --color-emerald-400: #34d399; --color-emerald-700: #047857; --color-emerald-800: #065f46; --color-emerald-950: #022c22; --color-orange-50: #fff7ed; --color-orange-300: #fdba74; --color-orange-400: #fb923c; --color-orange-700: #c2410c; --color-orange-800: #9a3412; --color-orange-950: #431407; --color-blue-50: #eff6ff; --color-blue-300: #93c5fd; --color-blue-400: #60a5fa; --color-blue-700: #1d4ed8; --color-blue-800: #1e40af; --color-blue-950: #172554; --color-amber-50: #fffbeb; --color-amber-300: #fcd34d; --color-amber-400: #fbbf24; --color-amber-700: #b45309; --color-amber-800: #92400e; --color-amber-950: #451a03; --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; --text-2xs: .6875rem; --text-2xs--line-height: 1rem; --container-container: 1140px; --container-wide: 1400px; --container-narrow: 720px; --radius: .375rem; --radius-lg: .5rem; --radius-xl: .75rem; --animate-fade-in-up: fade-in-up .6s ease-out both; --animate-fade-in: fade-in .8s ease-out both; --animate-gradient-shift: gradient-shift 8s ease-in-out infinite; --animate-subtle-bob: subtle-bob 3s ease-in-out infinite; }@keyframes fade-in-up{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes gradient-shift{0%,to{background-position:0% 50%}50%{background-position:100% 50%}}@keyframes subtle-bob{0%,to{transform:translateY(0)}50%{transform:translateY(-2px)}}@layer base{:root{color-scheme:light dark}html{-webkit-text-size-adjust:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0;background:#fff;color:var(--color-ink-900);font-family:var(--font-sans);font-size:15px;line-height:1.55}body.dark{background:var(--color-ink-950);color:var(--color-ink-100)}a{color:inherit;text-decoration:none}::selection{background:var(--color-ink-200);color:var(--color-ink-900)}.dark ::selection{background:var(--color-ink-800);color:var(--color-ink-100)}*{scrollbar-width:thin;scrollbar-color:var(--color-ink-300) transparent}*::-webkit-scrollbar{width:8px;height:8px}*::-webkit-scrollbar-thumb{background:var(--color-ink-200);border-radius:4px}.dark *::-webkit-scrollbar-thumb{background:var(--color-ink-800)}input,select,textarea,button{font:inherit}}@layer components{.grove-section{padding-top:3rem;padding-bottom:3rem}@media(min-width:640px){.grove-section{padding-top:4rem;padding-bottom:4rem}}.grove-section-tight{padding-top:2.5rem;padding-bottom:2.5rem}@media(min-width:640px){.grove-section-tight{padding-top:3rem;padding-bottom:3rem}}.grove-card{border:1px solid var(--color-ink-200);background:#fff;border-radius:var(--radius-lg);transition:border-color .15s ease,box-shadow .15s ease,background-color .15s ease}.dark .grove-card{border-color:var(--color-ink-800);background:var(--color-ink-950)}.grove-card-hover:hover{border-color:var(--color-ink-300);background:#f4f4f599;box-shadow:0 1px 2px #0000000a,0 4px 12px -2px #0000000f}.dark .grove-card-hover:hover{border-color:var(--color-ink-700);background:#18181b66;box-shadow:0 1px 2px #0006,0 4px 12px -2px #0000004d}.grove-badge{display:inline-flex;align-items:center;gap:.25rem;border-radius:.25rem;border:1px solid var(--color-ink-200);background:var(--color-ink-50);padding:.125rem .375rem;font-size:var(--text-2xs);line-height:var(--text-2xs--line-height);font-weight:500;color:var(--color-ink-700)}.dark .grove-badge{border-color:var(--color-ink-800);background:#18181b99;color:var(--color-ink-300)}.grove-badge-accent{border-color:var(--color-ink-900);background:var(--color-ink-900);color:#fff}.dark .grove-badge-accent{border-color:var(--color-ink-100);background:var(--color-ink-100);color:var(--color-ink-900)}.grove-badge-new{border-color:#6ee7b799;background:var(--color-emerald-50);color:var(--color-emerald-800)}.dark .grove-badge-new{border-color:#065f4699;background:#022c2266;color:var(--color-emerald-300)}.grove-badge-hot{border-color:#fdba7499;background:var(--color-orange-50);color:var(--color-orange-800)}.dark .grove-badge-hot{border-color:#9a341299;background:#43140766;color:var(--color-orange-300)}.grove-badge-mature{border-color:#93c5fd99;background:var(--color-blue-50);color:var(--color-blue-800)}.dark .grove-badge-mature{border-color:#1e40af99;background:#17255466;color:var(--color-blue-300)}.grove-badge-stale{border-color:#fcd34d99;background:var(--color-amber-50);color:var(--color-amber-800)}.dark .grove-badge-stale{border-color:#92400e99;background:#451a0366;color:var(--color-amber-300)}.grove-btn{display:inline-flex;align-items:center;justify-content:center;gap:.375rem;border-radius:var(--radius);border:1px solid transparent;padding:.375rem .75rem;font-size:.875rem;line-height:1.25rem;font-weight:500;transition:color .15s ease,background-color .15s ease,border-color .15s ease;text-decoration:none;cursor:pointer}.grove-btn-ghost{color:var(--color-ink-700)}.grove-btn-ghost:hover{background:var(--color-ink-100)}.dark .grove-btn-ghost{color:var(--color-ink-300)}.dark .grove-btn-ghost:hover{background:var(--color-ink-900)}.grove-btn-outline{border-color:var(--color-ink-200);background:#fff;color:var(--color-ink-900)}.grove-btn-outline:hover{border-color:var(--color-ink-300);background:var(--color-ink-50)}.dark .grove-btn-outline{border-color:var(--color-ink-800);background:var(--color-ink-950);color:var(--color-ink-100)}.dark .grove-btn-outline:hover{border-color:var(--color-ink-700);background:#18181b66}.grove-btn-primary{border-color:var(--color-ink-900);background:var(--color-ink-900);color:#fff}.grove-btn-primary:hover{background:var(--color-ink-800)}.dark .grove-btn-primary{border-color:var(--color-ink-100);background:var(--color-ink-100);color:var(--color-ink-900)}.dark .grove-btn-primary:hover{background:#fff}}
@@ -0,0 +1,165 @@
1
+ <!DOCTYPE html><html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="generator" content="Astro v6.4.5"><meta name="theme-color" content="#0a0a0c" media="(prefers-color-scheme: dark)"><meta name="theme-color" content="#fafafa" media="(prefers-color-scheme: light)"><link rel="icon" href="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"><link rel="sitemap" href="/sitemap.xml"><link rel="alternate" type="text/plain" href="/llms.txt" title="LLM usage guidance"><!-- Primary meta --><title>About — Grove Directory</title><meta name="description" content="About Grove Directory — A curated, health-aware developer directory."><link rel="canonical" href="https://example.com/about/"><!-- Open Graph --><meta property="og:type" content="website"><meta property="og:url" content="https://example.com/about/"><meta property="og:title" content="About — Grove Directory"><meta property="og:description" content="About Grove Directory — A curated, health-aware developer directory."><meta property="og:image" content="https://example.com/og-image.svg"><meta property="og:image:width" content="1200"><meta property="og:image:height" content="630"><meta property="og:image:alt" content="Grove Directory — A curated, health-aware developer directory powered by Grove."><meta property="og:site_name" content="Grove Directory"><meta property="og:locale" content="en"><!-- Twitter Card --><meta name="twitter:card" content="summary_large_image"><meta name="twitter:title" content="About — Grove Directory"><meta name="twitter:description" content="About Grove Directory — A curated, health-aware developer directory."><meta name="twitter:image" content="https://example.com/og-image.svg"><meta name="twitter:image:alt" content="Grove Directory — A curated, health-aware developer directory powered by Grove."><!-- JSON-LD: site-wide identity --><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebSite","name":"Grove Directory","description":"A curated, health-aware developer directory powered by Grove.","url":"https://example.com","inLanguage":"en","potentialAction":{"@type":"SearchAction","target":"https://example.com/projects?q={search_term_string}","query-input":"required name=search_term_string"}}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"Organization","name":"Grove Directory","url":"https://example.com","logo":"https://example.com/og-image.svg","sameAs":["https://github.com/tortuvshin/grove"]}</script><!-- JSON-LD: per-page (SoftwareSourceCode for [slug], etc.) --><script>
2
+ // THEME_INIT — runs before paint so the user never sees a
3
+ // flash of the wrong theme. State is persisted in localStorage
4
+ // under the key "grove-theme" (matching ThemeToggle.astro).
5
+ (function () {
6
+ var STORAGE_KEY = "grove-theme";
7
+ function getMode() {
8
+ try {
9
+ var stored = localStorage.getItem(STORAGE_KEY);
10
+ if (stored === "light" || stored === "dark" || stored === "system")
11
+ return stored;
12
+ } catch (e) {
13
+ // localStorage may be blocked (private mode, etc.) —
14
+ // fall through to the system default.
15
+ }
16
+ return "system";
17
+ }
18
+ function apply(mode) {
19
+ var resolved =
20
+ mode === "system"
21
+ ? (window.matchMedia("(prefers-color-scheme: dark)").matches
22
+ ? "dark"
23
+ : "light")
24
+ : mode;
25
+ var root = document.documentElement;
26
+ if (resolved === "dark") root.classList.add("dark");
27
+ else root.classList.remove("dark");
28
+ root.dataset.theme = mode;
29
+ root.dataset.resolvedTheme = resolved;
30
+ }
31
+ apply(getMode());
32
+ window.addEventListener("theme:set", function (e) {
33
+ apply(e.detail);
34
+ });
35
+ var mq = window.matchMedia("(prefers-color-scheme: dark)");
36
+ mq.addEventListener("change", function () {
37
+ if (getMode() === "system") apply("system");
38
+ });
39
+ })();
40
+ </script><link rel="stylesheet" href="/_astro/BaseLayout.DfpwR3jc.css">
41
+ <style>.grove-container[data-astro-cid-nj4gdmks]{max-width:var(--container-container, 1140px)}.grove-container-wide[data-astro-cid-nj4gdmks]{max-width:var(--container-wide, 1400px)}.grove-container-narrow[data-astro-cid-nj4gdmks]{max-width:var(--container-narrow, 720px)}
42
+ </style></head> <body class="min-h-screen bg-white text-ink-900 dark:bg-ink-950 dark:text-ink-100"> <header class="sticky top-0 z-50 border-b border-ink-200 bg-white/85 backdrop-blur dark:border-ink-800 dark:bg-ink-950/85"> <div class="mx-auto flex max-w-wide items-center gap-6 px-5 py-3 sm:px-7" style="max-width: var(--container-wide, 1400px);"> <a href="/" class="inline-flex items-center gap-2 text-[0.95rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"> <span aria-hidden="true" class="inline-block h-[18px] w-[18px] rounded bg-ink-900 dark:bg-ink-100"></span> <span>Grove Directory</span> </a> <nav class="hidden flex-1 md:block" aria-label="Primary"> <ul class="m-0 flex list-none gap-5 p-0"> <li> <a href="/" class="text-[0.875rem] font-medium text-ink-500 transition-colors hover:text-ink-900 dark:text-ink-400 dark:hover:text-ink-100"> Home </a> </li><li> <a href="/projects" class="text-[0.875rem] font-medium text-ink-500 transition-colors hover:text-ink-900 dark:text-ink-400 dark:hover:text-ink-100"> Browse </a> </li><li> <a href="/about" class="text-[0.875rem] font-medium text-ink-500 transition-colors hover:text-ink-900 dark:text-ink-400 dark:hover:text-ink-100"> About </a> </li> </ul> </nav> <div class="ml-auto flex items-center gap-2"> <a href="https://github.com/tortuvshin/grove" target="_blank" rel="noopener noreferrer" aria-label="Star" title="Star" class="grove-btn grove-btn-outline"> <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" fill="currentColor"> <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38v-1.33c-2.22.48-2.69-1.07-2.69-1.07-.36-.92-.89-1.17-.89-1.17-.73-.5.05-.49.05-.49.8.06 1.23.83 1.23.83.72 1.22 1.87.87 2.33.67.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48v2.19c0 .21.15.46.55.38A8 8 0 0 0 8 0Z"></path> </svg> <span>Star</span> </a> <button type="button" data-theme-toggle aria-label="Toggle theme" title="Toggle theme" class="relative inline-flex h-8 w-8 items-center justify-center rounded-md border border-ink-200 bg-white text-ink-700 transition-colors hover:border-ink-300 hover:bg-ink-50 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-200 dark:hover:border-ink-700 dark:hover:bg-ink-900/40"> <svg class="hidden h-4 w-4 dark:block" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> <circle cx="12" cy="12" r="4"></circle> <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"></path> </svg> <svg class="block h-4 w-4 dark:hidden" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z"></path> </svg> </button> <script type="module">const c="grove-theme";function o(){try{const e=localStorage.getItem(c);return e==="light"||e==="dark"||e==="system"?e:"system"}catch{return"system"}}function s(){const e=["light","dark","system"],n=o(),r=e[(e.indexOf(n)+1)%e.length];try{localStorage.setItem(c,r)}catch{}return window.dispatchEvent(new CustomEvent("theme:set",{detail:r})),r}function t(e){e.title=`Theme: ${o()} (click to switch)`}document.querySelectorAll("[data-theme-toggle]").forEach(e=>{t(e),e.addEventListener("click",()=>{s(),t(e)}),e.addEventListener("mouseenter",()=>t(e)),e.addEventListener("focus",()=>t(e))});</script> </div> </div> </header> <script type="module">document.addEventListener("click",t=>{if(t.defaultPrevented||t.metaKey||t.ctrlKey||t.shiftKey||t.altKey||t.button!==0)return;const r=t.target.closest('a[href^="#"]');if(!r)return;const e=r.getAttribute("href");if(e.length<=1)return;const n=document.querySelector(e);n&&(t.preventDefault(),n.scrollIntoView({behavior:"smooth",block:"start"}),history.replaceState(null,"",e))});</script> <main> <div class="mx-auto grove-container px-5 sm:px-7" data-astro-cid-nj4gdmks> <article class="py-12 sm:py-16"> <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
43
+ About
44
+ </span> <h1 class="mt-1 text-[1.75rem] sm:text-[2.25rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100 m-0">
45
+ About Grove Directory </h1> <p class="mt-4 max-w-[62ch] text-base text-ink-700 dark:text-ink-200 m-0"> Grove Directory is a community knowledge site built with <a href="https://github.com/tortuvshin/grove" target="_blank" rel="noopener noreferrer" class="text-ink-900 underline hover:text-ink-700 dark:text-ink-100 dark:hover:text-white">
46
+ Grove
47
+ </a>
48
+ — an open-source framework for growing useful structured knowledge.
49
+ </p> </article> </div> <section class="grove-section"> <div class="grove-container-narrow mx-auto px-5 sm:px-7"> <header class="mb-6 flex flex-col gap-2"> <h2 class="m-0 text-[1.5rem] sm:text-[1.625rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"> Why this exists </h2> <p class="m-0 text-[0.9375rem] text-ink-500 dark:text-ink-400"> Three principles guide what gets listed and how it&#39;s surfaced. </p> </header> <ol class="m-0 flex list-none flex-col gap-5 p-0"> <li class="flex gap-4"> <span aria-hidden="true" class="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md border border-ink-200 bg-ink-50 font-mono text-2xs text-ink-500 dark:border-ink-800 dark:bg-ink-900 dark:text-ink-400">
50
+ 01 </span> <div> <h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"> Curated, not scraped </h3> <p class="m-0 mt-1 text-[0.9375rem] leading-relaxed text-ink-500 dark:text-ink-400"> Every entry is hand-checked. If something here, it&#39;s because someone looked at the source, ran the build, and decided it was worth your time. </p> </div> </li><li class="flex gap-4"> <span aria-hidden="true" class="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md border border-ink-200 bg-ink-50 font-mono text-2xs text-ink-500 dark:border-ink-800 dark:bg-ink-900 dark:text-ink-400">
51
+ 02 </span> <div> <h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"> Plain text all the way down </h3> <p class="m-0 mt-1 text-[0.9375rem] leading-relaxed text-ink-500 dark:text-ink-400"> Records live in plain YAML, content in plain Markdown, and the static output in plain HTML. No database, no JavaScript framework, no CDN with sticky sessions. </p> </div> </li><li class="flex gap-4"> <span aria-hidden="true" class="flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-md border border-ink-200 bg-ink-50 font-mono text-2xs text-ink-500 dark:border-ink-800 dark:bg-ink-900 dark:text-ink-400">
52
+ 03 </span> <div> <h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"> Free to fork, free to extend </h3> <p class="m-0 mt-1 text-[0.9375rem] leading-relaxed text-ink-500 dark:text-ink-400"> This whole site is one repo. If you want your own copy, fork it. If you want to add a record, open a PR. The directory is the community. </p> </div> </li> </ol> </div> </section> <div class="mx-auto grove-container px-5 sm:px-7" data-astro-cid-nj4gdmks> <section class="py-12 sm:py-16" aria-labelledby="methodology-heading"> <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
53
+ Methodology
54
+ </span> <h2 id="methodology-heading" class="mt-1 text-[1.5rem] sm:text-[1.75rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100 m-0">
55
+ How records are picked, scored, and surfaced
56
+ </h2> <p class="mt-3 max-w-[62ch] text-sm text-ink-500 dark:text-ink-400 m-0">
57
+ The full editorial + scoring methodology lives in
58
+ <code class="font-mono text-ink-900 dark:text-ink-100">content/pages/methodology.md</code>.
59
+ Below is the rendered source so reviewers can audit the criteria.
60
+ </p> <pre class="mt-6 overflow-x-auto rounded-md border border-ink-200 bg-ink-50 p-4 text-xs leading-relaxed text-ink-800 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-200"><code>---
61
+ title: &quot;Methodology&quot;
62
+ description: &quot;How records are picked, scored, and surfaced in this directory.&quot;
63
+ ---
64
+
65
+ # Methodology
66
+
67
+ This page describes how entries in the directory are chosen, how the
68
+ curation signals are computed, and what we do (and don&#39;t) promise about
69
+ the data. The intent is to make the directory&#39;s logic auditable — every
70
+ number on a detail page is reproducible from the record&#39;s YAML.
71
+
72
+ ## Selection
73
+
74
+ A record is included if it is:
75
+
76
+ 1. **Open source.** Source is public under an OSI-approved license
77
+ (or public-domain dedication).
78
+ 2. **Actively maintained.** A pushed commit within the last 18 months
79
+ OR a declared maintenance status of `maintained` / `passive`.
80
+ 3. **Useful to a curious developer.** A working build, a real user
81
+ base (or a credible path to one), and a clear description of what
82
+ the project does.
83
+
84
+ We exclude: abandoned experiments, vapourware, projects whose entire
85
+ purpose is to sell a hosted plan, and any record that has not been
86
+ hand-checked by a maintainer.
87
+
88
+ ## Curation labels
89
+
90
+ The `curation.labels` field is a small, hand-set list. The supported
91
+ labels are:
92
+
93
+ - `hot` — high recent activity or attention (≥ 1 000 stars or a
94
+ commit within the last 30 days).
95
+ - `new` — added to the directory in the last 7 days.
96
+ - `mature` — long-running curated project, pushed &gt; 365 days ago,
97
+ but still maintained.
98
+ - `featured` — editor&#39;s pick for the front page.
99
+ - `needs-review` — open submission awaiting a second maintainer
100
+ look.
101
+
102
+ These labels are mutually additive, not exclusive. A `new` record
103
+ can also be `hot`; a `mature` record can also be `featured`.
104
+
105
+ ## Scoring
106
+
107
+ The detail page surfaces a five-dimension score block. The five
108
+ dimensions are:
109
+
110
+ - **Documentation** — is the README / docs site enough to onboard
111
+ without reading the source?
112
+ - **Onboarding** — install in &lt; 5 min, first useful result in &lt; 1 h.
113
+ - **Maintenance** — recent commits, issue close rate, release cadence.
114
+ - **Community** — issues answered, discussions active, contributors
115
+ per release.
116
+ - **Production-readiness** — CI, tests, security policy, releases
117
+ with semver.
118
+
119
+ Each dimension is scored 0-5. The overall score on the detail page
120
+ is the mean. Scores are stored on the record, not computed at build
121
+ time, so we can deliberately disagree with the raw signals (a 1-line
122
+ toy that happens to be wildly popular can still score low on
123
+ documentation).
124
+
125
+ ## Health signals
126
+
127
+ The `health` block on each record tracks four state machines:
128
+
129
+ - `status` — `healthy` / `slowing` / `stale` / `archived`.
130
+ - `tier` — `curated` / `community` / `experimental`.
131
+ - `issue_close_rate` — moving 90-day average.
132
+ - `last_release` — most recent semver tag.
133
+
134
+ A record&#39;s `status` transitions are time-boxed: a project with no
135
+ commits in 365 days flips from `slowing` to `stale`; 730 days flips
136
+ to `archived`. The transition is mechanical; the recovery is
137
+ manual (a maintainer sets `tier` back to `community` after review).
138
+
139
+ ## Lenses
140
+
141
+ The directory&#39;s home page surfaces three curated lenses:
142
+
143
+ - **Hot** — `curation.labels` includes `hot`, OR `github.stars` ≥
144
+ 1 000, OR `pushedAt` within 30 days.
145
+ - **New** — `pushedAt` within 7 days, OR `curation.labels` includes
146
+ `new`.
147
+ - **Mature** — `health.tier === &quot;curated&quot;` AND `pushedAt` &gt; 365 days
148
+ ago.
149
+
150
+ Each lens caps at 6 items on the home page; the full filtered list
151
+ is reachable via the lens tab on `/projects`.
152
+
153
+ ## What we don&#39;t promise
154
+
155
+ - **Real-time data.** The site is rebuilt on a schedule, not on
156
+ every push. Counts and dates can lag by up to one build cycle.
157
+ - **Verdict-free scores.** A high overall score means &quot;useful to a
158
+ curious developer&quot;, not &quot;best in class&quot; — judgement calls are
159
+ documented per record, not baked into the score.
160
+ - **Causal claims.** The &quot;best for&quot; / &quot;why listed&quot; / &quot;caveats&quot;
161
+ blocks are editorial notes, not derived signals. They reflect
162
+ what a maintainer thought at review time.
163
+ </code></pre> </section> </div> </main> <footer class="mt-16 border-t border-ink-200 bg-white text-ink-500 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-400"> <div class="mx-auto grid max-w-wide grid-cols-2 gap-8 px-5 py-12 md:grid-cols-4 sm:px-7" style="max-width: var(--container-wide, 1400px);"> <div class="col-span-2 md:col-span-1"> <div class="mb-2 inline-flex items-center gap-2 text-[0.95rem] font-semibold text-ink-900 dark:text-ink-100"> <span aria-hidden="true" class="inline-block h-3.5 w-3.5 rounded bg-ink-900 dark:bg-ink-100"></span> <span>Grove Directory</span> </div> <p class="m-0 max-w-[36ch] text-[0.8125rem] leading-relaxed text-ink-500 dark:text-ink-400"> A curated, health-aware developer directory. </p> </div> <div> <h4 class="mb-3 text-2xs font-semibold uppercase tracking-wider text-ink-900 dark:text-ink-100"> Discover </h4> <ul class="m-0 flex list-none flex-col gap-2 p-0"> <li> <a href="https://github.com/tortuvshin/grove" class="text-sm transition-colors hover:text-ink-900 dark:hover:text-ink-100" target="_blank" rel="noopener noreferrer"> Source on GitHub </a> </li><li> <a href="https://github.com/tortuvshin/grove/releases" class="text-sm transition-colors hover:text-ink-900 dark:hover:text-ink-100" target="_blank" rel="noopener noreferrer"> Latest release </a> </li> </ul> </div> <div> <h4 class="mb-3 text-2xs font-semibold uppercase tracking-wider text-ink-900 dark:text-ink-100"> Contribute </h4> <ul class="m-0 flex list-none flex-col gap-2 p-0"> <li> <a href="https://github.com/tortuvshin/grove/blob/main/CONTRIBUTING.md" class="text-sm transition-colors hover:text-ink-900 dark:hover:text-ink-100" target="_blank" rel="noopener noreferrer"> Submit via GitHub </a> </li><li> <a href="https://github.com/tortuvshin/grove/issues" class="text-sm transition-colors hover:text-ink-900 dark:hover:text-ink-100" target="_blank" rel="noopener noreferrer"> Report an issue </a> </li><li> <a href="https://github.com/tortuvshin/grove/blob/main/CONTRIBUTING.md" class="text-sm transition-colors hover:text-ink-900 dark:hover:text-ink-100" target="_blank" rel="noopener noreferrer"> Contributing guide </a> </li> </ul> </div> <div> <h4 class="mb-3 text-2xs font-semibold uppercase tracking-wider text-ink-900 dark:text-ink-100"> Project </h4> <ul class="m-0 flex list-none flex-col gap-2 p-0"> <li> <a href="https://github.com/tortuvshin/grove/blob/main/README.md" class="text-sm transition-colors hover:text-ink-900 dark:hover:text-ink-100" target="_blank" rel="noopener noreferrer"> README </a> </li><li> <a href="https://github.com/tortuvshin/grove/blob/main/CHANGELOG.md" class="text-sm transition-colors hover:text-ink-900 dark:hover:text-ink-100" target="_blank" rel="noopener noreferrer"> Changelog </a> </li> </ul> </div> </div> <div class="border-t border-ink-200 dark:border-ink-800"> <div class="mx-auto flex max-w-wide flex-wrap items-center gap-3 px-5 py-4 text-2xs text-ink-500 sm:px-7 dark:text-ink-400" style="max-width: var(--container-wide, 1400px);"> <span>© 2026 Grove Directory.</span> <span class="text-ink-300 dark:text-ink-700">·</span> <a href="https://github.com/tortuvshin/grove" target="_blank" rel="noopener noreferrer" class="transition-colors hover:text-ink-900 dark:hover:text-ink-100">
164
+ View source on GitHub
165
+ </a> </div> </div> </footer> </body> </html>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" viewBox="0 0 256 150"><path fill="#34A853" d="M255.285 143.47c-.084-.524-.164-1.042-.251-1.56a128.1 128.1 0 0 0-12.794-38.288 128.8 128.8 0 0 0-23.45-31.86 129.2 129.2 0 0 0-22.713-18.005c.049-.08.09-.168.14-.25 2.582-4.461 5.172-8.917 7.755-13.38l7.576-13.068c1.818-3.126 3.632-6.26 5.438-9.386a11.78 11.78 0 0 0 .662-10.484 11.67 11.67 0 0 0-4.823-5.536 11.85 11.85 0 0 0-5.004-1.61 12 12 0 0 0-2.218.018 11.74 11.74 0 0 0-8.968 5.798c-1.814 3.127-3.628 6.26-5.438 9.386l-7.576 13.069c-2.583 4.462-5.173 8.918-7.755 13.38-.282.487-.567.973-.848 1.467q-.586-.237-1.172-.462c-14.24-5.43-29.688-8.4-45.836-8.4-.442 0-.879 0-1.324.006-14.357.143-28.152 2.64-41.022 7.12a119 119 0 0 0-4.42 1.642c-.262-.455-.532-.911-.79-1.367-2.583-4.462-5.173-8.918-7.755-13.38l-7.576-13.07a3915 3915 0 0 1-5.439-9.386A11.74 11.74 0 0 0 48.5.048a11.7 11.7 0 0 0-5.01 1.612 11.7 11.7 0 0 0-4.077 4.063c-.281.474-.532.967-.742 1.473a11.8 11.8 0 0 0-.365 8.188c.259.786.594 1.554 1.023 2.296a3973 3973 0 0 1 5.439 9.386c2.53 4.357 5.054 8.713 7.58 13.069 2.582 4.462 5.168 8.918 7.75 13.38.02.038.046.075.065.112A129 129 0 0 0 45.32 64.38a129.7 129.7 0 0 0-22.2 24.015 128 128 0 0 0-9.34 15.24 128.2 128.2 0 0 0-10.843 28.764 131 131 0 0 0-1.951 9.524c-.087.518-.167 1.042-.247 1.56A125 125 0 0 0 0 149.118h256a130 130 0 0 0-.734-5.636z"/><path fill="#202124" d="M194.59 113.712c5.122-3.41 5.867-11.3 1.661-17.62-4.203-6.323-11.763-8.682-16.883-5.273-5.122 3.41-5.868 11.3-1.662 17.621 4.203 6.322 11.764 8.682 16.883 5.272Zm-116.072-5.25c4.206-6.321 3.46-14.21-1.662-17.62-5.123-3.41-12.68-1.05-16.886 5.27-4.203 6.323-3.458 14.212 1.662 17.622 5.122 3.41 12.683 1.05 16.886-5.272"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 814 1000"><path d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2m-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 814 1000"><path fill="#fff" d="M788.1 340.9c-5.8 4.5-108.2 62.2-108.2 190.5 0 148.4 130.3 200.9 134.2 202.2-.6 3.2-20.7 71.9-68.7 141.9-42.8 61.6-87.5 123.1-155.5 123.1s-85.5-39.5-164-39.5c-76.5 0-103.7 40.8-165.9 40.8s-105.6-57-155.5-127C46.7 790.7 0 663 0 541.8c0-194.4 126.4-297.5 250.8-297.5 66.1 0 121.2 43.4 162.7 43.4 39.5 0 101.1-46 176.3-46 28.5 0 130.9 2.6 198.3 99.2m-234-181.5c31.1-36.9 53.1-88.1 53.1-139.3 0-7.1-.6-14.3-1.9-20.1-50.6 1.9-110.8 33.7-147.1 75.8-28.5 32.4-55.1 83.6-55.1 135.5 0 7.8 1.3 15.6 1.9 18.1 3.2.6 8.4 1.3 13.6 1.3 45.4 0 102.5-30.4 135.5-71.3"/></svg>