## Summary - Closes [#103](#103). - Adds public SPA route `/account-deletion/` (no auth) with Hesychia / `ai.hesychia.chat` branding, in-app deletion steps, soft-delete data retention copy, and a contact path (30-day SLA) for users who cannot sign in. - Documents Play Console **Account deletion URL** in `ANDROID.md` and `android/README.md` (`https://hesychia.ai/account-deletion/`, legacy host, HashRouter form). ## Notes - Retention copy matches backend soft-delete (v1): no claim of full erasure / hard purge yet. - Play Console field still needs to be set out-of-band after deploy. ## Test plan - [ ] Open `/account-deletion/` signed out — page loads (no redirect to sign-in) - [ ] Confirm steps match Account → Delete my account + email confirm - [ ] Confirm retention sections are accurate vs soft-delete - [ ] `npm run test:ci -- --testPathPattern=AccountDeletionPage` - [ ] After beta deploy, set Play Console Account deletion URL to the public HTTPS URLReviewed-on: #110
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-scripts5) - Material UI (MUI) 5 + Emotion for components and theming
- React Router 6 for routing (
BrowserRouteron web;HashRouterunder Capacitor) - Axios for REST against
chat_backend(JWTAuthorizationheader) - 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):
- Git checkout at the pinned ref under
/opt/apps/src/chat_web_app_<env> npm ciinllm-fe/npm run build:<env>→ writes the static tree under/var/www/...- 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
localStoragekeyhesychia_analytics_consent_v1(granted/denied) - Banner links to
/terms_of_service/#analyticsfor the always-on vs optional split - Event catalog:
llm-fe/ANALYTICS.md
Related repos
- Backend: chat_backend — Django API + WebSocket
- Infra: server-infra —
app_catalog,host_apps,deploy.sh, web-static - Pattern reference: dta_webapp — node-static frontend deploy