@grove-dev/core 0.7.0 → 0.9.0

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 (159) hide show
  1. package/dist/audit.d.ts +3 -3
  2. package/dist/audit.d.ts.map +1 -1
  3. package/dist/audit.js +25 -6
  4. package/dist/audit.js.map +1 -1
  5. package/dist/awesome-readme.d.ts.map +1 -1
  6. package/dist/awesome-readme.js +31 -31
  7. package/dist/awesome-readme.js.map +1 -1
  8. package/dist/build-data.d.ts +1 -1
  9. package/dist/build-data.d.ts.map +1 -1
  10. package/dist/build-data.js +110 -80
  11. package/dist/build-data.js.map +1 -1
  12. package/dist/candidate.d.ts +34 -0
  13. package/dist/candidate.d.ts.map +1 -0
  14. package/dist/candidate.js +128 -0
  15. package/dist/candidate.js.map +1 -0
  16. package/dist/collections-io.d.ts +1 -1
  17. package/dist/collections-io.js +8 -8
  18. package/dist/collections-io.js.map +1 -1
  19. package/dist/collections.d.ts +2 -2
  20. package/dist/collections.d.ts.map +1 -1
  21. package/dist/collections.js +8 -9
  22. package/dist/collections.js.map +1 -1
  23. package/dist/collector.d.ts +1 -1
  24. package/dist/collector.d.ts.map +1 -1
  25. package/dist/collector.js +2 -2
  26. package/dist/collector.js.map +1 -1
  27. package/dist/config.d.ts +1 -1
  28. package/dist/config.d.ts.map +1 -1
  29. package/dist/config.js +4 -4
  30. package/dist/config.js.map +1 -1
  31. package/dist/content-body.d.ts.map +1 -1
  32. package/dist/content-body.js +22 -22
  33. package/dist/content-body.js.map +1 -1
  34. package/dist/contributors.d.ts.map +1 -1
  35. package/dist/contributors.js +22 -23
  36. package/dist/contributors.js.map +1 -1
  37. package/dist/decisions.d.ts +1 -1
  38. package/dist/decisions.d.ts.map +1 -1
  39. package/dist/decisions.js +18 -20
  40. package/dist/decisions.js.map +1 -1
  41. package/dist/directory-client.d.ts +6 -4
  42. package/dist/directory-client.d.ts.map +1 -1
  43. package/dist/directory-client.js +9 -4
  44. package/dist/directory-client.js.map +1 -1
  45. package/dist/directory-facets.d.ts +1 -1
  46. package/dist/directory-facets.d.ts.map +1 -1
  47. package/dist/directory-facets.js +35 -17
  48. package/dist/directory-facets.js.map +1 -1
  49. package/dist/directory-filter-keys.d.ts +53 -0
  50. package/dist/directory-filter-keys.d.ts.map +1 -0
  51. package/dist/directory-filter-keys.js +66 -0
  52. package/dist/directory-filter-keys.js.map +1 -0
  53. package/dist/directory-format.d.ts.map +1 -1
  54. package/dist/directory-format.js +15 -15
  55. package/dist/directory-format.js.map +1 -1
  56. package/dist/directory-lenses.d.ts +1 -1
  57. package/dist/directory-lenses.d.ts.map +1 -1
  58. package/dist/directory-lenses.js +66 -61
  59. package/dist/directory-lenses.js.map +1 -1
  60. package/dist/directory-repo.js +2 -2
  61. package/dist/directory-scores.d.ts.map +1 -1
  62. package/dist/directory-scores.js +25 -25
  63. package/dist/directory-scores.js.map +1 -1
  64. package/dist/directory-search.d.ts +4 -4
  65. package/dist/directory-search.d.ts.map +1 -1
  66. package/dist/directory-search.js +129 -110
  67. package/dist/directory-search.js.map +1 -1
  68. package/dist/directory-taxonomy.d.ts +1 -1
  69. package/dist/directory-taxonomy.d.ts.map +1 -1
  70. package/dist/directory-taxonomy.js +2 -2
  71. package/dist/directory-taxonomy.js.map +1 -1
  72. package/dist/enrich.d.ts.map +1 -1
  73. package/dist/enrich.js +15 -12
  74. package/dist/enrich.js.map +1 -1
  75. package/dist/evidence.d.ts +33 -0
  76. package/dist/evidence.d.ts.map +1 -0
  77. package/dist/evidence.js +181 -0
  78. package/dist/evidence.js.map +1 -0
  79. package/dist/github-client.js +6 -6
  80. package/dist/github.d.ts +9 -1
  81. package/dist/github.d.ts.map +1 -1
  82. package/dist/github.js +40 -13
  83. package/dist/github.js.map +1 -1
  84. package/dist/health.d.ts +1 -1
  85. package/dist/health.js +44 -44
  86. package/dist/host.js +2 -2
  87. package/dist/import-types.d.ts +24 -0
  88. package/dist/import-types.d.ts.map +1 -0
  89. package/dist/import-types.js +2 -0
  90. package/dist/import-types.js.map +1 -0
  91. package/dist/import-write.d.ts +12 -0
  92. package/dist/import-write.d.ts.map +1 -0
  93. package/dist/import-write.js +39 -0
  94. package/dist/import-write.js.map +1 -0
  95. package/dist/importer.d.ts +5 -3
  96. package/dist/importer.d.ts.map +1 -1
  97. package/dist/importer.js +4 -2
  98. package/dist/importer.js.map +1 -1
  99. package/dist/index.d.ts +74 -63
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +94 -75
  102. package/dist/index.js.map +1 -1
  103. package/dist/io.js +8 -8
  104. package/dist/llms.d.ts +1 -1
  105. package/dist/llms.d.ts.map +1 -1
  106. package/dist/llms.js +65 -64
  107. package/dist/llms.js.map +1 -1
  108. package/dist/markdown.d.ts +3 -33
  109. package/dist/markdown.d.ts.map +1 -1
  110. package/dist/markdown.js +2 -39
  111. package/dist/markdown.js.map +1 -1
  112. package/dist/og-image.d.ts +8 -8
  113. package/dist/og-image.d.ts.map +1 -1
  114. package/dist/og-image.js +91 -84
  115. package/dist/og-image.js.map +1 -1
  116. package/dist/page-document.d.ts +8 -8
  117. package/dist/page-document.d.ts.map +1 -1
  118. package/dist/page-document.js +48 -54
  119. package/dist/page-document.js.map +1 -1
  120. package/dist/parseReadme.d.ts.map +1 -1
  121. package/dist/parseReadme.js +9 -11
  122. package/dist/parseReadme.js.map +1 -1
  123. package/dist/prepare.d.ts +5 -5
  124. package/dist/prepare.d.ts.map +1 -1
  125. package/dist/prepare.js +32 -38
  126. package/dist/prepare.js.map +1 -1
  127. package/dist/readme-health.d.ts +33 -0
  128. package/dist/readme-health.d.ts.map +1 -0
  129. package/dist/readme-health.js +88 -0
  130. package/dist/readme-health.js.map +1 -0
  131. package/dist/related.d.ts +1 -1
  132. package/dist/repository-health.d.ts +11 -0
  133. package/dist/repository-health.d.ts.map +1 -0
  134. package/dist/repository-health.js +94 -0
  135. package/dist/repository-health.js.map +1 -0
  136. package/dist/schema.d.ts +72 -30
  137. package/dist/schema.d.ts.map +1 -1
  138. package/dist/schema.js +156 -144
  139. package/dist/schema.js.map +1 -1
  140. package/dist/site-artifacts.js +1 -1
  141. package/dist/sitemap.d.ts +2 -2
  142. package/dist/sitemap.d.ts.map +1 -1
  143. package/dist/sitemap.js +39 -39
  144. package/dist/sitemap.js.map +1 -1
  145. package/dist/slug.js +4 -4
  146. package/dist/sync-icons.js +15 -15
  147. package/dist/taxonomy-inference.d.ts +27 -0
  148. package/dist/taxonomy-inference.d.ts.map +1 -0
  149. package/dist/taxonomy-inference.js +38 -0
  150. package/dist/taxonomy-inference.js.map +1 -0
  151. package/dist/validate.d.ts +3 -3
  152. package/dist/validate.d.ts.map +1 -1
  153. package/dist/validate.js +73 -62
  154. package/dist/validate.js.map +1 -1
  155. package/dist/yaml.d.ts +47 -0
  156. package/dist/yaml.d.ts.map +1 -0
  157. package/dist/yaml.js +56 -0
  158. package/dist/yaml.js.map +1 -0
  159. package/package.json +6 -1
@@ -25,40 +25,40 @@ export function scoreTier(n) {
25
25
  export function scoreTierLabel(n) {
26
26
  switch (scoreTier(n)) {
27
27
  case 0:
28
- return "Very low";
28
+ return 'Very low';
29
29
  case 1:
30
- return "Low";
30
+ return 'Low';
31
31
  case 2:
32
- return "Medium";
32
+ return 'Medium';
33
33
  case 3:
34
- return "High";
34
+ return 'High';
35
35
  case 4:
36
- return "Very high";
36
+ return 'Very high';
37
37
  }
38
38
  }
39
39
  /** Short numeric label used inline next to a score bar, e.g. "82". */
40
40
  export function scoreLabel(n) {
41
- if (typeof n !== "number")
42
- return "—";
41
+ if (typeof n !== 'number')
42
+ return '—';
43
43
  return Math.round(n).toString();
44
44
  }
45
45
  /** Order of score dimensions, used by both list and detail. */
46
46
  export const SCORE_DIMENSIONS = [
47
- "activity",
48
- "maturity",
49
- "learning",
50
- "contribution",
51
- "docs",
52
- "overall",
47
+ 'activity',
48
+ 'maturity',
49
+ 'learning',
50
+ 'contribution',
51
+ 'docs',
52
+ 'overall',
53
53
  ];
54
54
  /** Human-readable label for a score dimension. */
55
55
  export const SCORE_LABELS = {
56
- activity: "Activity",
57
- maturity: "Maturity",
58
- learning: "Learning",
59
- contribution: "Contribution",
60
- docs: "Docs",
61
- overall: "Overall",
56
+ activity: 'Activity',
57
+ maturity: 'Maturity',
58
+ learning: 'Learning',
59
+ contribution: 'Contribution',
60
+ docs: 'Docs',
61
+ overall: 'Overall',
62
62
  };
63
63
  /**
64
64
  * Reasoning copy per dimension, surfaced in the detail page so the
@@ -66,11 +66,11 @@ export const SCORE_LABELS = {
66
66
  * curation.notes on each item carries the longer story.
67
67
  */
68
68
  export const SCORE_REASONING = {
69
- activity: "Recent commits, open and merged PRs, issue response time, release cadence.",
70
- maturity: "Repo age, stable structure, real-world production usage, contributor stability.",
71
- learning: "Codebase readability, useful architecture patterns, comment quality, examples.",
72
- contribution: "Open issues, recent merged PRs from new contributors, maintainer responsiveness, contribution guide.",
73
- docs: "README quality, architecture docs, contributing guide, examples, code comments.",
74
- overall: "Composite judgment — how likely this item is to satisfy a curious developer.",
69
+ activity: 'Recent commits, open and merged PRs, issue response time, release cadence.',
70
+ maturity: 'Repo age, stable structure, real-world production usage, contributor stability.',
71
+ learning: 'Codebase readability, useful architecture patterns, comment quality, examples.',
72
+ contribution: 'Open issues, recent merged PRs from new contributors, maintainer responsiveness, contribution guide.',
73
+ docs: 'README quality, architecture docs, contributing guide, examples, code comments.',
74
+ overall: 'Composite judgment — how likely this item is to satisfy a curious developer.',
75
75
  };
76
76
  //# sourceMappingURL=directory-scores.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"directory-scores.js","sourceRoot":"","sources":["../src/directory-scores.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;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,GAAwB;IACnD,UAAU;IACV,UAAU;IACV,UAAU;IACV,cAAc;IACd,MAAM;IACN,SAAS;CACV,CAAC;AAEF,kDAAkD;AAClD,MAAM,CAAC,MAAM,YAAY,GAAoC;IAC3D,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,GAAoC;IAC9D,QAAQ,EACN,4EAA4E;IAC9E,QAAQ,EACN,iFAAiF;IACnF,QAAQ,EACN,gFAAgF;IAClF,YAAY,EACV,sGAAsG;IACxG,IAAI,EACF,iFAAiF;IACnF,OAAO,EACL,8EAA8E;CACjF,CAAC"}
1
+ {"version":3,"file":"directory-scores.js","sourceRoot":"","sources":["../src/directory-scores.ts"],"names":[],"mappings":"AASA;;;;;;;;;;;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,GAAwB;IACnD,UAAU;IACV,UAAU;IACV,UAAU;IACV,cAAc;IACd,MAAM;IACN,SAAS;CACV,CAAC;AAEF,kDAAkD;AAClD,MAAM,CAAC,MAAM,YAAY,GAAoC;IAC3D,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,GAAoC;IAC9D,QAAQ,EAAE,4EAA4E;IACtF,QAAQ,EAAE,iFAAiF;IAC3F,QAAQ,EAAE,gFAAgF;IAC1F,YAAY,EACV,sGAAsG;IACxG,IAAI,EAAE,iFAAiF;IACvF,OAAO,EAAE,8EAA8E;CACxF,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { IndexRecord } from "./schema.js";
1
+ import type { IndexRecord } from './schema.js';
2
2
  /**
3
3
  * URL-driven filter state for the /items discovery page.
4
4
  *
@@ -38,7 +38,7 @@ export declare const SORT_OPTIONS: readonly [{
38
38
  readonly value: "alphabetical";
39
39
  readonly label: "Alphabetical";
40
40
  }];
41
- export type IndexSort = (typeof SORT_OPTIONS)[number]["value"];
41
+ export type IndexSort = (typeof SORT_OPTIONS)[number]['value'];
42
42
  /**
43
43
  * Read filters from a URLSearchParams (or anything URLSearchParams-shaped,
44
44
  * e.g. the result of `new URL(req.url).searchParams`).
@@ -66,7 +66,7 @@ export declare function totalPages(itemCount: number, pageSize?: number): number
66
66
  * component and the client-side rebuild in `DirectoryIndexClient`
67
67
  * so the two never drift.
68
68
  */
69
- export declare function paginationPageList(active: number, count: number): Array<number | "ellipsis">;
69
+ export declare function paginationPageList(active: number, count: number): Array<number | 'ellipsis'>;
70
70
  /** Resolve the effective sort from a filters object, applying default. */
71
71
  export declare function effectiveSort(f: IndexFilters): IndexSort;
72
72
  /** Resolve the effective page from a filters object, applying default. */
@@ -160,7 +160,7 @@ export declare function buildFacets(items: IndexRecord[], options?: {
160
160
  * in the default count-desc-then-alpha order. Curated tag ids
161
161
  * (topics.yml) flow through the `tags` key the same way.
162
162
  */
163
- order?: Partial<Record<"stacks" | "platforms" | "categories" | "tags" | "licenses", string[]>>;
163
+ order?: Partial<Record<'stacks' | 'platforms' | 'categories' | 'tags' | 'licenses', string[]>>;
164
164
  }): {
165
165
  stacks: {
166
166
  value: string;
@@ -1 +1 @@
1
- {"version":3,"file":"directory-search.d.ts","sourceRoot":"","sources":["../src/directory-search.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"directory-search.d.ts","sourceRoot":"","sources":["../src/directory-search.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,8EAA8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC7B,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B,CAAC;AAEF,0CAA0C;AAC1C,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;EAMf,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC;AAoB/D;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,eAAe,GAAG,YAAY,CA6BzE;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,EAAE,YAAY,GAAG,eAAe,CAcxE;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,CAAC,CAAC,EAAE,YAAY,GAAG,OAAO,CAYrD;AAED,2EAA2E;AAC3E,eAAO,MAAM,SAAS,KAAoB,CAAC;AA4C3C,wBAAgB,SAAS,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,GAAG,WAAW,EAAE,CAyC9E;AAED,iDAAiD;AACjD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,CAAC,EAAE,CAG/E;AAED,8DAA8D;AAC9D,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,MAAM,CAE1E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,UAAU,CAAC,CAW5F;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,CAAC,EAAE,YAAY,GAAG,SAAS,CAExD;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAErD;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC,EAAE,YAAY,GAAG,WAAW,EAAE,CAgHlF;AAED,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,CAAC,EAAE,YAAY,EAAE,UAAU,SAAK,GAAG,MAAM,CAGvE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,SAAK,GAAG,MAAM,CAElF;AAED,6DAA6D;AAC7D,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,YAAY,EAAE,UAAU,SAAK,GAAG,MAAM,CAE9E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,oEAAoE;AACpE,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,SAAS,CAAC,CAAC;AAEvF,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,YAAY,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;CACd;AAmBD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,CAAC,EAAE,YAAY,EACf,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,aAAa,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9D,UAAU,EAAE,CAkDd;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,CAAC,EAAE,YAAY,EACf,GAAG,EAAE,MAAM,YAAY,EACvB,KAAK,EAAE,MAAM,GACZ,YAAY,CAsBd;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,CAAC,EAAE;IACR,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,GAAG,WAAW,GAAG,YAAY,GAAG,MAAM,GAAG,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;CAChG;;eAuF2C,MAAM;eAAS,MAAM;;;eAArB,MAAM;eAAS,MAAM;;;eAArB,MAAM;eAAS,MAAM;;;eAArB,MAAM;eAAS,MAAM;;;;;;;eAArB,MAAM;eAAS,MAAM;;EAqBlE"}
@@ -1,27 +1,27 @@
1
- import { labelDisplay, lensDisplay, statusDisplay } from "./directory-display.js";
1
+ import { labelDisplay, lensDisplay, statusDisplay } from './directory-display.js';
2
2
  /** Available sort orders, in UI order. */
3
3
  export const SORT_OPTIONS = [
4
- { value: "recently-updated", label: "Recently updated" },
5
- { value: "most-starred", label: "Most starred" },
6
- { value: "recently-added", label: "Recently added" },
7
- { value: "best-overall", label: "Best overall" },
8
- { value: "alphabetical", label: "Alphabetical" },
4
+ { value: 'recently-updated', label: 'Recently updated' },
5
+ { value: 'most-starred', label: 'Most starred' },
6
+ { value: 'recently-added', label: 'Recently added' },
7
+ { value: 'best-overall', label: 'Best overall' },
8
+ { value: 'alphabetical', label: 'Alphabetical' },
9
9
  ];
10
- const DEFAULT_SORT = "recently-updated";
10
+ const DEFAULT_SORT = 'recently-updated';
11
11
  const DEFAULT_PAGE = 1;
12
12
  const DEFAULT_PAGE_SIZE = 20;
13
13
  const KEYS = {
14
- q: "q",
15
- stacks: "stack",
16
- platforms: "platform",
17
- categories: "category",
18
- tags: "tag",
19
- labels: "label",
20
- licenses: "license",
21
- statuses: "status",
22
- lens: "lens",
23
- sort: "sort",
24
- page: "page",
14
+ q: 'q',
15
+ stacks: 'stack',
16
+ platforms: 'platform',
17
+ categories: 'category',
18
+ tags: 'tag',
19
+ labels: 'label',
20
+ licenses: 'license',
21
+ statuses: 'status',
22
+ lens: 'lens',
23
+ sort: 'sort',
24
+ page: 'page',
25
25
  };
26
26
  /**
27
27
  * Read filters from a URLSearchParams (or anything URLSearchParams-shaped,
@@ -30,8 +30,9 @@ const KEYS = {
30
30
  export function filtersFromSearchParams(sp) {
31
31
  const rawSort = sp.get(KEYS.sort);
32
32
  // Backwards-compat: old "most-mature" sort now maps to "best-overall".
33
- const normalizedSort = rawSort === "most-mature" ? "best-overall" : rawSort;
34
- const sort = normalizedSort && SORT_OPTIONS.some((o) => o.value === normalizedSort)
33
+ const normalizedSort = rawSort === 'most-mature' ? 'best-overall' : rawSort;
34
+ const sort = normalizedSort &&
35
+ SORT_OPTIONS.some((o) => o.value === normalizedSort)
35
36
  ? normalizedSort
36
37
  : undefined;
37
38
  const rawPage = Number(sp.get(KEYS.page));
@@ -46,7 +47,10 @@ export function filtersFromSearchParams(sp) {
46
47
  tags: sp.getAll(KEYS.tags).filter(Boolean),
47
48
  labels: sp.getAll(KEYS.labels).filter(Boolean),
48
49
  licenses: sp.getAll(KEYS.licenses).filter(Boolean),
49
- statuses: sp.getAll(KEYS.statuses).flatMap((s) => s.split(",")).filter(Boolean),
50
+ statuses: sp
51
+ .getAll(KEYS.statuses)
52
+ .flatMap((s) => s.split(','))
53
+ .filter(Boolean),
50
54
  ...(lens !== null ? { lens } : {}),
51
55
  ...(sort !== undefined ? { sort } : {}),
52
56
  ...(page !== undefined ? { page } : {}),
@@ -74,7 +78,7 @@ export function searchParamsFromFilters(f) {
74
78
  for (const v of f.licenses ?? [])
75
79
  sp.append(KEYS.licenses, v);
76
80
  if (f.statuses?.length)
77
- sp.set(KEYS.statuses, f.statuses.join(","));
81
+ sp.set(KEYS.statuses, f.statuses.join(','));
78
82
  if (f.lens)
79
83
  sp.set(KEYS.lens, f.lens);
80
84
  if (f.sort && f.sort !== DEFAULT_SORT)
@@ -105,37 +109,64 @@ export const PAGE_SIZE = DEFAULT_PAGE_SIZE;
105
109
  * than vanishing.
106
110
  */
107
111
  function recordName(record) {
108
- return record.kind === "resource" ? record.title : record.name;
112
+ return record.kind === 'resource' ? record.title : record.name;
109
113
  }
110
114
  function projectStars(record) {
111
- return record.kind === "project" ? record.github?.stars ?? 0 : 0;
115
+ return record.kind === 'project' ? (record.github?.stars ?? 0) : 0;
112
116
  }
113
117
  function recordUpdatedAt(record) {
114
- if (record.kind === "project")
118
+ if (record.kind === 'project')
115
119
  return record.github?.pushedAt ?? null;
116
- if (record.kind === "resource")
120
+ if (record.kind === 'resource')
117
121
  return record.publishedAt ?? null;
118
122
  return null;
119
123
  }
124
+ /**
125
+ * When the record joined the directory.
126
+ *
127
+ * `addedAt` is the honest answer and the only one a record can state
128
+ * outright. The two fallbacks exist for records written before the
129
+ * field did:
130
+ *
131
+ * - `curation.reviewedAt` — what this function used to return on its
132
+ * own. It answers "when did a human last review this", not "when
133
+ * was this added", so a brand-new record (reviewed: false, no date)
134
+ * scored 0 and sank to the bottom of the one sort whose entire job
135
+ * is to surface it. Kept only as a second-best signal.
136
+ * - `github.createdAt` — the repository's own creation date. Wrong in
137
+ * absolute terms (the repo predates the directory entry) but it
138
+ * keeps unstamped records in a defensible order instead of a heap.
139
+ */
120
140
  function recordAddedAt(record) {
121
- return record.curation?.reviewedAt ?? null;
141
+ if (record.addedAt)
142
+ return record.addedAt;
143
+ if (record.curation?.reviewedAt)
144
+ return record.curation.reviewedAt;
145
+ if (record.kind === 'project')
146
+ return record.github?.createdAt ?? null;
147
+ return null;
122
148
  }
123
149
  export function applySort(items, sort) {
124
150
  const arr = items.slice();
125
151
  const ts = (s) => (s ? new Date(s).valueOf() : 0);
126
152
  switch (sort) {
127
- case "most-starred":
153
+ case 'most-starred':
128
154
  arr.sort((a, b) => projectStars(b) - projectStars(a));
129
155
  break;
130
- case "recently-updated":
131
- arr.sort((a, b) => ts(recordUpdatedAt(b)) - ts(recordUpdatedAt(a)));
156
+ // Date sorts break ties by name. Without it every record missing
157
+ // the key shares score 0 and the tail comes out in whatever order
158
+ // the index happened to be built in — stable, but arbitrary, and
159
+ // it reads as a bug to anyone paging that far.
160
+ case 'recently-updated':
161
+ arr.sort((a, b) => ts(recordUpdatedAt(b)) - ts(recordUpdatedAt(a)) ||
162
+ recordName(a).localeCompare(recordName(b)));
132
163
  break;
133
- case "recently-added":
134
- arr.sort((a, b) => ts(recordAddedAt(b)) - ts(recordAddedAt(a)));
164
+ case 'recently-added':
165
+ arr.sort((a, b) => ts(recordAddedAt(b)) - ts(recordAddedAt(a)) || recordName(a).localeCompare(recordName(b)));
135
166
  break;
136
- case "best-overall": {
167
+ case 'best-overall': {
137
168
  const reviewed = (a) => (a.curation?.reviewed ? 1 : 0);
138
- const visible = (a) => (a.visibility === "keep" ? 1 : 0);
169
+ const visible = (a) => (a.visibility === 'keep' ? 1 : 0);
139
170
  arr.sort((a, b) => {
140
171
  const reviewedDelta = reviewed(b) - reviewed(a);
141
172
  if (reviewedDelta !== 0)
@@ -147,7 +178,7 @@ export function applySort(items, sort) {
147
178
  });
148
179
  break;
149
180
  }
150
- case "alphabetical":
181
+ case 'alphabetical':
151
182
  arr.sort((a, b) => recordName(a).localeCompare(recordName(b)));
152
183
  break;
153
184
  }
@@ -177,8 +208,8 @@ export function paginationPageList(active, count) {
177
208
  const result = [];
178
209
  for (const page of sorted) {
179
210
  const previous = result.at(-1);
180
- if (typeof previous === "number" && page - previous > 1)
181
- result.push("ellipsis");
211
+ if (typeof previous === 'number' && page - previous > 1)
212
+ result.push('ellipsis');
182
213
  result.push(page);
183
214
  }
184
215
  return result;
@@ -208,37 +239,31 @@ export function filterRecords(items, f) {
208
239
  // License ids are case-insensitive (GitHub emits `MIT`, curated ids
209
240
  // are lowercase). Normalize the filter side too so `?license=MIT`
210
241
  // and `?license=mit` are equivalent.
211
- const licenses = f.licenses?.length
212
- ? new Set(f.licenses.map((l) => l.toLowerCase()))
213
- : null;
242
+ const licenses = f.licenses?.length ? new Set(f.licenses.map((l) => l.toLowerCase())) : null;
214
243
  const statuses = f.statuses?.length ? new Set(f.statuses) : null;
215
244
  return items.filter((a) => {
216
245
  // Text search — match name, owner, description, category, stack,
217
246
  // stacks, platforms, tags, backend, architecture, stateManagement,
218
247
  // bestFor, whyListed, license, status.
219
248
  if (q) {
220
- const repoUrl = a.kind === "project" ? a.repoUrl : a.links.github ?? "";
221
- const ownerMatch = /github\.com\/([^/]+)\//.exec(repoUrl ?? "")?.[1]?.toLowerCase() ?? "";
222
- const projectFields = a.kind === "project"
249
+ const repoUrl = a.kind === 'project' ? a.repoUrl : (a.links.github ?? '');
250
+ const ownerMatch = /github\.com\/([^/]+)\//.exec(repoUrl ?? '')?.[1]?.toLowerCase() ?? '';
251
+ const projectFields = a.kind === 'project'
223
252
  ? [
224
253
  a.stack,
225
254
  ...a.stacks,
226
255
  ...a.platforms,
227
256
  a.projectType,
228
- a.difficulty ?? "",
229
- a.codebaseSize ?? "",
257
+ a.difficulty ?? '',
258
+ a.codebaseSize ?? '',
230
259
  ...a.bestFor,
231
260
  ...a.whyListed,
232
- a.github?.license ?? "",
233
- a.health?.status ? statusDisplay(a.health.status) : "",
261
+ a.github?.license ?? '',
262
+ a.health?.status ? statusDisplay(a.health.status) : '',
234
263
  ]
235
264
  : [];
236
- const resourceFields = a.kind === "resource"
237
- ? [a.type, a.topic, a.author ?? ""]
238
- : [];
239
- const entityFields = a.kind === "entity"
240
- ? [a.type, a.location ?? "", a.parent ?? ""]
241
- : [];
265
+ const resourceFields = a.kind === 'resource' ? [a.type, a.topic, a.author ?? ''] : [];
266
+ const entityFields = a.kind === 'entity' ? [a.type, a.location ?? '', a.parent ?? ''] : [];
242
267
  const haystack = [
243
268
  recordName(a),
244
269
  ownerMatch,
@@ -249,20 +274,20 @@ export function filterRecords(items, f) {
249
274
  ...resourceFields,
250
275
  ...entityFields,
251
276
  ]
252
- .join(" ")
277
+ .join(' ')
253
278
  .toLowerCase();
254
279
  if (!haystack.includes(q))
255
280
  return false;
256
281
  }
257
282
  if (stacks) {
258
- if (a.kind !== "project")
283
+ if (a.kind !== 'project')
259
284
  return false;
260
285
  const allStacks = [a.stack, ...a.stacks].filter((stack) => Boolean(stack));
261
286
  if (!allStacks.some((s) => stacks.has(s)))
262
287
  return false;
263
288
  }
264
289
  if (platforms) {
265
- if (a.kind !== "project")
290
+ if (a.kind !== 'project')
266
291
  return false;
267
292
  if (!a.platforms.some((p) => platforms.has(p)))
268
293
  return false;
@@ -278,7 +303,7 @@ export function filterRecords(items, f) {
278
303
  return false;
279
304
  }
280
305
  if (licenses) {
281
- if (a.kind !== "project")
306
+ if (a.kind !== 'project')
282
307
  return false;
283
308
  // Curated `licenses` array takes precedence; fall back to the
284
309
  // GitHub sync's `github.license.spdx_id` (recorded as the
@@ -294,7 +319,7 @@ export function filterRecords(items, f) {
294
319
  // `[]`-suppresses-fallback semantic tested below.
295
320
  const curated = (a.licenses ?? []);
296
321
  const hasCurated = Array.isArray(a.licenses);
297
- const synced = (a.github?.license ?? "").toLowerCase();
322
+ const synced = (a.github?.license ?? '').toLowerCase();
298
323
  const candidates = new Set();
299
324
  if (hasCurated) {
300
325
  for (const l of curated)
@@ -309,7 +334,7 @@ export function filterRecords(items, f) {
309
334
  return false;
310
335
  }
311
336
  if (statuses) {
312
- if (a.kind !== "project")
337
+ if (a.kind !== 'project')
313
338
  return false;
314
339
  const status = a.health?.status;
315
340
  if (!status || !statuses.has(status))
@@ -323,20 +348,20 @@ export function filterRecords(items, f) {
323
348
  });
324
349
  }
325
350
  /** Serialize filters back to a browsable URL under `pathPrefix`. */
326
- export function hrefForFilters(f, pathPrefix = "") {
351
+ export function hrefForFilters(f, pathPrefix = '') {
327
352
  const params = searchParamsFromFilters(f);
328
- return `${pathPrefix}${params.size ? `?${params}` : ""}`;
353
+ return `${pathPrefix}${params.size ? `?${params}` : ''}`;
329
354
  }
330
355
  /**
331
356
  * URL for a given result page, keeping every other filter. One
332
357
  * definition for the server render and the client rebuild — they had
333
358
  * drifted as two hand-rolled copies.
334
359
  */
335
- export function hrefForPage(f, page, pathPrefix = "") {
360
+ export function hrefForPage(f, page, pathPrefix = '') {
336
361
  return hrefForFilters({ ...f, page }, pathPrefix);
337
362
  }
338
363
  /** URL that drops every filter but keeps the chosen sort. */
339
- export function hrefForClearedFilters(f, pathPrefix = "") {
364
+ export function hrefForClearedFilters(f, pathPrefix = '') {
340
365
  return hrefForFilters({ sort: f.sort }, pathPrefix);
341
366
  }
342
367
  /**
@@ -357,18 +382,18 @@ export function pagePathHref(pathPrefix, page) {
357
382
  }
358
383
  /** Which taxonomy file backs each filter key. `tags` live in topics.yml. */
359
384
  const CHIP_TAXONOMY_KIND = {
360
- stacks: "stacks",
361
- platforms: "platforms",
362
- categories: "categories",
363
- tags: "topics",
364
- licenses: "licenses",
385
+ stacks: 'stacks',
386
+ platforms: 'platforms',
387
+ categories: 'categories',
388
+ tags: 'topics',
389
+ licenses: 'licenses',
365
390
  };
366
391
  const CHIP_PREFIX = {
367
- stacks: "Stack",
368
- platforms: "Platform",
369
- categories: "Category",
370
- tags: "Tag",
371
- licenses: "License",
392
+ stacks: 'Stack',
393
+ platforms: 'Platform',
394
+ categories: 'Category',
395
+ tags: 'Tag',
396
+ licenses: 'License',
372
397
  };
373
398
  /**
374
399
  * Build a flat list of "active filter" chips for display + removal.
@@ -379,48 +404,46 @@ const CHIP_PREFIX = {
379
404
  * `Stack: react-native`, the client `Stack: React Native`.
380
405
  */
381
406
  export function activeFilterChips(f, context = {}) {
382
- const { taxonomy, pathPrefix = "" } = context;
407
+ const { taxonomy, pathPrefix = '' } = context;
383
408
  const out = [];
384
409
  const display = (key, value) => {
385
410
  const kind = CHIP_TAXONOMY_KIND[key];
386
- const name = kind
387
- ? taxonomy?.[kind]?.find((entry) => entry.id === value)?.name
388
- : undefined;
411
+ const name = kind ? taxonomy?.[kind]?.find((entry) => entry.id === value)?.name : undefined;
389
412
  return `${CHIP_PREFIX[key]}: ${name ?? value}`;
390
413
  };
391
414
  if (f.q && f.q.trim()) {
392
- out.push({ key: "q", value: "", label: `“${f.q}”` });
415
+ out.push({ key: 'q', value: '', label: `“${f.q}”` });
393
416
  }
394
417
  for (const v of f.stacks ?? []) {
395
- out.push({ key: "stacks", value: v, label: display("stacks", v) });
418
+ out.push({ key: 'stacks', value: v, label: display('stacks', v) });
396
419
  }
397
420
  for (const v of f.platforms ?? []) {
398
- out.push({ key: "platforms", value: v, label: display("platforms", v) });
421
+ out.push({ key: 'platforms', value: v, label: display('platforms', v) });
399
422
  }
400
423
  for (const v of f.categories ?? []) {
401
- out.push({ key: "categories", value: v, label: display("categories", v) });
424
+ out.push({ key: 'categories', value: v, label: display('categories', v) });
402
425
  }
403
426
  for (const v of f.tags ?? []) {
404
- out.push({ key: "tags", value: v, label: display("tags", v) });
427
+ out.push({ key: 'tags', value: v, label: display('tags', v) });
405
428
  }
406
429
  for (const v of f.labels ?? []) {
407
- out.push({ key: "labels", value: v, label: labelDisplay(v) ?? v });
430
+ out.push({ key: 'labels', value: v, label: labelDisplay(v) ?? v });
408
431
  }
409
432
  for (const v of f.licenses ?? []) {
410
- out.push({ key: "licenses", value: v, label: display("licenses", v) });
433
+ out.push({ key: 'licenses', value: v, label: display('licenses', v) });
411
434
  }
412
435
  if (f.lens) {
413
- out.push({ key: "lens", value: f.lens, label: lensDisplay(f.lens) });
436
+ out.push({ key: 'lens', value: f.lens, label: lensDisplay(f.lens) });
414
437
  }
415
438
  // Status: collapse the "stale,quiet" composite into a single chip.
416
439
  if (f.statuses && f.statuses.length) {
417
- const joined = f.statuses.join(",");
418
- if (joined === "stale,quiet") {
419
- out.push({ key: "statuses", value: "stale,quiet", label: statusDisplay("needs-maintainer") });
440
+ const joined = f.statuses.join(',');
441
+ if (joined === 'stale,quiet') {
442
+ out.push({ key: 'statuses', value: 'stale,quiet', label: statusDisplay('needs-maintainer') });
420
443
  }
421
444
  else {
422
445
  for (const v of f.statuses) {
423
- out.push({ key: "statuses", value: v, label: statusDisplay(v) });
446
+ out.push({ key: 'statuses', value: v, label: statusDisplay(v) });
424
447
  }
425
448
  }
426
449
  }
@@ -439,7 +462,7 @@ export function activeFilterChips(f, context = {}) {
439
462
  */
440
463
  export function removeFilter(f, key, value) {
441
464
  let next;
442
- if (key === "q" || key === "lens") {
465
+ if (key === 'q' || key === 'lens') {
443
466
  // Scalar filters are removed entirely rather than treated like arrays.
444
467
  const { [key]: _drop, ...rest } = f;
445
468
  next = rest;
@@ -450,9 +473,7 @@ export function removeFilter(f, key, value) {
450
473
  return f;
451
474
  // `needs-maintainer` is represented internally by the composite
452
475
  // statuses `stale,quiet`, but is displayed as one removable chip.
453
- const filtered = key === "statuses" && value === "stale,quiet"
454
- ? []
455
- : current.filter((v) => v !== value);
476
+ const filtered = key === 'statuses' && value === 'stale,quiet' ? [] : current.filter((v) => v !== value);
456
477
  if (filtered.length === 0) {
457
478
  const { [key]: _drop, ...rest } = f;
458
479
  next = rest;
@@ -481,15 +502,15 @@ export function buildFacets(items, options) {
481
502
  if (!filters)
482
503
  return null;
483
504
  const next = { ...filters };
484
- if (facet === "stacks")
505
+ if (facet === 'stacks')
485
506
  delete next.stacks;
486
- if (facet === "platforms")
507
+ if (facet === 'platforms')
487
508
  delete next.platforms;
488
- if (facet === "categories")
509
+ if (facet === 'categories')
489
510
  delete next.categories;
490
- if (facet === "tags")
511
+ if (facet === 'tags')
491
512
  delete next.tags;
492
- if (facet === "licenses")
513
+ if (facet === 'licenses')
493
514
  delete next.licenses;
494
515
  return next;
495
516
  };
@@ -505,20 +526,20 @@ export function buildFacets(items, options) {
505
526
  label: new Map(),
506
527
  license: new Map(),
507
528
  };
508
- const stackScoped = itemsForFacet("stacks");
509
- const platformScoped = itemsForFacet("platforms");
510
- const categoryScoped = itemsForFacet("categories");
511
- const tagScoped = itemsForFacet("tags");
512
- const licenseScoped = itemsForFacet("licenses");
529
+ const stackScoped = itemsForFacet('stacks');
530
+ const platformScoped = itemsForFacet('platforms');
531
+ const categoryScoped = itemsForFacet('categories');
532
+ const tagScoped = itemsForFacet('tags');
533
+ const licenseScoped = itemsForFacet('licenses');
513
534
  for (const a of stackScoped) {
514
- if (a.kind === "project") {
535
+ if (a.kind === 'project') {
515
536
  const allStacks = new Set([a.stack, ...a.stacks].filter((stack) => Boolean(stack)));
516
537
  for (const s of allStacks)
517
538
  counts.stack.set(s, (counts.stack.get(s) ?? 0) + 1);
518
539
  }
519
540
  }
520
541
  for (const a of platformScoped) {
521
- if (a.kind === "project") {
542
+ if (a.kind === 'project') {
522
543
  for (const p of a.platforms)
523
544
  counts.platform.set(p, (counts.platform.get(p) ?? 0) + 1);
524
545
  }
@@ -533,15 +554,13 @@ export function buildFacets(items, options) {
533
554
  }
534
555
  }
535
556
  for (const a of licenseScoped) {
536
- if (a.kind === "project") {
557
+ if (a.kind === 'project') {
537
558
  // Consult the curated `licenses` array first (lowercased for
538
559
  // facet-key consistency), then fall back to the GitHub-synced
539
560
  // SPDX id. Mirrors the filterRecords branch so facet counts
540
561
  // and filter results can never disagree.
541
- const curated = (a.licenses ?? [])
542
- .map((l) => l.toLowerCase())
543
- .filter(Boolean);
544
- const synced = (a.github?.license ?? "").toLowerCase();
562
+ const curated = (a.licenses ?? []).map((l) => l.toLowerCase()).filter(Boolean);
563
+ const synced = (a.github?.license ?? '').toLowerCase();
545
564
  const ids = curated.length ? curated : synced ? [synced] : [];
546
565
  for (const id of ids) {
547
566
  counts.license.set(id, (counts.license.get(id) ?? 0) + 1);