diff --git a/.jules/palette.md b/.jules/palette.md index ea004e2d..fe85b31d 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. + +## 2024-09-03 - Added Proxy Button for File Input in Header +**Learning:** In zero-build dashboard (scanner/dashboard/index.html), the native does not support firing the change event when a user attempts to select the same file path sequentially. Additionally, hiding the file input and exposing a proxy +

@@ -325,6 +326,7 @@

Dashboard

} const fileInput = document.getElementById('file'); +document.getElementById('header-browse').addEventListener('click', () => fileInput.click()); fileInput.addEventListener('change', () => { const selectedFile = fileInput.files?.[0]; fileInput.value = ''; diff --git a/tests/test_dashboard_core.py b/tests/test_dashboard_core.py index 75a5809f..be738615 100644 --- a/tests/test_dashboard_core.py +++ b/tests/test_dashboard_core.py @@ -77,7 +77,7 @@ 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 ">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 diff --git a/tests/test_dashboard_upload_proxy_contract.py b/tests/test_dashboard_upload_proxy_contract.py new file mode 100644 index 00000000..66e8405e --- /dev/null +++ b/tests/test_dashboard_upload_proxy_contract.py @@ -0,0 +1,38 @@ +"""Contracts for the dashboard file-upload proxy control.""" + +from __future__ import annotations + +import re +from pathlib import Path + + +DASHBOARD = Path("scanner/dashboard/index.html") + + +def _header_upload_button(html: str) -> str: + """Return the shipped header upload button tag.""" + match = re.search(r']*\bid="header-browse"[^>]*>', html) + assert match is not None, "header upload proxy button must exist" + return match.group(0) + + +def test_header_upload_proxy_preserves_primary_action_touch_target() -> None: + """The header proxy must not override the shared 44px primary-action target.""" + html = DASHBOARD.read_text(encoding="utf-8") + button = _header_upload_button(html) + + assert 'class="primary-action"' in button + assert "min-height:auto" not in button.replace(" ", "") + assert "padding:6px 12px" not in button + + +def test_header_upload_proxy_keeps_visible_name_and_native_file_boundary() -> None: + """The visible button owns the name while the hidden native input owns selection.""" + html = DASHBOARD.read_text(encoding="utf-8") + + assert ">Upload findings file" in html + assert '' in html + assert "document.getElementById('header-browse').addEventListener('click', () => fileInput.click())" in html + # Same-file re-selection is enabled by clearing the native input after handling it; + # it is not a property of the proxy button itself. + assert "fileInput.value = '';" in html