{
  "steward": "react-api-client-steward",
  "project": "Azure-AI-RAG-CSharp-Semantic-Kernel-Functions",
  "runDate": "2026-03-21",
  "runId": "2026-03-21T00-00-00",
  "findings": [
    {
      "id": "RAPI-ERROR-001",
      "title": "No error handling on /chat POST — unhandled promise rejection",
      "severity": "critical",
      "category": "error-handling",
      "file": "src/web/src/SupportAgent/Agent.js",
      "line": 29,
      "description": "The handlePrompt function fires a POST /chat fetch with no .catch(). Any network failure, CORS rejection, or JSON parse error causes a silent unhandled promise rejection. The user's prompt is appended to the chat but no reply ever arrives, leaving the UI in a permanently broken state with no user feedback.",
      "recommendation": "Add .catch(err => setError(err.message)) to the fetch chain and check response.ok before calling response.json().",
      "status": "open"
    },
    {
      "id": "RAPI-ERROR-002",
      "title": "No error handling on /session GET — silent session failure",
      "severity": "critical",
      "category": "error-handling",
      "file": "src/web/src/SupportAgent/Agent.js",
      "line": 48,
      "description": "The handleSession function has no .catch(). If the session endpoint is unavailable on mount, the session state stays as '' and every subsequent /chat POST sends sessionid: '' with no indication to the user that initialization failed.",
      "recommendation": "Add .catch() to handleSession, surface an error state, and prevent the Submit button from being active until a valid session is established.",
      "status": "open"
    },
    {
      "id": "RAPI-ERROR-003",
      "title": "HTTP error status codes never checked (response.ok not tested)",
      "severity": "critical",
      "category": "error-handling",
      "file": "src/web/src/SupportAgent/Agent.js",
      "line": 33,
      "description": "Neither fetch call inspects response.ok before calling response.json(). A 400, 500, or 503 from the backend is treated as a successful response. If the error body is not valid JSON, this throws into the unhandled rejection. If it is valid JSON, res.resp is undefined and a blank chat bubble is rendered silently.",
      "recommendation": "Check response.ok after fetch and throw an error before calling response.json() if the status is not in the 2xx range.",
      "status": "open"
    },
    {
      "id": "RAPI-ABSTRACT-001",
      "title": "No centralized API client — raw fetch calls inlined in component",
      "severity": "notable",
      "category": "abstraction",
      "file": "src/web/src/SupportAgent/Agent.js",
      "line": 29,
      "description": "Both API calls are raw fetch invocations inlined inside component event handlers. URL construction, header setup, and error handling are duplicated per call. There is no single place to add cross-cutting concerns such as auth headers, request logging, or retry logic.",
      "recommendation": "Extract a minimal apiClient.js module wrapping fetch with base URL injection, default headers, response.ok checking, and JSON parsing.",
      "status": "open"
    },
    {
      "id": "RAPI-LOADING-001",
      "title": "No loading state — Submit button not disabled during in-flight request",
      "severity": "notable",
      "category": "loading-state",
      "file": "src/web/src/SupportAgent/Agent.js",
      "line": 80,
      "description": "No loading state variable exists. The Submit button is never disabled while a request is pending. Users receive no visual feedback that a request is in-flight, and can submit multiple times concurrently.",
      "recommendation": "Add a loading state variable, disable the Submit button while loading is true, and display a typing indicator in the chat area.",
      "status": "open"
    },
    {
      "id": "RAPI-RACE-001",
      "title": "Multiple concurrent submits possible — no request serialization",
      "severity": "notable",
      "category": "race-condition",
      "file": "src/web/src/SupportAgent/Agent.js",
      "line": 80,
      "description": "Because the Submit button is never disabled, rapid clicks fire multiple concurrent POST /chat requests. Responses append to the message list in network-arrival order, not prompt order, causing out-of-order AI responses in the chat UI.",
      "recommendation": "Disable the Submit button while a request is pending (resolved by RAPI-LOADING-001) and/or implement a request queue.",
      "status": "open"
    },
    {
      "id": "RAPI-RACE-002",
      "title": "No AbortController on session fetch — state update on unmounted component",
      "severity": "notable",
      "category": "race-condition",
      "file": "src/web/src/SupportAgent/Agent.js",
      "line": 56,
      "description": "The useEffect that calls handleSession() on mount has no cleanup function and no AbortController. If the component unmounts before the fetch resolves, setSession() is called on an unmounted component. In React 18 StrictMode, the effect runs twice in development, creating two racing session fetches.",
      "recommendation": "Use AbortController in the useEffect and return a cleanup function that calls controller.abort().",
      "status": "open"
    },
    {
      "id": "RAPI-TYPE-001",
      "title": "No TypeScript — API response shapes entirely untyped",
      "severity": "notable",
      "category": "typing",
      "file": "src/web/src/SupportAgent/Agent.js",
      "line": 37,
      "description": "The project uses plain JavaScript. res.session_id and res.resp are accessed without runtime validation or static types. If the backend changes response field names, the failure is silent — undefined values propagate into state and render as blank content.",
      "recommendation": "Migrate to TypeScript and define SessionResponse and ChatResponse interfaces, or add runtime shape guards at minimum.",
      "status": "open"
    },
    {
      "id": "RAPI-TYPE-002",
      "title": "No PropTypes on ChatLayout — missing shape validation for messages prop",
      "severity": "notable",
      "category": "typing",
      "file": "src/web/src/SupportAgent/ChatLayout.js",
      "line": 6,
      "description": "ChatLayout accesses messages.messages inside a map call with no PropTypes definition. If Agent.js ever passes incorrectly shaped props, the failure manifests as a runtime crash.",
      "recommendation": "Add PropTypes validation to ChatLayout or migrate to TypeScript with typed component props.",
      "status": "open"
    },
    {
      "id": "RAPI-ERROR-004",
      "title": "console.log leaks API response payloads in production",
      "severity": "minor",
      "category": "error-handling",
      "file": "src/web/src/SupportAgent/Agent.js",
      "line": 38,
      "description": "console.log(res) is called on every /chat response in production builds, exposing AI-generated content, session identifiers, and any server metadata to anyone with browser DevTools access.",
      "recommendation": "Remove console.log(res) or guard it behind a development-only check: if (process.env.NODE_ENV === 'development') console.log(res);",
      "status": "open"
    },
    {
      "id": "RAPI-RACE-003",
      "title": "Missing session in useEffect dependency array",
      "severity": "minor",
      "category": "race-condition",
      "file": "src/web/src/SupportAgent/Agent.js",
      "line": 56,
      "description": "The useEffect reads the session variable to guard the handleSession() call but does not declare session in its dependency array. ESLint react-hooks/exhaustive-deps would flag this, and it is a maintenance trap if the component lifecycle is refactored.",
      "recommendation": "Add session to the useEffect dependency array and handle re-fetch logic explicitly, or restructure so the guard condition is outside the effect.",
      "status": "open"
    },
    {
      "id": "RAPI-ABSTRACT-002",
      "title": "Base URL correctly externalized via REACT_APP_API_HOST environment variable",
      "severity": "info",
      "category": "abstraction",
      "file": "src/web/src/SupportAgent/Agent.js",
      "line": 29,
      "description": "Both API calls construct the endpoint URL using process.env.REACT_APP_API_HOST. No localhost or production URL is hardcoded in the source. The README documents the required .env setup.",
      "recommendation": "No action required.",
      "status": "open"
    },
    {
      "id": "RAPI-ABSTRACT-003",
      "title": ".env correctly excluded from version control via .gitignore",
      "severity": "info",
      "category": "abstraction",
      "file": "src/web/.gitignore",
      "line": 24,
      "description": "The .gitignore file excludes .env and all .env.*.local variants, preventing accidental commit of environment-specific configuration including the API host URL.",
      "recommendation": "No action required.",
      "status": "open"
    }
  ],
  "summary": {
    "critical": 3,
    "notable": 6,
    "minor": 2,
    "info": 2,
    "total": 13
  }

}
