Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 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
3 changes: 3 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,6 @@
## 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-16 - Proxy Button for Native File Input
**Learning:** When replacing a native file input with a proxy button for better UX/consistency, visually hiding the input with `sr-only` and proxying the click via a button improves interaction without breaking accessibility contract tests, as long as `aria-label` is preserved on the new button. Furthermore, resetting `fileInput.value = ''` inside the change handler is crucial in SPAs to allow the user to select the same file consecutively.
**Action:** Always maintain the original `aria-label` on proxy elements and handle sequential file selection manually in vanilla JS SPA contexts.
8 changes: 7 additions & 1 deletion scanner/dashboard/index.html
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
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.
Resolved
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
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" aria-label="Upload findings file">Upload file</button>
<input type="file" id="file" accept="application/json,.json" class="sr-only" tabindex="-1" aria-hidden="true">
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
</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
28 changes: 28 additions & 0 deletions tests/test_dashboard_file_upload_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,34 @@ 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 stay keyboard-native and use reusable visual states."""
html = _dashboard_html()

assert (
'<button type="button" id="upload-btn" class="upload-action" '
'aria-label="Upload findings file">Upload file</button>'
) 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" aria-label=' not in html
assert 'id="upload-btn" style=' not in html


def test_proxy_hides_the_native_input_without_duplicate_accessible_naming() -> None:
"""Only the native proxy button should appear in the accessibility tree."""
html = _dashboard_html()

assert (
'<input type="file" id="file" accept="application/json,.json" '
'class="sr-only" tabindex="-1" aria-hidden="true">'
) in html
assert 'aria-hidden="true" aria-label="Upload findings file"' not 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 Down
Loading