# Milestone 2 Implementation Report: Automated Anti-Regression & Linter Suite (R3)

**Author:** `worker_m2_1`  
**Date:** 2026-10-02  
**Target Milestone:** Milestone 2 (M2) — Automated Anti-Regression & Linter Suite (R3)  
**Project:** FreeExile Native ES Modules WebApp & iOS Metal Engine  

---

## 1. Executive Summary

Milestone 2 establishes an automated, multi-tiered anti-regression and static analysis suite safeguarding FreeExile's internationalization (i18n) architecture. The suite prevents the introduction of unlocalized strings, enforces strict dictionary parity across all 9 supported languages (`vi`, `en`, `zh`, `ja`, `ko`, `th`, `de`, `ru`, `es`), resolves dangling template keys, and provides thorough unit and E2E verification of live reactive language switching without page reloads.

All deliverables have been implemented strictly adhering to the project's engineering standards (Python 3.11 strict typing, zero hardcoded test outputs, soft line caps $\le 350$ lines, zero regressions on existing suites).

---

## 2. Deliverable Details

### 2.1 Static Linter (`tools/lint/check_i18n_hygiene.py`)
- **File Path**: `tools/lint/check_i18n_hygiene.py` (336 lines, compliant with $\le 350$ soft cap).
- **Architecture**: Python 3.11 CLI static analyzer with UTF-8 stdout reconfiguration and atomic error reporting.
- **Rule 1 — Zero Hardcoded Vietnamese UI Text**:
  - Scans UI JavaScript modules (default: `client/webapp/js/ui/chat_ui.js`).
  - Regex pattern: `[\u00C0-\u1EF9]` (covers all precomposed Vietnamese diacritics and tone marks).
  - Strips single-line (`//`) and multi-line (`/* ... */`) comments while preserving line indices.
  - Allowlist mechanism: Permits default fallback strings passed as 3rd arguments to `t('key', params, 'fallback')` and default seed definitions (`CHANNELS`, `RARITY_INFO`, `ELEMENT_NAMES`, `CHANNEL_I18N`). Any raw string literal containing Vietnamese diacritics in UI display logic is flagged as an `ERROR`.
- **Rule 2 — 9-Language Dictionary Parity**:
  - Validates `client/webapp/js/data/chat_i18n_catalog.js` (77 keys) and `client/webapp/js/data/i18n_catalog.js` (143 keys).
  - Enforces symmetric key matching: $Keys(vi) = Keys(L)$ for each $L \in \{en, zh, ja, ko, th, de, ru, es\}$.
  - Zero tolerance for missing or extraneous keys.
- **Rule 3 — Missing / Dangling Key Resolution**:
  - Scans `client/webapp/index.html` and `client/webapp/js/ui/templates/*.js` for `data-i18n`, `data-chat-i18n`, `data-i18n-placeholder`, `data-tooltip-title-key`, `data-tooltip-desc-key`, and related attributes.
  - Compares every referenced key against the combined catalog ($143 + 77 = 220$ keys).
  - Flags any dangling key not found in the master dictionaries.
- **CLI Options**:
  - `--strict`: Exits with code 1 if any violation is found; exits with code 0 on clean pass.
  - `--json-out <path>`: Exports a structured JSON audit report (e.g. `audit/i18n_report.json`).
  - `--target-files`: Configurable file targets for Rule 1 scanning.

### 2.2 Unit Test Suite (`tests/unit/test_i18n_event_bus.py`)
- **File Path**: `tests/unit/test_i18n_event_bus.py` (327 lines, compliant with $\le 350$ soft cap).
- **Execution Engine**: `pytest` / `unittest` driving an in-memory Node.js ES module evaluation harness with simulated DOM elements, custom event dispatching, and persistent storage.
- **Test Matrix (23 Test Cases Across 5 Groups)**:
  - **Group 1: Observer / Event Bus Mechanics (Tests 1–5)**:
    - `test_01_listener_registration_and_callback`: Asserts `onLocaleChanged` receives `('en', 'vi', dict)`.
    - `test_02_multiple_listeners_isolated`: Asserts multiple listeners execute in sequence (`['A', 'B', 'C']`).
    - `test_03_listener_exception_resilience`: Asserts subscriber failure does not halt subsequent subscribers.
    - `test_04_listener_unsubscription`: Asserts unbind callback halts subsequent notifications.
    - `test_05_custom_event_window_dispatch`: Asserts `window.dispatchEvent` fires `freeexile:localeChanged` with details.
  - **Group 2: Fallback Chaining & Parameter Interpolation (Tests 6–10)**:
    - `test_06_exact_locale_lookup`: Asserts `t('chat_btn_send')` in `en` returns `'Send'`.
    - `test_07_fallback_chain_current_to_en`: Asserts missing key in `ja` falls back to `en`.
    - `test_08_fallback_chain_en_to_vi`: Asserts missing key in `ja` and `en` falls back to `vi`.
    - `test_09_fallback_to_raw_key`: Asserts non-existent key returns key name or explicit fallback.
    - `test_10_parameter_substitution`: Asserts `{sec}` interpolation (`'Wait 5s'` / `'Chờ 5s'`).
  - **Group 3: 9-Language Dictionary Parity & Key Presence (Tests 11–15)**:
    - `test_11_all_9_locales_present`: Asserts presence of all 9 locales in both catalogs.
    - `test_12_vi_en_dictionary_parity`: Asserts exact set equality between `vi` and `en`.
    - `test_13_all_8_channel_keys_present`: Asserts `chat_channel_1` through `chat_channel_8` exist in all 9 languages.
    - `test_14_chat_permission_keys_present`: Asserts all system warning, guild, party, and level gate keys exist.
    - `test_15_chat_rarity_and_element_keys_present`: Asserts all 5 rarities and 5 elements exist in all 9 languages.
  - **Group 4: Chat UI Reactive DOM Integration (Tests 16–20)**:
    - `test_16_chat_ui_subscribes_to_i18n_bus`: Asserts `chatUI.initChatUI()` registers with `FreeExileI18n`.
    - `test_17_language_change_updates_all_channel_tabs`: Asserts tabs dynamically update (`World`, `Zone`, `Guild`).
    - `test_18_language_change_updates_placeholder_and_cooldown`: Asserts `#chat-input.placeholder` updates.
    - `test_19_language_change_updates_send_button`: Asserts `#chat-send-btn` updates from `'Gửi'` to `'Send'`.
    - `test_20_language_change_preserves_chat_history`: Asserts ring buffer length remains constant across language swaps.
  - **Group 5: Edge Cases & Persistence (Tests 21–23)**:
    - `test_21_invalid_locale_handled_gracefully`: Asserts `null` or empty string does not corrupt locale.
    - `test_22_localstorage_persistence`: Asserts `localStorage['freeexile_locale']` updates.
    - `test_23_null_or_empty_key_resilience`: Asserts `t('')` and `t(null)` return safely without exceptions.

### 2.3 E2E Test Suite (`tests/e2e/test_i18n_reactive_switching_e2e.py`)
- **File Path**: `tests/e2e/test_i18n_reactive_switching_e2e.py` (192 lines, compliant with $\le 350$ soft cap).
- **Execution Engine**: Playwright with headless Microsoft Edge (`channel='msedge'`) against an ephemeral local HTTP server (`127.0.0.1` on dynamic port).
- **Verifications**:
  - Initial load state in Vietnamese (`vi`): Tab 1 = "Thế Giới", Button = "Gửi".
  - Reactive switch to English (`en`): Verifies `window.__sessionNavCount === 1` (zero page reloads).
  - DOM inspection: All 8 tabs updated in English, Send button updated to "Send", placeholder updated.
  - System channel permission feedback: Tab 6 shows server announcement warning and "Locked" cooldown badge.
  - Multi-language round-trip cycle: `VI -> EN -> ZH -> JA -> KO -> VI` with accurate non-ASCII CJK glyph rendering.
  - Console Error Gate: Asserts 0 unhandled `pageerror` and 0 unhandled `console.error` events throughout the entire session.

---

## 3. Verification Commands & Results

| Verification Target | Command | Result | Notes |
|---------------------|---------|--------|-------|
| Static i18n Linter | `python tools/lint/check_i18n_hygiene.py --strict` | **PASS (code 0)** | 0 violations across Rules 1, 2, 3 |
| Unit Test Suite | `pytest tests/unit/test_i18n_event_bus.py` | **PASS (code 0)** | 23 / 23 passed in 0.37s |
| E2E Test Suite | `pytest tests/e2e/test_i18n_reactive_switching_e2e.py` | **PASS (code 0)** | 1 / 1 passed in 5.71s |
| Code & Doc Hygiene | `python tools/lint/check_code_and_doc_hygiene.py --strict` | **PASS (code 0)** | All files $\le 350$ lines soft cap |
| Chat Unit Regression | `pytest tests/unit/test_challenger_chat_m2.py tests/unit/test_challenger_m2_chat_adversarial.py` | **PASS (code 0)** | 20 / 20 passed (zero backward regressions) |
| Typography E2E Regression | `pytest tests/e2e/test_ui_typography_i18n_wiki_streamlining_e2e.py` | **PASS (code 0)** | 60 / 60 passed (zero backward regressions) |

---

## 4. Quantitative Compliance Metrics

- `tools/lint/check_i18n_hygiene.py`: **336 lines** ($\le 350$ soft cap)
- `tests/unit/test_i18n_event_bus.py`: **327 lines** ($\le 350$ soft cap)
- `tests/e2e/test_i18n_reactive_switching_e2e.py`: **192 lines** ($\le 350$ soft cap)
- Functions/Methods: 0 functions exceed the 50-line cap.
