diff --git a/.jules/palette.md b/.jules/palette.md index 4075499..d36f05a 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -9,3 +9,7 @@ ## 2024-08-05 - Dynamic Rendering Cursor Position **Learning:** When recreating input elements dynamically during client-side rendering (e.g. updating `innerHTML` after a search keystroke), re-focusing the element isn't enough. Simply placing the cursor at the end of the value disrupts users who are editing text in the middle of a string. **Action:** Always capture `e.target.selectionStart` and `e.target.selectionEnd` before the DOM is replaced, and use `el.setSelectionRange(start, end)` after the element is re-rendered to maintain a seamless typing experience. + +## 2024-09-06 - Table Row Button Accessibility +**Learning:** When using `role="button"` on a `` element, screen readers will announce the element as a button. However, the generic `aria-label="View details for finding"` overrides the entire row's text content, making it impossible for users to know *which* finding they are activating. Furthermore, dynamic filtering needs `aria-live="polite"` on the result count so users know their search or filter changed the number of results. +**Action:** Always provide unique context in `aria-label` attributes on rows or buttons (e.g., incorporating the file name or ID). Always wrap filter result counts in `aria-live="polite"` regions. diff --git a/scanner/dashboard/index.html b/scanner/dashboard/index.html index 398ea8e..af239ea 100644 --- a/scanner/dashboard/index.html +++ b/scanner/dashboard/index.html @@ -154,7 +154,7 @@

No findings loaded

const rows = filtered.map(({f,i})=>{ const s = String(f.severity||'INFO').toUpperCase(); const block = isDeployBlocking(f); - return ` + return ` ${s} ${esc(f.message)} ${esc(f.file)}:${esc(f.line)} @@ -163,9 +163,13 @@

No findings loaded

`; }).join(''); + const subText = filtered.length !== ALL.length + ? `Showing ${filtered.length} of ${ALL.length} findings · ${blocking} deploy-blocking (gate ${blocking?'active':'clear'})` + : `${ALL.length} findings · ${blocking} deploy-blocking (gate ${blocking?'active':'clear'})`; + app.innerHTML = `

Dashboard

-

${ALL.length} findings · ${blocking} deploy-blocking (gate ${blocking?'active':'clear'})

+

${subText}

${cards}
Deploy-blocking
${blocking}
@@ -181,7 +185,7 @@

Dashboard

- ${rows||''} + ${rows||''}
SeverityFindingFileCategoryStatus
No findings match the filter.
No findings match your criteria. Try adjusting your search or severity filter.
`; diff --git a/tests/test_dashboard_core.py b/tests/test_dashboard_core.py index 4759263..aad4325 100644 --- a/tests/test_dashboard_core.py +++ b/tests/test_dashboard_core.py @@ -48,7 +48,7 @@ def test_dashboard_rows_are_keyboard_accessible(): html = dashboard_index_path().read_text(encoding="utf-8") assert 'tabindex="0" role="button"' in html - assert 'aria-label="View details for finding"' in html + assert 'aria-label="View details: ${esc(f.rule_id)} in ${esc(f.file)}"' in html assert "tbody tr:focus-visible" in html assert "aria-label=\"Upload findings file\"" in html assert "aria-label=\"Search findings\"" in html