From 3c563be54e576878f717bace8f833b63ecdf0242 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 2 Aug 2026 21:12:32 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20[UX=20improvement]=20?= =?UTF-8?q?=EB=8F=99=EC=A0=81=20=ED=8C=8C=EC=9D=BC=20=ED=81=AC=EA=B8=B0=20?= =?UTF-8?q?=EC=A0=9C=ED=95=9C=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=B0=8F=20?= =?UTF-8?q?=EC=9D=BC=EA=B4=84=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=B4=9D=20?= =?UTF-8?q?=ED=81=AC=EA=B8=B0=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/palette.md | 3 +++ CHANGELOG.md | 1 + saas_web.py | 13 +++++++++++-- tests/test_saas_web.py | 4 +++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/.jules/palette.md b/.jules/palette.md index a1cf208..956ecf2 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -1,3 +1,6 @@ +## 2026-08-02 - Dynamic Limits and Client Side Form Validation +**Learning:** Client-side file size validations using hardcoded UI text values fall out of sync easily when logic bounds like `MAX_UPLOAD_BYTES` are changed, degrading user experience. Validating the combined total size of batched file uploads provides better immediate inline feedback. +**Action:** Use `formatBinaryBytes(MAX_UPLOAD_BYTES)` in JavaScript code to construct file limit strings dynamically, keeping UI validation messages consistent with server-side limits. Ensure batch uploads evaluate total file sizes to give immediate feedback before lengthy submission attempts. ## 2024-07-12 - Intercepting batch form submissions for testing visual loading states **Learning:** Extending the learning from 2024-06-13, intercepting form submissions using `e.preventDefault()` via `page.evaluate()` is essential for capturing screenshot and video evidence of loading states (e.g., button disabling, spinner appearing) on forms like batch upload where the submission would normally reload the page or download an archive. **Action:** When testing visual loading states with Playwright, always inject an event listener using `page.evaluate()` to call `e.preventDefault()` on the form's `submit` event to freeze the UI in its loading state for verification. diff --git a/CHANGELOG.md b/CHANGELOG.md index ebfe94a..a3f9ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,3 +4,4 @@ ### Added - 다중 파일 업로드 선택 시 즉각적인 파일 개수 피드백 및 제한 초과 경고 메시지 추가 - 일괄 업로드 폼에 대상 바이트 프리셋 버튼과 총 파일 크기 미리보기를 추가하여 사용성을 개선했습니다. +- 다중 파일 업로드 선택 시 총 파일 크기 검증 기능과 동적 제한 메시지 추가 diff --git a/saas_web.py b/saas_web.py index 3a7b035..07f18b0 100644 --- a/saas_web.py +++ b/saas_web.py @@ -229,10 +229,11 @@ async def add_security_headers(request: Request, call_next): return; } const text = formatBinaryBytes(file.size); + const limitStr = formatBinaryBytes(MAX_UPLOAD_BYTES); if (file.size > MAX_UPLOAD_BYTES) { - input.setCustomValidity('File exceeds 5 GiB limit.'); + input.setCustomValidity('File exceeds ' + limitStr + ' limit.'); input.setAttribute('aria-invalid', 'true'); - preview.innerText = 'Selected file size: ' + text + ' (exceeds 5 GiB limit)'; + preview.innerText = 'Selected file size: ' + text + ' (exceeds ' + limitStr + ' limit)'; preview.style.color = '#dc3545'; return; } @@ -325,6 +326,14 @@ async def add_security_headers(request: Request, call_next): preview.style.color = '#dc3545'; return; } + const limitStr = formatBinaryBytes(MAX_UPLOAD_BYTES); + if (totalSize > MAX_UPLOAD_BYTES) { + input.setCustomValidity('Total size exceeds ' + limitStr + ' limit.'); + input.setAttribute('aria-invalid', 'true'); + preview.innerText = 'Selected ' + files.length + ' file(s) (' + formatBinaryBytes(totalSize) + ', total size exceeds ' + limitStr + ' limit)'; + preview.style.color = '#dc3545'; + return; + } preview.innerText = 'Selected ' + files.length + ' file(s) (' + formatBinaryBytes(totalSize) + ')'; } diff --git a/tests/test_saas_web.py b/tests/test_saas_web.py index 57b879d..fa3cdda 100644 --- a/tests/test_saas_web.py +++ b/tests/test_saas_web.py @@ -52,7 +52,9 @@ def test_get_ui_includes_binary_file_size_validation(self): self.assertIn("const MAX_UPLOAD_BYTES = 5 * 1024 * 1024 * 1024;", html) self.assertIn("['B', 'KiB', 'MiB', 'GiB']", html) - self.assertIn("File exceeds 5 GiB limit.", html) + self.assertIn("const limitStr = formatBinaryBytes(MAX_UPLOAD_BYTES);", html) + self.assertIn("File exceeds ' + limitStr + ' limit.", html) + self.assertIn("Total size exceeds ' + limitStr + ' limit.", html) self.assertIn("preview.style.color = '#0f6674';", html) self.assertIn('onchange="updateFileSizePreview(this)"', html)