diff --git a/CLAUDE.md b/CLAUDE.md
index cb085d5..cf6df97 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -2,7 +2,7 @@
> **Audience:** AI assistants and developers working on this codebase.
> **Purpose:** Authoritative reference for architecture, conventions, gotchas, and decisions.
-> **Last reviewed:** May 2026 (Phase 19 complete + mobile API gap-fill Phases A–E: issue comments, dashboard stats with severity breakdown, area_name/assigned_to_name in issue payload, notification inbox, area_id on issues, CSRF-exempt pattern for all new blueprints)
+> **Last reviewed:** May 2026 (Phase 19 complete + mobile API gap-fill Phases A–E + customer UI refinements: contract filter scoping on Inspections/Issues list, dynamic invitation email domain from `request.host_url`, "Your Facilities" card-grid dashboard panel)
---
@@ -637,6 +637,18 @@ Pages using this pattern: `issues/form.html` (create), `issues/list.html` (filte
The issues list and inspections list both accept a `contract_id` query param that filters the DB query to facilities belonging to that contract (`facility.project_id == contract_id`) and narrows the facility dropdown in the rendered HTML.
+**Customer role — contract filter scoping:** In `inspections.index()` and `issues.index()`, the `projects` list passed to the template is scoped to contracts the customer is assigned to via `CustomerAssignment`. Non-customer roles still receive all active projects. This prevents customers from seeing contracts they have no assignment to in the Contract filter dropdown.
+
+### Customer Dashboard — "Your Facilities" Panel
+
+Rendered in `dashboard.html` for `current_user.role == 'customer'`. Uses a Bootstrap card grid instead of a table:
+
+- **Grid:** `col-12 col-sm-6 col-lg-4` — 3 per row on large, 2 on medium, 1 on small.
+- **Collapse (> 9 facilities):** First 9 cards are shown; a "Show all N facilities" toggle reveals the rest. Controlled by inline JS (`toggleFacilities` button, `VISIBLE = 9` constant).
+- **Live search (> 6 facilities):** A `#facilitySearch` text input filters `.facility-col` cards in real time by matching against the card's full text content. The show-more bar hides while a search query is active.
+- **Count badge:** The card header always shows the total facility count as a `badge bg-secondary rounded-pill`.
+- The JS block is only emitted when `customer_facilities|length > 9`; the search input is only emitted when `customer_facilities|length > 6`.
+
### Inspection Execute Page — UX Patterns
- **Photo upload-on-select**: `uploadPhotoField(input)` fires immediately on `` change. XHR to `POST //upload-photo`. On success, the server path is written to `` and a `` is shown.
@@ -735,6 +747,9 @@ timeout = 30
| 60 | **`flag_issue` offcanvas form must include ``** | `IssueForm.facility_id` has `DataRequired()`. The hand-written offcanvas form in `execute.html` is not rendered by WTForms, so it must explicitly send `facility_id`. Without it, `form.validate_on_submit()` silently returns `False`, the server responds `200 OK` with the `flag_issue.html` template, and the JS treats `res.ok` as success — no issue is ever saved. Fix: `` inside `#flagIssueForm`. |
| 61 | **Contract→Facility cascade UI pattern: contract selector is UI-only, not a WTForms field** | The "Log New Issue" form (`issues/form.html`) and both filter bars (`issues/list.html`, `inspections/list.html`) use a plain HTML `