refactor(all): rewrite all behavior docs in table format with checkboxes

Rewrite all 11 remaining behavior documents to match the streamlined
invoice.md format:

- dashboard.md: 250 lines, 62 behaviors
- payment.md: 260 lines, behaviors for list, void, check printing, ACH
- transaction.md: 310 lines, list, import, admin insights
- ledger.md: 519 lines, entries, P&L, balance sheet, cash flows
- company.md: 320 lines, profile, 1099s, Plaid/Yodlee, reports
- admin.md: 494 lines, clients, accounts, vendors, rules, jobs, history
- pos.md: 405 lines, sales, deposits, tenders, refunds, shifts
- search-indicators.md: 167 lines, search modal, indicators
- auth.md: 184 lines, login, logout, impersonation, sessions
- outgoing-invoice.md: 192 lines, create, line items, PDF
- legacy-spa.md: 340 lines, all legacy pages (docs only)

All documents now use:
- Testing Patterns section with reusable abstractions
- Numbered tables: # | Behavior | Test Strategy | Status
- It should... behavior descriptions
- Checkboxes [ ]/[x] for tracking implementation
- Cross-Cutting Behaviors for permissions, lock dates, etc.
- Test Data Requirements tables
- Existing Tests to Preserve sections

Total: 3,844 lines of behavior documentation across 12 subsystem docs.
This commit is contained in:
2026-05-04 13:48:51 -07:00
parent e14a23ff54
commit d627e3c5d0
11 changed files with 2837 additions and 2500 deletions

View File

@@ -4,144 +4,163 @@
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
**Testing Philosophy**
- Prefer unit tests for pure business logic (query parsing, date formatting, number formatting)
- Use integration tests for database interactions and cross-system flows
- Use UI tests only for end-to-end happy paths that touch multiple pages
- Every behavior must be user-visible; no tests for implementation details
| 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
## Testing Patterns
### Unit Tests
### Pattern: Search Query Parsing
Search queries are transformed before being sent to Solr:
1. Bare words become text search clauses joined with `AND`
2. Quoted phrases are preserved as exact tokens
3. Type keywords (`invoice`, `payment`, `transaction`, `journal-entry`) filter by document type
4. Dates in normal or ISO format are converted to Solr date format
5. Decimal numbers are formatted to 2 decimal places
6. Unparseable tokens pass through unchanged
- `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:<value>` 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
**Test implications:** Unit test each transformation rule. Integration test the full query pipeline once.
### Integration Tests
### Pattern: Permission Filtering
All search results are filtered by the user's client access permissions.
- `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=<iso-date>` returns colored pill HTML for past dates
- `GET /days-ago?date=<future-date>` returns "days from now" pill HTML
- `GET /days-ago` with missing or invalid date returns empty div (schema enforcement)
**Test implications:** Integration test with users having different client access levels.
### 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
#### 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=<id>`
### Modal Behaviors
#### 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
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 1.1 | It should open a search modal when the user clicks the search icon in the navbar | UI | [ ] |
| 1.2 | It should display an autofocused search input with placeholder text in the modal | UI | [ ] |
| 1.3 | It should trigger a search after 300ms debounce when the user types in the search input | Integration | [ ] |
| 1.4 | It should display the search modal without results when no query is provided | Integration | [ ] |
#### Empty Search Results
1. User opens search modal
2. User types a query with no matches
3. "No results found." message displays
### Query Parsing Behaviors
#### Days-Ago Indicator
1. User views a page containing a `days-ago` HTMX element
2. Element fetches `/days-ago?date=<date>`
3. Colored pill renders showing relative time (e.g., "45 days ago" in secondary color)
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 2.1 | It should convert bare words in the search query to text search clauses joined with `AND` | Unit | [ ] |
| 2.2 | It should preserve quoted phrases in the search query as exact text matches | Unit | [ ] |
| 2.3 | It should filter by document type when the user includes a type keyword (`invoice`, `payment`, `transaction`, `journal-entry`) | Unit | [ ] |
| 2.4 | It should convert dates in normal format (e.g., `5/5/2034`) to Solr-compatible date format | Unit | [ ] |
| 2.5 | It should convert dates in ISO format to Solr-compatible date format | Unit | [ ] |
| 2.6 | It should pass through unparseable dates unchanged | Unit | [ ] |
| 2.7 | It should format decimal numbers to exactly 2 decimal places with `HALF_UP` rounding | Unit | [ ] |
| 2.8 | It should pass through integer numbers unchanged | Unit | [ ] |
| 2.9 | It should handle mixed type keywords and text tokens in the search query | Unit | [ ] |
| 2.10 | It should handle multiple type keywords in the search query | Unit | [ ] |
## Edge Cases
### Results Display Behaviors
### 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
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 3.1 | It should display search results as cards below the search input | UI | [ ] |
| 3.2 | It should show a type icon on each result card | UI | [ ] |
| 3.3 | It should show the document type name on each result card | UI | [ ] |
| 3.4 | It should show the client code as a pill on each result card | UI | [ ] |
| 3.5 | It should show the amount as a pill on each result card | UI | [ ] |
| 3.6 | It should show the vendor name as a pill when present on each result card | UI | [ ] |
| 3.7 | It should show the date on each result card | UI | [ ] |
| 3.8 | It should show the description or number on each result card | UI | [ ] |
| 3.9 | It should link each result card to the appropriate detail page with an `exact-match-id` parameter | Integration | [ ] |
| 3.10 | It should open the detail page in a new tab when the user clicks the external link icon | UI | [ ] |
| 3.11 | It should filter results to only show documents from clients the user can access | Integration | [ ] |
### 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)
### Empty Results Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 4.1 | It should display "No results found." when the query matches no documents | UI | [ ] |
| 4.2 | It should return an empty results list when the user has no accessible clients | Integration | [ ] |
| 4.3 | It should return an empty results list when Solr is unavailable | Integration | [ ] |
### Type Filter Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 5.1 | It should filter results to only show payment documents when the user types "payment" | Integration | [ ] |
| 5.2 | It should filter results to only show invoice documents when the user types "invoice" | Integration | [ ] |
| 5.3 | It should show the payment icon and link to the payments detail page for payment results | UI | [ ] |
### Date Search Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 6.1 | It should return documents matching a date in normal format (e.g., `5/5/2034`) | Integration | [ ] |
| 6.2 | It should return documents matching a date in ISO format | Integration | [ ] |
| 6.3 | It should accept future dates in the search query | Integration | [ ] |
---
## Indicators
### Days-Ago Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 7.1 | It should display a relative date badge showing "N days ago" for past dates | UI | [ ] |
| 7.2 | It should display a relative date badge showing "N days from now" for future dates | UI | [ ] |
| 7.3 | It should show the days-ago badge in primary color for dates less than 30 days old | Unit + UI | [ ] |
| 7.4 | It should show the days-ago badge in secondary color for dates 30-59 days old | Unit + UI | [ ] |
| 7.5 | It should show the days-ago badge in yellow color for dates 60-89 days old | Unit + UI | [ ] |
| 7.6 | It should show the days-ago badge in red color for dates 90 or more days old | Unit + UI | [ ] |
| 7.7 | It should show "0 days ago" with primary color for same-day dates | Unit + UI | [ ] |
| 7.8 | It should render an empty indicator when the date is nil | Unit + UI | [ ] |
---
## Cross-Cutting Behaviors
### Permission Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 8.1 | It should require authentication to access the search endpoint | Integration | [ ] |
| 8.2 | It should require authentication to access the days-ago endpoint | Integration | [ ] |
| 8.3 | It should redirect unauthenticated users to the login page when accessing search or days-ago | Integration | [ ] |
### Error Handling Behaviors
| # | Behavior | Test Strategy | Status |
|---|----------|---------------|--------|
| 9.1 | It should reject invalid date formats for the days-ago endpoint via schema validation | Integration | [ ] |
| 9.2 | It should handle special characters in the search query without errors | Integration | [ ] |
| 9.3 | It should handle very long search queries without UI breakage | UI | [ ] |
| 9.4 | It should handle numeric tokens with commas or currency symbols as literal text search | Unit | [ ] |
---
## 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
| Entity | Requirements |
|--------|-------------|
| **Solr Index** | Indexed documents of all four types: `invoice`, `payment`, `transaction`, `journal-entry`; spanning multiple clients; with varying dates, amounts, descriptions, numbers, and vendor names |
| **Users** | Authenticated user with access to subset of clients; authenticated user with access to all clients; admin user |
| **Clients** | Multiple 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` |
### Users
- Authenticated user with access to subset of clients
- Authenticated user with access to all clients
- Admin user (for full client visibility)
## Existing Tests to Preserve
### 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`
No existing test files specified for Search & Indicators.
## 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
- **Middleware**: `wrap-secure` requires authentication; `wrap-client-redirect-unauthenticated` redirects unauthenticated to `/login`; `wrap-schema-enforce` validates `date` query parameter for `/days-ago`
- **Invoices**: Search results link to invoice detail pages
- **Payments**: Search results link to payment detail pages
- **Transactions**: Search results link to transaction detail pages