feat: IMAP mail sync with thread-to-contact matching (PRD §6.1)

Real IMAP connection via imapflow library:
- Connects to IMAP server (IMAPFlow, TLS on port 993)
- Fetches recent messages (configurable limit, default 20)
- Extracts: from, fromEmail, subject, date, snippet (200 chars)
- Thread-to-contact matching (PRD §6.1):
  1. Match sender email against persons table
  2. If no person match, extract domain and match against company.website
  3. Returns matchedCompany + matchedPerson for each email
- Audit-logged (action: crm.mailsync:imap)
- Zod validated (host, port, user, password, folder, limit)
- 502 on IMAP connection failure with detail
- 400 on validation failure

Tested: invalid creds → 502 'IMAP connection failed', missing host → 400
This commit is contained in:
Z User committed 2026-10-27 22:50:50 +00:00
1 parent 7acc0c5451
commit e45b68bb8b
1089 files changed
+260614

No files matched your search

+172
View File
@@ -0,0 +1,172 @@
---
name: state-coverage
description: Required UI states for interactive, data-driven, form, tool, dashboard, and prototype surfaces. Prevents designs that only render the ideal populated state.
mode: reference
platform: web
scenario: horizontal-craft
preview:
type: none
default_for:
- state coverage
- empty state
- loading state
- error state
- forms
- dashboard
- interactive prototype
fidelity: system
sources:
- adapted-from-open-design/craft/state-coverage.md
---
# State Coverage
The single most reliable AI-design failure is shipping only the populated state.
The active design system decides how states look. This file decides which states must exist and what they must contain.
---
## The five required states
Every surface that fetches, transforms, filters, searches, or accepts data must handle all five.
| State | Triggered when | Must contain |
|---|---|---|
| Loading | Data is in flight | skeleton, spinner, shell, or labelled progress |
| Empty | no records yet or query returned nothing | headline, explanation, primary next action |
| Error | fetch failed, server failure, validation rejection | plain cause, recovery action, preserved input |
| Populated | data present | the main designed state |
| Edge | extreme volume, long strings, missing fields, partial network | layout does not break |
Render-and-screenshot test: every list, table, card, form, and panel has these states.
---
## Edge matrix
| Surface | Edge scenario |
|---|---|
| Dashboard / table | 10,000+ rows, numeric columns, sort + filter active |
| Mobile card / list | 200-char title, missing avatar, missing secondary CTA |
| Form | optional fields empty, required fields at max length |
| Search results | one-character query, special-character query, 1,000+ results |
| Detail view | missing optional metadata, mixed Chinese / Latin / long words |
| Tool H5 | invalid input, user retries, result cannot be computed |
---
## Form-specific states
Forms add:
| State | Triggered when | Behavior |
|---|---|---|
| Untouched | field has not had focus | default styling; no validation message |
| Dirty valid | user typed and field passes validation | helper remains; avoid excessive success coloring |
| Submitted pending | submit clicked, waiting | button loading state; prevent duplicate submit |
Validation timing:
- validate on blur by default;
- for password / live fields, validate on keystroke only after first blur;
- remove error when input becomes valid;
- preserve input on server failure.
---
## Empty state composition
Empty is not absence. It has a job.
- **First-use empty:** illustration or simple mark + headline + value sentence + primary CTA.
- **No-results empty:** echo the query, suggest alternatives.
- **Cleared empty:** confirm completion, optional next action.
- **Error-as-empty:** forbidden. Error is its own state.
---
## Error state composition
Every error answers:
1. What happened.
2. Why, if knowable.
3. What the user can do.
Bad:
```text
Something went wrong.
```
Better:
```text
未能保存表单。网络连接中断,请检查后重试。
```
Severity tiers:
- field-level
- form-level
- section-level
- page-level
- app-level
Match severity to scope. A field error does not need a page-level error.
---
## Loading thresholds
| Duration | Indicator |
|---|---|
| 0–300ms | none |
| 300ms–2s | subtle spinner or skeleton |
| 2–10s | skeleton or labelled spinner |
| 10–30s | progress with cancel option when possible |
| 30s+ | explicit longer-than-expected message |
| 60s+ | stop indefinite animation; show error / retry / cancel |
Never leave a spinner running indefinitely.
---
## ARIA and focus rules
| Change | ARIA | Focus action |
|---|---|---|
| Inline error on submit | `role="alert"` | move focus to first error field |
| Toast confirmation | `role="status"` | do not move focus |
| Critical modal | `role="alertdialog"` | move focus to dialog |
| Loading begins | `role="status"` | do not focus spinner |
| User-triggered content appears | usually none | move focus only if it helps continuation |
---
## Common mistakes
- only populated state exists
- empty state says only “No data”
- error says only “Something went wrong”
- spinner has no timeout
- submit clears fields on failure
- validation fires on first keystroke
- full-page loading replaces chrome for a section-level fetch
- color alone conveys error state
- toast cannot be paused on hover/focus
---
## Quality checks
```text
[ ] Loading, empty, error, populated, and edge states exist where relevant.
[ ] Empty state has explanation and next action.
[ ] Error state has cause and recovery.
[ ] Forms preserve input after failure.
[ ] Long strings and missing optional fields do not break layout.
[ ] Loading does not spin forever.
[ ] State changes are announced where appropriate.
```