From f4f3aadbebd1d79c3dc9c9b4662aab88caef9b50 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 26 Aug 2026 23:43:37 -0700 Subject: [PATCH 1/5] vehicles filter --- .../EmailUpdates/EmailUpdatesForm.svelte | 5 ++ .../HarnessSelector/HarnessSelector.svelte | 17 ++--- src/lib/utils/carSearch.js | 14 ++++ src/routes/vehicles/+page.svelte | 67 ++++++++++++++++++- 4 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 src/lib/utils/carSearch.js diff --git a/src/lib/components/EmailUpdates/EmailUpdatesForm.svelte b/src/lib/components/EmailUpdates/EmailUpdatesForm.svelte index 25bf33ad..41e21a01 100644 --- a/src/lib/components/EmailUpdates/EmailUpdatesForm.svelte +++ b/src/lib/components/EmailUpdates/EmailUpdatesForm.svelte @@ -11,6 +11,11 @@ let everything = true; + // Lets a page prefill the car field, e.g. from a search that found nothing + export function setCar(value) { + car.set(value); + } + $: primaryCategory = EMAIL_CATEGORIES.find(({ key }) => key === defaultCategory); $: $selectedCategories = everything ? EMAIL_CATEGORIES.map(({ key }) => key) : [defaultCategory]; diff --git a/src/lib/components/HarnessSelector/HarnessSelector.svelte b/src/lib/components/HarnessSelector/HarnessSelector.svelte index 8b9431ed..4a663654 100644 --- a/src/lib/components/HarnessSelector/HarnessSelector.svelte +++ b/src/lib/components/HarnessSelector/HarnessSelector.svelte @@ -8,6 +8,7 @@ import { allHarnesses, vehicleHarnesses, genericHarnesses } from '$lib/utils/harnesses'; import { selectedCar } from '../../../store'; import { NO_HARNESS_OPTION } from '$lib/constants/vehicles.js'; + import { searchTerms, matchesSearch } from '$lib/utils/carSearch.js'; import NoteCard from '$lib/components/NoteCard.svelte'; import DropdownItem from './HarnessDropdownItem.svelte'; @@ -78,20 +79,14 @@ } } - // Normalize diacritics for matching (e.g., "Škoda" -> "Skoda") - function normalizeDiacritics(str) { - return str.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); - } - /* Filtered Dropdown */ let inputValue = ""; let inputRef; - $: searchTerms = normalizeDiacritics(inputValue.toLowerCase()).split(/\s+/).filter(Boolean); - $: filteredItems = $harnesses.filter(item => { - const car = normalizeDiacritics(`${item.car} ${item.yearList ?? ''}`.toLowerCase()); // add all years in range - return searchTerms.every(term => car.includes(term)); - }); + $: terms = searchTerms(inputValue); + $: filteredItems = $harnesses.filter(item => + matchesSearch(`${item.car} ${item.yearList ?? ''}`, terms) // add all years in range + ); const handleClear = () => { // clear search input or close menu @@ -162,7 +157,7 @@ {@html ChevronIcon}
+ +

Last updated: {compatibilityMeta.last_updated}

- {#each Object.entries(vehicles) as [make, cars]} - {#if cars.length !== 0} + {#each filteredVehicles as [make, cars]} {@const brand_img_path = `/src/lib/images/vehicles/brand-icons/Logo-${make}.png`}
{#if brand_images[brand_img_path]} @@ -194,8 +220,14 @@
{/each} - {/if} {/each} + + {#if filteredVehicles.length === 0} +
+ openpilot doesn't support {filter} yet + Get an email when it does → +
+ {/if}
@@ -308,12 +340,41 @@ } } + .vehicle-filter { + box-sizing: border-box; + width: 100%; + padding: 1rem; + font: inherit; + background: #fff; + border: 1px solid #a0a0a0; + + &::placeholder { + color: #656565; + } + + &:focus-visible { + outline: none; + border-color: #464646; + } + } + .last-updated { text-align: center; font-style: italic; margin-bottom: 1rem; } + .no-matches { + display: grid; + gap: 0.5rem; + justify-items: center; + margin-top: 2rem; + padding: 2rem; + text-align: center; + background-color: var(--color-card-background); + border: 1px solid rgba(0, 0, 0, 0.4); + } + /* TODO: extract shared card class */ .recommended-cars { margin: 3rem 0; From d544658e73d7a115b9e62c3947ca2860cd17657a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 26 Aug 2026 23:50:06 -0700 Subject: [PATCH 2/5] export only one --- .../components/EmailUpdates/EmailUpdatesForm.svelte | 8 +++++--- .../HarnessSelector/HarnessSelector.svelte | 7 +++---- src/lib/utils/carSearch.js | 13 ++++++------- src/routes/vehicles/+page.svelte | 13 +++---------- 4 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/lib/components/EmailUpdates/EmailUpdatesForm.svelte b/src/lib/components/EmailUpdates/EmailUpdatesForm.svelte index 41e21a01..b105e9bb 100644 --- a/src/lib/components/EmailUpdates/EmailUpdatesForm.svelte +++ b/src/lib/components/EmailUpdates/EmailUpdatesForm.svelte @@ -1,5 +1,6 @@
@@ -72,7 +66,6 @@ openpilot doesn't support {filter} yet - Get an email when it does → + Get an email when it does → {/if} From fb02c4f915e7f9a69b563fa6acc8c068a1f55995 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Aug 2026 00:00:01 -0700 Subject: [PATCH 3/5] improve perf --- src/routes/vehicles/+page.svelte | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/routes/vehicles/+page.svelte b/src/routes/vehicles/+page.svelte index 1e8081cf..37112b3c 100644 --- a/src/routes/vehicles/+page.svelte +++ b/src/routes/vehicles/+page.svelte @@ -26,12 +26,13 @@ let filter = ''; - $: filteredVehicles = Object.entries(vehicles) - .map(([make, cars]) => [ - make, - cars.filter(car => matchesSearch(`${car.name} ${car.year_list.replaceAll(',', '')}`, filter)), - ]) - .filter(([, cars]) => cars.length !== 0); + // Unmatched cars are hidden with CSS + $: matches = new Set( + Object.values(vehicles) + .flat() + .filter(car => matchesSearch(`${car.name} ${car.year_list.replaceAll(',', '')}`, filter)) + .map(car => car.name) + );
@@ -128,17 +129,19 @@

Last updated: {compatibilityMeta.last_updated}

- {#each filteredVehicles as [make, cars]} + {#each Object.entries(vehicles) as [make, cars]} + {#if cars.length !== 0} {@const brand_img_path = `/src/lib/images/vehicles/brand-icons/Logo-${make}.png`} -
+ {@const matchCount = cars.filter(car => matches.has(car.name)).length} +
{#if brand_images[brand_img_path]} {make} car brand {/if} -

{make} ({cars.length})

+

{make} ({matchCount})

{#each cars as car_info} -
+
@@ -213,9 +216,10 @@
{/each} + {/if} {/each} - {#if filteredVehicles.length === 0} + {#if matches.size === 0}
openpilot doesn't support {filter} yet Get an email when it does → @@ -570,4 +574,9 @@ #faq { margin-bottom: 3rem; } + + /* Last, so it beats the `display` the rows and make headers set for themselves */ + .filtered-out { + display: none; + } From 31a0483489a01939d216861ed8bdf239dc99f56a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Aug 2026 00:06:47 -0700 Subject: [PATCH 4/5] epic --- src/routes/vehicles/+page.svelte | 64 ++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/src/routes/vehicles/+page.svelte b/src/routes/vehicles/+page.svelte index 37112b3c..72397e6e 100644 --- a/src/routes/vehicles/+page.svelte +++ b/src/routes/vehicles/+page.svelte @@ -13,6 +13,7 @@ import YoutubeIcon from '$lib/icons/social/youtube.svg?raw'; import InfoIcon from '$lib/icons/ui/info.svg?raw'; + import CloseIcon from '$lib/icons/ui/close.svg?raw'; import CarIcon from '$lib/icons/features/car.svg?raw'; import CommaFourImage from '$lib/images/products/comma-four/four_screen_on.png'; @@ -118,14 +119,22 @@
- + +
+ + {#if filter} + + {/if} +

Last updated: {compatibilityMeta.last_updated}

@@ -337,24 +346,47 @@ } } + .filter-label { + display: block; + margin-bottom: 0.5rem; + font-size: 1.125rem; + font-weight: 700; + } + + .filter-field { + position: relative; + } + .vehicle-filter { box-sizing: border-box; width: 100%; - padding: 1rem; + padding: 1rem 3rem 1rem 1rem; + color: #000; font: inherit; - background: #fff; - border: 1px solid #a0a0a0; + background: var(--color-card-background); + border: 1px solid #000; - &::placeholder { - color: #656565; + &:focus-visible { + outline: 2px solid #000; + outline-offset: -3px; } - &:focus-visible { - outline: none; - border-color: #464646; + &::-webkit-search-cancel-button { + display: none; } } + .clear { + position: absolute; + top: 0; + right: 13px; + height: 100%; + padding: 0; + cursor: pointer; + background-color: transparent; + border: none; + } + .last-updated { text-align: center; font-style: italic; From 66cf37ca19204bf7795ea6a940e3385a520da212 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 27 Aug 2026 00:31:43 -0700 Subject: [PATCH 5/5] style --- src/routes/vehicles/+page.svelte | 92 +++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 26 deletions(-) diff --git a/src/routes/vehicles/+page.svelte b/src/routes/vehicles/+page.svelte index 72397e6e..8063063c 100644 --- a/src/routes/vehicles/+page.svelte +++ b/src/routes/vehicles/+page.svelte @@ -26,6 +26,7 @@ const brand_images = import.meta.glob('$lib/images/vehicles/brand-icons/*.png', { eager: true }); let filter = ''; + const total = Object.values(vehicles).flat().length; // Unmatched cars are hidden with CSS $: matches = new Set( @@ -119,25 +120,31 @@
- -
- - {#if filter} - - {/if} +
+
+ + + {matches.size}/{total} · + updated {compatibilityMeta.last_updated} + +
+
+ + {#if filter} + + {/if} +
-

Last updated: {compatibilityMeta.last_updated}

- {#each Object.entries(vehicles) as [make, cars]} {#if cars.length !== 0} {@const brand_img_path = `/src/lib/images/vehicles/brand-icons/Logo-${make}.png`} @@ -346,11 +353,50 @@ } } - .filter-label { - display: block; + .filter-card { + margin: 3rem 0; + padding: 3rem; + background-color: var(--color-card-background); + border: 1px solid rgba(0, 0, 0, .4); + } + + @media screen and (max-width: 768px) { + .filter-card { + padding: 2rem 1rem; + } + } + + .filter-head { + display: flex; + flex-wrap: wrap; + align-items: baseline; + justify-content: space-between; + gap: 0.25rem 1rem; margin-bottom: 0.5rem; + } + + .filter-label { + font-size: 1.125rem; + font-weight: 700; + } + + .filter-meta { + color: rgba(0, 0, 0, 0.55); + font-size: 1rem; + font-style: italic; + } + + .filter-meta .count { + color: #000; font-size: 1.125rem; + font-style: normal; font-weight: 700; + opacity: 0; + transition: opacity 0.1s; + } + + .filter-meta .count.searching { + opacity: 1; } .filter-field { @@ -363,7 +409,7 @@ padding: 1rem 3rem 1rem 1rem; color: #000; font: inherit; - background: var(--color-card-background); + background: #fff; border: 1px solid #000; &:focus-visible { @@ -387,12 +433,6 @@ border: none; } - .last-updated { - text-align: center; - font-style: italic; - margin-bottom: 1rem; - } - .no-matches { display: grid; gap: 0.5rem;