Integrates the Leads and Lead Verification screens with the live be-crm
backend (crm.lead.* / crm.leadVerification.* / crm.media.*) behind a
mode-agnostic data layer that still falls back to the local mock when the
Shell isn't configured.
Data layer (new)
- src/lib/leads-api.ts — crm.lead.search/get/stats/create/update/
updateStatus/assign/sendForVerification + media (presign upload/download)
- src/lib/verify-api.ts — crm.leadVerification.search/get/stats/verify/
markUnverified/assign/reassign/moveToPending
Backend → FE wiring
- Assignee / creator names: read the resolved DTO fields and, as a safety
net, resolve member ids → real names against crm.team.member.search
(be-crm currently echoes the raw principalId as the name).
- Created By: read the createdBy object the backend returns (was blank).
- Site photos: upload via crm.media.presignUpload → PUT, persist through
crm.lead.attachment.add (per photo, after create), render in the detail
popup via crm.media.presignDownload.
- Duplicate guard: surface the backend's real error (detail / duplicate_lead)
instead of the SDK's opaque "data command failed: 400".
UX
- Leads detail: Property Photos gallery + edit-mode photo add/remove.
- Verification: "Change Assignee" now opens a searchable, scrollable popup
instead of a long inline dropdown.
- Removed the static "Storm Zone" tag from lead cards/detail; storm banner
only renders when the lead carries storm data.
Reference / follow-ups
- leads.http, leads.postman_collection.json — be-crm data-door requests.
- LEADS_BACKEND_CHANGES.md — required be-crm changes (name resolution,
assignee carry-over on sendForVerification) verified against :4010.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>