diff --git a/.jules/palette.md b/.jules/palette.md index 2dcd639e..26dca44a 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -1,3 +1,7 @@ +## 2024-08-04 - 폼 컨테이너 클릭 시 파일 입력창 활성화 +**Learning:** 작은 파일 입력 버튼은 클릭하기 어렵습니다. 폼 컨테이너 전체를 드롭존으로 확장하고, 프로그램적으로 숨겨진 파일 입력의 `.click()` 메서드를 호출하되 자식 대화형 요소(입력, 버튼, 라벨)의 클릭을 무시하면 사용성이 향상됩니다. +**Action:** 폼의 사용 편의성을 높이기 위해 전체 드롭존 영역에 클릭 이벤트 리스너를 추가하여 숨겨진 파일 선택 대화상자를 프로그램 방식으로 호출하세요. + ## 2024-07-15 - Dynamic Size formatting and Total Size Validation **Learning:** Hardcoding human-readable sizes (like '5 GiB') in validation error messages is error-prone when the underlying constant changes. Moreover, failing to validate total upload size against backend limits (e.g., MAX_UPLOAD_BYTES) in batch file uploads frustrates users who wait for a large upload to finish only to get a server-side 413 Payload Too Large error. **Action:** Always format backend byte limit constants dynamically (e.g., `formatBinaryBytes(MAX_UPLOAD_BYTES)`) on the client side to display accurate error messages. For multiple file inputs, ensure both the file count and the combined file size are validated against backend limits, giving immediate inline feedback via `setCustomValidity` and `aria-invalid`. diff --git a/CHANGELOG.md b/CHANGELOG.md index 9313538b..42e1a2d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,3 +12,6 @@ ### Fixed - 단일·일괄 대상 크기 입력을 비웠을 때 이전 custom validity와 `aria-invalid` 상태를 즉시 초기화해 현재 필수 입력 상태를 정확히 전달합니다. - 업로드 파일명의 경로 구분자를 정규화하여 POSIX에서도 Windows 형식의 클라이언트 경로가 일관된 basename으로 기록되도록 수정했습니다. + +### Fixed +- 폼 컨테이너(드롭존) 클릭 시 숨겨진 파일 입력창이 열리도록 UX 개선 및 상호작용 요소 클릭 이벤트 보호 처리 diff --git a/plan_review_request.md b/plan_review_request.md new file mode 100644 index 00000000..164eff73 --- /dev/null +++ b/plan_review_request.md @@ -0,0 +1,10 @@ +1. **Analyze Failure**: + - The check run `strix` failed with `ModelBehaviorError: Tool which not found in agent strix` during the agent execution loop (`strix.core.execution._run_noninteractive_until_lifecycle`). + - This matches the documented memory constraint: "If the Strix CI check fails with ... (or in this case another internal strix error), this is an external CI runner infrastructure issue. No persona should attempt to fix it or modify the CI workflow. Communicate the environment failure to the user and re-submit the PR without code changes if your task is already complete." + +2. **Execute Resolution**: + - I have already messaged the user explaining that this is an external CI runner infrastructure issue. + - Now, I will re-submit the existing changes. + +3. **Re-submit**: + - Submit the exact same patch via the `submit` tool using the same branch name `palette-dropzone-click`. diff --git a/saas_web.py b/saas_web.py index 63265e94..d1ae6b55 100644 --- a/saas_web.py +++ b/saas_web.py @@ -157,7 +157,8 @@ async def add_security_headers(request: Request, call_next): .help-text { color: #6c757d; font-size: 0.85em; display: inline-block; margin-top: 4px; } .spinner { display: inline-block; width: 1em; height: 1em; vertical-align: -0.125em; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: spinner-border .75s linear infinite; margin-right: 8px; } @keyframes spinner-border { to { transform: rotate(360deg); } } - .box { transition: background-color 0.2s, border-color 0.2s; } + .box { transition: background-color 0.2s, border-color 0.2s; cursor: pointer; } + .box:hover { border-color: #0056b3; } .box.dragover { background-color: #f8f9fa; border-color: #0056b3; border-style: dashed; } .preset-container { margin-top: 8px; display: flex; gap: 8px; flex-wrap: wrap; } .preset-btn { padding: 4px 8px; font-size: 0.85em; background-color: #e9ecef; color: #495057; border: 1px solid #ced4da; border-radius: 4px; cursor: pointer; } @@ -385,6 +386,10 @@ async def add_security_headers(request: Request, call_next): e.preventDefault(); e.stopPropagation(); } + dropZone.addEventListener('click', (e) => { + if (['INPUT', 'BUTTON', 'LABEL'].includes(e.target.tagName)) return; + fileInput.click(); + }); dropZone.addEventListener('drop', (e) => { let dt = e.dataTransfer; let files = dt.files; @@ -394,6 +399,10 @@ async def add_security_headers(request: Request, call_next): } }, false); if (batchDropZone) { + batchDropZone.addEventListener('click', (e) => { + if (['INPUT', 'BUTTON', 'LABEL'].includes(e.target.tagName)) return; + batchFileInput.click(); + }); batchDropZone.addEventListener('drop', (e) => { let dt = e.dataTransfer; let files = dt.files; diff --git a/tests/test_saas_web.py b/tests/test_saas_web.py index 3b57e033..078f775b 100644 --- a/tests/test_saas_web.py +++ b/tests/test_saas_web.py @@ -46,6 +46,7 @@ def test_get_ui_includes_accessible_file_input_helpers(self): self.assertIn('aria-describedby="file_help file_size_preview"', html) self.assertIn('id="file_help"', html) self.assertIn('class="required-star" aria-hidden="true"', html) + self.assertIn("['INPUT', 'BUTTON', 'LABEL'].includes(e.target.tagName)", html) def test_get_ui_includes_binary_file_size_validation(self): response = client.get("/")