Skip to content

fix: close 3 remaining low-priority audit gaps (youtube lifecycle, catalog parity, a11y)#234

Merged
heznpc merged 2 commits into
mainfrom
fix/remaining-audit-gaps
Jun 17, 2026
Merged

fix: close 3 remaining low-priority audit gaps (youtube lifecycle, catalog parity, a11y)#234
heznpc merged 2 commits into
mainfrom
fix/remaining-audit-gaps

Conversation

@heznpc

@heznpc heznpc commented Jun 17, 2026

Copy link
Copy Markdown
Owner

Follow-up to the completeness audit. A deep-read + adversarial-verify pass confirmed three independent, low-severity gaps as real. A fourth — Firefox strict_min_version — was investigated and dismissed: 121 is conservative vs the true ~109 API floor (the build drops service_worker), so it over-restricts rather than over-promises.

1. YouTube subtitle-manager lifecycle

YouTubeSubtitleManager.destroy() existed and was unit-tested but was never called — SPA-navigating onto a Skilljar certification page left its MutationObserver + global window 'message' listener alive for the page's lifetime.

  • Wire a symmetric lifecycle: destroy() + null on the cert-disable branch of onRouteChange, and rebuild a fresh manager (via a new ensureSubtitleManager() helper, also used at init) when returning to a normal lesson. The manager has no in-place reset, so a bare destroy() without re-creation would silently kill subtitles for the rest of the SPA session — the re-creation prevents that regression.
  • New e2e youtube-lifecycle.spec.js drives create → teardown → rebuild via a read-only _sb.hasSubtitleManager seam. Revert-checked (fails on the teardown assertion without the fix).

2. Catalog partial translation

Two course titles — Claude with Amazon Bedrock and Claude with Google Cloud's Vertex AI — sat untranslated (value == English key) in 7 of 12 catalogs, while the same files' cloudDeployment section translated them.

  • Fill the 14 catalog values from each locale's own cloudDeployment translation (brand tokens stay English, as they already do there).
  • Add Check 6 to check-dict-coverage.js (already run in CI) to fail any catalog key translated in some dicts but left English in others. Uniform passthrough (Claude 101) is not flagged. The terms Vertex AI / Bedrock themselves are correct protected passthroughs — only the title strings were the gap.

3. Accessibility

  • Shortcuts help overlay had no dialog semantics → add role="dialog", aria-modal, aria-labelledby (title now id'd), and a translated aria-label on the icon-only × button (new SHORTCUT_LABELS.close, 13 locales — the glyph alone announces as "multiplication sign").
  • Popup language <select> label gains for="lang-select" for an accessible name.
  • New e2e a11y.spec.js + unit popup-a11y.test.js, both revert-checked.

Validation

lint · format · every check:* gate (incl. new Check 6) · 559 unit · 25 e2e — all green. Each fix independently revert-checked.

…talog parity, a11y)

Follow-up to the completeness audit — three independent, low-severity gaps a
deep-read + adversarial-verify pass confirmed as real (a fourth, the Firefox
strict_min_version, was investigated and dismissed: 121 is conservative vs the
true ~109 API floor, so it over-restricts rather than over-promises).

1. YouTube subtitle-manager lifecycle (src/content/content.js)
   YouTubeSubtitleManager.destroy() existed + was unit-tested but was NEVER
   called, so SPA-navigating onto a Skilljar certification page left its
   MutationObserver + global window 'message' listener alive for the page's
   lifetime. Wire a symmetric lifecycle: destroy()+null on the cert-disable
   branch of onRouteChange, and rebuild a fresh manager (via a new
   ensureSubtitleManager() helper, also used at init) when returning to a normal
   lesson — the manager has no in-place reset, so a bare destroy() without
   re-creation would silently kill subtitles for the rest of the session.
   New e2e youtube-lifecycle.spec.js drives create→teardown→rebuild via the
   read-only _sb.hasSubtitleManager seam (revert-checked).

2. Catalog partial translation (src/data/*.json + check-dict-coverage.js)
   Two course titles — "Claude with Amazon Bedrock" and "Claude with Google
   Cloud's Vertex AI" — sat untranslated (value == English key) in 7 of 12
   catalogs while the SAME files' cloudDeployment section translated them. Fill
   the 14 catalog values from each locale's own cloudDeployment translation
   (brand tokens stay English, as they already do there). Add Check 6 to
   check-dict-coverage.js (run in CI) to fail any catalog key translated in some
   dicts but left English in others — uniform passthrough like "Claude 101" is
   not flagged. (The terms Vertex AI / Bedrock themselves are correct protected
   passthroughs; only the title strings were the gap.)

3. Accessibility (keyboard-shortcuts.js, popup.html, constants.js)
   The shortcuts help overlay had no dialog semantics; add role="dialog",
   aria-modal, and aria-labelledby pointing at the (now id'd) title, plus a
   translated aria-label on the icon-only × button (new SHORTCUT_LABELS.close,
   13 locales — the glyph alone announces as "multiplication sign"). The popup
   language <select> label gains for="lang-select" so it has an accessible name.
   New e2e a11y.spec.js + unit popup-a11y.test.js (both revert-checked).

All green: lint, format, every check:* gate, 559 unit, 25 e2e.
@heznpc heznpc enabled auto-merge (squash) June 17, 2026 21:58
The companion Claude Code plugin re-exposes src/data/*.json; the catalog
title fix (7 locales) made the bundled terms.*.json copies stale. Rebuilt
via `npm run build:plugin` (no prettier on generated files).
@heznpc heznpc merged commit e463c62 into main Jun 17, 2026
9 checks passed
@heznpc heznpc deleted the fix/remaining-audit-gaps branch June 17, 2026 22:21
heznpc added a commit that referenced this pull request Jun 17, 2026
…237)

An adversarial review of PRs #234-236 found no runtime bugs (the youtube
lifecycle, dict reverts, a11y changes, and Check 7 all verified correct), but
flagged two test/gate-quality issues + one harness nit:

- Check 7 negative test was VACUOUS: it set de.claudePlatform.Skills =
  "Fähigkeiten", which is already the current value, so the mutation was a no-op
  and the test proved nothing. Rewrite it as a real-data assertion that asserts
  its precondition explicitly (de translates "Skills"; ja's _protected registers
  it; de's does not) so it can never go vacuous, then proves the full run stays
  clean — locking that Check 7 is per-locale, not a global term blocklist.
- Document Check 7's SCOPE: it catches protected terms used as standalone
  section KEYS; embedded brand tokens inside longer strings are owned by the
  runtime restore pass (a substring scan would false-positive).
- shortcutsOverlayA11y op now guards on _sb/toggleShortcutsHelp before calling,
  matching the other harness ops.

lint · format · check:dict-coverage · 9 dict-coverage unit tests · a11y e2e — green.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant