westfarn f22441371b
Deploy Beta / unit-tests (push) Successful in 13s
Unit Tests / test (push) Successful in 11s
Deploy Beta / deploy-beta (push) Successful in 3m5s
Surface render crashes and make chat layout viewport-safe (#61) (#63)
## Summary
- Closes #61
- Replaces #62, which was opened from a branch that still carried the drawer commit already merged in #60 and therefore conflicted. Same change, cherry-picked cleanly onto current `master`.
- **Makes the mobile white screen diagnosable.** The app had **no error boundary anywhere**, so any throw in the dashboard unmounted the whole tree and left an empty `#root`. That matches the reported behavior exactly: brief flash, white page, other routes fine on a fresh load, blank again when navigating back to `/`. `AppErrorBoundary` renders the error name, message, stack, URL and user agent with Reload / Copy details buttons, so the real exception can be read and copied **from the phone**. It is keyed on the route path, so navigating away clears a crash.
- **Responsive layout fixes** for the clipping visible in mobile emulation: `100vw` → `100%`, `100dvh` for the chat shell so mobile URL bars can't push the input row off screen, `min-width: 0` / `flex-shrink` on the input row so attach, model select, textarea and send compress instead of overflowing, `env(safe-area-inset-*)` padding on header, drawer and input area, and message bubbles that wrap long tokens (`overflow-wrap: anywhere`, `max-width` on `pre`/`img`/`table`).
- **Perf:** particle canvas pauses while the tab is hidden, honors `prefers-reduced-motion`, caps particle count for the O(n²) link pass, and re-seeds only on real size changes. Chat auto-scroll now fires on message change rather than on every render.

## Test plan
- [x] `npm run test:ci` — 25 suites / 112 tests pass (adds `AppErrorBoundary` coverage for the pass-through and crash paths)
- [x] `npm run build` — clean
- [ ] Phone browser: sign in and land on `/` — chat renders, or the error card shows the real exception (paste into #61)
- [ ] Phone browser: no horizontal scroll; attach, model select, textarea and send all visible
- [ ] Phone browser: input row still reachable with the keyboard open
- [ ] Long code block / unbroken string stays inside the bubble
- [ ] Desktop: sidebar, chat and input unchanged; background animation still runs
- [ ] Background the tab and return — animation resumes

## Notes
The crash is not reproducible in desktop mobile emulation, so this ships the boundary as the diagnostic path instead of guessing at the throwing call site. Once the error card appears on the phone, the stack should point straight at the cause. Worth landing before the Capacitor Android wrap (#20), since the same failure mode is much harder to inspect inside a WebView.Reviewed-on: #63
2026-07-29 05:43:16 -07:00

Hesychia (chat_web_app)

Frontend for Hesychia — a React + TypeScript single-page app for a calm AI assistant experience (quiet, rest, stillness).

Public product domain: hesychia.ai (replacing chat.aimloperations.com).

Companion backend: chat_backend

Production serve is node-static (not Docker). Hosts build with npm into a document root; the shared web-static nginx container from server-infra serves those assets. Same pattern as dta_webapp.

Android ships the same CRA build/ via Capacitor (display name Hesychia; see llm-fe/ANDROID.md).

Domain migration

Role Current (still live) Target
Web UI chat.aimloperations.com hesychia.ai
REST / WS API chatbackend.aimloperations.com TBD under hesychia.ai (infra cutover)
Deploy webroot /var/www/{env}.chat.aimloperations/html Keep until server-infra renames

Committed .env.production / .env.beta still point at the current *.aimloperations.com API hosts so builds keep working. Flip those URLs (and deploy webroots / NPM) when DNS and server-infra cut over to hesychia.ai.

Stack

  • React 18, TypeScript, Create React App (react-scripts 5)
  • Material UI (MUI) 5 + Emotion for components and theming
  • React Router 6 for routing (BrowserRouter on web; HashRouter under Capacitor)
  • Axios for REST against chat_backend (JWT Authorization header)
  • WebSocket for live chat (REACT_APP_BACKEND_WS_API_BASE_URL)
  • Capacitor 7 for Android (and later iOS) WebView shells
  • Jest + React Testing Library for unit tests

Repository layout

chat_web_app/
├── README.md
├── .gitea/workflows/          # Unit tests + deploy-prod / deploy-beta
└── llm-fe/                    # CRA + Capacitor npm root
    ├── package.json
    ├── capacitor.config.ts
    ├── ANDROID.md             # Android build, signing, Play Store notes
    ├── android/               # Native Android project (committed)
    ├── assets/                # Source icon/splash for @capacitor/assets
    ├── .env.development       # Local dev API/WS URLs
    ├── .env.beta
    ├── .env.production
    ├── .env.mobile            # Capacitor shell API/WS URLs
    ├── public/
    └── src/
        ├── axiosApi.js        # Axios instances (JWT Authorization)
        ├── App.tsx
        ├── index.tsx
        └── llm-fe/
            ├── pages/         # Route-level views
            ├── components/    # Shared & feature UI
            ├── contexts/      # Auth, account, WebSocket, theme
            ├── platform/      # Capacitor routing / native chrome
            └── ui-kit/        # MD* design-system wrappers

Prerequisites

  • Node.js 20 (matches CI)
  • npm (lockfile: llm-fe/package-lock.json)
  • Running chat_backend locally for full chat/auth (default in .env.development: port 8001)
  • For Android: JDK 17+, Android Studio / SDK (see llm-fe/ANDROID.md)

Local setup

1. Clone and install

git clone ssh://git@git.aimloperations.com:30009/ai_ml_operations/chat_web_app.git
cd chat_web_app/llm-fe
npm ci

2. Configure environment

CRA loads mode-specific env files. Variables must be prefixed with REACT_APP_ to be visible in the browser bundle.

File Used when Backend (current)
.env.development npm start http://127.0.0.1:8001
.env.production npm run build / build:prod https://chatbackend.aimloperations.com + prod Tianji ID
.env.beta npm run build:beta https://beta.chatbackend.aimloperations.com + beta Tianji ID
.env.mobile npm run build:mobile prod by default (override for beta shell)

Each committed env sets REACT_APP_DEPLOY_ENV (production / beta) and REACT_APP_TIANJI_WEBSITE_ID.

Required keys (already set in the committed env files):

REACT_APP_BACKEND_REST_API_BASE_URL=http://127.0.0.1:8001/api/
REACT_APP_BACKEND_WS_API_BASE_URL=ws://127.0.0.1:8001/ws/chat_again/

These are baked into the JS at build time. There is no host secret file for this frontend (unlike Django apps under ~/Documents/secrets/). Change the committed .env.* files if API domains change, then redeploy.

SSO (Google / Microsoft)

OAuth client IDs/secrets live in chat_backend env (GOOGLE_OAUTH_*, MICROSOFT_OAUTH_*). The SPA only redirects to /api/auth/oauth/<provider>/start/ and lands on /auth/callback/ with JWTs. SSO buttons appear when /public/settings/ reports the provider as configured.

Capacitor / mobile: IdP redirect URIs must match the backend callback URL (not the WebView origin). Custom URL schemes / in-app browser for native OAuth are out of scope for the initial web SSO; use the web app for Google/Microsoft sign-in until native redirect handling is added.

Optional local overrides (gitignored): .env.local, .env.development.local, .env.production.local, .env.mobile.local.

3. Start the backend

From the chat_backend repo (adjust to however that service is run locally):

# example — follow chat_backend README for the current command
cd chat_backend
# run Django on the port matching .env.development (8001)

4. Start the dev server

cd llm-fe
npm start

Open http://localhost:3000.

5. Android (Capacitor)

cd llm-fe
npm run build:mobile
npm run android:open

Full signing / Play Store notes: llm-fe/ANDROID.md.

npm scripts

Command Purpose
npm start CRA dev server (port 3000)
npm run build Production build into llm-fe/build/
npm run build:prod Prod build + copy to /var/www/prod.chat.aimloperations/html
npm run build:beta Beta env build + copy to /var/www/beta.chat.aimloperations/html
npm run build:mobile Mobile env build + cap sync android
npm run android:open Open android/ in Android Studio
npm run android:sync cap sync android only
npm run assets:generate Regenerate Android icons/splash from assets/
npm test Jest watch mode
npm run test:ci Single CI run (CI=true, no watch)

build:prod / build:beta are intended for deploy hosts (or a machine that can write those /var/www/... paths). Paths must match server-infra app_catalog.chat_web_app.webroot_pattern until renamed:

/var/www/{env}.chat.aimloperations/html

Tests

cd llm-fe
npm test          # watch
npm run test:ci   # one-shot (matches Gitea Unit Tests)

CI / deployment

Gitea Actions workflows in .gitea/workflows/ (same pattern as dta_webapp):

Workflow Trigger Behavior
unit-tests.yml push/PR to master npm ci + npm run test:ci in llm-fe/
deploy-beta.yml push to master (after unit tests in-workflow) server-infra/scripts/deploy.sh --app chat_web_app --env beta --ref <sha>
deploy-prod.yml manual workflow_dispatch only tests, then deploy --env prod

Push/merge to master auto-deploys beta only. Prod requires the Gitea Run workflow button on Deploy Prod.

Deploy flow (on each webserver listed in host_apps):

  1. Git checkout at the pinned ref under /opt/apps/src/chat_web_app_<env>
  2. npm ci in llm-fe/
  3. npm run build:<env> → writes the static tree under /var/www/...
  4. web-static nginx serves that root on the host port (prod 8082, beta 8083)

Manual deploy from the control node:

~/Documents/repos/server-infra/scripts/deploy.sh \
  --app chat_web_app --env prod --ref <sha>

NPM (not managed by Ansible) should balance public Hesychia domains to:

  • adama:8082 + roslin:8082 + ai-server-4080:8082 (prod)
  • beta hosts on 8083 / beta.chat.aimloperations.com (see server-infra)

See server-infra IMPLEMENTATION.md.

Tianji analytics

Build Env file Website ID
prod .env.production REACT_APP_TIANJI_WEBSITE_ID (prod site)
beta .env.beta distinct beta site ID (set after creating website in Tianji)
  • Script: https://tianji.aimloperations.com/tracker.js
  • Page views always load on prod/beta (no consent gate)
  • Custom events / identify require analytics consent (AnalyticsConsentBanner + trackEvent / identifyUser)
  • Consent choice stored in localStorage key hesychia_analytics_consent_v1 (granted / denied)
  • Banner links to /terms_of_service/#analytics for the always-on vs optional split
  • Event catalog: llm-fe/ANALYTICS.md
  • Backend: chat_backend — Django API + WebSocket
  • Infra: server-infraapp_catalog, host_apps, deploy.sh, web-static
  • Pattern reference: dta_webapp — node-static frontend deploy
S
Description
Front end react app for chat site
Readme
40 MiB
Languages
JavaScript 51.4%
TypeScript 47.9%
Swift 0.2%
HTML 0.2%
CSS 0.1%
Other 0.1%