Skip to content
Closed
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 @@ -61,3 +61,7 @@
## 2026-08-06 - Dashboard status semantics
**Learning:** A loaded report with zero findings is a successful security outcome, not the same state as missing input. Announcing the same update through multiple live regions can also create duplicate screen-reader output.
**Action:** Keep one pre-existing polite, atomic status region; separate unloaded and clean-report states with an explicit loaded sentinel; and centralize English finding-count grammar in one formatter.

## 2026-08-06 - Proxying Native File Inputs
**Learning:** Native `<input type="file">` elements are notoriously difficult to style consistently across browsers and look out-of-place in polished header navigation bars. However, completely replacing them can break accessibility or violate CSP if done incorrectly.
**Action:** Always replace visually unappealing native file inputs with a styled proxy `<button>` (using existing design system tokens like `class="primary-action"` or inline styles based on `--surface` and `--border`) and hide the actual input using `.sr-only` and `tabindex="-1"`. Connect the button to the input securely using a JavaScript `.addEventListener('click', ...)` to trigger `.click()` on the input.
8 changes: 7 additions & 1 deletion scanner/dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@
<span class="logo"></span><span class="brand">AppGuardrail</span>
<span class="spacer"></span>
<span class="meta" id="src">no findings loaded</span>
<input type="file" id="file" accept="application/json,.json" aria-label="Upload findings file" style="margin-left:12px">
<button type="button" id="header-browse" class="tag" style="margin-left:12px; cursor:pointer" aria-controls="file">Upload report</button>
<input type="file" id="file" accept="application/json,.json" class="sr-only" tabindex="-1" aria-hidden="true">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐ŸŽฏ Functional Correctness | ๐ŸŸก Minor | โšก Quick win

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- relevant HTML ---'
sed -n '80,105p' scanner/dashboard/index.html
printf '%s\n' '--- file input references ---'
rg -n -C 4 'fileInput|id="file"|sr-only|aria-hidden|tabindex' scanner/dashboard/index.html
printf '%s\n' '--- stylesheet definitions ---'
rg -n -C 5 '\.sr-only|sr-only' scanner/dashboard

Repository: ContextualWisdomLab/appguardrail

Length of output: 8332


๐ŸŒ Web query:

WAI-ARIA aria-hidden focusable elements tabindex -1 programmatic focus native file input click label display none specification

๐Ÿ’ก Result:

Using aria-hidden="true" on focusable elements is strongly discouraged by accessibility specifications and best practices because it creates inconsistent and often broken experiences for assistive technology (AT) users [1][2]. Focusable Elements and aria-hidden While the aria-hidden="true" attribute is intended to remove an element and its children from the accessibility tree, browsers and AT often struggle to handle focusable elements marked as aria-hidden [1][2]. If an element is focusable (e.g., has a positive or default tabindex, or is a native interactive element like a button or input), it remains in the tab order even if marked aria-hidden [3][2]. When a user focuses these elements, browser behavior is not interoperable: some AT may announce them without a name, some may ignore them entirely, and others may treat them normally [2]. Specifications are actively evolving to improve how user agents handle these scenarios to mitigate author errors, but reliance on this behavior is unsafe [4][5]. The modern, recommended alternative for removing content from the accessibility tree while also preventing keyboard interaction is the HTML inert attribute [2]. Native File Input Styling and Accessibility To style a native file input, the industry-standard "visually hidden label" pattern is the only universally accessible approach [6][7]. 1. Do not use display: none or visibility: hidden on the input: These CSS properties remove the element from the accessibility tree and, in many browsers, will prevent it from receiving focus or responding to events [1][6][7]. 2. Use the visually hidden (clip) technique: Hide the using CSS that keeps it in the document flow but makes it invisible (e.g., absolute positioning, clipping, or zero-dimension sizing) [6][7]. 3. Associate with a label: Use a with a for attribute matching the input's id [6]. When a user clicks the label, the browser natively triggers the file picker dialog [8][6]. This is a "trusted" event, which is required for security reasons [9]. 4. Avoid programmatic click delegation: Attempting to trigger the file picker via JavaScript (e.g., element.click) on a hidden input frequently fails because browsers block such actions unless they are part of a trusted, direct user gesture [8][9]. Relying on the native label association ensures the browser trusts the interaction [6][9]. In summary, always ensure interactive elements remain exposed to the accessibility tree (avoid aria-hidden="true" on them) and use the native label association to trigger file inputs rather than trying to force programmatic clicks [2][6][7].

Citations:


aria-hidden="true"๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  ํŒŒ์ผ ์ž…๋ ฅ์˜ ์ ‘๊ทผ์„ฑ ๊ฒฝ๋กœ๋ฅผ ์ˆ˜์ •ํ•˜์„ธ์š”.

tabindex="-1"๋Š” ํƒญ ์ˆœ์„œ์—์„œ๋งŒ ์ œ์™ธํ•˜๋ฉฐ, ์Šคํฌ๋ฆฝํŠธ ํฌ์ปค์Šค๋Š” ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ํ˜„์žฌ์˜ .sr-only ํŒŒ์ผ ์ž…๋ ฅ์— aria-hidden="true"๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์ ‘๊ทผ์„ฑ ํŠธ๋ฆฌ์™€ ํฌ์ปค์Šค ๊ฐ€๋Šฅ ์ƒํƒœ๊ฐ€ ์ถฉ๋Œํ•ฉ๋‹ˆ๋‹ค. aria-hidden์„ ์ œ๊ฑฐํ•˜๊ณ  ํŒŒ์ผ ์ž…๋ ฅ์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„์„ ์ œ๊ณตํ•˜์„ธ์š”. ํŒŒ์ผ ์„ ํƒ์„ ํ”„๋ก์‹œํ•˜๋ ค๋ฉด <label for="file"> ํŒจํ„ด์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

๐Ÿค– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scanner/dashboard/index.html` at line 96, Update the file input with id file
by removing aria-hidden="true" and providing an accessible name, using an
associated label with for="file" if file selection is triggered through a proxy.
Preserve tabindex="-1" and the existing sr-only styling.

</header>
<p id="findings-summary" class="sr-only" role="status" aria-live="polite" aria-atomic="true"></p>
<main id="app"></main>
Expand Down Expand Up @@ -312,6 +313,11 @@ <h1>Dashboard</h1>
}

const fileInput = document.getElementById('file');
const headerBrowseBtn = document.getElementById('header-browse');
if(headerBrowseBtn) {
headerBrowseBtn.addEventListener('click', () => fileInput.click());
}

fileInput.addEventListener('change', () => {
const selectedFile = fileInput.files?.[0];
fileInput.value = '';
Expand Down
1 change: 0 additions & 1 deletion tests/test_dashboard_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ def test_dashboard_rows_are_keyboard_accessible():
assert 'tabindex="0" role="button"' in html
assert 'title="View details for finding"' in html
assert "tbody tr:focus-visible" in html
assert "aria-label=\"Upload findings file\"" in html
assert "aria-label=\"Search findings\"" in html
assert "aria-label=\"Filter by severity\"" in html
assert "tr.addEventListener('keydown'" in html
Expand Down
Loading