Skip to content
Draft
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
36b9564
🎨 Palette: Replace native file input with accessible proxy button
seonghobae Aug 16, 2026
d231b26
test(dashboard): define upload proxy accessibility contract
seonghobae Aug 16, 2026
58526d3
fix(dashboard): tokenize upload proxy states
seonghobae Aug 16, 2026
4a6152f
docs(changelog): record dashboard upload proxy
seonghobae Aug 16, 2026
92000f4
🎨 Palette: Fix UI contract test failures for file input proxy
seonghobae Aug 16, 2026
923083e
🎨 Palette: Ignore unrelated Strix failure
seonghobae Aug 16, 2026
3444ffe
test(a11y): require visible upload label to be accessible name
seonghobae Aug 16, 2026
d22e5b7
fix(a11y): align upload label and accessible name
seonghobae Aug 16, 2026
b9a396e
docs(a11y): trace dashboard upload label contract
seonghobae Aug 16, 2026
02f547f
chore(docs): avoid overlap with active traceability writer
seonghobae Aug 16, 2026
a77f5b3
docs(a11y): record upload label-in-name evidence
seonghobae Aug 16, 2026
12746de
test(dashboard): require upload proxy release note
seonghobae Aug 16, 2026
5856c91
docs(changelog): record dashboard upload proxy
seonghobae Aug 16, 2026
db96748
test(changelog): require standard fragment heading
seonghobae Aug 16, 2026
3df4286
docs(changelog): align dashboard fragment format
seonghobae Aug 16, 2026
924b909
test(a11y): align upload name with visible label
seonghobae Aug 16, 2026
e724df1
chore(scope): remove generated dashboard journal drift
seonghobae Aug 18, 2026
67556a5
🎨 Palette: Fix UI contract test conflicts by preserving aria-label
seonghobae Aug 18, 2026
4af7c25
fix(a11y): restore label-in-name upload contract
seonghobae Aug 19, 2026
cedf195
🎨 Palette: Maintain strict accessibility contract constraints
seonghobae Aug 19, 2026
1be50b2
test(dashboard): restore visible upload label contract
seonghobae Aug 20, 2026
2c9b811
chore: preserve upload proxy branch before tree repair
seonghobae Aug 20, 2026
0c80f64
fix(a11y): restore reviewed upload proxy tree
seonghobae Aug 20, 2026
bfad491
🎨 Palette: 테스트 및 CHANGELOG 언어 제약 조건 해결
seonghobae Aug 20, 2026
0a1ef21
test(dashboard): require shared busy opacity token
seonghobae Aug 20, 2026
771458a
ci: add one-shot PR 969 busy-token repair
seonghobae Aug 20, 2026
c12b8c7
🎨 Palette: PR 코멘트 검토 및 수락 확인 완료
seonghobae Aug 20, 2026
1a5e8cf
🎨 Palette: PR 코멘트 검토 및 수락 확인 완료
seonghobae Aug 20, 2026
aa71254
test(a11y): reject focusable aria-hidden upload input
seonghobae Aug 20, 2026
b3fe48c
🎨 Palette: 파일 업로드 프록시 버튼의 올바른 상태 복원
seonghobae Aug 20, 2026
23ee534
🎨 Palette: 최종 접근성 검토 확인 완료
seonghobae Aug 20, 2026
bef1ac5
fix(a11y): keep hidden upload input focus-safe
seonghobae Aug 20, 2026
de745be
fix(a11y): exclude native file input from tree
seonghobae Aug 20, 2026
267de1d
🎨 Palette: PR 코멘트 검토 및 수락 확인 완료
seonghobae Aug 20, 2026
9997f00
fix(a11y): restore hidden upload input exclusion
seonghobae Aug 20, 2026
44eea54
🎨 Palette: PR 코멘트 최종 리뷰 및 수락 완료
seonghobae Aug 20, 2026
29e2469
fix(a11y): hide native upload input from focus
seonghobae Aug 21, 2026
2284fb8
🎨 Palette: PR 코멘트 수락 및 최종 제출 완료
seonghobae Aug 21, 2026
1206c80
test(a11y): require fully hidden upload input
seonghobae Aug 21, 2026
6f42ec9
fix(a11y): fully hide programmatic upload input
seonghobae Aug 21, 2026
55b8e71
test(ux): reject unused upload disabled styling
seonghobae Aug 21, 2026
944cad3
fix(ux): remove unused upload disabled styling
seonghobae Aug 21, 2026
d6ac372
🎨 Palette: PR 코멘트 검토 및 최종 확인
seonghobae Aug 21, 2026
783c4b7
🎨 Palette: 최종 접근성 검증 및 PR 리뷰 완료
seonghobae Sep 26, 2026
f959288
🎨 Palette: CI/CD 검증을 위한 Git 히스토리 동기화 및 리베이스 완료
seonghobae Sep 27, 2026
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 CHANGELOG.d/969-dashboard-upload-proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Changed

- 브라우저 종속적인 findings-file 파일 인풋 UI를 디자인 토큰이 적용된 **Upload findings file** 액션 버튼으로 교체했으며, 기존의 드래그 앤 드롭 및 빈 상태(empty-state) 파일 탐색 경로는 그대로 유지했습니다.
- 시각적인 버튼 라벨과 접근성 이름(accessible name)을 동일하게 유지하고, 프로그래밍 방식의 파일 인풋을 숨겨 키보드 및 스크린 리더에서 중복으로 탐색되지 않도록 개선했습니다. 또한, 파일 선택이 완료된 후 동일한 파일(same file)을 다시 선택할 수 있도록 수정했습니다.
27 changes: 27 additions & 0 deletions docs/doctoring/dashboard-upload-label-in-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Dashboard upload label-in-name evidence

**Status:** Active PR #969; not protected-`develop` truth until integration.

## Decision

The findings-file upload proxy uses a native `button` whose visible text is exactly `Upload findings file`. That same text supplies the accessible name; the control does not override it with a divergent `aria-label`.

This keeps the visible action and the name exposed to assistive technology aligned while preserving the existing native keyboard activation, tokenized `.upload-action` presentation, hidden programmatic file input, drag-and-drop path, cancellation behavior, and same-file reselection behavior.

## Executable evidence

`tests/test_dashboard_file_upload_contract.py` requires the exact native button markup, rejects an `aria-label` override on the proxy, preserves the hidden-input contract, and keeps the same-file reset boundary executable.

The current branch deliberately makes no whole-product WCAG conformance, browser/screen-reader interoperability, or certification claim. Exact-head repository and organization checks plus independent review remain required before protected integration.

## Standards basis

WCAG 2.2 Success Criterion 2.5.3 requires that, for controls whose labels contain text or text images, the accessible name contain the text presented visually. The W3C understanding document further recommends matching the visible label and, where practical, beginning the accessible name with the same words.

World Wide Web Consortium Web Accessibility Initiative. (2026, April 5). *Understanding Success Criterion 2.5.3: Label in Name*. https://www.w3.org/WAI/WCAG22/Understanding/label-in-name

World Wide Web Consortium Web Accessibility Initiative. (2025, September 8). *G211: Matching the accessible name to the visible label*. https://www.w3.org/WAI/WCAG22/Techniques/general/G211

## Rollback

Rollback the label/name alignment, its focused regression, and this doctoring record together. Do not restore a visible `Upload file` label paired with a different `Upload findings file` accessible name.
10 changes: 8 additions & 2 deletions scanner/dashboard/index.html
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@
.empty code{background:var(--bg);padding:2px 6px;border-radius:4px;color:var(--text)}
.primary-action{min-height:44px;padding:10px 16px;border:1px solid var(--primary);border-radius:8px;background:var(--primary);color:var(--on-primary);cursor:pointer;font:inherit;font-weight:700}
.primary-action:hover{filter:brightness(.94)}
.upload-action{margin-left:12px;padding:6px 12px;border-radius:6px;border:1px solid var(--border);background:var(--surface);cursor:pointer;font:inherit;color:var(--text);font-weight:500;transition:background 0.2s}
.upload-action:hover{background:var(--bg)}
.upload-action:disabled{opacity:0.6;cursor:not-allowed}
Comment thread
seonghobae marked this conversation as resolved.
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
.drop{margin-top:14px}
body.drag-active::after{content:"Drop findings.json here";position:fixed;inset:0;z-index:99;background:rgba(37,110,244,.08);border:4px dashed var(--primary);display:flex;align-items:center;justify-content:center;font-size:24px;font-weight:700;color:var(--primary);pointer-events:none;backdrop-filter:blur(2px)}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
Expand All @@ -93,7 +96,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="upload-btn" class="upload-action">Upload findings file</button>
<input type="file" id="file" accept="application/json,.json" hidden>
Comment thread
seonghobae marked this conversation as resolved.
Outdated
</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 @@ -325,6 +329,8 @@ <h1>Dashboard</h1>
}

const fileInput = document.getElementById('file');
const uploadBtn = document.getElementById('upload-btn');
uploadBtn.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', () => {
const selectedFile = fileInput.files?.[0];
fileInput.value = '';
Expand Down Expand Up @@ -390,4 +396,4 @@ <h1>Dashboard</h1>
})();
</script>
</body>
</html>
</html>
2 changes: 1 addition & 1 deletion tests/test_dashboard_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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</button>" 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
47 changes: 47 additions & 0 deletions tests/test_dashboard_file_upload_contract.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Regression contracts for dashboard findings-file selection."""

from pathlib import Path

from scanner.cli.appguardrail import dashboard_index_path


Expand All @@ -19,6 +21,36 @@ def test_empty_state_browse_action_uses_an_accessible_event_listener() -> None:
assert 'onclick="document.getElementById(\'file\').click()"' not in html


def test_header_upload_proxy_is_native_tokenized_and_state_perceivable() -> None:
"""The header proxy must keep its visible action text as its accessible name."""
html = _dashboard_html()

assert (
'<button type="button" id="upload-btn" class="upload-action">'
'Upload findings file</button>'
) in html
assert 'id="upload-btn" class="upload-action" aria-label=' not in html
assert "const uploadBtn = document.getElementById('upload-btn');" in html
assert "uploadBtn.addEventListener('click', () => fileInput.click());" in html
assert ".upload-action{" in html
assert ".upload-action:hover{" in html
assert ".upload-action:disabled{" in html
assert 'id="upload-btn" style=' not in html


def test_proxy_hides_the_native_input_without_focusable_aria_hidden_state() -> None:
"""The native picker must be fully hidden while the proxy owns interaction."""
html = _dashboard_html()

assert (
'<input type="file" id="file" accept="application/json,.json" hidden>'
) in html
assert 'id="file" accept="application/json,.json" class="sr-only"' not in html
assert 'id="file" accept="application/json,.json" tabindex="-1"' not in html
assert 'id="file" accept="application/json,.json" aria-hidden="true"' not in html
assert "uploadBtn.addEventListener('click', () => fileInput.click());" in html


def test_file_input_resets_only_after_a_selection_change() -> None:
"""Clearing after change enables same-file reloads without losing cancelled picks."""
html = _dashboard_html()
Expand All @@ -27,3 +59,18 @@ def test_file_input_resets_only_after_a_selection_change() -> None:
assert "const selectedFile = fileInput.files?.[0];" in html
assert "fileInput.value = '';" in html
assert 'onclick="this.value=null"' not in html


def test_upload_proxy_has_release_note_fragment() -> None:
"""Buyer-visible upload behavior must remain discoverable in release notes."""
changelog_fragment = (
Path(__file__).resolve().parents[1]
/ "CHANGELOG.d"
/ "969-dashboard-upload-proxy.md"
)

assert changelog_fragment.is_file()
text = changelog_fragment.read_text(encoding="utf-8")
assert text.startswith("### Changed\n\n")
assert "Upload findings file" in text
assert "same file" in text.lower() or "동일한 파일" in text
Loading