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

## 2026-08-15 - Native file inputs replacement
**Learning:** `aria-hidden="true"`는 파일 입력 요소를 접근성 트리에서 제거한다. native input을 숨기고 접근성을 보장하기 위해서 프록시 버튼을 사용할 수 있다.
**Action:** `aria-hidden="true"`, `class="sr-only" tabindex="-1"`을 native input에 적용하여 숨기고 proxy button을 통해 동작을 실행한다.
12 changes: 11 additions & 1 deletion scanner/dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,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">
<input type="file" id="file" accept="application/json,.json" aria-label="Upload findings file" class="sr-only" tabindex="-1" aria-hidden="true">
<button type="button" id="file-proxy-btn" class="primary-action">Upload findings</button>
</header>
<p id="findings-summary" class="sr-only" role="status" aria-live="polite" aria-atomic="true"></p>
<main id="app" tabindex="-1"></main>
Expand Down Expand Up @@ -388,6 +389,15 @@ <h1>Dashboard</h1>
if(isDeployBlocking(f)!==expect) console.error('selftest FAIL', f, 'expected', expect);
}
})();

// proxy file upload for a11y
(function() {
const proxyBtn = document.getElementById('file-proxy-btn');
const actualInput = document.getElementById('file');
if (proxyBtn && actualInput) {
proxyBtn.addEventListener('click', () => actualInput.click());
}
})();
</script>
</body>
</html>
21 changes: 21 additions & 0 deletions tests/test_dashboard_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from html.parser import HTMLParser

import pytest
from pathlib import Path

from scanner.cli.appguardrail import (dashboard_index_path,
dashboard_tokens_path,
Expand Down Expand Up @@ -302,3 +303,23 @@ def test_dashboard_search_escape_clears_input():
assert "e.key === 'Escape'" in html
assert "query = '';" in html
assert "render();" in html


def test_dashboard_file_upload_proxy_is_accessible():
"""Verify that the native file input is fully hidden and proxy button handles upload."""
html = dashboard_index_path().read_text(encoding="utf-8")

# Assert exact hiding attributes on the native input
assert 'class="sr-only"' in html and 'tabindex="-1"' in html and 'aria-hidden="true"' in html
assert 'type="file"' in html and 'id="file"' in html

# Assert the proxy button exists
assert 'id="file-proxy-btn"' in html and 'class="primary-action"' in html

# Assert that JS proxy listener is bound
assert "proxyBtn.addEventListener('click', () => actualInput.click())" in html

def test_file_upload_proxy_accessibility_contract_is_documented_accurately():
"""Ensure the specific learning about AOM removal is documented."""
journal = (Path(__file__).parent.parent / ".jules" / "palette.md").read_text(encoding="utf-8")
assert '`aria-hidden="true"`는 파일 입력 요소를 접근성 트리에서 제거' in journal
Loading