docs: handoff — in-app card intake (#7) live + device-confirmed at v0.1.0:100

#7 shipped in v100 and verified on the box; Grant device-tested on his phone —
both camera capture and photo-library upload work.

- matrix-intake guide: new "In-app card intake (#7)" subsection (the CRM's own
  twin of the Matrix M3 card flow), plus a load-bearing caveat — server.py now
  imports parse + spark, so those two must stay nio-free or the CRM image breaks.
- AGENTS.md Current state: rewritten for v100; v99 device fixes confirmed;
  next steps reset to the standing mobile on-device gate.
This commit is contained in:
Keysat
2026-06-20 14:40:07 -05:00
parent 463f624548
commit 622d454461
2 changed files with 29 additions and 8 deletions
+22
View File
@@ -23,6 +23,11 @@ Spark). See *Fuzzy matching* below. Tests green (27/27 backend + the offline bot
- A **separate process**, not part of the CRM. Its only third-party dep, `matrix-nio`, lives
in `backend/matrix_intake/requirements.txt` and **must never** be added to the stdlib CRM
(`backend/server.py`). Runs on the Spark (placement per `standards/guides/placement.md`).
**Caveat (since v0.1.0:100): `parse.py` + `spark.py` are now ALSO imported by the stdlib CRM**
— the in-app card endpoint `POST /api/intake/card` lazily imports them (they're nio-free:
`parse``spark``ingest/llm`, no nio, no `crm_client`). So **keep `parse`/`spark` nio-free**
a nio-coupled helper added to either would break the CRM image, not just the bot. See the
*In-app card intake* note under *Business-card capture* and `docs/handoffs/in-app-card-intake-plan.md`.
- It **drafts; a human approves.** Nothing is written autonomously — every CRM write follows a
`yes` reply in the proposal thread. This is exempt from "agents draft, humans send" the same
way the digest is: it's internal data entry to our own CRM, not outward LP contact.
@@ -149,6 +154,23 @@ existing flow (parse → match → disambiguate → approve → `log-communicati
vLLM's PIL — most clients (Element iOS) transcode to JPEG on upload, but confirm on-device; ③ the
offline tests stub the vision call (`test_spark.py`); the download + real OCR is **live-smoke only**.
### In-app card intake (#7 — the CRM's own twin of M3, v0.1.0:100, ships in the s9pk)
The same card flow, **in the app** instead of Matrix: a mobile camera button (`MobileCardCapture`
in `frontend/index.html`, top bar left of the quick-log pencil) → take/pick a photo → `POST
/api/intake/card`. The endpoint (`server.handle_intake_card`, authenticated **member+**, read-only)
**lazily imports `matrix_intake/parse` + `spark`** (the nio-free core — see the *What it is* caveat),
vision-transcribes (local VL via Spark Control), runs the **same** email/phone/LinkedIn integrity
rule + `find_intake_match`/`find_intake_candidates`, and returns `{ok, transcription, proposal,
match, candidates}` (soft-fails: 200 `{ok:false, reason:"unreadable"}`, 502 `vision_unavailable`).
**Nothing is written** there — an editable mobile review sheet POSTs the approved proposal to
`log-communication` tagged **`source="app_card"`** (vs `matrix_card`/`matrix_intake`). The client
**downscales via `<canvas>` to JPEG** before upload, which also **normalizes iPhone HEIC→JPEG**
(sidesteps the M3 HEIC-in-vLLM limit above). Differences from M3: **form-field edits only** (no
NL-revise), and it **ships in the s9pk** (server + frontend), not bot-only. Tests:
`backend/test_intake_card.py` (stubs vision+parse like `test_spark.py`); the camera/canvas/OCR path
is on-device-only. Plan + locked decisions: `docs/handoffs/in-app-card-intake-plan.md`.
## Fuzzy matching (server-side, ships in the s9pk)
`GET /api/intake/match` returns `{match, candidates}`. `find_intake_match` is unchanged —