[← Back to Reviews Index](../Stewards%20Reviews%20Index.md)

# React UX Review — Azure-AI-RAG-CSharp-Semantic-Kernel-Functions

| Field | Value |
|---|---|
| Project | Azure-AI-RAG-CSharp-Semantic-Kernel-Functions |
| Steward | React UX Steward (RUX) |
| Run Date | 2026-03-21 |
| Findings | 🔴 4 Critical · 🟡 7 Notable · 🟢 5 Minor · ℹ️ 2 Info |
| Total | 18 |

---

## 1. UX Overview

The React frontend is a single-page application bootstrapped with Create React App. It presents an "Azure Support Agent Demo" — a RAG-powered chat interface that allows users to type natural-language questions about Azure services and receive AI-generated answers sourced from a document index.

**Key user flows identified:**

1. **Session initialisation** — On mount, the app fetches a session ID from `/session`.
2. **Chat message submission** — User types a question, clicks Submit, message is sent to `/chat`, and the AI response is appended to the chat history.
3. **Message rendering** — Messages are displayed as floating chat bubbles in a scrollable container, with HTML content parsed via `html-react-parser`.

**Component inventory (React):**

| File | Purpose |
|---|---|
| `src/App.js` | Root router — single `/` route |
| `src/Main.js` | Page shell: header, body, service cards, disclaimer |
| `src/AzureFreeAccount.js` | Promotional banner (static) |
| `src/AzureServiceCards.js` | Three static info cards |
| `src/SupportAgent/Agent.js` | Core chat state and logic |
| `src/SupportAgent/ChatLayout.js` | Message bubble renderer |

The frontend is small (6 JS files) but carries significant UX debt across loading, error, accessibility, and chat-pattern categories.

---

## 2. Loading State Assessment

**Finding: No loading state during session initialisation.**
`handleSession()` is called on mount. If the `/session` endpoint is slow or unavailable, the user sees nothing different — no spinner, no disabled state, no message. The chat input is already fully interactive and the user can attempt to send a message before a session ID exists.

**Finding: No loading/pending state during AI response.**
When the user submits a prompt, `handlePrompt()` fires a `fetch()` to `/chat`. There is no loading indicator of any kind — no spinner, no "typing…" indicator, no disabled input. The user has no idea whether their message was received or whether the system is working. The Submit button remains active and clickable throughout.

**Finding: Chat input is not disabled while awaiting a response.**
While a fetch is in-flight, the user can click Submit again, launching a second concurrent fetch with the same (now-empty) `chatPrompt`. There is no `isLoading` state guarding the Submit button or the TextField.

---

## 3. Error Handling UX Assessment

**Finding: All API errors are silently discarded.**
`handlePrompt()` uses `.then()` chains without any `.catch()`. If the `/chat` fetch fails (network error, 500, CORS failure), the promise rejects silently. The user sees their message appear in the chat but never receives a reply, with no explanation. The console shows a `console.log(res)` on success but nothing on failure.

**Finding: Session initialisation failure is also silent.**
`handleSession()` has no `.catch()`. If `/session` returns an error or the network is down, `session` remains `''`. Subsequent chat submissions send `sessionid: ''` to the API, which may cause server-side failures — again with no user feedback.

**Finding: No retry mechanism.**
There is no retry button, no "Try again" affordance, and no indication that the system encountered a problem. Users who hit a transient error have no recourse other than reloading the page.

**Finding: `console.log(res)` left in production path.**
`Agent.js` line 38 logs the raw API response to the console on every successful chat message. This is a debug artifact that should not be present in production code and may leak information about response structure.

---

## 4. Empty State Assessment

**Finding: First-run experience is adequate (info-level positive).**
The chat is pre-seeded with a greeting message: "Hello, how can I assist you today?". This avoids a completely blank chat window on first load. The TextField also has a `defaultValue` pre-filled with an example question, which provides a clear call-to-action for new users.

**Finding: No empty state for the chat list when cleared.**
While the initial greeting handles the first-run case, there is no explicit empty-state guard. If the `message` array were ever emptied (e.g., a "clear chat" feature added later), the chat area would render an empty scroll container with no messaging.

---

## 5. Form Validation Feedback Assessment

**Finding: No form validation on the chat input.**
The Submit button is always enabled regardless of whether `chatPrompt` is empty. Clicking Submit with an empty input calls `handlePrompt('')`, which sends `{ input: '', sessionid: session }` to the API. The API presumably returns an unhelpful or error response, and — given there is no error handling — the user simply sees nothing happen.

**Finding: No input length constraint.**
There is no `maxLength` or character-count indicator on the TextField. Users could submit arbitrarily long strings, potentially causing API rejections that are again silently discarded.

**Finding: No indication that Message is a required field.**
The TextField label reads "Message" with helper text "Chat with AI Support Agent". There is no asterisk or required indicator, though functionally an empty submission is meaningless.

---

## 6. Accessibility Assessment

**Finding: Header image has no `alt` attribute.**
In `Main.js` line 30–33, the header image (`header_img.jpg`) is rendered with `<img src={...} height={'210px'} style={{ display: 'block' }} />` — no `alt` attribute at all. Screen readers will either skip it or announce the full file path/URL, which is meaningless to users.

**Finding: Submit button icon has no accessible label for icon-only context.**
The Send icon is rendered with `endIcon={<SendIcon />}` inside a Button that also contains the visible text "Submit", so the button label is readable. However, `SendIcon` carries no `aria-label` or `aria-hidden`. This is a minor concern given the visible text, but the icon should be marked `aria-hidden="true"` to avoid redundant announcement.

**Finding: Chat bubble direction is communicated only by visual float position.**
Messages from the user float right (blue), messages from the AI float left (green). There is no accessible distinction: no `role`, no `aria-label`, no `aria-live` region, and no author label attached to each bubble. Screen reader users cannot distinguish who sent each message or be notified of new messages.

**Finding: No `aria-live` region for incoming chat messages.**
New AI responses are appended to the message list via state update, but there is no `aria-live="polite"` region announcing the update. Screen reader users will not be alerted when a new message arrives.

**Finding: Chat area is not keyboard-navigable for reading messages.**
The `.AgentArea` div is a scrollable overflow container. Individual bubbles have no `tabindex`, role, or focusable structure. Keyboard users cannot navigate message history with the keyboard.

**Finding: Page `<title>` is "React App".**
`public/index.html` has `<title>React App</title>` — the Create React App default. This is meaningless for screen readers (which announce the title on page load) and for browser tabs/bookmarks.

**Finding: Buttons in AzureFreeAccount have no `href` or `onClick`.**
"Start free" and "Pay as you go" buttons in `AzureFreeAccount.js` have no `href`, no `onClick`, and no `aria-label`. They render as interactive buttons that do nothing when activated, which is confusing for all users and especially for keyboard/screen reader users who may focus them expecting navigation.

**Finding: `lang` attribute on `<html>` is present (positive, info-level).**
`public/index.html` includes `lang="en"` on the `<html>` element, which is correct for English-language accessibility.

---

## 7. Chat UX Patterns Assessment

**Finding: No auto-scroll to the latest message.**
The `.AgentArea` container uses `flex-direction: column-reverse` in CSS, which visually reverses the message order so newer messages appear at the bottom — but there is no programmatic scroll-to-bottom triggered on new messages. As messages accumulate, the user must manually scroll down to see the latest response. There is no `useRef` scroll management in `Agent.js` or `ChatLayout.js`.

**Finding: No typing/generating indicator.**
While the AI response is being fetched, there is no animated "..." indicator, no "Agent is thinking" message, and no skeleton placeholder. Users must wait in silence for the response to appear.

**Finding: No streaming of AI responses.**
The fetch call waits for the complete JSON response (`response.json()`) before rendering anything. The full response is displayed all at once. If the API supports streaming (SSE or chunked transfer), the UI does not take advantage of it. For long AI answers, this creates a poor experience.

**Finding: User can send duplicate/concurrent messages.**
As noted in section 2, the Submit button is never disabled. A user could click Submit repeatedly and fire multiple concurrent fetches. Responses would arrive out of order and append in unpredictable sequence.

**Finding: No message timestamps or status indicators.**
Messages have no timestamp, no "sent" / "delivered" indicator, and no author label. In a chat interface, temporal context helps users understand the conversation flow.

**Finding: Key prop uses mutable index variable (`i++`) in ChatLayout.**
`ChatLayout.js` line 9–10 maps messages using `key={i}` where `i` is mutated mid-expression (`key={i++}`). The Box inside also uses `key={i++}`, meaning both the outer div and the Box attempt to use the same (or consecutive) index as their key. This is a React anti-pattern: keys must be stable and unique per list; using an index that is simultaneously mutated for two elements in the same iteration is incorrect and will produce unstable re-renders.

---

## 8. Responsive Design Assessment

**Finding: Layout uses fixed pixel row heights.**
`Main.css` defines `grid-template-rows: 250px 455px 165px auto`. These fixed heights are not responsive. On mobile (< 768px), the header may be cramped, the chat area height is fixed at 455px and the chat body section may overflow or be truncated. The layout does not adapt to smaller viewports.

**Finding: Chat input layout is not mobile-friendly.**
In `Agent.js`, the TextField is set to `width: '80%'` and the Button uses hard-coded `margin-left: 3` (24px). On narrow screens (< 768px), this layout does not stack vertically — the input and button remain side-by-side and may be too small to interact with comfortably.

**Finding: Header uses fixed `paddingRight: 10` / `paddingLeft: 15` MUI spacing units.**
`Main.js` applies `sx={{ paddingRight: 10, paddingLeft: 15, paddingTop: 2 }}` on the header Stack. MUI spacing units are multiples of 8px, so these are 80px and 120px respectively. On mobile, this removes a significant amount of the usable screen width and may cause the header image to overflow.

**Finding: Header image has a fixed height of 210px.**
The `<img>` in `Main.js` has `height={'210px'}` with no `max-width` or responsive sizing. On small screens it may overflow or force horizontal scrolling.

**Finding: No `@media` queries or responsive breakpoint handling in custom CSS.**
The custom CSS files (`Main.css`, `Agent.css`, `ChatLayout.css`) contain no media queries. All responsive adaptation would need to come from MUI's `sx` prop and Grid system, but the Grid usage in `AzureServiceCards.js` uses `xs={12}` / `xs={10}` breakpoints correctly, which is a positive pattern, partially mitigating the issue.

---

## 9. Findings Table

| Severity | ID | Title | File |
|---|---|---|---|
| 🔴 Critical | RUX-LOAD-001 | No loading/pending state during AI response fetch | `src/web/src/SupportAgent/Agent.js` |
| 🔴 Critical | RUX-LOAD-002 | Chat input and Submit not disabled while response is in-flight | `src/web/src/SupportAgent/Agent.js` |
| 🔴 Critical | RUX-ERROR-001 | Silent failure on chat API error — user receives no feedback | `src/web/src/SupportAgent/Agent.js` |
| 🔴 Critical | RUX-A11Y-001 | Chat messages have no `aria-live` region — screen readers not notified | `src/web/src/SupportAgent/ChatLayout.js` |
| 🟡 Notable | RUX-LOAD-003 | No loading state during session initialisation | `src/web/src/SupportAgent/Agent.js` |
| 🟡 Notable | RUX-ERROR-002 | Silent failure on session fetch error | `src/web/src/SupportAgent/Agent.js` |
| 🟡 Notable | RUX-ERROR-003 | No retry mechanism for failed API calls | `src/web/src/SupportAgent/Agent.js` |
| 🟡 Notable | RUX-CHAT-001 | No typing/generating indicator while AI is responding | `src/web/src/SupportAgent/Agent.js` |
| 🟡 Notable | RUX-CHAT-002 | No auto-scroll to latest message on new response | `src/web/src/SupportAgent/Agent.js` |
| 🟡 Notable | RUX-FORM-001 | Submit allowed with empty input — no validation guard | `src/web/src/SupportAgent/Agent.js` |
| 🟡 Notable | RUX-A11Y-002 | Header image missing `alt` attribute | `src/web/src/Main.js` |
| 🟢 Minor | RUX-CHAT-003 | No message timestamps or sender labels on chat bubbles | `src/web/src/SupportAgent/ChatLayout.js` |
| 🟢 Minor | RUX-CHAT-004 | No streaming of AI responses (full response displayed at once) | `src/web/src/SupportAgent/Agent.js` |
| 🟢 Minor | RUX-A11Y-003 | Page `<title>` is "React App" — not meaningful | `src/web/public/index.html` |
| 🟢 Minor | RUX-A11Y-004 | Non-functional "Start free" and "Pay as you go" buttons | `src/web/src/AzureFreeAccount.js` |
| 🟢 Minor | RUX-RESP-001 | Fixed pixel row heights in grid layout break on mobile | `src/web/src/Main.css` |
| 🟢 Minor | RUX-CHAT-005 | Unstable and mutated `key` prop in message list map | `src/web/src/SupportAgent/ChatLayout.js` |
| ℹ️ Info | RUX-INFO-001 | `console.log` left in production chat response path | `src/web/src/SupportAgent/Agent.js` |
| ℹ️ Info | RUX-INFO-002 | First-run experience has a greeting and pre-filled example prompt | `src/web/src/SupportAgent/Agent.js` |

---

## 10. Recommended UX Improvements

| Finding | Recommended Action | Priority |
|---|---|---|
| RUX-LOAD-001 | Add `isLoading` state; show a CircularProgress or "Agent is typing…" bubble while fetch is in-flight | Critical |
| RUX-LOAD-002 | Disable the TextField and Submit button when `isLoading === true` | Critical |
| RUX-ERROR-001 | Add `.catch(err => ...)` to `handlePrompt()`; append a user-visible error bubble (e.g., "Sorry, something went wrong. Please try again.") | Critical |
| RUX-A11Y-001 | Wrap the message list in a `<div role="log" aria-live="polite" aria-label="Chat messages">` container | Critical |
| RUX-ERROR-002 | Add `.catch()` to `handleSession()`; display an alert or disabled-state banner if the session cannot be established | High |
| RUX-ERROR-003 | Add a "Retry" button that re-invokes `handlePrompt()` with the last failed prompt on network/API error | High |
| RUX-LOAD-003 | Show a brief "Connecting…" message or spinner in the chat area while the session initialises | High |
| RUX-CHAT-001 | Add a temporary "typing" bubble (e.g., animated dots) to the message list while awaiting the AI response | High |
| RUX-CHAT-002 | Add a `useRef` on the bottom of the chat list and call `scrollIntoView()` whenever `message` state changes | High |
| RUX-FORM-001 | Disable the Submit button when `chatPrompt.trim() === ''`; optionally show inline validation on blur | High |
| RUX-A11Y-002 | Add `alt="Azure Support Agent demo header illustration"` (or `alt=""` if purely decorative) to the header `<img>` | High |
| RUX-CHAT-003 | Display a timestamp and sender label ("You" / "Agent") on each chat bubble | Medium |
| RUX-CHAT-004 | Implement SSE or streaming fetch if the `/chat` API supports it; otherwise ensure the loading indicator covers the full wait time | Medium |
| RUX-A11Y-003 | Set `<title>Azure Support Agent Demo</title>` in `public/index.html` | Medium |
| RUX-A11Y-004 | Wire the "Start free" and "Pay as you go" buttons to appropriate URLs or remove them if they are placeholders | Medium |
| RUX-RESP-001 | Replace fixed `grid-template-rows` pixel heights with `minmax()` or `auto` rows; add media queries for mobile breakpoints | Medium |
| RUX-CHAT-005 | Replace mutable index key with a stable ID: assign a `uuid` or timestamp-based ID to each message object when it is added to state | Medium |
| RUX-INFO-001 | Remove `console.log(res)` from `handlePrompt()` before production deployment | Low |

---

## Footer

> This review is based on **static analysis** of source files only. No runtime, browser, or network behaviour was observed. Findings reflect the code as written on 2026-03-21.
>
> Generated by: **React UX Steward** (`reactux-steward`)
> Argentic root: `C:\ARview\Argentic1`
