Mobile P3b: investor name + contact-pill editing (update-row)

Adds the editable half of BRIEF §3a's mobile grid set: rename an investor
and add/edit/remove its contact pills from the mobile detail sheet.

New POST /api/fundraising/update-row is the one-row read-fresh-modify-write
twin of log-communication: it mutates only the target row's name/contacts in
the canonical grid blob server-side, then bumps the version + re-syncs the
relational tables. It never accepts a whole-grid payload, so a stale mobile
client can't clobber concurrent edits to other rows (the reason mobile avoids
the whole-grid PUT). _sanitize_fundraising_contacts whitelists the known pill
fields as the trust boundary; removing a pill is soft on the classic contacts
directory (only the grid pill + fundraising_contacts row drop).

Frontend: MobileFundraisingGrid gains an Edit bottom-sheet (name input + pill
editor with client-side dedup); money stays desktop-only. New CSS is
theme-var-only so it flips in light mode.

Verified: test_fundraising_update_row.py (24 assertions, real HTTP), full
suite 37/37, render-smoke + a 375px jsdom interaction harness green.
This commit is contained in:
Keysat
2026-06-19 17:07:29 -05:00
parent 099d87dad2
commit 3f93daf28e
4 changed files with 436 additions and 27 deletions
+7 -22
View File
@@ -107,27 +107,12 @@ Subsystem rules live in `docs/guides/` and lazy-load in Claude Code via `.claude
## Current state
_**Box live at v0.1.0:94**; `main` (pushed through `e6a8945`) ahead by mobile Phases 06 + drag-reorder views — **all deploy-pending** (no s9pk built yet). **The fundraising grid + email capture is the canonical system of record.** Active thread: **mobile-first redesign** — **all 4 surfaces + the light theme done (P0P6)**. **Plan (Grant, 2026-06-19): finish features first — P3b name/pill edit → Phase 7 design-conformance pass, in that order next session — then Grant does device testing + deploy** (NOT before; everything is unverified on a real phone). Per-phase detail + backlog: `ROADMAP.md` / `EVALUATION.md`; history: git log + `start9/0.4/startos/versions/`._
_**Box live at v0.1.0:94**; `main` ahead by mobile Phases 06 + P3b name/pill edit + drag-reorder views — **all deploy-pending** (no s9pk built yet). **The fundraising grid + email capture is the canonical system of record.** Active thread: **mobile-first redesign** — **all 4 surfaces + light theme + P3b (name/pill edit) done; BRIEF §3a editable set complete**. **Plan (Grant, 2026-06-19): finish features first — P3b done → Phase 7 design-conformance pass next — then Grant does device testing + deploy** (NOT before; everything is unverified on a real phone). Per-phase detail + backlog: `ROADMAP.md` / `EVALUATION.md`; history: git log + `start9/0.4/startos/versions/`._
- **Mobile redesign — all 4 core surfaces built + committed (Grid · Contacts · Pipeline · Reminders).** Each is a rules-of-hooks-safe `useIsMobile()` wrapper → `Mobile*`/`Desktop*` pair (**desktop untouched**), re-authored against the real API on shared primitives `<BottomSheet>`/`useIsMobile()`/`StageChip`/`MobileDetailRow`. Foundation: bottom-tab bar + `:root` mobile vars (P1); 4-stage enum + read-only derived grid signals (`existing_investor`/`last_activity_at`/`staleness`/`opportunity_id`) injected on GET, **stripped on write at both points** (P0/P3a `_computed_row_values` + `stripComputedRows`). **Mobile writes use one-row endpoints only — never whole-grid PUT** (BRIEF §3a). Per-phase detail in `ROADMAP.md`.
- **This session — P6 light theme (committed `e6a8945`, frontend-only).** App-wide light theme behind
`:root[data-theme="light"]`; **dark stays default** (pre-paint boot script in `<head>` reads
`localStorage.venture_crm_theme`; no `prefers-color-scheme`). **App-wide toggle:** labeled control
in the desktop sidebar footer + sun/moon icon in the mobile top bar, both off one `theme` state in
`App`. Color pairs taken from the **full Claude Design export** (`design/_imports/2026-06-19_zip-file/`,
with the previously-missing `store.js` + `*App.dc.html` `DCLogic` palettes) — exact dark+light for
every stage/recency/note/priority/reminder/money tint. **Method = zero dark regression by
construction:** grew `:root` to 44 themed slots whose **dark values == the originals byte-for-byte**
(verified), then migrated **319 hex→`var()`** (script for structural; targeted edits for semantic/
chip helpers — `StageChip` now className-based, `PIPELINE_STAGE_CHIP` removed). Mobile surfaces +
chrome are fully var-based → **mobile light complete**. **Desktop light rough edges** (bespoke
`<style>` shades: login glow/scrollbar/table-hover/KPI green, the legacy off-palette `.badge-*`
family, dark-tuned shadows) deferred to the new **Phase 7 conformance pass** (per Grant: conform
*everything* — buttons/colors/functionality — to the Claude Design export). Verified: render-smoke
green; jsdom interaction harness on the **authed shell** (boot-dark → toggle→light+persist+relabel →
toggle→dark, 7/7); var parity + dark-identity + no-undefined-var checks green. **No real-phone check.**
- **Prior session (`ee9db64`, no backend change):** **P4 Pipeline** (`MobilePipeline` = CSS scroll-snap swipe-between-stages + segmented control + dots, per-card / move + tap→detail w/ stage sheet; shares `PATCH /api/opportunities/{id}/stage`; view+advance-only) + **P5 Reminders** (`MobileReminders` = urgency-grouped list over `/api/reminders` + Active/Done/All filter; `ReminderRow` pointer-drag **swipe-left=done / swipe-right=snooze +7d**; tap→create/edit sheet). Verified: render-smoke + jsdom-375px harness (12/12 each); reviewer-passed (notably **P5 `pointercancel` no longer fires a spurious mark-done**). Reusable swipe-test technique noted in memory.
- **Mobile redesign — all 4 core surfaces built + committed (Grid · Contacts · Pipeline · Reminders).** Each is a rules-of-hooks-safe `useIsMobile()` wrapper → `Mobile*`/`Desktop*` pair (**desktop untouched**), re-authored against the real API on shared primitives `<BottomSheet>`/`useIsMobile()`/`StageChip`/`MobileDetailRow`. Foundation: bottom-tab bar + `:root` mobile vars (P1); 4-stage enum + read-only derived grid signals (`existing_investor`/`last_activity_at`/`staleness`/`opportunity_id`) injected on GET, **stripped on write at both points** (P0/P3a `_computed_row_values` + `stripComputedRows`). **Mobile writes use one-row endpoints only — never whole-grid PUT** (BRIEF §3a): log-communication, pipeline link/stage, reminders, and now **`POST /api/fundraising/update-row`** (P3b name/pill edit). Per-phase detail in `ROADMAP.md`.
- **This session — P3b investor name + contact-pill edit (frontend + backend; not yet committed at write time → see git log).** New **version-safe `POST /api/fundraising/update-row`** (`handle_update_fundraising_row`) — the one-row read-fresh-modify-write twin of `handle_log_fundraising_communication`: reads the canonical grid blob server-side, mutates ONLY the target row's `investor_name`/`contacts`, writes back with a version bump + `sync_fundraising_relational`. Never a whole-grid payload → can't clobber concurrent edits to other rows (BRIEF §3a). Server-side `_sanitize_fundraising_contacts` (9-field whitelist; drops name+email-empty pills) is the trust boundary. Frontend: `MobileFundraisingGrid` detail gains an **Edit** bottom-sheet (investor-name input + pill add/edit/remove of name·email·title, preserving each pill's location/LinkedIn fields, **client-side dedup** by email→name) → saves through update-row → reloads; money stays desktop-only. Pill removal is **soft** on the classic `contacts` directory (only the grid pill + `fundraising_contacts` row drop). New CSS is theme-var-only (`.pill-edit*`/`.sheet-addbtn`/`.fs-detail-edit`). Verified: **`backend/test_fundraising_update_row.py`** (24 assertions over real HTTP — rename/add/edit/remove, other-row-untouched, relational + classic-contacts sync, soft-remove, name-only-pill kept, 5 guards); full suite **37/37**; render-smoke green; throwaway jsdom-375px harness drove the real edit flow (14/14: open→edit→rename→remove→add→save, asserted `POST update-row` + **no whole-grid PUT** + preserved fields). Reviewer-passed (nits were deliberate sibling-pattern parity). **No real-phone check.**
- **Prior session (`e6a8945`) — P6 light theme (frontend-only).** App-wide light theme behind `:root[data-theme="light"]` (dark default; pre-paint boot script reads `localStorage.venture_crm_theme`); toggle in desktop sidebar footer + mobile top bar off one `App` `theme` state. 44 themed `:root` slots (dark == originals byte-for-byte), 319 hex→`var()` migrated, `StageChip` className-based. **Mobile light complete; desktop-light rough edges** (bespoke `<style>` shades: login glow/scrollbar/table-hover/KPI green, legacy off-palette `.badge-*`, dark shadows) → **Phase 7**. Verified render-smoke + jsdom toggle harness (7/7). **No real-phone check.** Earlier (`ee9db64`): **P4 Pipeline** + **P5 Reminders** (swipe-done/snooze) — detail in `ROADMAP.md`.
- **Live (deployed):** W2 NL query (v94; remaining: in-room smoke + web "Ask" box); W1 reminders (v93); grid Pipeline (v88); Matrix intake + Gmail capture (DWD) + daily digest; Thesis/Architect (dual-approval); outreach — all draft-only.
- **Tests:** **36/36 backend green** (`python3 backend/run_tests.py`), `py_compile` clean, render-smoke green, fresh-DB migrate clean.
- **Next (next session = finish features, in order):** 1) **P3b** name/pill edit — new version-safe `POST /api/fundraising/update-row` (single-row name/contacts; **never** whole-grid PUT) + a bottom-sheet pill editor (add/edit/remove, client-side dedup) + test; completes BRIEF §3a's editable set. 2) **Phase 7 design-conformance pass** against the full Claude Design export (`design/_imports/2026-06-19_zip-file/`) — buttons/colors/spacing/functionality across all surfaces, incl. finishing the P6 desktop-light rough edges (run `design-checker`). **Then (Grant, after feature-complete):** deploy P0P7 + view-reorder in one s9pk (**authorize + version-bump first**) and device-test light/dark on a real phone. Later backlog: W2 web Ask box + smoke; W3 bot grid-mutations; W1b nurture-gap.
- **Open / risks:** all mobile work + **P6 light theme** **built but never deployed or tested on a real phone/browser** (render-smoke + jsdom only — verify on a device, both themes); **P6 desktop-light has known rough edges** (bespoke `<style>` shades, legacy `.badge-*` family, dark shadows → Phase 7); **P3b deferred**; W2 happy-path only; **Claude/Architect path unverified live on the box**; v2.0 reserve-asset spine **not canonical** (needs dual sign-off); doc drift — `crm-overview.md`/`EVALUATION.md` still call `lp_profiles` live.
- **Tests:** **37/37 backend green** (`python3 backend/run_tests.py`; +`test_fundraising_update_row.py`), `py_compile` clean, render-smoke green, fresh-DB migrate clean.
- **Next (next session = finish features):** 1) **Phase 7 design-conformance pass** against the full Claude Design export (`design/_imports/2026-06-19_zip-file/`) — buttons/colors/spacing/functionality across all surfaces, incl. finishing the P6 desktop-light rough edges (run `design-checker`). **Then (Grant, after feature-complete):** deploy P0P6 + P3b + view-reorder in one s9pk (**authorize + version-bump first**) and device-test light/dark on a real phone. Later backlog: W2 web Ask box + smoke; W3 bot grid-mutations; W1b nurture-gap.
- **Open / risks:** all mobile work + **P6 light theme + P3b edit** **built but never deployed or tested on a real phone/browser** (render-smoke + jsdom only — verify on a device, both themes); **P6 desktop-light has known rough edges** (bespoke `<style>` shades, legacy `.badge-*` family, dark shadows → Phase 7); W2 happy-path only; **Claude/Architect path unverified live on the box**; v2.0 reserve-asset spine **not canonical** (needs dual sign-off); doc drift — `crm-overview.md`/`EVALUATION.md` still call `lp_profiles` live.