diff --git a/.jules/palette.md b/.jules/palette.md
index 2dcd639e..d007d637 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -1,3 +1,7 @@
+## 2026-08-10 - [Make drop zones clickable]
+**Learning:** Users often try to click large drop zones instead of just the 'Choose File' button. Wrapping the form in a clickable area while ignoring interactive children provides a larger, more intuitive hit target without breaking standard form controls.
+**Action:** Always add a click listener to the drop zone container that triggers the hidden/small file input, ensuring to ignore clicks on child inputs, buttons, and labels.
+
## 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 4cca1ced..2f595162 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,3 +11,4 @@
### Fixed
- 단일·일괄 대상 크기 입력을 비웠을 때 이전 custom validity와 `aria-invalid` 상태를 즉시 초기화해 현재 필수 입력 상태를 정확히 전달합니다.
+- 드롭 영역 클릭 시 파일 입력창이 열리도록 개선 (UX 향상)
diff --git a/saas_web.py b/saas_web.py
index 0ef95a1e..7a2d4ba0 100644
--- a/saas_web.py
+++ b/saas_web.py
@@ -191,6 +191,7 @@ async def add_security_headers(request: Request, call_next):
diff --git a/tests/test_saas_web.py b/tests/test_saas_web.py
index cd45dbc3..56e9a1c0 100644
--- a/tests/test_saas_web.py
+++ b/tests/test_saas_web.py
@@ -57,6 +57,25 @@ def test_get_ui_includes_binary_file_size_validation(self):
self.assertIn("Total file size exceeds ' + limitText + ' limit.", html)
self.assertIn("preview.style.color = '#0f6674';", html)
self.assertIn('onchange="updateFileSizePreview(this)"', html)
+ self.assertIn("e.target instanceof Element", html)
+ self.assertIn("e.target.closest('input, button, label')", html)
+ self.assertNotIn("['INPUT', 'BUTTON', 'LABEL'].includes(e.target.tagName)", html)
+ self.assertIn("fileInput.click();", html)
+
+ def test_get_ui_defers_listener_setup_until_batch_markup_exists(self):
+ response = client.get("/")
+ self.assertEqual(response.status_code, 200)
+ html = response.text
+
+ self.assertIn("function initializeUi()", html)
+ self.assertIn("if (document.readyState === 'loading')", html)
+ self.assertIn(
+ "document.addEventListener('DOMContentLoaded', initializeUi, { once: true });",
+ html,
+ )
+ self.assertIn("initializeUi();", html)
+ self.assertIn("window.updateFileSizePreview = updateFileSizePreview;", html)
+ self.assertIn("window.updateBatchFilePreview = updateBatchFilePreview;", html)
def test_security_headers_present_without_plain_http_hsts(self):
response = client.get("/")