Skip to content
Open
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.

## 2024-05-24 - Keyboard Shortcut Tooltips
**Learning:** Icon-only buttons that map to keyboard shortcuts (like Escape to close details) need both a visual hint (`title="... (Key)"`) and a programmatic hint (`aria-keyshortcuts="Key"`) to fully support mouse and screen reader accessibility.
**Action:** Always include both attributes alongside `aria-label` when adding keyboard listeners to UI action buttons.
4 changes: 2 additions & 2 deletions scanner/dashboard/console.html
Original file line number Diff line number Diff line change
Expand Up @@ -158,15 +158,15 @@ <h1>AppGuardrail Console</h1>
const rows=(s.findings||[]).map(f=>`<tr><td><span class="pill" style="background:${SEV[f.severity]||'var(--info)'}">${esc(f.severity)}</span></td>
<td><code>${esc(f.rule_id)}</code></td><td>${esc((f.message||"").split("\n")[0].slice(0,120))}</td>
<td><code>${esc(f.file)}:${esc(f.line)}</code></td></tr>`).join("");
d.innerHTML=`<button type="button" class="close-btn" aria-label="Close details">βœ•</button><strong>Scan #${esc(s.id)}</strong> <span class="muted">${esc(s.created_at)} Β· ${esc(s.repo||"β€”")}</span>
d.innerHTML=`<button type="button" class="close-btn" aria-label="Close details" title="Close (Esc)" aria-keyshortcuts="Escape">βœ•</button><strong>Scan #${esc(s.id)}</strong> <span class="muted">${esc(s.created_at)} Β· ${esc(s.repo||"β€”")}</span>
<table style="margin-top:8px"><thead><tr><th scope="col">Severity</th><th scope="col">Rule</th><th scope="col">Finding</th><th scope="col">Location</th></tr></thead>
<tbody>${rows||'<tr><td colspan="4" class="muted">No findings.</td></tr>'}</tbody></table>`;
d.querySelector(".close-btn").addEventListener("click",closeDetail);
scrollDetailIntoView(d);
d.focus({preventScroll:true});
}catch(e){
if(requestId!==currentDetailRequest)return;
d.innerHTML='<button type="button" class="close-btn" aria-label="Close details">βœ•</button><div role="alert" class="err">Error loading details: '+esc(e.message)+'</div>';
d.innerHTML='<button type="button" class="close-btn" aria-label="Close details" title="Close (Esc)" aria-keyshortcuts="Escape">βœ•</button><div role="alert" class="err">Error loading details: '+esc(e.message)+'</div>';
d.querySelector(".close-btn").addEventListener("click",closeDetail);
scrollDetailIntoView(d);
d.focus({preventScroll:true});
Expand Down
7 changes: 7 additions & 0 deletions tests/test_console_accessibility_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ def test_console_rows_preserve_cell_content_for_screen_readers():

assert 'role="button" title="View scan details"' in html
assert 'aria-label="View scan details"' not in html

def test_console_close_button_exposes_shortcut():
"""The close button must expose its Escape key shortcut."""
html = _console_html()

assert 'title="Close (Esc)"' in html
assert 'aria-keyshortcuts="Escape"' in html
Loading