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:
@@ -108,12 +108,11 @@ Subsystem rules live in `docs/guides/` and lazy-load in Claude Code via `.claude
|
|||||||
|
|
||||||
## Current state
|
## Current state
|
||||||
|
|
||||||
_**Box live at v0.1.0:99 (deployed + verified 2026-06-20)**. This session = **Grant's real-phone device-test round 2**: four in-app fixes (CRM half → v99 s9pk, **installed & booted clean**) + a Matrix intake-bot cleanup (**redeployed on the Spark; intake room purged to 0, bot confirmed to hold room mod-power**) + a written plan for in-app camera card intake. **The fundraising grid + email capture is the canonical system of record.** History: git log + `start9/0.4/startos/versions/`._
|
_**Box live at v0.1.0:100 (deployed + verified 2026-06-20)** — clean boot, no migration. This session = **built feature #7, in-app camera business-card intake** (server endpoint + mobile component), shipped in the v100 s9pk + installed, AND Grant's prior-round (v99) device fixes **confirmed on-device**. **The fundraising grid + email capture is the canonical system of record.** History: git log + `start9/0.4/startos/versions/`._
|
||||||
|
|
||||||
- **Device-test round 2 — 4 fixes shipped to v0.1.0:99 (CRM half).** (1) **Intake fuzzy match** no longer over-indexes on generic firm words — `_name_similarity` scores **distinctive** tokens only (generic descriptors like "Investment Group"/"Capital"/"Family Office" stripped via `_GENERIC_ORG_WORDS`); "Fortitude Investment Group" no longer surfaces Aether/Russell. (2) **Mobile grid "Last contact"/staleness sort is reversible** (`SortSheet` opt-in `dir`/`onToggleDir`; other surfaces untouched). (3) **Mobile "Edit investor" prefills a contact's email** — `GET /api/fundraising/state` heals a blank grid pill email from `fundraising_contacts.contact_id → contacts.email` (fill-only, by pill order then name; next one-row save persists it; `fundraising_contact_emails_by_row`). (4) **Quick-log pencil icon renders** — `.quicklog-btn svg { width;height;flex:none }` (iOS collapses a sole, centered, attribute-only-sized flex-child svg; the v97 fix only changed its color).
|
- **In-app card intake (#7) — BUILT + LIVE + device-confirmed (v0.1.0:100, 2026-06-20).** Grant tested on his phone: **both camera capture and photo-library upload work**. Mobile camera button (top bar, left of the quick-log pencil) → take/pick a photo → **`POST /api/intake/card`** (`handle_intake_card`, **member+**, read-only): lazily imports the bot's nio-free `parse`+`spark`, vision-transcribes (local VL via Spark Control), runs the same email/phone/LinkedIn integrity rule + `find_intake_match`/`find_intake_candidates`, returns `{ok,transcription,proposal,match,candidates}` (soft-fails: 200 `unreadable`, 502 `vision_unavailable`). `MobileCardCapture` shows an editable review sheet; **Save** reuses `log-communication` tagged **`source="app_card"`** — a human approves every write. Client `<canvas>` downscale also normalizes iPhone HEIC→JPEG. No migration, no new dep, no bot change. Detail: `docs/guides/matrix-intake.md` *In-app card intake* + `docs/handoffs/in-app-card-intake-plan.md`.
|
||||||
- **Matrix intake-bot — thread auto-delete on decision + retroactive purge — DONE & LIVE (Spark, bot-only, NOT in the s9pk).** Approve/reject now `redact_thread(intake_room, root)` (clears card + ack + main-timeline nudge + the user's photo/note), mirroring the email-review room; the scan matches `rel_type=m.thread` children OR the bot's own `m.in_reply_to` nudge (narrowed in the reviewer pass so it can't catch an unrelated human reply to the root). One-time `backend/matrix_intake/redact_intake.py` (dry-run default; `--apply`) cleared the backlog to 0. **Bot already holds room mod-power** (confirmed live — user messages + a card image redacted, 0 failures). No more in-Matrix "✅ logged" confirmation after a commit (by design, like email). Detail: `docs/guides/matrix-intake.md` (compose service is `intake`; redaction is rate-limited, re-run `--apply` till a dry-run reports 0).
|
- **Grant's v99 device-tests — CONFIRMED on-device** (this session): Edit-investor contact-email prefill, quick-log **pencil icon** renders, intake **ignores generic firm tokens**, mobile **staleness sort reversible** both ways. v99 (CRM) + the Matrix intake-bot thread-redaction (Spark, bot-only, NOT in the s9pk; intake room purged to 0) remain live.
|
||||||
- **In-app camera card intake (#7) — PLAN + decisions LOCKED, not built yet.** `docs/handoffs/in-app-card-intake-plan.md`: reuses the nio-free transcribe/parse core (`server.py` already imports `llm`; `matrix_intake/parse.py`+`spark.py` are nio-free) → **one endpoint** `POST /api/intake/card` + **one mobile component** (camera button left of the pencil). No bot refactor, no new dep, no migration. Decisions: `source="app_card"`, **form-field edits only** (no NL-edit), **any authenticated member**, ships in the s9pk. **Ready to build on Grant's go-ahead.**
|
- **Tests: 43/43 backend green** (`python3 backend/run_tests.py`), `py_compile` clean, render-smoke green (`cd start9/0.4 && make render-smoke`). New `backend/test_intake_card.py` (18 checks; stubs vision+parse like `test_spark.py`). Reviewer-agent passes on both halves: **APPROVE, no blockers**. Vision/OCR + the camera/canvas path are **on-device / live-smoke only**.
|
||||||
- **Mobile-first redesign — deployed (v95–v97), on-device test in progress.** 4 surfaces (Grid·Contacts·Pipeline·Reminders) + light theme + installable PWA + 4-stage funnel; desktop untouched. Standing gate: light/dark across surfaces + login (375px gutters, safe-area), install→standalone launch, swipe/sheet interactions (only jsdom-smoked). Other live features: W2 NL query (v94), W1 reminders (v93), grid Pipeline (v88), Gmail capture + daily digest, Thesis/Architect (dual-approval), outreach — all draft-only. **Business-card intake (M3, Matrix bot) LIVE since v98** (vision OCR via the daily-driver model; `source="matrix_card"`; captures name/email/title/city/LinkedIn/phone/mobile, integrity-checked).
|
- **Mobile-first redesign — live (v95–v100), on-device test ongoing.** 4 surfaces (Grid·Contacts·Pipeline·Reminders) + light theme + installable PWA + 4-stage funnel; desktop untouched. M3 Matrix card intake live since v98 (`source="matrix_card"`). Other live: W2 NL query (v94), W1 reminders (v93), grid Pipeline (v88), Gmail capture + daily digest, Thesis/Architect (dual-approval), outreach — all draft-only.
|
||||||
- **Tests: 42/42 backend green** (`python3 backend/run_tests.py`), `py_compile` clean, frontend render-smoke green (`make render-smoke`). New `test_grid_email_heal.py` + intake generic-word/all-generic-edge cases. A **reviewer-agent pass** of the whole session diff returned APPROVE (no blockers); its one substantive finding (the redact predicate) is fixed above. Vision/OCR (Matrix + the planned in-app path) is **live-smoke only**.
|
- **Next:** (1) Finish the standing mobile on-device gate (light/dark across surfaces + login 375px/safe-area, install→standalone, swipe/sheets — only jsdom-smoked). (2) Spot-confirm #7 corners on the box as they come up: a real-card `source="app_card"` row, attach-to-existing vs add-new, blurry-card retry, OCR/phone-mapping accuracy. (3) Optional #7 follow-ups if wanted: NL-revise on the card, client-side crop for OCR.
|
||||||
- **Next:** (1) Grant device-tests the 4 v99 fixes on his phone (esp. the **pencil icon** — root-caused + render-smoke-green but device-confirm) + re-tests cards (OCR/phone mapping); (2) **build #7 (in-app camera card intake) on go-ahead** — decisions locked, spec ready; (3) finish the standing mobile on-device gate (light/dark + install + swipe/sheets).
|
- **Open / risks:** **vision OCR can misread a character** on a card small-in-frame (resolution-bound — Spark Control downscales to ~2 MP; `mara.com→marac.com` reproduced at temp 0; mitigation: fill the frame / future client crop). iPhone **HEIC** now re-encoded to JPEG client-side in the **app** path (still a risk on the Matrix path). phone/mobile/LinkedIn land on the **contact record**, not the grid pill (only city syncs to the pill). **Claude/Architect path unverified live on the box**; v2.0 reserve-asset spine **not canonical**; PWA iOS status bar fixed `black` in light theme (deferred, dark default); doc drift — `crm-overview.md`/`EVALUATION.md` still call `lp_profiles` live; minor UI cleanups (`.login-title` dead CSS, `MobileDetailRow` unused, Pipeline "Committed" tile shows grid-committed) in git history.
|
||||||
- **Open / risks:** **vision OCR can misread a character** on a card small-in-frame (resolution-bound — Spark Control downscales to ~2 MP; `mara.com→marac.com` reproduced at temp 0; mitigations: fill the frame, or a future client-side crop); **iPhone HEIC** may not decode in vLLM (most clients send JPEG); phone/mobile/LinkedIn land on the **contact record**, not the grid pill (by design — only city syncs to the pill); intake redaction needs the bot's room **mod power** or users' messages linger; **Claude/Architect path unverified live on the box**; v2.0 reserve-asset spine **not canonical**; **PWA iOS status bar fixed `black`** in light theme (header seam; deferred, dark is default); doc drift — `crm-overview.md`/`EVALUATION.md` still call `lp_profiles` live; assorted minor UI cleanups (`.login-title` dead CSS, `MobileDetailRow` unused, Pipeline "Committed" tile shows grid-committed) tracked in git history.
|
|
||||||
|
|||||||
@@ -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
|
- 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
|
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`).
|
(`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
|
- 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
|
`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.
|
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
|
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**.
|
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)
|
## Fuzzy matching (server-side, ships in the s9pk)
|
||||||
|
|
||||||
`GET /api/intake/match` returns `{match, candidates}`. `find_intake_match` is unchanged —
|
`GET /api/intake/match` returns `{match, candidates}`. `find_intake_match` is unchanged —
|
||||||
|
|||||||
Reference in New Issue
Block a user