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 @@ -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 `<tr>` 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.
10 changes: 7 additions & 3 deletions scanner/dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ <h1>No findings loaded</h1>
const rows = filtered.map(({f,i})=>{
const s = String(f.severity||'INFO').toUpperCase();
const block = isDeployBlocking(f);
return `<tr data-i="${i}" tabindex="0" role="button" aria-label="View details for finding">
return `<tr data-i="${i}" tabindex="0" role="button" aria-label="View details: ${esc(f.rule_id)} in ${esc(f.file)}">
<td><span class="chip" style="background:${SEV[s]?SEV[s].color:'var(--info)'}">${s}</span></td>
<td>${esc(f.message)}</td>
<td class="file">${esc(f.file)}:${esc(f.line)}</td>
Expand All @@ -163,9 +163,13 @@ <h1>No findings loaded</h1>
</tr>`;
}).join('');

const subText = filtered.length !== ALL.length
? `Showing ${filtered.length} of ${ALL.length} findings · <strong>${blocking}</strong> deploy-blocking (gate ${blocking?'active':'clear'})`
: `${ALL.length} findings · <strong>${blocking}</strong> deploy-blocking (gate ${blocking?'active':'clear'})`;

app.innerHTML = `
<h1>Dashboard</h1>
<p class="sub">${ALL.length} findings · <strong>${blocking}</strong> deploy-blocking (gate ${blocking?'active':'clear'})</p>
<p class="sub" aria-live="polite">${subText}</p>
<div class="cards">${cards}
<div class="card"><div class="lbl"><span class="dot" style="background:var(--primary)"></span>Deploy-blocking</div><div class="n">${blocking}</div></div>
</div>
Expand All @@ -181,7 +185,7 @@ <h1>Dashboard</h1>
</div>
<div class="panel"><table>
<thead><tr><th>Severity</th><th>Finding</th><th>File</th><th>Category</th><th>Status</th></tr></thead>
<tbody>${rows||'<tr><td colspan="5" style="color:var(--muted);padding:24px">No findings match the filter.</td></tr>'}</tbody>
<tbody>${rows||'<tr><td colspan="5" style="color:var(--muted);padding:24px;text-align:center">No findings match your criteria. Try adjusting your search or severity filter.</td></tr>'}</tbody>
</table></div>
</div>
</div>`;
Expand Down
2 changes: 1 addition & 1 deletion tests/test_dashboard_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading