diff --git a/.jules/palette.md b/.jules/palette.md index ea004e2d..a7a77cb4 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -81,3 +81,7 @@ ## 2026-08-12 - Skip to Content Accessibility **Learning:** Screen reader and keyboard-only users experience significant friction when forced to navigate through repetitive header controls on every page load. **Action:** Keep a visible-on-focus skip link as the first interactive element, target a programmatically focusable main container, and give the focused link a high-contrast outline. + +## 2025-02-18 - External Links Context Switch Accessibility +**Learning:** External links with `target="_blank"` cause a sudden context switch, which can disorient screen reader users if they are not explicitly warned beforehand. +**Action:** When using `target="_blank"`, explicitly warn users of the context switch by adding a visible advisory hint (like `↗`) with visually hidden text (like ` (opens in a new tab)`), rather than a universal screen-reader-only rule or replacing the visible label in the accessible name. diff --git a/scanner/dashboard/index.html b/scanner/dashboard/index.html index 132bc31b..be5cb296 100644 --- a/scanner/dashboard/index.html +++ b/scanner/dashboard/index.html @@ -281,7 +281,7 @@

Dashboard

function openDetail(f){ lastFocus = document.activeElement; const s = String(f.severity||'INFO').toUpperCase(); - const refs = (f.references||[]).map(r=>`${esc(r)}`).join('
'); + const refs = (f.references||[]).map(r=>`${esc(r)} (opens in a new tab)`).join('
'); const owasp = (f.owasp||[]).join(', '); const cwe = (f.cwe||[]).join(', '); const d = document.getElementById('detail'); diff --git a/tests/test_dashboard_external_link_context_contract.py b/tests/test_dashboard_external_link_context_contract.py new file mode 100644 index 00000000..650e52f1 --- /dev/null +++ b/tests/test_dashboard_external_link_context_contract.py @@ -0,0 +1,18 @@ +"""Accessibility contract for dashboard links that open a new browser tab.""" + +from pathlib import Path + + +_DASHBOARD = Path("scanner/dashboard/index.html") + + +def test_external_reference_link_warns_visually_and_in_accessible_name() -> None: + """New-tab references keep the visible URL and expose a perceivable context hint.""" + source = _DASHBOARD.read_text(encoding="utf-8") + + assert 'target="_blank" rel="noopener"' in source + assert 'aria-label="${esc(r)} (opens in a new tab)"' not in source + assert ( + '${esc(r)} ' + ' (opens in a new tab)' + ) in source