Mobile Phase 8h: Grid detail stage/reminder cards + Open-in-Grid deep-link
Grid full-screen investor detail, conformed to the dc anatomy: - G4: pipeline stage as a single tappable .detail-tap-card (chip + Change/Add) - G5: dedicated Reminder card fed by the soonest active reminder; tri-state (loading → disabled "Checking…" so a pre-load tap can't POST a duplicate; none → "No reminder set"; object → edit). Edits PATCH in place, else POST. - G6 (notes timeline) was already in place. Open-in-Grid deep-link, now on all three mobile detail surfaces (Contacts, Pipeline, Reminders): a shared shell openInvestorInGrid(rowId) sets a one-shot gridUiAction object the mobile grid consumes on mount to open that investor's detail; the desktop grid drains the unrecognized object so it can't linger. Each surface gets its grid row id from a server-injected source_row_id: contacts via contact_grid_signals, opportunities via the durable fundraising_investor_id join, reminders via the investor_id join. All are read-only/GET-only or field-allowlist writes, so none need a strip point. Tests: source_row_id injection assertions for contacts, opportunities, and reminders; full suite 40/40. Client surfaces jsdom-verified.
This commit is contained in:
@@ -8,7 +8,10 @@ sourced from the fundraising grid (the canonical investor model), for the mobile
|
||||
- `pipeline_stage` -> that investor's live derived stage (drives the card's stage pill),
|
||||
or null when the investor isn't in the pipeline.
|
||||
- `priority` -> that investor's priority flag (drives the mobile Contacts Priority sort, 8d).
|
||||
A contact with no grid link (pure classic/legacy contact) gets committed 0 / stage null / priority false.
|
||||
- `source_row_id` -> that investor's grid row id (the "Open investor in Grid" deep-link target, 8h),
|
||||
present for ANY grid-linked contact (even a zero-commit prospect), null otherwise.
|
||||
A contact with no grid link (pure classic/legacy contact) gets committed 0 / stage null / priority false
|
||||
/ source_row_id null.
|
||||
Signals are derived fresh on read and never stored on the contact. Synthetic data only.
|
||||
|
||||
Run: cd backend && python3 test_contacts_grid_signals.py
|
||||
@@ -148,6 +151,15 @@ def main():
|
||||
check((vince or {}).get("priority") is False,
|
||||
f"Vince.priority is False (no grid link) (got {(vince or {}).get('priority')!r})")
|
||||
|
||||
# ── source_row_id signal: the "Open investor in Grid" deep-link target (8h) ──
|
||||
print("\n[source_row_id: Open-in-Grid deep-link target = the linked investor's grid row id]")
|
||||
check((jane or {}).get("source_row_id") == "rowAcme",
|
||||
f"Jane.source_row_id == 'rowAcme' (got {(jane or {}).get('source_row_id')!r})")
|
||||
check((pat or {}).get("source_row_id") == "rowBeta",
|
||||
f"Pat.source_row_id == 'rowBeta' (present for a zero-commit linked contact) (got {(pat or {}).get('source_row_id')!r})")
|
||||
check((vince or {}).get("source_row_id") is None,
|
||||
f"Vince.source_row_id is None (no grid link) (got {(vince or {}).get('source_row_id')!r})")
|
||||
|
||||
# ── the get-by-id endpoint carries the same signals (mobile detail sheet, 8b) ──
|
||||
print("\n[get-by-id: /api/contacts/{id} also injects committed + pipeline_stage]")
|
||||
st, d = _req(port, "GET", f"/api/contacts/{jane['id']}", token)
|
||||
@@ -193,6 +205,9 @@ def main():
|
||||
# The winning (higher-committed) link is Mega Fund LP, which is not flagged → priority follows it.
|
||||
check(jd.get("priority") is False,
|
||||
f"multi-linked contact's priority follows the higher-committed investor (Mega, unflagged) (got {jd.get('priority')!r})")
|
||||
# The deep-link target also follows the winning link → Mega's grid row (rowMega), not rowAcme.
|
||||
check(jd.get("source_row_id") == "rowMega",
|
||||
f"multi-linked contact's source_row_id follows the higher-committed investor (rowMega) (got {jd.get('source_row_id')!r})")
|
||||
finally:
|
||||
httpd.shutdown()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user