## Summary
- Closes#59
- Mobile conversation drawer is now a fixed sheet with stronger stacking over dashboard content
- Adds explicit close (X) control and empty-state copy when there are no conversations
- Keeps existing close-on-select / New Chat behavior
## Test plan
- [ ] Mobile: open Conversations drawer — sheet + overlay appear above content
- [ ] Tap X or overlay — drawer closes
- [ ] Empty conversation list shows guidance text
- [ ] Select conversation / New Chat closes drawer and chat still works
- [ ] Desktop sidebar layout unchangedReviewed-on: #60
## Summary
- Follow-up for #54 (localStorage guard in #55 did not fix mobile white screen)
- Confirmed in production bundle: `ProtectedRoutes` rendered `<Outlet key={Date.now()} />`
- That remounts the entire authenticated tree on **every** parent re-render (account fetch, WebSocket connect/status, conversations, messages)
- On mobile this shows as: brief UI flash → solid white page (matches screen recording)
- Also memoize styled-components theme in `GlobalThemeWrapper` so `ParticleBackground` does not tear down/restart its canvas on unrelated re-renders
## Evidence
Production JS currently contains:
```js
e?(0,ma.jsx)(a.sv,{},Date.now()):(0,ma.jsx)(a.C5,{to:"/signin/",replace:!0})
```
i.e. authenticated → `<Outlet key={Date.now()} />`
## Test plan
- [ ] Mobile Vivaldi/Chrome: sign in on hesychia.ai → dashboard stays visible (header, conversations toggle, input)
- [ ] Pull-to-refresh while logged in → UI returns and stays
- [ ] Desktop login still works; switching conversations / WS reconnect does not blank the page
- [ ] Open mobile menu / Conversations sidebar still worksReviewed-on: #56
## Summary
- Closes#54
- Add `getLocalStorageSafe()` helper in `tokenStorage.js` to handle browsers that expose `localStorage` but throw `SecurityError` on access
- Wrap all JWT token get/set/remove calls in try/catch so auth bootstrap no longer crashes React on mobile
## Problem
On mobile browsers, hesychia.ai flashes the UI briefly after login then goes white. Auth startup reads tokens from `localStorage` without guarding against storage access errors (common in private mode or blocked storage).
## Test plan
- [ ] Open hesychia.ai on mobile Safari/Chrome (normal mode) and sign in — dashboard should render
- [ ] Repeat in private/incognito mode — app should not white-screen (may require re-login each session if storage blocked)
- [ ] Verify existing web login/logout still works on desktop
- [ ] Run `npm test -- tokenStorage` in `llm-fe`Reviewed-on: #55
## Summary
- Closes#51
- Trim prompt Yup validation; refuse empty/whitespace in submit handlers and `sendMessage`
- Add WebSocketContext test for empty send rejection
## Test plan
- [x] `WebSocketContext.test.js` empty/whitespace send cases
- [ ] Manually confirm whitespace-only input cannot submit on desktop + mobile viewport
- [ ] Confirm normal messages still send
Related BE: https://git.aimloperations.com/ai_ml_operations/chat_backend/issues/31Reviewed-on: #52
## Summary
Closes#49.
- Show **Hesychia.ai** wordmark with the brand mark on the sign-in page
- Regenerate brand PNGs with true transparent corners (source soft-glow RGBA had no fully clear pixels)
- Update SignIn test for the new heading
## Test plan
- [ ] Open `/signin/` — mark + **Hesychia.ai** above Sign In
- [ ] Confirm no black square around mark on dark particle background
- [ ] `npm test -- --testPathPattern=SignIn.test --watchAll=false`Reviewed-on: #50
## Summary
- Fixes wrong relative imports in `AnalyticsSession.tsx` (`../` → `../../`) so `npm run build:beta` can resolve contexts/utils.
- Unblocks beta deploy; empty docroot was causing `https://beta.hesychia.ai/` nginx 403.
Fixes#47
## Test plan
- [ ] CI unit tests pass
- [ ] Beta deploy workflow succeeds after merge
- [ ] `https://beta.hesychia.ai/` returns 200 with the SPAReviewed-on: #48
## Summary
Closes#30.
- Replace old infinity brand mark with the cyan Greek-key (meander) Hesychia emblem
- Update web favicons / PWA icons (`public/`), Capacitor source art (`assets/`), and regenerate Android launcher + splash densities
- Wire mark into Header / Header2, Sign In / Sign Up, and Sidenav branding
## Test plan
- [ ] Favicon + apple-touch icon show new mark in browser tab / home screen
- [ ] Header and auth screens show mark + Hesychia wordmark
- [ ] `npm run assets:generate` already run; Android launcher / splash use new art
- [ ] Old infinity artwork no longer present in `assets/` / `public/` brand slotsReviewed-on: #46
Version consent in localStorage (v1 + legacy migrate), point banner at Terms analytics section, and document the always-on vs optional policy. Banner/provider shipped in #42.
## Summary
Closes#39.
- Shared `TermsOfServiceDocument` used by public + acknowledge flows
- `/terms_of_service/` reachable without auth; `/legal/terms-of-service` alias
- Expanded clauses including **Analytics and Cookies** (always-on page views vs consent-gated events)
- Acknowledge still posts `/user/acknowledge_tos/` when signed in
- Links from Sign In, Sign Up, and Footer
## Test plan
- [ ] Visit `/terms_of_service/` logged out — document renders, no Acknowledge
- [ ] Visit `/legal/terms-of-service` — same public content
- [ ] Sign in as user needing ToS — Acknowledge posts and navigates home
- [ ] Confirm Sign In / Sign Up / Footer / consent banner link to ToS
- [ ] `npm run test:ci -- --testPathPattern='TermsOfService|Footer'`Reviewed-on: #43
## Summary
- Frontend half of ai_ml_operations/chat_backend#24
- SSO buttons on Sign In / Sign Up (shown when `/public/settings/` reports provider configured)
- New `/auth/callback/` route stores JWTs, loads account, sends new SSO users through Stripe Checkout
- Error states for denied consent, unverified/missing email, link conflict, etc.
- README note on Capacitor / native redirect URI limits
## Pair with
- Backend PR: `chat_backend` branch `feature/sso-oauth-24`
## Test plan
- [ ] `npm test -- --watchAll=false --testPathPattern="SignIn.test|SignUp.test|AuthCallback.test"`
- [ ] With backend OAuth configured, Continue with Google/Microsoft from Sign In works
- [ ] First-time SSO register lands in Stripe Checkout when `needs_checkout=1`
- [ ] Existing linked user signs in and reaches app / TOS
- [ ] Error query (`email_unverified`, `access_denied`) shows message + back linkReviewed-on: #41
## Summary
- Pair with chat_backend password reset (#1)
- Fix forgot-password request: execute invisible reCAPTCHA, POST `user/reset_password/` with await, correct `/signin/` back link
- Restyle confirmation + set-password pages to match Sign In glass UI
- Set-password: validate link via GET, await POST, enforce ≥8 chars + number + special char
- Sign In “Forgot Password?” navigates to `/password_reset/`
## Test plan
- [ ] `npm test -- --watchAll=false --testPathPattern=PasswordReset.test`
- [ ] From Sign In → Forgot Password → submit email → confirmation page
- [ ] Open email link `/set_password/?slug=…` → set password → redirected to Sign In
- [ ] Invalid/expired slug redirects to Sign In
- [ ] End-to-end with backend `feature/password-reset-1`Reviewed-on: #40
## Summary
- Closes#31
- Public `/signup/` page (Sign In–matched UI): register → JWT → `POST /api/finance/checkout/` → redirect to Stripe hosted Checkout
- `/billing/success` and `/billing/cancel` return URLs
- Sign In ↔ Sign Up links; Sign Up link only when backend `enable_account_registration` is true
Depends on backend PR: registration flag + public settings + gated `/user/create/`
## Test plan
- [ ] `cd llm-fe && CI=true npm run test:ci -- --testPathPattern='SignIn|SignUp'`
- [ ] With registration disabled: `/signup/` shows unavailable; Sign In has no Sign up link
- [ ] With registration enabled: form validates, happy path redirects to Checkout URL
- [ ] Cancel return explains retry; success prompts continue / sign-inReviewed-on: #34
## Summary
- Closes#29
- Rename user-facing product from AIML Operations chat to **Hesychia** (headers, sidenav brand, footer, ToS welcome, sample page)
- Update page metadata (`title`, description, Open Graph, `manifest.json` app name)
- Point README / env comments at **hesychia.ai**; keep live API hosts and deploy webroots until infra cutover
## Android display name
Capacitor tree is not on `master` yet ([#28](#28)). Display name / `appName` → **Hesychia** pushed on `ship-android-capacitor-20` so it lands with that wrap. Package id stays `com.aimloperations.chat` (Play identity; rename later if desired).
## Out of scope (per issue)
- New logo artwork (#30)
- Backend system-prompt identity (`chat_backend` #20)
## Test plan
- [ ] Web UI shows **Hesychia** in header / sidenav / footer (no old product name)
- [ ] Browser tab title and PWA manifest name are Hesychia
- [ ] Footer still credits AI ML Operations, LLC as developer
- [ ] `npm run test:ci` (Footer test updated)
- [ ] After #28 merges: Android launcher / activity title shows HesychiaReviewed-on: #32
## Summary
- Closes#24
- Introduce `AppRouter` + `getAppRouter()` so Capacitor uses `HashRouter` (filesystem bundle, no nginx rewrite) while web keeps `BrowserRouter`
- Isolate platform checks in `nativePlatform` (no `Capacitor.isNativePlatform()` sprinkled through the route tree)
- Auth forced redirects (`axiosApi` → `/signin/`) use hash-safe `redirectToAppPath` on native
- Leave CRA `homepage` / absolute `PUBLIC_URL` asset paths unchanged so `/var/www/{env}.chat.aimloperations/html` deploy stays intact; HashRouter keeps document URL at the bundle root so assets resolve
## Test plan
- [x] Unit tests: `AppRouter` + `nativePlatform` (`npm run test:ci`)
- [ ] Capacitor build: open nested route (`/#/account/`), force WebView reload — no blank/404
- [ ] Browser back/forward + Android hardware back with HashRouter
- [ ] Web deploy smoke: deep link reload at `/account/` still works via nginx rewrite
- [ ] Confirm login redirects after 401 still land on sign-in (web path + native hash)Reviewed-on: #27
## Summary
- Closes#23
- Add WebSocket reconnect with exponential backoff + jitter (capped attempts), heartbeat keepalives, and reconnect on browser visibility/`online` plus Capacitor `App`/`Network` plugins when present
- Refresh JWT before reconnect (coordinates with #22) so conversation refetch after a drop still works
- Mid-stream drop recovery: mark interrupted + refetch conversation; surface connecting/reconnecting/disconnected state and disable send while down
## Test plan
- [x] Unit tests: `websocketReconnect` helpers + `WebSocketProvider` reconnect/resume/send (`npm run test:ci`)
- [ ] Background app ~30s and ~10min, then resume — chat reconnects without reload
- [ ] Airplane mode on/off and cellular↔WiFi — reconnect indicator then restored send
- [ ] Drop mid-stream response — interrupted banner + conversation refetch
- [ ] Confirm nginx/`/ws/` proxy idle timeout stays above heartbeat (25s) or allows ping payloadsReviewed-on: #25
## Summary
- Closes#18
- Replace stale `favicon.jpg` / old ICO with multi-size `favicon.ico`, PNG variants (`16x16`, `32x32`), `logo192.png` / `logo512.png`, and `apple-touch-icon.png` generated from the new brand mark
- Point `index.html` and `manifest.json` at the new assets; drop the CRA placeholder app name in the manifest
## Test plan
- [ ] Hard-refresh the app (or clear site data) and confirm the new logo appears in the browser tab
- [ ] Check DevTools Network for `favicon.ico` / PNG icons — no 404s and no requests for `favicon.jpg`
- [ ] On mobile Safari / iOS home-screen add, confirm apple-touch-icon looks correct
- [ ] Confirm PWA/manifest icons resolve to the new 192/512 assetsReviewed-on: #19
## Summary
- Closes#16
- Clear unused imports/bindings, empty `{}` prop patterns, `==` vs `===`, and hook dependency gaps across App, contexts, and pages
- Replace invalid `calc(3rem + unset)` in `index.css` so Css Minimizer no longer fails
## Test plan
- [x] `npm run build` in `llm-fe` succeeds (no ESLint / postcss-calc errors)
- [ ] Spot-check SignIn, dashboard, account, feedback, document storage pages still render
- [ ] Confirm WebSocket/message flows still work after MessageContext dep cleanupReviewed-on: #17
## Summary
Implements [chat_web_app#13](#13) Part A: wire the CRA frontend into the same **node-static** deploy path as `dta_webapp` (no Docker for serving).
- `build:prod` / `build:beta` write into `/var/www/{env}.chat.aimloperations/html`
- API/WS URLs come from `REACT_APP_*` (`.env.production` / `.env.beta`) instead of hardcoded localhost
- Gitea unit tests + auto deploy-prod on `master`; deploy-beta via `workflow_dispatch`
- README documents local dev, env files, and webroot layout
Branch includes the #11 unit-test commit (`test:ci`) so CI has a green suite.
Companion server-infra branch (Part B): `issue-13-register-chat-web-app` on `ai_ml_operations/server-infra`.
## Test plan
- [ ] `cd llm-fe && npm run test:ci`
- [ ] Merge + ensure Unit Tests workflow passes on PR
- [ ] Merge server-infra `issue-13-register-chat-web-app`, then sync/pull on runner
- [ ] Redeploy web-static (or full host provision) so nginx listens on **8082**
- [ ] Manual: `server-infra/scripts/deploy.sh --app chat_web_app --env prod --ref <sha>`
- [ ] NPM: point `chat.aimloperations.com` at `adama:8082` + `roslin:8082` + `ai-server-4080:8082`
- [ ] Confirm built assets hit `https://chatbackend.aimloperations.com`Reviewed-on: #15