Mobile foundation (Phase 1) + harden opportunity stage validation
Phase 1 mobile foundation (additive, no desktop change): :root mobile vars, a 4-tab bottom nav bar + mobile account/logout popover wired into App, a bottom-sheet CSS primitive, and .mobile-only/.desktop-only utilities -- all display:none >=768px. The <BottomSheet> React component + useIsMobile() + the per-surface 15px type bump are deferred to Phase 2 (first use); light theme to Phase 6. Review hardening (fresh-eyes pass on the Phase 0+1 diff): validate stage in handle_create_opportunity + handle_update_opportunity against PIPELINE_STAGES -- the narrower 4-stage enum makes a stale-client write of a legacy value invisible to the report ORDER BY CASEs and unsettable from the UI. Use the canonical pipelineStageLabel in the opportunity detail select; document the intentional graveyard omission in the existing_investor / staleness helpers. Tests: stage-validation regression in test_grid_pipeline_link.py + empty source_row_id guard in test_pipeline_stages_v2.py; 36/36 green, render-smoke green.
This commit is contained in:
@@ -149,6 +149,16 @@ def main():
|
||||
f"funnel fields preserved, not reseeded (got stage={opp2.get('stage')}, amt={opp2.get('expected_amount')})")
|
||||
check(_opp_count_live(fr_id) == 1, "still exactly one live opp (no duplicate)")
|
||||
|
||||
# ── stage validation: legacy/invalid values rejected (4-stage enum guard) ──
|
||||
# The stage check precedes the contact lookup in handle_create_opportunity, so a fake
|
||||
# contact_id still surfaces the stage error first.
|
||||
print("\n[validation: legacy stage values rejected by stage + create endpoints]")
|
||||
st, _ = _req(port, "PATCH", f"/api/opportunities/{opp_id}/stage", token, {"stage": "outreach"})
|
||||
check(st >= 400, f"PATCH legacy stage 'outreach' rejected (got {st})")
|
||||
st, _ = _req(port, "POST", "/api/opportunities", token,
|
||||
{"name": "X", "contact_id": "x", "stage": "due_diligence"})
|
||||
check(st >= 400, f"POST opportunity with legacy stage 'due_diligence' rejected (got {st})")
|
||||
|
||||
# ── read-injection: GET state shows pipeline flag + stage, derived live ──
|
||||
print("\n[read-injection: GET /state exposes read-only pipeline + pipeline_stage]")
|
||||
st, d = _req(port, "GET", "/api/fundraising/state", token)
|
||||
|
||||
Reference in New Issue
Block a user