From a28b88446ce5fbfd0698c1362039e1d04fa5e36f Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 18 Jul 2026 21:39:12 +0000 Subject: [PATCH] feat(a11y): add context-specific aria labels to demo UI table actions Provides context-specific `aria-label` attributes to repetitive action buttons and links in the viewer demo UI session history table, using the row's document filename to improve screen reader accessibility. --- .jules/palette.md | 3 +++ src/main/resources/static/assets/viewer/demo.js | 16 +++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/.jules/palette.md b/.jules/palette.md index a34ea59..ca403ef 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -13,3 +13,6 @@ ## 2026-07-13 - Async Table Actions UX **Learning:** Adding explicit loading and disabled states to table action buttons that invoke asynchronous processes helps prevent redundant API calls and visually assures the user that their request is being handled. **Action:** Consistently apply `disabled` state and `Loading...` text changes to inline table action buttons linked to async workflows, and carefully preserve underlying DOM structures with `Array.from(btn.childNodes)` during the loading cycle to avoid rendering regressions. +## 2026-07-18 - Context-specific ARIA labels for table actions +**Learning:** Repetitive table action buttons or links (e.g., 'Details', 'Open') without context-specific labels are highly confusing for screen reader users, who hear the same generic text repeated for every row. +**Action:** Always provide dynamically generated, context-specific `aria-label` attributes that include row-specific identifiers (like the filename) for repetitive table actions. diff --git a/src/main/resources/static/assets/viewer/demo.js b/src/main/resources/static/assets/viewer/demo.js index 51466a8..0bdc36f 100644 --- a/src/main/resources/static/assets/viewer/demo.js +++ b/src/main/resources/static/assets/viewer/demo.js @@ -81,13 +81,16 @@ function updateJob(jobId, patch, { refreshKpisAfterUpdate = true } = {}) { } } -function createLink(href, label) { +function createLink(href, label, ariaLabel) { const link = document.createElement("a"); link.href = href; link.textContent = label; link.className = "table-link"; link.target = "_blank"; link.rel = "noopener noreferrer"; + if (ariaLabel) { + link.setAttribute("aria-label", ariaLabel); + } return link; } @@ -110,11 +113,14 @@ async function openJsonDocument(url, title) { : "Unable to load JSON evidence with the current tenant claim."; } -function createActionButton(label, onClick) { +function createActionButton(label, onClick, ariaLabel) { const button = document.createElement("button"); button.type = "button"; button.textContent = label; button.className = "btn btn-secondary btn-compact"; + if (ariaLabel) { + button.setAttribute("aria-label", ariaLabel); + } button.addEventListener("click", onClick); return button; } @@ -153,13 +159,13 @@ function renderHistory(history = loadHistory()) { btn.replaceChildren(...initialChildren); btn.disabled = false; }); - })); + }, `Details for ${job.fileName || "Document"}`)); actionsCell.appendChild(createActionButton("Status JSON", () => { void openJsonDocument(job.statusUrl, "Clearfolio status JSON"); - })); + }, `Status JSON for ${job.fileName || "Document"}`)); } if (job.jobId) { - actionsCell.appendChild(createLink(`/viewer/${encodeURIComponent(job.jobId)}`, "Open viewer")); + actionsCell.appendChild(createLink(`/viewer/${encodeURIComponent(job.jobId)}`, "Open viewer", `Open viewer for ${job.fileName || "Document"}`)); } row.append(fileCell, statusCell, submittedCell, actionsCell);