# Search & Indicators Behaviors ## Overview The Search subsystem provides a global full-text search dialog accessible from the main navigation bar. It queries a Solr index of invoices, payments, transactions, and journal entries, returning results filtered by the user's client permissions. The Indicators subsystem provides small UI utilities like relative date badges (e.g., "5 days ago") used across the application. ## Routes & Pages | Route | Method | Handler | Purpose | |-------|--------|---------|---------| | `GET /search` | GET | `:search` | Opens the search modal dialog | | `POST /search` | POST | `:search` | Executes search query, returns results HTML | | `GET /days-ago` | GET | `::days-ago` | Returns a relative date badge pill (HTMX) | ## Behaviors ### Unit Tests - `q->solr-q` transforms user query into Solr query string - Bare words become `_text_:"word"` clauses joined with `AND` - Quoted phrases are preserved as single tokens - Keywords `invoice`, `payment`, `transaction`, `journal-entry` map to `type:` filters - Dates in `normal-date` format (e.g., `5/5/2034`) are converted to Solr date format - Dates in `iso-date` format are converted to Solr date format - Unparseable dates pass through unchanged - Decimal numbers (`123.45`) are formatted to 2 decimal places with HALF_UP rounding - Integers pass through unchanged - Multiple tokens are joined with `AND` - `try-cleanse-date` parses `normal-date` and `iso-date` formats, returning Solr-formatted date - `try-cleanse-date` returns original string for unparseable input - `try-parse-number` formats decimal strings to exactly 2 decimal places - `try-parse-number` returns non-decimal strings unchanged - `search-results` filters Solr results by `can-see-client?` against user's allowed clients - `days-ago*` returns "N days ago" pill with color `:primary` for < 30 days - `days-ago*` returns "N days ago" pill with color `:secondary` for 30-59 days - `days-ago*` returns "N days ago" pill with color `:yellow` for 60-89 days - `days-ago*` returns "N days ago" pill with color `:red` for 90+ days - `days-ago*` returns "N days from now" pill with color `:primary` for future dates - `days-ago*` returns empty `[:div]` for nil date ### Integration Tests - `GET /search` with authenticated user returns 200 with search modal HTML - Search modal contains text input with `hx-post="/search"`, `hx-target="#search-results"`, `hx-indicator="#search"` - Search input triggers on `keyup changed delay:300ms` and `search` events - `POST /search` with query parameter `q` returns HTML search results - Search results include card for each Solr document with type icon, client code, amount, vendor name, date, and description - Each result card links to the appropriate detail page (`/invoices`, `/transactions`, `/ledger`, `/payments`) with `exact-match-id` parameter - Results are filtered to only show documents from clients the user can access - `GET /search` without `q` parameter returns modal dialog (not results) - `POST /search` without `q` parameter returns modal dialog (not results) - `GET /days-ago?date=` returns colored pill HTML for past dates - `GET /days-ago?date=` returns "days from now" pill HTML - `GET /days-ago` with missing or invalid date returns empty div (schema enforcement) ### UI Tests (SSR) #### Happy Path: Search and View Result 1. Authenticated user clicks search icon in navbar 2. Search modal opens with autofocused input and placeholder "5/5/2034 Magheritas" 3. User types a query (e.g., "invoice 1000") 4. After 300ms debounce, HTMX POSTs to `/search` 5. Results appear below input as cards 6. Each card shows: type icon, type name, client code pill, amount pill, vendor pill (if present), date, and description/number 7. User clicks external link icon on a result 8. Result opens in new tab on the appropriate detail page with `exact-match-id` set #### Search with Type Filter 1. User opens search modal 2. User types "payment" 3. Results are filtered to only show `type:payment` documents 4. Each result card shows payment icon and links to `/payments/?exact-match-id=` #### Search with Date 1. User opens search modal 2. User types "5/5/2034" 3. Date is parsed and converted to Solr format 4. Results matching that date appear #### Empty Search Results 1. User opens search modal 2. User types a query with no matches 3. "No results found." message displays #### Days-Ago Indicator 1. User views a page containing a `days-ago` HTMX element 2. Element fetches `/days-ago?date=` 3. Colored pill renders showing relative time (e.g., "45 days ago" in secondary color) ## Edge Cases ### Search - **Special characters in query**: Solr special characters are not escaped in user query (relies on phrase wrapping) - **Empty query**: Modal renders without results; no Solr query executed - **Very long query**: Passes through to Solr; UI handles long text via flex layout - **No accessible clients**: Returns empty results even if Solr has matching documents - **Solr unavailable**: Behavior depends on `solr/impl` (MockSolrClient returns nil/empty) - **Mixed type keywords and text**: "invoice 1000" produces `type:invoice AND _text_:"1000"` - **Multiple type keywords**: "invoice payment" produces `type:invoice AND type:payment` (likely zero results) - **Numeric tokens with commas/currency**: `$1,000.50` passes through as literal text search - **Future dates**: Date parsing accepts future dates; Solr query includes them ### Indicators - **Nil date**: Returns empty div, no error - **Invalid date format**: Schema enforcement rejects before handler executes - **Same-day date**: Returns "0 days ago" with primary color - **Very old dates**: Returns "N days ago" with red color (90+ days threshold) ## Test Data Requirements ### Solr Index - Indexed documents of all four types: `invoice`, `payment`, `transaction`, `journal-entry` - Documents span multiple clients - Documents with varying dates, amounts, descriptions, numbers, and vendor names - Documents with and without vendor associations ### Users - Authenticated user with access to subset of clients - Authenticated user with access to all clients - Admin user (for full client visibility) ### Datomic Entities - Clients with `:client/code` and `:client/name` - Invoices with `:invoice/invoice-number`, `:invoice/total`, `:invoice/date`, `:invoice/client`, `:invoice/vendor` - Payments with `:payment/check-number`, `:payment/amount`, `:payment/date`, `:payment/client`, `:payment/vendor` - Transactions with `:transaction/description-original`, `:transaction/amount`, `:transaction/date`, `:transaction/client`, `:transaction/vendor` - Journal entries with `:journal-entry/amount`, `:journal-entry/date`, `:journal-entry/client`, `:journal-entry/vendor`, `:journal-entry/line-items` ## Dependencies ### External Services - **Solr**: Full-text search index (`auto-ap.solr`). Uses `MockSolrClient` in test environments without Solr configured - **Datomic**: Client visibility checks pull user/client associations ### Frontend Libraries - **HTMX**: Modal loading, search debounce (`keyup changed delay:300ms`), indicator spinner - **Alpine.js**: Modal card structure ### Middleware Stack - `wrap-secure`: Requires authentication for search and days-ago endpoints - `wrap-client-redirect-unauthenticated`: Redirects unauthenticated to `/login` - `wrap-schema-enforce`: Validates `date` query parameter for `/days-ago` ### Related Subsystems - **Invoices**: Search results link to invoice detail pages - **Payments**: Search results link to payment detail pages - **Transactions**: Search results link to transaction detail pages - **Ledger**: Search results link to ledger for journal entries