Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@
## 2026-06-10 - Add inline form helpers and client-side validation
**Learning:** Combining aria-describedby for helper text and aria-hidden on visual required indicators improves screen reader clarity while providing necessary visual cues to sighted users.
**Action:** Always pair visible required markers with aria-hidden, relying on the native required attribute for semantics, and link helper text with aria-describedby.
## 2024-05-24 - Dynamic Human-Readable File Size Preview
**Learning:** Users often struggle to conceptualize large numbers in bytes (e.g., 2000000000), leading to magnitude errors. Providing a live, ARIA-announced preview in familiar units (MB, GB) right next to the input drastically improves confidence and prevents submission mistakes.
**Action:** Always pair raw byte inputs with a dynamic, human-readable preview using an `aria-live` region.
15 changes: 13 additions & 2 deletions saas_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,30 @@
<body>
<div class="box">
<h2>Shrink Media File</h2>
<form action="/shrink" method="post" enctype="multipart/form-data" onsubmit="const btn = document.getElementById('submit-btn'); setTimeout(() => { btn.disabled = true; btn.innerText = 'Processing...'; }, 10);">
<form action="/shrink" method="post" enctype="multipart/form-data" id="shrink-form">
<p>
<label for="file">Media File: <span class="required-star" aria-hidden="true">*</span></label><br>
<input type="file" id="file" name="file" accept="audio/*,video/*" aria-describedby="file_help" required>
<br><span id="file_help" class="help-text">Select an audio or video file to shrink.</span>
</p>
<p>
<label for="target_bytes">Target Bytes: <span class="required-star" aria-hidden="true">*</span></label><br>
<input type="number" id="target_bytes" name="target_bytes" value="2000000000" min="1" aria-describedby="target_bytes_help" required>
<input type="number" id="target_bytes" name="target_bytes" value="2000000000" min="1" aria-describedby="target_bytes_help target_bytes_preview" required oninput="const val = parseInt(this.value, 10); const preview = document.getElementById('target_bytes_preview'); if (isNaN(val) || val <= 0) { preview.innerText = ''; } else if (val < 1000) { preview.innerText = val + ' B'; } else if (val < 1000000) { preview.innerText = (val / 1000).toFixed(2) + ' KB'; } else if (val < 1000000000) { preview.innerText = (val / 1000000).toFixed(2) + ' MB'; } else { preview.innerText = (val / 1000000000).toFixed(2) + ' GB'; }">
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
<br><span id="target_bytes_help" class="help-text">Maximum allowed file size in bytes (e.g., 2000000000 for ~2GB)</span>
<br><span id="target_bytes_preview" class="help-text" aria-live="polite" style="font-weight: bold; color: #28a745;">2.00 GB</span>
</p>
<button type="submit" id="submit-btn">Upload and Shrink</button>
</form>
<script>
document.getElementById('shrink-form').addEventListener('submit', function() {
const btn = document.getElementById('submit-btn');
setTimeout(() => {
btn.disabled = true;
btn.innerText = 'Processing...';
btn.setAttribute('aria-busy', 'true');
}, 10);
});
</script>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
</div>
</body>
</html>
Expand Down
11 changes: 11 additions & 0 deletions tests/test_saas_web.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ def test_get_ui_includes_accessible_file_input_helpers(self):
self.assertIn('id="file_help"', html)
self.assertIn('class="required-star" aria-hidden="true"', html)

def test_get_ui_includes_accessible_target_size_preview(self):
response = client.get("/")
self.assertEqual(response.status_code, 200)
html = response.text

self.assertIn('aria-describedby="target_bytes_help target_bytes_preview"', html)
self.assertIn('id="target_bytes_preview"', html)
self.assertIn('aria-live="polite"', html)
self.assertIn(">2.00 GB</span>", html)
self.assertIn("btn.setAttribute('aria-busy', 'true')", html)

@patch("saas_web.media_shrinker.convert_file")
@patch("saas_web.tempfile.mkdtemp")
def test_shrink_media_success(self, mock_mkdtemp, mock_convert_file):
Expand Down