Conversation
- Add search bar UI and client handler (form submit, clear, placeholder) - Add SearchService for getSearchTerm and buildSearchRegexPattern (ReDoS-safe) - Use SearchService in index query and NavigationService.applySearchFilter - Preserve filters in search form; include search in clear-all and active state - Respect reduced motion for search bar; fix clear button a11y and template comment
WalkthroughIntroduces server- and client-side search for the Case Studies page. Adds a SearchService that normalizes incoming query params, escapes and limits regex patterns, and builds MongoDB $or search conditions. NavigationService gains applySearchFilter and integrates it into existing filter composition. UrlService adds filterParamKeys and normalizeFilterParams and normalizes req.data.query usage. The case-studies module exposes indexQuery and refactors setupIndexData/setupShowData into reusable helpers. Views, SCSS, and a new public search-handler.js add a dedicated search bar UI, clear behavior, URL construction, and total-pages attribute handling. Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🔍 Vulnerabilities of
|
| digest | sha256:330e6d584c0154939a52aaf6dc15cc4facb13767f16f43bcad7f6d0d5eb4f5b9 |
| vulnerabilities | |
| platform | linux/amd64 |
| size | 172 MB |
| packages | 971 |
📦 Base Image node:24-alpine
Description
Description
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
|
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@website/modules/asset/ui/src/scss/_cases.scss`:
- Around line 181-183: Remove the no-op placeholder rule: delete the
.cs_search-bar-input-wrapper:focus-within .cs_search-bar-input::placeholder
block because .cs_search-bar-input::placeholder already sets color to $gray-300;
if the intent was to change the placeholder color on focus-within, update that
selector to a different value instead of duplicating $gray-300.
- Around line 175-179: Remove the conflicting CSS selector rules
(.cs_search-bar-input:not(:placeholder-shown) ~ .cs_search-bar-clear,
.cs_search-bar-input:focus ~ .cs_search-bar-clear, and the duplicate
.cs_search-bar-clear--visible rule) and keep a single modifier rule
.cs_search-bar-clear--visible { display: flex; }, then update search-handler.js
to stop setting clearButton.style.display and instead manage visibility by
toggling the modifier class (use clearButton.classList.add/remove or toggle on
the input events and initialization). This ensures JavaScript-driven visibility
uses the .cs_search-bar-clear--visible class and CSS only provides the
presentation.
In `@website/modules/case-studies-page/index.js`:
- Around line 12-17: The call to self.filterByIndexPage currently discards its
return and breaks the builder-style chain started with
self.pieces.find(...).applyBuildersSafely(...).perPage(...); update the code to
use the returned query object from filterByIndexPage so chaining remains
consistent — either call .filterByIndexPage(...) as part of the chain after
.perPage(...) or reassign query with query = self.filterByIndexPage(query,
req.data.page); ensure you reference the existing functions self.pieces.find,
applyBuildersSafely, perPage and self.filterByIndexPage when making the change.
In `@website/modules/case-studies-page/services/NavigationService.js`:
- Around line 80-92: NavigationService.applySearchFilter duplicates the $or
search-condition found in buildIndexQuery; move the query-shape into
SearchService so there's a single source of truth. Add a method on SearchService
(e.g., buildSearchCondition(searchTerm) or
buildSearchConditionFromRegex(regexPattern)) that returns the { $or: [...] }
object (using the same fields: title, portfolioTitle, etc.), keep
SearchService.buildSearchRegexPattern for the regex, and update
callers—NavigationService.applySearchFilter and the buildIndexQuery function—to
call the new SearchService.buildSearchCondition(...) (or pass the regex) instead
of inlining the $or block. Ensure both callers still short-circuit when there is
no pattern.
In `@website/modules/case-studies-page/services/SearchService.js`:
- Around line 32-38: In buildSearchRegexPattern, prevent unbounded regex sizes
by enforcing a max searchTerm length (e.g., MAX_SEARCH_TERM_LENGTH = 200) before
escaping and joining; trim and then truncate searchTerm to that max length
(preserving Unicode characters), return null if empty after trim, then continue
using REGEX_ESCAPE on the truncated term and split/join as before so the
resulting pattern sent to MongoDB is size-limited; update any relevant callers
if they expect full-term behavior.
In `@website/modules/case-studies-page/views/index.html`:
- Around line 31-40: The native clear button on the <input
id="case-studies-search"> (type="search") causes UX bugs; change its type
attribute to "text" so the browser does not render the native × and rely on your
existing custom clear behavior, i.e. update the input element with id
"case-studies-search" from type="search" to type="text" in the view;
alternatively, if you prefer keeping type="search", add a "search" event
listener in search-handler.js that calls performSearch(searchInput.value) so
clearing via the native button triggers the same search flow.
In `@website/public/js/modules/case-studies-page/search-handler.js`:
- Around line 57-69: The call to searchInput.focus() inside handleClearClick is
ineffective because performSearch('') triggers a page reload (via
performSearch), so remove the misleading focus() call; update handleClearClick
to clear the input, call updateClearButtonVisibility(searchInput, clearButton),
then invoke performSearch('') without attempting to focus, referencing the
function handleClearClick and performSearch and keeping
updateClearButtonVisibility as-is.
- Around line 51-54: The handler currently does a live DOM query on every
keystroke; instead capture the clear button once in initSearchHandler and use a
closure so the input event handler uses that cached reference: move or redefine
handleSearchInput inside initSearchHandler so it uses the existing clearButton
variable (instead of calling document.querySelector('.cs_search-bar-clear')),
call updateClearButtonVisibility(event.target, clearButton) from that closure,
and update the event listener wiring to use the new closure; remove the
redundant document.querySelector call from the outer handleSearchInput (or
delete the outer function) to avoid repeated DOM queries.
- Around line 33-37: The code in performSearch (using buildSearchUrl) currently
calls history.pushState(...) then window.location.reload(), which creates a
duplicate history entry; remove the pushState+reload pattern and perform a
single navigation to the built URL instead (e.g., replace the two calls with
window.location.assign(newUrl) or window.location.href = newUrl) so only one
history entry is created and the page navigates to the search results once.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
website/public/images/search.svgis excluded by!**/*.svg
📒 Files selected for processing (6)
website/modules/asset/ui/src/scss/_cases.scsswebsite/modules/case-studies-page/index.jswebsite/modules/case-studies-page/services/NavigationService.jswebsite/modules/case-studies-page/services/SearchService.jswebsite/modules/case-studies-page/views/index.htmlwebsite/public/js/modules/case-studies-page/search-handler.js
- Drive clear button visibility by class only; remove dead CSS/placeholder rule - Use type=text to avoid native search clear conflicting with custom clear - performSearch: use location.assign only (no pushState+reload phantom history) - handleSearchInput: close over clearButton; remove no-op focus() before navigate - SearchService: buildSearchCondition, 200-char cap, Prettier format - Chain parent beforeIndex/beforeShow so req.data is set before getFilters - Do not reassign query from filterByIndexPage (it returns undefined; fixes query.req)
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@website/modules/case-studies-page/views/index.html`:
- Around line 51-70: The loops generating hidden inputs assume each query param
(data.query.industry, data.query.stack, data.query.caseStudyType,
data.query.partner) is an array; normalize each to an array before iterating so
single-string query values are handled correctly (e.g., coerce to [value] when
not already an array) and then iterate to render the <input type="hidden"
name="...[]" value="..."/> elements for each item.
- Around line 386-389: The IIFE sets window.totalPages from
el.getAttribute('data-total-pages') but doesn't handle parseInt returning NaN;
update the logic in the IIFE that reads the element with id
'case-studies-page-data' (variable el) so you parse the attribute into a local
variable (e.g., parsed), validate it with Number.isInteger(parsed) and a >0
check (or !Number.isNaN) and only then assign to window.totalPages; otherwise
fallback to 1. Ensure the assignment uses this validated parsed value so
window.totalPages never becomes NaN.
In `@website/public/js/modules/case-studies-page/search-handler.js`:
- Around line 54-65: In handleClearClick, avoid triggering a navigation/search
when the input is already empty: get the element by id 'case-studies-search',
and if its value (use .trim()) is already empty, just update the clear button
visibility via updateClearButtonVisibility(searchInput, clearButton) and return
early; only set searchInput.value = '' and call performSearch('') when the input
contains non-empty text. This change touches handleClearClick and uses
updateClearButtonVisibility and performSearch to locate the logic to modify.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (6)
website/modules/asset/ui/src/scss/_cases.scsswebsite/modules/case-studies-page/index.jswebsite/modules/case-studies-page/services/NavigationService.jswebsite/modules/case-studies-page/services/SearchService.jswebsite/modules/case-studies-page/views/index.htmlwebsite/public/js/modules/case-studies-page/search-handler.js
- Ensure filter params are always arrays for safe template iteration - Guard window.totalPages against NaN - Skip navigation when clearing already-empty search input
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
website/modules/case-studies-page/services/UrlService.js (1)
113-117: 🛠️ Refactor suggestion | 🟠 Major
buildCaseStudyUrlclosure captures rawreq.querywhilereq.data.queryis normalized.
attachIndexDatastores the normalized object inreq.data.query(line 113) but thebuildCaseStudyUrlclosure at line 117 still closes over rawreq.query.buildCaseStudyUrlinternally callsensureArrayso the output is correct today, but the inconsistency makes the contract fragile — any future caller relying on both surfaces may get different filter representations.attachShowDatausesqueryParams(normalized) consistently;attachIndexDatashould match.♻️ Proposed fix
- reqCopy.data.query = UrlService.normalizeFilterParams(req.query || {}); + const queryParams = UrlService.normalizeFilterParams(req.query || {}); + reqCopy.data.query = queryParams; // Set default visible tags count reqCopy.data.defaultVisibleTagsCount = 5; reqCopy.data.buildCaseStudyUrl = (caseStudyUrl) => - UrlService.buildCaseStudyUrl(caseStudyUrl, req.query); + UrlService.buildCaseStudyUrl(caseStudyUrl, queryParams);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@website/modules/case-studies-page/services/UrlService.js` around lines 113 - 117, The buildCaseStudyUrl closure currently closes over the raw req.query causing inconsistency with the normalized params stored on reqCopy.data.query; update the closure to pass the normalized query (reqCopy.data.query) into UrlService.buildCaseStudyUrl so it consistently uses UrlService.normalizeFilterParams output (ensure you reference the existing symbols: reqCopy.data.query and UrlService.buildCaseStudyUrl) and remove the dependency on the raw req.query to avoid future contract fragility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@website/modules/case-studies-page/views/index.html`:
- Line 39: Update the aria-label on the search input (the attribute aria-label
in the HTML view) so it accurately reflects the actual search scope; change it
from "Search case studies by title" to something like "Search case studies by
title or description" to match the frontend focus placeholder ('Try a title or
description' in the JS handler) and the server-side regex that queries both
title and description fields.
- Around line 388-389: The current assignment allows zero or negative values to
become window.totalPages; update the logic that computes parsed (from
el.getAttribute('data-total-pages')) to validate it is a positive integer (e.g.,
parsed > 0) before using it, and fallback to 1 if parsed is NaN, <= 0, or not
finite; modify the block around the variables parsed and window.totalPages so
window.totalPages is always a positive integer (use parsed only when
Number.isFinite(parsed) && parsed > 0, otherwise set 1).
In `@website/public/js/modules/case-studies-page/search-handler.js`:
- Around line 84-88: The blur handler currently checks event.target.value raw,
so whitespace-only strings are treated as non-empty; update handleSearchBlur to
mirror buildSearchUrl by trimming the input first (use
event.target.value.trim()) and only set the placeholder to 'Search case studies'
when the trimmed value is empty; ensure you reference the same input element
(event.target) and avoid changing other behavior.
---
Outside diff comments:
In `@website/modules/case-studies-page/services/UrlService.js`:
- Around line 113-117: The buildCaseStudyUrl closure currently closes over the
raw req.query causing inconsistency with the normalized params stored on
reqCopy.data.query; update the closure to pass the normalized query
(reqCopy.data.query) into UrlService.buildCaseStudyUrl so it consistently uses
UrlService.normalizeFilterParams output (ensure you reference the existing
symbols: reqCopy.data.query and UrlService.buildCaseStudyUrl) and remove the
dependency on the raw req.query to avoid future contract fragility.
ℹ️ Review info
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (3)
website/modules/case-studies-page/services/UrlService.jswebsite/modules/case-studies-page/views/index.htmlwebsite/public/js/modules/case-studies-page/search-handler.js
feat(case-studies): add search with shared SearchService and safe regex
Adds a Case Studies search UI and client handlers plus a shared SearchService. The SearchService normalizes input, enforces length caps, escapes metacharacters and builds ReDoS-resistant regex conditions used in NavigationService and index queries. The search form preserves other filters, supports clear/reset without extra history entries, indicates active state, and respects reduced-motion and accessibility improvements.