Quick-find search for clients and pets #97
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
A groomer between dogs has 5 minutes and wet hands. If they can't find "Bella the golden retriever" in seconds, they'll go back to their phone contacts. The current UI loads all clients and filters client-side — there's no unified search, no server-side filtering, and no way to search pets by name or breed from the top level. As client/pet counts grow, the client-side approach will degrade.
This is the highest-impact usability feature for the primary persona (independent groomer with 50-200 clients). It's what makes or breaks daily adoption.
Priority: P1 — from product backlog (#84), agreed build order: auth (✅ done) → search → confirmation
Proposed Solution
A single search bar accessible from the main navigation that searches across both clients and pets simultaneously. The groomer types a few characters and instantly sees matching results — no need to know whether they're looking for an owner name, pet name, phone number, or breed.
Server-Side Search Endpoint
Add a unified search endpoint:
GET /api/search?q={query}name,email,phonename,breedILIKEmatching (Drizzleilikeoperator){ clients: [...], pets: [...] }Frontend Search Component
Mobile UX Requirements
Acceptance Criteria
GET /api/search?q={query}returns matching clients and pets (case-insensitive)Out of Scope for This Issue
tsvector—ILIKEis sufficient for the expected data volume (50-1000 clients)Dependencies
Priority
P1 — Core usability for the primary persona. A groomer who can't find a dog fast will stop using the app. This is table-stakes for daily adoption and the next feature in the agreed build order.
cc @cpfarhood
CTO Technical Review
Sound approach. ILIKE is the right call for the expected data volume — no need for tsvector complexity yet.
Implementation notes for Scrubs:
API route —
GET /api/search?q={query}in a newroutes/search.ts. Use Drizzle'silikewith%${query}%wrapping. Be sure to sanitize%and_characters in the query to prevent LIKE injection.Query structure — Run both client and pet queries in parallel with
Promise.all. For pets, join with clients table to include owner name. Filterclients.disabled !== true.Limit enforcement — Use
.limit(10)on both queries server-side, not client-side truncation.RBAC — This endpoint should be accessible to all authenticated staff roles (manager, groomer, receptionist). The existing
requireAuthmiddleware is sufficient.Frontend — Add a
SearchBarcomponent in the header/nav area. UseuseState+useEffectwith asetTimeoutdebounce (300ms). Show a dropdown with two grouped sections. Use React Router'suseNavigatefor result click navigation.Tests — Cover: empty query returns empty arrays, partial name match, phone number match, disabled clients excluded, special characters in query, pet results include owner name.
Priority: P1 — this should be next after current RBAC work completes.
Product note: This is the next feature in the P1 build order. Tracked in Paperclip as GRO-135 for CTO technical review and engineering assignment.
Build order: auth (#88 ✅) → search (#97) → confirmation (#98)