# FREEEXILE: COMPREHENSIVE SURVEY REPORT
## Chat UI Architecture, Hardcoded String Audit & Dynamic i18n Reactive Re-rendering

- **Date**: 2026-10-02
- **Author**: `explorer_survey_chat_1`
- **Scope**: Chat UI (`client/webapp/js/ui/chat_ui.js`), HTML templates (`index.html`, `template_catalog_system.js`), i18n Subsystem (`client/webapp/js/data/i18n.js`, `i18n_catalog.js`), Client/Server Protocols.
- **Reference**: `ORIGINAL_REQUEST.md` (2026-10-02T01:42:06Z), `PROJECT.md` (orchestrator_13).

---

## 1. Executive Summary

A comprehensive forensic investigation of the FreeExile WebApp Chat UI (`client/webapp/js/ui/chat_ui.js`) and its integration with the game interface was conducted.

### Core Discoveries:
1. **Total Isolation from the Core i18n Subsystem**:
   `chat_ui.js` maintains a localized constant `CHANNEL_I18N` solely for the 8 channel names across 9 languages, completely independent of `FreeExileI18nEngine` and `I18N_CATALOG`. It does not import or subscribe to `window.FreeExileI18n`.
2. **Broken Reactive Language Switching**:
   `chat_ui.js` attaches an event listener only to `document.getElementById('lang-select')?.addEventListener('change', ...)`. Programmatic language changes (via `FreeExileI18n.setLocale(...)` or settings modals) do NOT fire the DOM `'change'` event, leaving Chat UI completely unresponsive.
3. **Partial & Cosmetic Re-render on Manual Selection**:
   Even when the user manually changes `#lang-select`, `chat_ui.updateLanguage()` only mutates `.chat-channel-tab` button labels and `#chat-ticker-channel`. It leaves `CHANNELS[id].label` untouched in Vietnamese, meaning:
   - All rendered and newly arriving chat messages in the log continue to display Vietnamese channel badges (e.g., `[Thế Giới]`, `[Bang Hội]`).
   - Timestamps are hardcoded to `'vi-VN'`.
   - Active input placeholders remain in Vietnamese (`Nhập tin nhắn [Thế Giới]...`).
   - Cooldown and permission restriction notices remain in Vietnamese.
   - All rich item tooltip modal strings remain in Vietnamese.
4. **Pervasive Hardcoded Vietnamese Microcopy**:
   There are **42 distinct hardcoded strings** across `chat_ui.js`, `index.html`, and `template_catalog_system.js` covering channel tabs, channel permissions, system notices, cooldown badges, item rarity names, element names, crafter fallbacks, and HMAC cryptographic badges.
5. **Architectural Code Size Constraint**:
   `chat_ui.js` currently stands at **342 lines of code**, hovering just 8 lines under the **350-line Soft Cap** mandated by `GEMINI.md` and enforced by `check_code_and_doc_hygiene.py`. All proposed enhancements must decouple the dictionary into a catalog or utilize the centralized `I18N_CATALOG` to avoid violating code hygiene rules.

---

## 2. Codebase Architecture & File Mapping

```
client/webapp/
├── index.html                                 # Contains static #chat-dock markup & #modal-item-link-tooltip
├── js/
│   ├── main.js                                # Imports & calls initChatUI(), wires Enter/Escape
│   ├── data/
│   │   ├── i18n.js                            # FreeExileI18nEngine singleton, dispatches 'freeexile:localeChanged'
│   │   └── i18n_catalog.js                    # 9-language master dictionary (143 keys per language)
│   └── ui/
│       ├── chat_ui.js                         # Core Chat UI controller (342 lines)
│       └── templates/
│           ├── template_catalog_system.js     # Template 'modal-chat-dock' & 'modal-item-link-tooltip'
│           └── template_manager.js            # Dynamic template mount engine
client/src/
├── chat/
│   └── ChatManager.ts                         # Client-side iOS chat manager (parallel domain logic)
└── i18n/
    └── LocalizationManager.ts                 # Client-side TypeScript i18n manager
server/chat/
├── chat_types.py                              # Python DTOs: ChatChannelType (1-8), ItemSnapshotDTO, ChatMessageDTO
└── chat_service.py                            # Server-authoritative distributed chat engine & HMAC verifier
```

---

## 3. Exhaustive Inventory of Hardcoded Strings

Below is the complete audit of every hardcoded string in `client/webapp/js/ui/chat_ui.js` and its associated HTML templates.

### 3.1. Channel Names & Badges
| Location in `chat_ui.js` | Current Hardcoded String | Key Category | Proposed Localization Key | English Translation |
|---|---|---|---|---|
| Line 7 | `'Thế Giới'` | Channel 1 | `chat_channel_world` | `World` |
| Line 8 | `'Khu Vực'` | Channel 2 | `chat_channel_zone` | `Zone` |
| Line 9 | `'Bang Hội'` | Channel 3 | `chat_channel_guild` | `Guild` |
| Line 10 | `'Đội Ngũ'` | Channel 4 | `chat_channel_party` | `Party` |
| Line 11 | `'Mật Thư'` | Channel 5 | `chat_channel_whisper` | `Whisper` |
| Line 12 | `'Hệ Thống'` | Channel 6 | `chat_channel_system` | `System` |
| Line 13 | `'Chiêu Mộ'` | Channel 7 | `chat_channel_recruit` | `Recruit` |
| Line 14 | `'Góp Ý'` | Channel 8 | `chat_channel_feedback` | `Feedback` |

*Note: In `index.html` (lines 156-157) and `template_catalog_system.js` (lines 439-446), these exact 8 strings are also hardcoded directly into the tab buttons without `data-i18n` attributes.*

### 3.2. Channel Permission & Restriction Messages
| Location in `chat_ui.js` | Current Hardcoded String | Condition | Proposed Localization Key | English Translation |
|---|---|---|---|---|
| Line 124 | `'Kênh Hệ Thống chỉ dành cho thông báo từ máy chủ.'` | Channel 6 (System) | `chat_perm_system_only` | `System channel is reserved for server announcements.` |
| Line 125 | `'Chưa gia nhập Bang Hội.'` | Channel 3 (No Guild) | `chat_perm_no_guild` | `You have not joined a Guild.` |
| Line 126 | `'Chưa gia nhập Tổ Đội.'` | Channel 4 (No Party) | `chat_perm_no_party` | `You have not joined a Party.` |
| Line 127 | `'Cần đạt cấp 20 để phát tán kênh Thế Giới.'` | Channel 1 (Lv < 20) | `chat_perm_world_min_level` | `Requires Level {level} to speak in World channel.` |
| Line 128 | `'Cần đạt cấp 10 để dùng kênh Chiêu Mộ.'` | Channel 7 (Lv < 10) | `chat_perm_recruit_min_level` | `Requires Level {level} to use Recruit channel.` |
| *ChatManager.ts:104* | `'Tài khoản đang bị tạm khóa chat.'` | Context `isMuted` | `chat_perm_muted` | `Account chat is temporarily muted.` |

### 3.3. Input Field, Cooldown & Ticker Microcopy
| Location | Current Hardcoded String | Context | Proposed Localization Key | English Translation |
|---|---|---|---|---|
| `chat_ui.js:147` | `'Nhập tin nhắn [' + ch.label + ']...'` | Active channel permitted | `chat_input_placeholder` | `Enter message [{channel}]...` |
| `index.html:162` | `placeholder="Nhập tin nhắn..."` | Initial DOM input placeholder | `chat_input_placeholder_default` | `Enter message...` |
| `chat_ui.js:156` | `'Khóa'` | Cooldown badge when not permitted | `chat_badge_locked` | `Locked` |
| `chat_ui.js:163` | `'Chờ ' + sec + 's'` | Cooldown timer remaining | `chat_badge_wait` | `Wait {sec}s` |
| `index.html:162` | `'Chờ 15s'` | Initial static cooldown badge | `chat_badge_wait_initial` | `Wait 15s` |
| `index.html:162` | `'Gửi'` | Send button text | `chat_btn_send` | `Send` |
| `index.html:152` | `'Chạm để mở kênh đàm đạo...'` | Minimized ticker placeholder text | `chat_ticker_default` | `Tap to open chat...` |

### 3.4. Chat Log Formatting & System Alerts
| Location in `chat_ui.js` | Current Hardcoded String | Context | Proposed Localization Key | English Translation |
|---|---|---|---|---|
| Line 117 | `'Hiệp Khách'` | Fallback player name | `chat_sender_fallback` | `Exile` |
| Line 135 | `senderName: 'Hệ Thống'` | System notice sender | `chat_system_sender` | `System` |
| Line 137 | `'[Cảnh Báo] ' + text` | Alert message banner prefix | `chat_system_alert_prefix` | `[Alert] ` |
| Line 265 | `'vi-VN'` | Date time locale string formatting | *(Dynamic locale mapping)* | `en-US`, `zh-CN`, `ja-JP`, etc. |
| Line 266 | `safeSender = escapeHtml(msg.senderName \|\| 'Hiệp Khách')` | Chat log sender fallback | `chat_sender_fallback` | `Exile` |

### 3.5. Item Link Tooltip Modal (`#modal-item-link-tooltip`)
| Location in `chat_ui.js` | Current Hardcoded String | Context | Proposed Localization Key | English Translation |
|---|---|---|---|---|
| Line 38: Rarity 1 | `'Phàm Phẩm'` | Normal / Common | `item_rarity_1` | `Common` |
| Line 38: Rarity 2 | `'Linh Phẩm'` | Magic / Refined | `item_rarity_2` | `Refined` |
| Line 39: Rarity 3 | `'Cực Phẩm'` | Rare / Pinnacle | `item_rarity_3` | `Rare` |
| Line 39: Rarity 4 | `'Thần Phẩm'` | Unique / Divine | `item_rarity_4` | `Divine` |
| Line 40: Rarity 5 | `'Thái Cổ'` | Ancient / Primal | `item_rarity_5` | `Primal` |
| Line 42: Element 1 | `'Kim'` | Metal element | `element_metal` | `Metal` |
| Line 42: Element 2 | `'Mộc'` | Wood element | `element_wood` | `Wood` |
| Line 42: Element 3 | `'Thủy'` | Water element | `element_water` | `Water` |
| Line 42: Element 4 | `'Hỏa'` | Fire element | `element_fire` | `Fire` |
| Line 42: Element 5 | `'Thổ'` | Earth element | `element_earth` | `Earth` |
| Line 183 | `'Vật Phẩm'` | Fallback item name | `chat_item_fallback_name` | `Item` |
| Line 184 | `' Hệ'` | Element suffix (`${elemName} Hệ`) | `chat_item_element_suffix` | ` Element` |
| Line 184 | `'Cấp ' + snap.itemLevel` | Item Level | `chat_item_level` | `Lv. {level}` |
| Line 184 | `'Cấp ?'` | Unknown item level | `chat_item_level_unknown` | `Lv. ?` |
| Line 186 | `'Thiên Công'` | Fallback crafter for verified items | `chat_item_crafter_default` | `Celestial Smith` |
| Line 186, Line 277 | `'Chưa rõ'` | Fallback crafter for unverified items | `chat_item_crafter_unknown` | `Unknown` |
| Line 192 | `'• Không có dữ liệu thuộc tính (Chưa xác thực)'` | Unverified affix placeholder | `chat_item_unverified_affixes` | `• No attribute data (Unverified)` |
| Line 193 | `'• Không có thuộc tính bổ trợ'` | Verified item without affixes | `chat_item_no_affixes` | `• No affixes` |
| Line 198 | `'✓ HMAC Xác Thực'` | Verified HMAC cryptographic badge | `chat_item_badge_verified` | `✓ HMAC Verified` |
| Line 198 | `'⚠ Chưa Xác Thực'` | Unverified / tampered badge | `chat_item_badge_unverified` | `⚠ Unverified` |
| `index.html:170` | `'Chế tác:'` | Crafter label in tooltip footer | `chat_item_crafter_label` | `Crafted by:` |

---

## 4. Lifecycle, Initialization & Re-rendering Analysis

### 4.1. Initialization Flow
1. **Self-Invocation & Duplicate Calls**:
   `chat_ui.js` calls `initChatUI()` on `DOMContentLoaded` (or immediately if `document.readyState !== 'loading'`).
   Concurrently, `client/webapp/js/main.js` imports `initChatUI` and also calls it on `DOMContentLoaded`.
   This leads to multiple calls on startup. Currently, `initChatUI()` does not guard against duplicate calls, which binds multiple duplicate event listeners on `#chat-send-btn`, `#chat-minimized-bar`, and `#lang-select`.
2. **Ignored Stored Locale on Boot**:
   `localStorage.getItem('freeexile_locale')` is read by `FreeExileI18nEngine` in `i18n.js`. However, `initChatUI()` does not query `FreeExileI18n.getLocale()`, nor does it call `updateLanguage()` with the active locale. Consequently, if a player returns with an English preference, the Chat UI boots in Vietnamese until the user re-toggles `#lang-select`.

### 4.2. Current Re-render Functions
There are only two partial update functions in `chat_ui.js`:
- `renderChatLog()`: Clears `#chat-messages-container` and reconstructs message rows from `channelHistories.get(activeChannel)`.
  *Defect*: Uses `ch.label` which is frozen in Vietnamese. Does not support locale-sensitive timestamps.
- `updateLanguage(lang)`:
  *Defect*: Only iterates over `.chat-channel-tab` and sets `tab.textContent = CHANNEL_I18N[l][key]`, and sets `#chat-ticker-channel`.
  *Missing*:
  - Does NOT update `CHANNELS[id].label`.
  - Does NOT re-render `renderChatLog()`.
  - Does NOT update `updateInputPlaceholder()`.
  - Does NOT update `updateCooldownUI()`.
  - Does NOT update `#chat-send-btn` or `#chat-ticker-text`.
  - Does NOT update active `#modal-item-link-tooltip`.

---

## 5. Interaction with Settings & i18n Subsystem

### 5.1. Existing i18n Architecture (`client/webapp/js/data/i18n.js`)
The FreeExile WebApp already possesses a robust reactive engine:
```javascript
class FreeExileI18nEngine {
  constructor() {
    this.catalog = typeof I18N_CATALOG !== 'undefined' ? I18N_CATALOG : {};
    this.currentLocale = 'vi';
    this.listeners = [];
  }
  setLocale(locale) {
    this.currentLocale = locale;
    localStorage.setItem('freeexile_locale', locale);
    this.updateDOM();
    this.listeners.forEach(fn => fn(locale));
    window.dispatchEvent(new CustomEvent('freeexile:localeChanged', {
      detail: { locale, dict: this.catalog[locale] || {} }
    }));
  }
  onLocaleChanged(callback) { this.listeners.push(callback); }
  t(key, params, fallback) { ... }
}
```

### 5.2. Root Causes of Language Desynchronization
1. **No Subscription to Observer or Event**:
   `chat_ui.js` never calls `FreeExileI18n.onLocaleChanged(...)` and never listens to `window.addEventListener('freeexile:localeChanged', ...)`.
2. **Direct DOM Binding to `#lang-select`**:
   `chat_ui.js` only listens to `document.getElementById('lang-select')?.addEventListener('change', ...)`.
   When settings, modals, or automated scripts invoke `applyLanguage('en')` or `FreeExileI18n.setLocale('en')`, the DOM select's `.value` property is updated programmatically. In standard browser DOM behavior, programmatic property updates **never trigger a `'change'` event**. Hence, `chat_ui.js` remains completely deaf to the change.
3. **Template Clones Lack Synchronization**:
   `client/webapp/js/ui/templates/template_catalog_system.js` registers `'modal-chat-dock'` and `'modal-item-link-tooltip'` using static strings that have no `data-i18n` attributes.

---

## 6. Recommended Architecture for Dynamic Re-rendering

To achieve 100% reactive localization with **zero page reloads** while respecting the **350-line Soft Cap** for `chat_ui.js`, the following architectural design is recommended.

```
       [User Action / Settings Modal / Script]
                         │
                         ▼
             FreeExileI18n.setLocale(lang)
                         │
        ┌────────────────┴─────────────────┐
        ▼                                  ▼
   updateDOM()                Event: 'freeexile:localeChanged'
(Declarative [data-i18n])     or onLocaleChanged(lang) callback
                                           │
                                           ▼
                                chatUI.handleLanguageChange(lang)
                                           │
       ┌───────────────────┬───────────────┴───────────────┬───────────────────┐
       ▼                   ▼                               ▼                   ▼
Update CHANNELS[id]    Update Tabs &              Update Input &         Re-render Chat Log &
     labels             Ticker Bar                 Cooldown UI             Open Tooltips
(Localized names)  ([data-i18n] sync)           (Placeholder & text)   (renderChatLog() in-place)
```

### 6.1. Subscription Implementation in `chat_ui.js`
Replace the isolated `#lang-select` listener with a dual-layer subscriber:
```javascript
export function registerI18nSubscriber() {
  if (typeof window === 'undefined') return;

  // 1. Direct Engine Callback
  if (window.FreeExileI18n?.onLocaleChanged) {
    window.FreeExileI18n.onLocaleChanged((lang) => handleLanguageChange(lang));
  }

  // 2. CustomEvent Fallback (Cross-framework & decoupled)
  window.addEventListener('freeexile:localeChanged', (e) => {
    handleLanguageChange(e.detail?.locale || window.FreeExileI18n?.getLocale() || 'vi');
  });

  // 3. Fallback DOM select listener
  document.getElementById('lang-select')?.addEventListener('change', (e) => {
    if (window.FreeExileI18n?.setLocale) {
      window.FreeExileI18n.setLocale(e.target.value);
    } else {
      handleLanguageChange(e.target.value);
    }
  });
}
```

### 6.2. In-Place Re-render Pipeline (`handleLanguageChange`)
`handleLanguageChange(lang)` executes an atomic update across all UI elements without reloading the page:
1. **Update Channel Metas**:
   Dynamically resolve each channel's active label via `t('chat_channel_' + ch.key)`.
2. **Channel Tabs**:
   Update all `.chat-channel-tab` elements.
3. **Ticker Bar**:
   Update `#chat-ticker-channel` and reset `#chat-ticker-text` if it contains the default placeholder.
4. **Input & Cooldown Badges**:
   Call `updateInputPlaceholder()` and `updateCooldownUI()`.
5. **Send Button**:
   Update `#chat-send-btn` text via `t('chat_btn_send')`.
6. **Chat Log In-Place Re-render**:
   Call `renderChatLog()`. Existing messages in the active channel are re-rendered with localized channel tags (`[World]`, `[Guild]`) and localized time format (`toLocaleTimeString(localeCode)`).
7. **Tooltip Modal Sync**:
   If `#modal-item-link-tooltip` is currently visible, re-invoke `openItemTooltip` with the active snapshot to instantly translate the rarity, element, crafter, and verification status.

### 6.3. Decoupling Dictionaries to Obey the 350-Line Soft Cap
In `GEMINI.md`:
> *"File logic (Python, TypeScript, C++) bắt buộc $\le 350$ dòng (Soft Cap), không bao giờ vượt quá $500$ dòng (Hard Cap)."*

`chat_ui.js` is currently 342 lines. Adding 42 localization keys inline across 9 languages would add ~300 lines, violently blowing past both the Soft Cap (350) and Hard Cap (500).

**Solution**:
Extend `client/webapp/js/data/i18n_catalog.js` (a designated catalog file where the limit is 700 Soft / 1000 Hard Cap; currently 634 lines) or place chat keys in a dedicated catalog file `client/webapp/js/data/chat_i18n_catalog.js`.
In `chat_ui.js`, replace the 28-line `CHANNEL_I18N` table with a clean lookup helper `t(key, params, fallback)` that delegates to `window.FreeExileI18n.t(key, params, fallback)`. This actually **reduces** line count in `chat_ui.js` by ~20 lines, bringing it down to ~325 lines—well below the 350-line Soft Cap!

---

## 7. Interaction with Existing Test Suites

### 7.1. `tests/unit/test_webapp_chat_ui.py`
This test suite currently passes 18/18 tests.
Key considerations for the upcoming implementation:
- `test_02_all_eight_channel_tabs_in_index_html` verifies that initial labels in `index.html` are:
  `["Thế Giới", "Khu Vực", "Bang Hội", "Đội Ngũ", "Mật Thư", "Hệ Thống", "Chiêu Mộ", "Góp Ý"]`.
  Initial HTML should retain Vietnamese defaults (or `data-i18n` attributes) while allowing dynamic switching.
- `test_10_multilingual_channel_dictionary` imports `chatModule.CHANNEL_I18N`.
  To ensure 100% backwards compatibility, `CHANNEL_I18N` should remain exported as an alias or populated from the central catalog.
- `test_16` and `test_17` verify `✓ HMAC Xác Thực` and `⚠ Chưa Xác Thực`.
  In tests running under Node.js mock DOM, if no locale is set, the fallback must default to the existing Vietnamese strings unless `updateLanguage` is explicitly called.

### 7.2. `tests/e2e/test_ui_typography_i18n_wiki_streamlining_e2e.py`
- `test_f04_all_languages_have_143_keys` asserts `cnt == 143` keys in `i18n_catalog.js`.
  If keys are added to `i18n_catalog.js`, the test must be updated or keys must be registered via a modular extension catalog `chat_i18n_catalog.js` registered with `FreeExileI18nEngine`.

---

## 8. Summary Checklist for Implementers

- [ ] Register `chat_channel_*`, `chat_perm_*`, `chat_item_*`, and `chat_badge_*` localization keys in all 9 supported languages.
- [ ] Connect `chat_ui.js` to `window.FreeExileI18n.onLocaleChanged` and the `freeexile:localeChanged` event.
- [ ] Implement `handleLanguageChange(lang)` to re-render channel tabs, placeholder, badges, ticker, chat log messages, and open tooltips.
- [ ] Check active locale on `initChatUI()` boot to avoid defaulting to Vietnamese when stored language is English.
- [ ] Add guard against duplicate event listeners in `initChatUI()`.
- [ ] Maintain `chat_ui.js` $\le 350$ lines.
- [ ] Run `python -m pytest tests/unit/test_webapp_chat_ui.py` and `python tools/lint/check_code_and_doc_hygiene.py`.
