@grove-dev/astro 0.2.14 → 0.2.16

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grove-dev/astro",
3
- "version": "0.2.14",
3
+ "version": "0.2.16",
4
4
  "type": "module",
5
5
  "description": "Astro framework adapter for Grove. Components, layouts, design tokens, and a default template.",
6
6
  "license": "MIT",
@@ -43,8 +43,8 @@
43
43
  "./layouts/*": "./src/layouts/*.astro"
44
44
  },
45
45
  "dependencies": {
46
- "@grove-dev/core": "0.2.14",
47
- "@grove-dev/ui": "1.0.4",
46
+ "@grove-dev/core": "0.2.16",
47
+ "@grove-dev/ui": "1.0.6",
48
48
  "astro": "^6.4.6"
49
49
  },
50
50
  "peerDependencies": {
@@ -10,10 +10,18 @@
10
10
  * /resources/<slug>, ecosystem-map → /entities/<slug>) pass the
11
11
  * correct href via `RecordSection`'s `hrefFor` callback.
12
12
  *
13
+ * V1 (alternative) API: pass `routeSlug` (e.g. `"projects"`,
14
+ * `"resources"`, `"entities"`) to let the component derive the
15
+ * detail URL itself as `/${routeSlug}/${slug}`. This is a thin
16
+ * alternative to passing `href`; the page-level `hrefFor` callback
17
+ * is still the recommended API.
18
+ *
13
19
  * V1 (legacy) API: also accepts `item` + `pathPrefix` as a
14
- * convenience for callers that haven't migrated. When both are
15
- * provided, `href` wins; when only `pathPrefix` is provided, the
16
- * detail href is `${pathPrefix}/${item.slug}`.
20
+ * convenience for callers that haven't migrated. When any of
21
+ * `href` / `routeSlug` / `pathPrefix` is provided, the priority is
22
+ * `href` > `routeSlug` > `pathPrefix`. The `pathPrefix` default
23
+ * is `"/items"` for V0 backwards-compat, but the canonical V1
24
+ * pattern is to pass `routeSlug` (or `href` directly).
17
25
  */
18
26
  import type { IndexRecord, Resource, ProjectRecord, IndexProjectRecord } from "@grove-dev/core";
19
27
  import Icon from "./Icon.astro";
@@ -26,12 +34,16 @@ type CardRecord = IndexRecord | Resource | ProjectRecord | IndexProjectRecord;
26
34
  interface Props {
27
35
  /** V1 API — full Resource/IndexProjectRecord. */
28
36
  record?: CardRecord;
29
- /** V1 API — full detail-page URL. */
37
+ /** V1 API — full detail-page URL. Wins over `routeSlug` / `pathPrefix`. */
30
38
  href?: string;
39
+ /** V1 API — blueprint route slug (e.g. "projects", "resources",
40
+ * "entities"). When `href` is not provided, the detail URL is
41
+ * derived as `/${routeSlug}/${slug}`. */
42
+ routeSlug?: string;
31
43
  /** Legacy API — singular item, equivalent to `record`. */
32
44
  item?: CardRecord;
33
45
  /** Legacy API — URL prefix, equivalent to deriving from
34
- * blueprint routeSlug + slug. */
46
+ * blueprint routeSlug + slug. Default `"/items"` (V0 leftover). */
35
47
  pathPrefix?: string;
36
48
  /** When true, show owner/repo below the title. Default true. */
37
49
  showOwner?: boolean;
@@ -47,6 +59,7 @@ interface Props {
47
59
  const {
48
60
  record: recordProp,
49
61
  href: hrefProp,
62
+ routeSlug,
50
63
  item: itemProp,
51
64
  pathPrefix = "/items",
52
65
  showOwner = true,
@@ -62,7 +75,12 @@ const slug = (item as { slug: string }).slug;
62
75
  let detailHref: string;
63
76
  if (typeof hrefProp === "string" && hrefProp.length > 0) {
64
77
  detailHref = hrefProp;
78
+ } else if (typeof routeSlug === "string" && routeSlug.length > 0) {
79
+ // V1 alternative API: blueprint routeSlug → /<routeSlug>/<slug>.
80
+ const base = routeSlug.replace(/^\/+|\/+$/g, "");
81
+ detailHref = base ? `/${base}/${slug}` : `/${slug}`;
65
82
  } else {
83
+ // V0 legacy fallback: explicit `pathPrefix` (default "/items").
66
84
  const base = pathPrefix.replace(/\/$/, "");
67
85
  detailHref = `${base}/${slug}`;
68
86
  }
@@ -187,6 +205,16 @@ const category = (item as { category?: string }).category;
187
205
  data-event-item={slug}
188
206
  class="relative z-10 font-mono text-ink-500 hover:text-ink-900 dark:text-ink-400 dark:hover:text-ink-100"
189
207
  >
208
+ {/* `data-event` and `data-event-*` are forwarding hooks
209
+ for downstream analytics. Sites wire their own
210
+ gtag/PostHog/Plausible handler in BaseLayout.astro
211
+ to read these and forward to their analytics
212
+ provider. The V1 names are stable: `data-event` is
213
+ the action ("external_repo_click"), `data-event-source`
214
+ is the surface ("card_owner" | "card_view_repo" |
215
+ "detail_owner" | "detail_view_github" | "detail_homepage"
216
+ | "detail_distribution"), and `data-event-item` is
217
+ the record slug. */}
190
218
  <span class="text-ink-400 dark:text-ink-500">@</span>{owner}{repo ? <span class="text-ink-400 dark:text-ink-500">/{repo}</span> : ""}
191
219
  </a>
192
220
  <span class="mx-1 text-ink-300 dark:text-ink-700">·</span>
@@ -25,9 +25,9 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "@astrojs/check": "^0.9.9",
28
- "@grove-dev/astro": "0.2.14",
29
- "@grove-dev/cli": "0.2.14",
30
- "@grove-dev/core": "0.2.14",
28
+ "@grove-dev/astro": "0.2.16",
29
+ "@grove-dev/cli": "0.2.16",
30
+ "@grove-dev/core": "0.2.16",
31
31
  "@tailwindcss/vite": "^4.3.0",
32
32
  "astro": "^6.4.4",
33
33
  "marked": "^18.0.0",
@@ -1,6 +1,6 @@
1
1
  # example.com — full directory
2
2
 
3
- Generated: 2026-06-13T15:14:22.567Z
3
+ Generated: 2026-06-15T13:49:39.319Z
4
4
  Records: 8
5
5
 
6
6
  ---
@@ -2,61 +2,61 @@
2
2
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3
3
  <url>
4
4
  <loc>https://example.com/</loc>
5
- <lastmod>2026-06-13T15:14:22.098Z</lastmod>
5
+ <lastmod>2026-06-15T13:49:38.754Z</lastmod>
6
6
  <changefreq>daily</changefreq>
7
7
  <priority>1.0</priority>
8
8
  </url>
9
9
  <url>
10
10
  <loc>https://example.com/projects</loc>
11
- <lastmod>2026-06-13T15:14:22.098Z</lastmod>
11
+ <lastmod>2026-06-15T13:49:38.754Z</lastmod>
12
12
  <changefreq>daily</changefreq>
13
13
  <priority>0.9</priority>
14
14
  </url>
15
15
  <url>
16
16
  <loc>https://example.com/projects/coolify</loc>
17
- <lastmod>2026-06-13T15:14:22.098Z</lastmod>
17
+ <lastmod>2026-06-15T13:49:38.754Z</lastmod>
18
18
  <changefreq>weekly</changefreq>
19
19
  <priority>0.7</priority>
20
20
  </url>
21
21
  <url>
22
22
  <loc>https://example.com/projects/gitea</loc>
23
- <lastmod>2026-06-13T15:14:22.098Z</lastmod>
23
+ <lastmod>2026-06-15T13:49:38.754Z</lastmod>
24
24
  <changefreq>weekly</changefreq>
25
25
  <priority>0.7</priority>
26
26
  </url>
27
27
  <url>
28
28
  <loc>https://example.com/projects/hoppscotch</loc>
29
- <lastmod>2026-06-13T15:14:22.098Z</lastmod>
29
+ <lastmod>2026-06-15T13:49:38.754Z</lastmod>
30
30
  <changefreq>weekly</changefreq>
31
31
  <priority>0.7</priority>
32
32
  </url>
33
33
  <url>
34
34
  <loc>https://example.com/projects/inventree</loc>
35
- <lastmod>2026-06-13T15:14:22.098Z</lastmod>
35
+ <lastmod>2026-06-15T13:49:38.754Z</lastmod>
36
36
  <changefreq>weekly</changefreq>
37
37
  <priority>0.7</priority>
38
38
  </url>
39
39
  <url>
40
40
  <loc>https://example.com/projects/label-studio</loc>
41
- <lastmod>2026-06-13T15:14:22.098Z</lastmod>
41
+ <lastmod>2026-06-15T13:49:38.754Z</lastmod>
42
42
  <changefreq>weekly</changefreq>
43
43
  <priority>0.7</priority>
44
44
  </url>
45
45
  <url>
46
46
  <loc>https://example.com/projects/logseq</loc>
47
- <lastmod>2026-06-13T15:14:22.098Z</lastmod>
47
+ <lastmod>2026-06-15T13:49:38.754Z</lastmod>
48
48
  <changefreq>weekly</changefreq>
49
49
  <priority>0.7</priority>
50
50
  </url>
51
51
  <url>
52
52
  <loc>https://example.com/projects/private-gpt</loc>
53
- <lastmod>2026-06-13T15:14:22.098Z</lastmod>
53
+ <lastmod>2026-06-15T13:49:38.754Z</lastmod>
54
54
  <changefreq>weekly</changefreq>
55
55
  <priority>0.7</priority>
56
56
  </url>
57
57
  <url>
58
58
  <loc>https://example.com/projects/wakapi</loc>
59
- <lastmod>2026-06-13T15:14:22.098Z</lastmod>
59
+ <lastmod>2026-06-15T13:49:38.754Z</lastmod>
60
60
  <changefreq>weekly</changefreq>
61
61
  <priority>0.7</priority>
62
62
  </url>
@@ -162,20 +162,51 @@ const languages = extras.github?.languages
162
162
  const totalLanguageBytes = Math.max(1, languages.reduce((sum, [, b]) => sum + b, 0));
163
163
 
164
164
  // ── Kind-aware JSON-LD ─────────────────────────────────────────
165
+ // Mirrors the openapps field set (10+ fields for projects):
166
+ // name, headline, description, url, codeRepository, sameAs,
167
+ // programmingLanguage, license, applicationCategory,
168
+ // operatingSystem, keywords, dateCreated, dateModified,
169
+ // interactionStatistic, author, isAccessibleForFree.
165
170
  let jsonLd: Record<string, unknown>;
166
171
  if (isProject && proj) {
172
+ const sameAs = [repoUrl, homepageUrl].filter((value): value is string =>
173
+ typeof value === "string" && value.length > 0,
174
+ );
175
+ const keywords = (record.tags ?? []).length > 0 ? (record.tags ?? []).join(", ") : undefined;
176
+ const operatingSystem = (proj.platforms ?? []).length > 0
177
+ ? (proj.platforms ?? []).join(", ")
178
+ : undefined;
179
+ const applicationCategory = (record.category ?? "") || undefined;
180
+ const dateCreated = (record as { curation?: { reviewedAt?: string } }).curation?.reviewedAt;
181
+ const dateModified = pushedAt ?? dateCreated;
182
+ const interactionStatistic = stars > 0
183
+ ? {
184
+ "@type": "InteractionCounter",
185
+ interactionType: { "@type": "LikeAction" },
186
+ userInteractionCount: stars,
187
+ }
188
+ : undefined;
167
189
  jsonLd = {
168
190
  "@context": "https://schema.org",
169
191
  "@type": "SoftwareSourceCode",
170
192
  name,
193
+ headline: name,
171
194
  description: record.description ?? undefined,
172
195
  url: repoUrl || homepageUrl || undefined,
173
196
  codeRepository: repoUrl || undefined,
197
+ sameAs: sameAs.length > 0 ? sameAs : undefined,
174
198
  programmingLanguage: language ?? undefined,
175
199
  license: licenseSpdx ?? undefined,
200
+ applicationCategory,
201
+ operatingSystem,
202
+ keywords,
203
+ dateCreated: dateCreated ?? undefined,
204
+ dateModified: dateModified ?? undefined,
205
+ interactionStatistic,
176
206
  author: ownerRepo
177
207
  ? { "@type": "Organization", name: ownerRepo.owner, url: `https://github.com/${ownerRepo.owner}` }
178
208
  : undefined,
209
+ isAccessibleForFree: true,
179
210
  };
180
211
  } else if (record.kind === "resource") {
181
212
  const resourceSchemaType: Record<string, string> = {
@@ -189,20 +220,25 @@ if (isProject && proj) {
189
220
  "@context": "https://schema.org",
190
221
  "@type": resourceSchemaType[record.type] ?? "CreativeWork",
191
222
  name,
223
+ headline: name,
192
224
  description: record.description || undefined,
193
225
  url: homepageUrl || undefined,
194
226
  author: record.author ? { "@type": "Person", name: record.author } : undefined,
195
227
  datePublished: record.publishedAt || undefined,
228
+ keywords: (record.tags ?? []).length > 0 ? (record.tags ?? []).join(", ") : undefined,
229
+ isAccessibleForFree: true,
196
230
  };
197
231
  } else {
198
232
  jsonLd = {
199
233
  "@context": "https://schema.org",
200
234
  "@type": record.type === "person" ? "Person" : "Organization",
201
235
  name,
236
+ headline: name,
202
237
  description: record.description || undefined,
203
238
  url: homepageUrl || undefined,
204
239
  foundingDate: record.founded || undefined,
205
240
  location: record.location || undefined,
241
+ keywords: (record.tags ?? []).length > 0 ? (record.tags ?? []).join(", ") : undefined,
206
242
  };
207
243
  }
208
244
  ---
@@ -264,6 +300,17 @@ if (isProject && proj) {
264
300
  {labelDisplay(l) ?? prettySlug(l)}
265
301
  </span>
266
302
  ))}
303
+ {record.curation?.reviewed && (
304
+ <span
305
+ class="inline-flex items-center gap-1 rounded border border-emerald-300/60 bg-emerald-50 px-1.5 py-0.5 text-[10px] font-medium text-emerald-800 dark:border-emerald-800/60 dark:bg-emerald-950/40 dark:text-emerald-300"
306
+ title="A human curator reviewed this record. (No reviewer name is shown — the framework is reviewer-agnostic; consumers can wire the curation.by field into their own badge component.)"
307
+ >
308
+ <svg viewBox="0 0 16 16" width="10" height="10" aria-hidden="true" fill="currentColor">
309
+ <path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0Zm3.41 5.09a.75.75 0 0 0-1.06 0L6.75 8.69 5.65 7.59a.75.75 0 1 0-1.06 1.06l1.65 1.65a.75.75 0 0 0 1.06 0l4.11-4.11a.75.75 0 0 0 0-1.06Z" />
310
+ </svg>
311
+ Curator reviewed
312
+ </span>
313
+ )}
267
314
  {healthLabel && (
268
315
  <span class="grove-badge grove-badge-stale">{healthLabel}</span>
269
316
  )}
@@ -21,6 +21,7 @@ import BaseLayout from "@grove-dev/astro/layouts/BaseLayout.astro";
21
21
  import SmartLensTabs from "@grove-dev/astro/components/SmartLensTabs.astro";
22
22
  import RefinePanel from "@grove-dev/astro/components/RefinePanel.astro";
23
23
  import IndexRow from "@grove-dev/astro/components/IndexRow.astro";
24
+ import Pagination from "@grove-dev/astro/components/Pagination.astro";
24
25
  import ExploreByCategory from "@grove-dev/astro/components/ExploreByCategory.astro";
25
26
  import ExploreByStack from "@grove-dev/astro/components/ExploreByStack.astro";
26
27
  import {
@@ -36,6 +37,7 @@ import {
36
37
  paginate,
37
38
  searchParamsFromFilters,
38
39
  sortDisplay,
40
+ SORT_OPTIONS,
39
41
  totalPages,
40
42
  } from "@grove-dev/astro";
41
43
 
@@ -165,6 +167,7 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
165
167
  <form
166
168
  method="get"
167
169
  action={`/${slug}`}
170
+ id="search-form"
168
171
  class="mt-6 flex flex-col gap-2 sm:flex-row"
169
172
  role="search"
170
173
  >
@@ -178,9 +181,10 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
178
181
  class="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-ink-400"
179
182
  fill="currentColor"
180
183
  >
181
- <path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.75.75 0 0 1-1.06 1.06l-3.04-3.04ZM11.5 7a4.5 4.5 0 1 0-9 0 4.5 4.5 0 0 0 9 0Z" />
184
+ <path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.5 4.5 0 1 0-9 0 4.5 4.5 0 0 0 9 0Z" />
182
185
  </svg>
183
186
  <input
187
+ id="search-input"
184
188
  type="search"
185
189
  name="q"
186
190
  value={filters.q ?? ""}
@@ -188,7 +192,28 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
188
192
  class="h-10 w-full rounded-md border border-ink-200 bg-white py-2 pl-9 pr-3 text-sm text-ink-900 placeholder:text-ink-400 focus:border-ink-900 focus:outline-none focus:ring-2 focus:ring-ink-900/10 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-100"
189
193
  />
190
194
  </label>
191
- {filters.sort && <input type="hidden" name="sort" value={filters.sort} />}
195
+ <label class="relative">
196
+ <span class="sr-only">{`Sort ${itemLabelPlural()}`}</span>
197
+ <select
198
+ id="sort-select"
199
+ name="sort"
200
+ class="h-10 cursor-pointer appearance-none rounded-md border border-ink-200 bg-white py-1.5 pl-3 pr-8 text-sm font-medium text-ink-700 transition-colors hover:border-ink-300 focus:border-ink-900 focus:outline-none focus:ring-2 focus:ring-ink-900/10 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-300 dark:hover:border-ink-700 dark:focus:border-ink-100 dark:focus:ring-ink-100/15"
201
+ >
202
+ {SORT_OPTIONS.map((o) => (
203
+ <option value={o.value} selected={o.value === sort}>{o.label}</option>
204
+ ))}
205
+ </select>
206
+ <svg
207
+ viewBox="0 0 16 16"
208
+ width="11"
209
+ height="11"
210
+ aria-hidden="true"
211
+ class="pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-ink-400"
212
+ fill="currentColor"
213
+ >
214
+ <path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" />
215
+ </svg>
216
+ </label>
192
217
  <button type="submit" class="grove-btn grove-btn-primary">Search</button>
193
218
  </form>
194
219
 
@@ -278,7 +303,28 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
278
303
  </p>
279
304
  <a href={`/${slug}`} class="grove-btn grove-btn-outline mt-4">Clear filters</a>
280
305
  </div>
281
- <nav id="client-pagination" class="mt-8 flex flex-wrap items-center justify-center gap-1.5" aria-label="Pagination"></nav>
306
+ <nav class="mt-8 flex flex-wrap items-center justify-center gap-1.5" aria-label="Pagination">
307
+ <Pagination current={page} total={pages} hrefFor={hrefForPage} />
308
+ </nav>
309
+ </div>
310
+ </section>
311
+
312
+ <section class="border-t border-ink-200 dark:border-ink-800">
313
+ <div class="grove-container-wide mx-auto px-5 sm:px-7">
314
+ <div class="flex flex-col items-center gap-3 py-12 text-center">
315
+ <h2 class="m-0 text-xl font-semibold tracking-tight text-ink-900 dark:text-ink-100">
316
+ {`Know a real ${itemLabel()} that belongs here?`}
317
+ </h2>
318
+ <p class="m-0 max-w-2xl text-sm text-ink-500 dark:text-ink-400">
319
+ {`Open a pull request with a YAML file. We review every submission against the bar — no marketing, no tutorials.`}
320
+ </p>
321
+ <a
322
+ href={siteConfig.submitUrl ?? "/submit"}
323
+ class="grove-btn grove-btn-primary mt-2"
324
+ >
325
+ {`Submit ${itemLabel()}`}
326
+ </a>
327
+ </div>
282
328
  </div>
283
329
  </section>
284
330
 
@@ -288,6 +334,49 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
288
334
  var list = document.getElementById("results-list");
289
335
  if (!list) return;
290
336
 
337
+ // Sort dropdown: navigate on change so the server re-renders
338
+ // the sorted list (URL is the source of truth for filters/sort).
339
+ var sortSelect = document.getElementById("sort-select");
340
+ if (sortSelect) {
341
+ sortSelect.addEventListener("change", function () {
342
+ var params = new URLSearchParams(location.search);
343
+ if (sortSelect.value && sortSelect.value !== "recently-updated") {
344
+ params.set("sort", sortSelect.value);
345
+ } else {
346
+ params.delete("sort");
347
+ }
348
+ params.delete("page");
349
+ var qs = params.toString();
350
+ location.assign(qs ? "?" + qs : location.pathname);
351
+ });
352
+ }
353
+
354
+ // Search: 250ms debounce; pressing Enter still submits
355
+ // immediately via the form's normal submit handler.
356
+ var searchInput = document.getElementById("search-input");
357
+ var searchForm = document.getElementById("search-form");
358
+ if (searchInput && searchForm) {
359
+ var debounceTimer = null;
360
+ var submit = function () {
361
+ var params = new URLSearchParams(location.search);
362
+ var term = searchInput.value.trim();
363
+ if (term) params.set("q", term);
364
+ else params.delete("q");
365
+ params.delete("page");
366
+ var qs = params.toString();
367
+ location.assign(qs ? "?" + qs : location.pathname);
368
+ };
369
+ searchInput.addEventListener("input", function () {
370
+ if (debounceTimer) window.clearTimeout(debounceTimer);
371
+ debounceTimer = window.setTimeout(submit, 250);
372
+ });
373
+ searchForm.addEventListener("submit", function (event) {
374
+ event.preventDefault();
375
+ if (debounceTimer) window.clearTimeout(debounceTimer);
376
+ submit();
377
+ });
378
+ }
379
+
291
380
  function values(params, key) {
292
381
  return params.getAll(key).filter(Boolean);
293
382
  }
@@ -396,21 +485,9 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
396
485
  });
397
486
  });
398
487
 
399
- var pager = document.getElementById("client-pagination");
400
- if (pager) {
401
- pager.innerHTML = "";
402
- if (totalPages > 1) {
403
- for (var number = 1; number <= totalPages; number += 1) {
404
- var link = document.createElement("a");
405
- link.href = pageHref(params, number);
406
- link.textContent = String(number);
407
- link.className = "grove-btn min-w-9 px-2 py-1.5 text-2xs " +
408
- (number === page ? "grove-btn-primary" : "grove-btn-outline");
409
- if (number === page) link.setAttribute("aria-current", "page");
410
- pager.appendChild(link);
411
- }
412
- }
413
- }
488
+ // Pagination is server-rendered (see <Pagination /> in the
489
+ // template) so the static HTML contains the page-number
490
+ // links for SEO. No client-side rendering loop here.
414
491
 
415
492
  var chipHost = document.getElementById("active-filters");
416
493
  if (chipHost) {