## Summary
- Closes the public-page gaps from #124: each route gets its own title, description, canonical, heading, links, and JSON-LD, and unknown URLs have a real 404 document.
- Adds HeyCatch and the Google tag (`G-V12DLRLPWR`) only on the production site and the iOS and Android apps. Tianji still loads on beta.
- Needs the server-infra change that stops serving the app shell for missing paths.
## Test plan
- [ ] Build production and confirm `/` and `/pricing/` HTML include a unique title, description, canonical, one h1, social tags, related links, and Organization JSON-LD.
- [ ] Confirm a nonsense path is served as `404.html` once the nginx change is deployed.
- [ ] Confirm beta and local web do not load HeyCatch or gtag, and that a production or native build does.
- [ ] Open the live site after deploy and click around, then mark HeyCatch installed in its dashboard.
Reviewed-on: #125
## Summary
- The Capacitor shell buys through StoreKit (iOS 15) and Play Billing instead of RevenueCat.
- After the store sheet succeeds, the app POSTs the signed transaction or Play purchase token to `/monetization/store/confirm/`.
- Restore reads current store entitlements and confirms each one. Web checkout stays on Stripe.
- The store account token from `GET /monetization/subscription/` is sent as `appAccountToken` / `obfuscatedAccountId`.
Depends on the `chat_backend` PR for issue #78 (`feat/78-native-store-purchases`).
## Test plan
- [x] `storeBilling`, `revenueCat`, `finance`, and `BillingSection` unit tests
- [ ] iOS sandbox purchase and restore against a backend with `APPLE_IAP_BUNDLE_ID` set
- [ ] Play license-test purchase and restore against a backend with `GOOGLE_PLAY_SERVICE_ACCOUNT_JSON` set
- [ ] Web billing still opens Stripe Checkout
Reviewed-on: #122
## Summary
- Closes [#97](#97) — message action row: copy (raw markdown + code-block copy), thumbs up/down with optimistic updates + down-reason popover, and export menu (PDF / DOCX / CSV / XLSX / TXT) for a single message plus conversation-level export in the chat toolbar.
- Closes [#98](#98) — versioned WS `citations` frame parsing, Sources list under assistant bubbles, clickable `[n]` markers, and history hydrate from `Prompt.citations`.
- Backend companion for ratings: [chat_backend#67](ai_ml_operations/chat_backend#67). Thumbs UI posts to `prompt_feedback` once that lands; votes rehydrate from `conversation_details.feedback`.
- Export libs (`pdfmake`, `docx`, `papaparse`, `xlsx`) are dynamically imported so they stay out of the main path until used.
## Test plan
- [ ] Stream a grounded answer → Sources appear after stream; inline `[n]` highlights the matching source; reload keeps Sources.
- [ ] Non-grounded turn → no Sources header.
- [ ] Copy message + code block; confirm checkmark ~2s; failure path shows toast.
- [ ] Thumbs up/down optimistic UI; clear by re-click; down opens reason popover without blocking the vote (needs chat_backend#67).
- [ ] Export one message and whole conversation in all five formats; check searchable PDF text and DOCX structure.
- [ ] Action row: hover reveal on desktop, always visible on last/touch; hidden while streaming.
- [ ] `npm test -- --testPathPattern='wsFrames|clipboard|exportChat|promptFeedback|ConversationDetailCard'`Reviewed-on: #101
## 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