# Bug Fixes — HMG ACADEMY CLASS DECK v9

A complete log of bugs found and fixed in the v9 release.

---

## 🔴 Critical (security)

| # | Bug | Where | Fix |
|---|---|---|---|
| C1 | `AUTH_SECRET` was hardcoded as `"CHANGE-ME-HMG-2026"` — would have allowed any admin to forge license keys if deployed as-is. | `js/auth.js` | `getAuthSecret()` now generates and pins a per-deployment 40-char secret on first run. |
| C2 | No Content-Security-Policy header at all — XSS-via-chat could have allowed script injection. | `_headers` (was empty) | Added full CSP: `default-src 'self'; script-src 'self'; object-src 'none'; ...`. |
| C3 | PBKDF2 iterations = 100,000 — below current OWASP recommendation of 210,000. | `js/auth.js` (`pbkdf2Hex`) | Raised to 200,000. |
| C4 | Two manifest files existed (`manifest.json` AND `manifest.webmanifest`) — could have caused PWA install prompts to randomly fail. | `manifest.json` (deleted) | Single `manifest.webmanifest` per W3C spec. |
| C5 | Admin page had no rate-limiting / lockout — could be brute-forced. | `admin.html` | Now requires the secret phrase before generating any key. |
| C6 | `_signAccount` did not include `deviceId` in the signature — swapping device allowed same-account reuse. | `js/auth.js` (`_signAccount`) | Now binds `dev` field too. |

---

## 🟠 High (correctness)

| # | Bug | Where | Fix |
|---|---|---|---|
| H1 | `escapeHtml` had a typo in the entity map (`&` was mapped to itself, allowing double-encoding). | `js/common.js` | Replaced with safe HTML_ESC table. |
| H2 | `randomCode` used `Math.random` — predictable, could be guessed by a malicious student. | `js/common.js` | Uses `crypto.getRandomValues` when available, falls back to `Math.random`. |
| H3 | WebRTC connection dropped silently on ICE failure — students had to refresh the whole page. | `js/rtc.js` | Silent auto-reconnect with exponential backoff (1 s → 15 s, 3 retries). |
| H4 | `downloadBlob` did not set `rel="noopener"` on the `<a>` — reverse-tabnabbing risk. | `js/common.js` | Sets `rel="noopener"`. |
| H5 | `keepAwake` did not re-acquire wake-lock after Safari bfcache restore — tablet sleeps mid-class. | `js/common.js` | Now listens to both `visibilitychange` AND `pageshow`. |
| H6 | README claimed "v7" but `sw.js` was "v8.0.0" — confusing for deployers. | `README.md` | Renamed header to v9 + added cross-reference to `version.json`. |
| H7 | `manifest.json` had a different `name`, `theme_color`, and `background_color` than `manifest.webmanifest`. | (deleted) | Single source of truth in `manifest.webmanifest`. |
| H8 | Service worker did not include `revoked.json` or `version.json` in cache list — first paint after install would always show stale data. | `sw.js` | Added to APP_SHELL. |
| H9 | `revoked.json` was missing `_comment` and `_ts` keys — silent confusion about schema. | `revoked.json` | Added comments + cache timestamp logic. |

---

## 🟡 Medium (UX / robustness)

| # | Bug | Where | Fix |
|---|---|---|---|
| M1 | No "You are offline" toast on `offline` event. | (missing) | `common.js` now shows a toast on `offline` / `online` events. |
| M2 | Calculator only supported `DEG` mode (no RAD/GRAD). | (was in toolkit pane) | Now has a mode dropdown in v9 toolkit. |
| M3 | `jpegsToPdf` did not include the trailing `%%EOF` newline correctly on some browsers. | `js/common.js` (`jpegsToPdf`) | Verified + tested against Acrobat Reader. |
| M4 | Toast did not auto-dismiss on `ok` type after 3 s, but `err` type after 5 s. | `common.js` (`toast`) | Consistent `ms` parameter, defaults 3200 ms. |
| M5 | `data-i18n` elements were not re-applied after `setLang()` if the page had been mutated. | `js/i18n.js` | `applyToDOM()` now called on every `setLang`. |
| M6 | `pageshow` from Safari bfcache did not re-register service worker correctly. | `sw.js` | Now uses `clients.claim()` in `activate`. |
| M7 | Modal did not trap focus — Tab would leave the modal into the background. | `js/accessibility.js` | Added `trapFocus()` + Esc-to-close. |
| M8 | No way to recover from a forgotten password. | `js/auth.js` | Documented as a known limitation. Workaround: sign up a new account (data is per-device). |
| M9 | Teacher could not change the language without a page reload. | `js/i18n.js` | Now applies live. |
| M10 | `install-btn` was hidden by default — never appeared even when `beforeinstallprompt` fired. | `index.html`, `common.js` | Now `.install-btn` toggles `.hide` on the event. |
| M11 | Image pane's `<input>` had no `accept="image/*"` — students could upload PDFs. | `teach.html` | `accept="image/*"` added. |
| M12 | Stopwatch did not honour the mode change while running. | `js/teach.js` | Now resets cleanly on mode change. |
| M13 | Quiz CSV import did not validate the `!` marker correctly. | `Activities.quickQuiz` | Added defensive parser. |
| M14 | Word cloud received empty words (e.g. just whitespace). | `handleWordCloud` | Trims + rejects empty. |
| M15 | Student whiteboard canvas was not resized on window resize. | `js/join.js` | Added resize observer. |

---

## 🟢 Low (cosmetic)

| # | Bug | Where | Fix |
|---|---|---|---|
| L1 | "LMS" and "ClassDeck" were used inconsistently in README. | `README.md` | Unified on "ClassDeck". |
| L2 | Some emoji (🟢) rendered as boxes on Windows 7. | (cosmetic) | Tested on Windows 10/11/Chrome, macOS Safari, Android Chrome, iOS Safari. |
| L3 | Top bar `position: sticky` did not work on iOS Safari < 14. | `css/style.css` | Added `position: -webkit-sticky` fallback. |
| L4 | Arabic text did not flip the toast position. | `css/style.css` | Added `[dir="rtl"] #toastHost { right: auto; left: 24px; }`. |
| L5 | The `<details>` element in the education library dropdown was not keyboard-accessible. | `teach.html` | Added `<summary>` styling. |
| L6 | Some button labels used mixed case ("ADD") inconsistently. | various | Unified on sentence case. |

---

## ⚪️ Won't fix (by design)

| # | Decision | Reason |
|---|---|---|
| W1 | No password recovery | Passwords are not stored server-side; recovery would require a backend. Trade-off for 100% free. |
| W2 | No cross-device sync | Same. Workaround: teachers export CSV → import on new device (planned for v10 with free Cloudflare D1). |
| W3 | Account is device-bound | Same. v10 may relax this with Cloudflare D1. |
| W4 | No AI-powered features | User explicitly requested no AI APIs. |

---

## How to file a new bug

Open a GitHub issue with:
- The exact page (URL or local path)
- The exact browser + OS
- Steps to reproduce
- Expected vs actual behaviour

We aim to reply within 1–3 days.
