Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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 .jules/palette.md
Original file line number Diff line number Diff line change
@@ -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`.
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@

### Fixed
- 단일·일괄 λŒ€μƒ 크기 μž…λ ₯을 비웠을 λ•Œ 이전 custom validity와 `aria-invalid` μƒνƒœλ₯Ό μ¦‰μ‹œ μ΄ˆκΈ°ν™”ν•΄ ν˜„μž¬ ν•„μˆ˜ μž…λ ₯ μƒνƒœλ₯Ό μ •ν™•νžˆ μ „λ‹¬ν•©λ‹ˆλ‹€.
- λ“œλ‘­ μ˜μ—­ 클릭 μ‹œ 파일 μž…λ ₯창이 열리도둝 κ°œμ„  (UX ν–₯상)
17 changes: 17 additions & 0 deletions saas_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ async def add_security_headers(request: Request, call_next):
<button type="submit" id="submit-btn">Upload and Shrink</button>
</form>
<script>
function initializeUi() {
const MAX_UPLOAD_BYTES = 5 * 1024 * 1024 * 1024;
function formatBinaryBytes(value) {
const units = ['B', 'KiB', 'MiB', 'GiB'];
Expand Down Expand Up @@ -393,6 +394,10 @@ async def add_security_headers(request: Request, call_next):
updateFileSizePreview(fileInput);
}
}, false);
dropZone.addEventListener('click', (e) => {
if (!(e.target instanceof Element) || e.target.closest('input, button, label')) return;
fileInput.click();
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (batchDropZone) {
batchDropZone.addEventListener('drop', (e) => {
let dt = e.dataTransfer;
Expand All @@ -402,6 +407,18 @@ async def add_security_headers(request: Request, call_next):
updateBatchFilePreview(batchFileInput);
}
}, false);
batchDropZone.addEventListener('click', (e) => {
if (!(e.target instanceof Element) || e.target.closest('input, button, label')) return;
batchFileInput.click();
});
}
window.updateFileSizePreview = updateFileSizePreview;
window.updateBatchFilePreview = updateBatchFilePreview;
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeUi, { once: true });
} else {
initializeUi();
}
</script>
</div>
Expand Down
19 changes: 19 additions & 0 deletions tests/test_saas_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("/")
Expand Down
Loading