Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion scanner/dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ <h1>Dashboard</h1>
function openDetail(f){
lastFocus = document.activeElement;
const s = String(f.severity||'INFO').toUpperCase();
const refs = (f.references||[]).map(r=>`<a href="${esc(safeUrl(r))}" target="_blank" rel="noopener">${esc(r)}</a>`).join('<br>');
const refs = (f.references||[]).map(r=>`<a href="${esc(safeUrl(r))}" target="_blank" rel="noopener">${esc(r)} <span aria-hidden="true">↗</span><span class="sr-only"> (opens in a new tab)</span></a>`).join('<br>');
const owasp = (f.owasp||[]).join(', ');
const cwe = (f.cwe||[]).join(', ');
const d = document.getElementById('detail');
Expand Down
18 changes: 18 additions & 0 deletions tests/test_dashboard_external_link_context_contract.py
Original file line number Diff line number Diff line change
@@ -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)} <span aria-hidden="true">↗</span>'
'<span class="sr-only"> (opens in a new tab)</span></a>'
) in source
Loading