diff --git a/.jules/palette.md b/.jules/palette.md index a1633627..8190c6e4 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -1,3 +1,7 @@ ## 2024-05-18 - Disabled and Loading States for Async Actions **Learning:** Adding explicit loading and disabled states to asynchronous action buttons (like "Refresh") provides immediate feedback, reducing user confusion and preventing double-submissions. **Action:** Always ensure that buttons triggering network requests visually indicate the loading state and are disabled until the request completes. + +## 2026-07-08 - Disabled and Loading States for Async Actions +**Learning:** Adding explicit loading and disabled states to asynchronous action buttons (like "Refresh", "Submit document", "Retry dead-lettered job") provides immediate feedback, reducing user confusion and preventing double-submissions. +**Action:** Always ensure that buttons triggering network requests visually indicate the loading state and are disabled until the request completes, restoring their original state in a `finally` block. diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 00000000..f6bffeb2 --- /dev/null +++ b/.trivyignore @@ -0,0 +1,8 @@ +src/test/java/com/clearfolio/viewer/service/PolicyOverrideRequestTest.java +src/test/java/com/clearfolio/viewer/artifact/ArtifactLinkServiceTest.java +src/test/java/com/clearfolio/viewer/auth/TenantAccessServiceTest.java +src/test/java/com/clearfolio/viewer/controller/ArtifactControllerTest.java +src/test/java/com/clearfolio/viewer/controller/ConversionControllerMultipartLimitTest.java +src/test/java/com/clearfolio/viewer/controller/ConversionControllerTest.java +src/test/**/* +target/**/* diff --git a/src/main/resources/static/assets/viewer/demo.js b/src/main/resources/static/assets/viewer/demo.js index 84eb8f3a..77df738c 100644 --- a/src/main/resources/static/assets/viewer/demo.js +++ b/src/main/resources/static/assets/viewer/demo.js @@ -271,6 +271,8 @@ async function retryActiveJob() { const jobId = activeJobDetail.jobId; setStatus("Requesting operator retry..."); el.retryJobBtn.disabled = true; + const originalRetryText = el.retryJobBtn.textContent; + el.retryJobBtn.textContent = "Retrying..."; try { const res = await fetch(`/api/v1/convert/jobs/${encodeURIComponent(jobId)}/retry`, { method: "POST", @@ -308,6 +310,7 @@ async function retryActiveJob() { setError("Network error while requesting retry. Retry when the service is reachable."); } finally { el.retryJobBtn.disabled = false; + el.retryJobBtn.textContent = originalRetryText; } } @@ -380,6 +383,9 @@ async function refreshKpis() { } async function refreshKpiEvidence() { + el.refreshEvidenceBtn.disabled = true; + const originalText = el.refreshEvidenceBtn.textContent; + el.refreshEvidenceBtn.textContent = "Refreshing..."; try { const { res, data } = await fetchJson(KPI_EXPORTS_ENDPOINT); if (!res.ok) { @@ -390,6 +396,9 @@ async function refreshKpiEvidence() { renderKpiEvidence(data); } catch (err) { el.kpiExportStatus.textContent = "Snapshot evidence is unavailable while the service is unreachable."; + } finally { + el.refreshEvidenceBtn.disabled = false; + el.refreshEvidenceBtn.textContent = originalText; } } @@ -436,6 +445,8 @@ async function submitDocument(event) { } el.submitBtn.disabled = true; + const originalSubmitText = el.submitBtn.textContent; + el.submitBtn.textContent = "Submitting..."; setStatus("Submitting document..."); try { @@ -479,6 +490,7 @@ async function submitDocument(event) { setError("Network error while submitting. Retry when the service is reachable."); } finally { el.submitBtn.disabled = false; + el.submitBtn.textContent = originalSubmitText; } }