Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,6 @@
## 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-10-10 - Blocking Interactions on Non-Native Loading Elements
**Learning:** Adding `aria-busy="true"` to non-native elements (like `<tr role="button">`) provides screen reader feedback but fails to natively block interactions. Keyboard and mouse users can still re-trigger async actions while they are loading.
**Action:** Always pair `aria-busy="true"` with CSS visual disabling (e.g. `pointer-events: none; opacity: 0.7;`) and explicitly add JavaScript event guards (`if (el.getAttribute('aria-busy') === 'true') return;`) in `click` and `keydown` handlers to fully block duplicate interactions.
5 changes: 3 additions & 2 deletions scanner/dashboard/console.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
.err{color:var(--crit);font-weight:600}
code{background:var(--bg);padding:1px 5px;border-radius:4px}
.hidden{display:none}
[aria-busy="true"]{pointer-events:none;opacity:0.7}
</style>
</head>
<body>
Expand Down Expand Up @@ -135,8 +136,8 @@ <h1>AppGuardrail Console</h1>
<td>${esc(s.created_at)}</td><td>${esc(s.repo||"—")}</td><td><code>${esc((s.commit||"—").slice(0,10))}</code></td>
<td>${s.total}</td><td>${pill(s.deploy_blocking,"var(--crit)")}</td><td>${pill(s.new_blocking,"var(--high)")}</td></tr>`).join("")||'<tr><td colspan="6" class="muted">No scans. POST to /api/v1/scans from CI.</td></tr>';
document.querySelectorAll("tr.scan").forEach(tr=>{
tr.onclick=()=>detail(tr.dataset.id,tr);
tr.addEventListener('keydown', e => { if(e.key === 'Enter' || e.key === ' ') { e.preventDefault(); detail(tr.dataset.id,tr); } });
tr.onclick=()=>{if(tr.getAttribute("aria-busy")==="true")return;detail(tr.dataset.id,tr);};
tr.addEventListener('keydown', e => { if(e.key === 'Enter' || e.key === ' ') { e.preventDefault(); if(tr.getAttribute("aria-busy")==="true")return; detail(tr.dataset.id,tr); } });
});
}catch(e){ $("#msg").classList.remove("hidden");$("#app").classList.add("hidden");
$("#msg").innerHTML=`<span class="err">${esc(e.message)}</span>`; }
Expand Down
Loading