bugproof-demo-shoplite · case_20260926_7e5e
Search is case-sensitive regression
The product search was behaving like a strict match, only returning items that exactly matched the case used in the search query, so searching for "mug" would miss products containing "Mug" or "MUG". This regression was caused by a recent performance optimization that removed the code which previously converted both the search term and the catalog entries to lower case before comparison, restoring sensitivity to upper and lower case differences. The fix restores the toLowerCase() normalization on both the search query and the catalog entries being searched, ensuring the search remains case-insensitive and returns expected results regardless of the casing used in the search term.
Written by IBM watsonx.ai ibm/granite-4-h-small from the case evidence.
Reproduced — a new test failed before the fix
3 of 3 repro tests failed on the unfixed code
AssertionError: expected [] to have a length of 1 but got +0
Culprit commit identified
Pinned by git bisect in 6 steps · matches the seeded answer key
649b24a perf(catalog): memoize search index
Fixed — the repro test and the full suite pass
117 of 117 tests pass after the fix
Reviewed — the Critic approved
Repro test genuinely encodes the bug: three cases with lowercase/uppercase needles against real catalog data
APPROVED
Tests untouched — the fix changed no existing test
The fix commit only adds the repro test; no existing test was modified or deleted (git diff of the fix commit)
+ tests/bugproof/bug-05-search-case.test.ts
Written by the Fixer, whose edit permission covers src/ only; the check above confirms no existing test changed.
--- a/src/catalog/search.ts
+++ b/src/catalog/search.ts
@@ -11,12 +11,12 @@ function indexFor(products: readonly Product[]): IndexEntry[] {
let index = indexCache.get(products);
if (index === undefined) {
- index = products.map((product) => ({ product, haystack: `${product.name} ${product.category}` }));
+ index = products.map((product) => ({ product, haystack: `${product.name} ${product.category}`.toLowerCase() }));
indexCache.set(products, index);
}
return index;
}
-/** Substring search over product name and category. */
+/** Case-insensitive substring search over product name and category. */
export function searchProducts(products: readonly Product[], query: string): Product[] {
- const needle = query.trim();
+ const needle = query.trim().toLowerCase();
if (needle === "") return [...products];The repro test fails on the code before the fix and passes on the fix.
git clone https://github.com/GegarinSagolsem/bugproof-demo-shoplite && cd bugproof-demo-shoplite && npm ci git checkout 6ff3e78~1 # the code before the fix git checkout 6ff3e78 -- tests/bugproof/bug-05-search-case.test.ts # add the repro test npx vitest run tests/bugproof/bug-05-search-case.test.ts # fails: the bug is reproduced git checkout 6ff3e78 # the fix npx vitest run tests/bugproof/bug-05-search-case.test.ts # passes
commit 649b24a04f65f9aa6049d9235b0b9459c4c4cdb0
Author: GegarinSagolsem <176033453+GegarinSagolsem@users.noreply.github.com>
Date: Fri Sep 25 18:55:13 2026 +0000
perf(catalog): memoize search index
---
src/catalog/search.ts | 28 ++++++++++++++++++++++------
1 file changed, 22 insertions(+), 6 deletions(-)
diff --git a/src/catalog/search.ts b/src/catalog/search.ts
index c24eb52..6a6587b 100644
--- a/src/catalog/search.ts
+++ b/src/catalog/search.ts
@@ -1,11 +1,27 @@
import type { Product } from "./products";
-/** Case-insensitive substring search over product name and category. */
+interface IndexEntry {
+ product: Product;
+ haystack: string;
+}
+
+// Search runs on every keystroke, so build the searchable text once per product list.
+const indexCache = new WeakMap<readonly Product[], IndexEntry[]>();
+
+function indexFor(products: readonly Product[]): IndexEntry[] {
+ let index = indexCache.get(products);
+ if (index === undefined) {
+ index = products.map((product) => ({ product, haystack: `${product.name} ${product.category}` }));
+ indexCache.set(products, index);
+ }
+ return index;
+}
+
+/** Substring search over product name and category. */
export function searchProducts(products: readonly Product[], query: string): Product[] {
- const needle = query.trim().toLowerCase();
+ const needle = query.trim();
if (needle === "") return [...products];
- return products.filter(
- (product) =>
- product.name.toLowerCase().includes(needle) || product.category.toLowerCase().includes(needle),
- );
+ return indexFor(products)
+ .filter((entry) => entry.haystack.includes(needle))
+ .map((entry) => entry.product);
}
Regression: product search became case-sensitive after commit 649b24a ("perf(catalog): memoize search index") silently removed .toLowerCase() normalization from both the search needle and haystack while refactoring to add index memoization. Fixed by restoring .toLowerCase() on both the haystack (in indexFor) and the needle (in searchProducts), preserving the memoization. Full suite 117/117 green, repro test confirmed RED then GREEN.