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
Expand Up @@ -13,3 +13,7 @@
## 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-27 - Dynamic ARIA Labels for Iterative Table UI
**Learning:** Repetitive inline table action buttons and links (such as "Details" and "Open viewer") fail basic accessibility when they share the exact same visible label but point to different document contexts, creating confusion for screen reader users.
**Action:** Always provide dynamically generated `aria-label` attributes to repetitive action buttons and links by appending row-specific identifiers (e.g., `Details for [Filename]`) and preserve the dynamically injected label even across temporary async state transformations.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## [Unreleased]
- 접근성: 데모 페이지 내 액션 버튼 및 링크에 context-specific ARIA label 추가 (`demo.js`의 `createLink`, `createActionButton` 변경)

## [Unreleased]
### Added
- **UI UX 개선**: 'Details' 버튼 클릭 시, 작업 상세 정보 로드 중에 사용자가 명시적인 로딩 상태를 확인할 수 있도록 'Loading...' 텍스트와 비활성화 상태를 표시하도록 추가했습니다.
Comment on lines +1 to 6
Expand Down
25 changes: 20 additions & 5 deletions src/main/resources/static/assets/viewer/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -110,12 +113,15 @@ 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";
button.addEventListener("click", onClick);
if (ariaLabel) {
button.setAttribute("aria-label", ariaLabel);
}
return button;
}

Expand Down Expand Up @@ -143,23 +149,32 @@ function renderHistory(history = loadHistory()) {
submittedCell.textContent = job.submittedAt || "";
actionsCell.className = "table-actions";

const fileDesc = job.fileName || "Document";

if (job.statusUrl) {
actionsCell.appendChild(createActionButton("Details", (e) => {
const btn = e.currentTarget;
const initialChildren = Array.from(btn.childNodes);
btn.disabled = true;
const currentAriaLabel = btn.getAttribute("aria-label");
if (currentAriaLabel) {
btn.setAttribute("aria-label", `Loading details for ${fileDesc}`);
}
btn.textContent = "Loading...";
openJobDetail(job).finally(() => {
btn.replaceChildren(...initialChildren);
if (currentAriaLabel) {
btn.setAttribute("aria-label", currentAriaLabel);
}
btn.disabled = false;
});
}));
}, `Details for ${fileDesc}`));
actionsCell.appendChild(createActionButton("Status JSON", () => {
void openJsonDocument(job.statusUrl, "Clearfolio status JSON");
}));
}, `Status JSON for ${fileDesc}`));
}
if (job.jobId) {
actionsCell.appendChild(createLink(`/viewer/${encodeURIComponent(job.jobId)}`, "Open viewer"));
actionsCell.appendChild(createLink(`/viewer/${encodeURIComponent(job.jobId)}`, "Open viewer", `Open viewer for ${fileDesc}`));
}

row.append(fileCell, statusCell, submittedCell, actionsCell);
Expand Down
Loading