diff --git a/.jules/palette.md b/.jules/palette.md index ea004e2d2..7759242ed 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -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을 통해 동작을 실행한다. diff --git a/scanner/dashboard/index.html b/scanner/dashboard/index.html index 132bc31b2..fd01e0b8f 100644 --- a/scanner/dashboard/index.html +++ b/scanner/dashboard/index.html @@ -93,7 +93,8 @@ AppGuardrail no findings loaded - + +

@@ -388,6 +389,15 @@

Dashboard

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()); + } +})(); \ No newline at end of file diff --git a/tests/test_dashboard_core.py b/tests/test_dashboard_core.py index 75a5809f8..e7094f40e 100644 --- a/tests/test_dashboard_core.py +++ b/tests/test_dashboard_core.py @@ -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, @@ -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