{
  "summary": "Tested the new FloatingHelp launcher stack + AI chat panel and the upgraded /api/chat SSE endpoint. Backend: 8/9 pytest tests passed (empty/oversize 422, SSE stream + meta event, seeded jobs grounding, networking OEM grounding, quote->escalate=true, out-of-scope polite refusal with escalate=false, session history contextual follow-up). Frontend desktop (1440x900) + mobile (390x844): launcher renders on home, Expert->/contact, WhatsApp wa.me target=_blank, FAB opens dark floating panel anchored above FAB (desktop) and full-width bottom sheet (mobile, 0px horizontal overflow). Secondary buttons fade out while panel is open and return on minimise. Markdown-bold renders as <strong>. Escape minimises after fully animating out. Streaming works: typing dots -> streamed reply -> follow-up suggestion chips appear and are clickable. Markers [[ESCALATE]] / [[SUGGEST:]] are stripped from UI. Quote request produced escalate CTA which opens meeting-modal-backdrop. Minimise preserves conversation; close+clear wipes it; sessionStorage survives reload. Contextual chips confirmed on /solutions/cybersecurity-resilience (DPDP/SOC/security). Widget correctly hidden on /admin. No console errors.",
  "backend_issues": {
    "critical": [],
    "minor": [
      {"endpoint": "/api/chat", "issue": "Rate-limit 429 not observed after a 25-request burst from preview URL — rateLimited() keys on req.headers['x-forwarded-for'] which comes as a comma-joined chain from the CDN and varies between edges, so the bucket may not accumulate per real client. Consider taking the first IP from the X-Forwarded-For list."}
    ]
  },
  "frontend_issues": {
    "ui_bugs": [],
    "integration_issues": [],
    "design_issues": []
  },
  "test_report_links": [
    "/app/backend/tests/test_chat_sse.py",
    "/app/test_reports/pytest/chat_sse.xml"
  ],
  "action_items": [
    "(Optional/minor) Harden rate limiter: parse first IP from X-Forwarded-For so chat rate-limit works behind the CDN."
  ],
  "critical_code_review_comments": [
    "server.js clientIp() returns the raw X-Forwarded-For header string — behind CDNs this is a comma-separated list, which weakens rate limiting. Suggest splitting on ',' and trimming the first entry.",
    "FloatingHelp.tsx body-scroll-lock effect returns a cleanup only when the condition is true at mount — if viewport crosses the breakpoint while the panel is open the cleanup is skipped. Not a crash, just a small edge case.",
    "chatPrompt.js loads canonical facts synchronously at require() time; a missing file silently falls back to the base prompt (acceptable, flagged for awareness)."
  ],
  "updated_files": [
    "/app/backend/tests/test_chat_sse.py (new)"
  ],
  "success_rate": {"backend": "89%", "frontend": "100%"},
  "test_credentials": "n/a — chat endpoint is public; admin was only used to confirm widget is NOT rendered on /admin.",
  "seed_data_creation": "none",
  "retest_needed": false,
  "should_main_agent_self_test": true,
  "context_for_next_testing_agent": "FloatingHelp widget + /api/chat SSE verified end-to-end on preview URL. Session-chat state lives in sessionStorage key 'aviokan-chat-v1'. Rate-limit test is flaky from the preview URL because of CDN X-Forwarded-For — don't fail on it unless the clientIp() fix is applied. Mobile breakpoint for bottom sheet is <=639px. Chat replies via gpt-5.4 take 5-25s; always wait for data-testid=chat-suggestion before asserting stream completed.",
  "rca of the issue": "Rate-limit test failure RCA: rateLimited('chat:'+clientIp) uses req.headers['x-forwarded-for'] directly. The Emergent preview edge forwards 2-3 IPs comma-joined and the edge-chain changes between requests, so the per-client bucket key varies and the 20/window cap is never reached in a burst. Fix: const ip = (req.headers['x-forwarded-for']||'').split(',')[0].trim() || req.socket.remoteAddress || 'local'."
}
