Skip to content

feat(ui): P0 a11y essentials on index.html + personalize.html#24

Merged
fbmoulin merged 1 commit into
mainfrom
chore/ui-a11y-essentials
May 11, 2026
Merged

feat(ui): P0 a11y essentials on index.html + personalize.html#24
fbmoulin merged 1 commit into
mainfrom
chore/ui-a11y-essentials

Conversation

@fbmoulin
Copy link
Copy Markdown
Owner

@fbmoulin fbmoulin commented May 11, 2026

Summary

Closes the P0 batch from the UX audit. Visual identity preserved; this is strictly assistive-tech, keyboard-navigation, and discoverability work. 7 a11y categories addressed across both Flask templates; 14 new regression tests lock down the contract.

What's in (7 categories)

# Issue Fix
A1 <input> only had placeholder, no <label> — AT announced "edit" <label class=sr-only for=urlInput> + sr-only utility
A2 alert() for validation errors — blocked AT, ugly mobile, unstyled Inline #errorMessage with role=alert, aria-live=assertive
A3 Log container not announced — SSE updates invisible to screen readers role=log + aria-live=polite + aria-label
A4 No <form> wrapping → Enter via manual keypress handler <form id=downloadForm> + type=submit
A5 Focus didn't migrate on state changes — keyboard users lost the cursor Focus moves to error/success target
A6 No aria-busy during long worker — AT didn't know task was in flight <main aria-busy> toggled around fetches
A7 onclick= inline mixed with addEventListener — CSP-hostile, inconsistent All event handlers in inline script (consistent, future-CSP-friendly)

Discoverability bonuses

  • New <a href=\"/personalize\"> link at the bottom of /. Previously the personalize feature was only reachable via URL bar or docs.
  • "← Voltar" link from /personalize back to /.

Visual polish (collateral)

  • Subtitle contrast #666#4a4a4a to clear WCAG AA on the white-glass card.
  • .error red darkened (#c1272d#991b1b) + bolded for visibility.
  • :focus-visible outline (3px solid + 2px offset) on every interactive element — keyboard users see where focus lands.

Behavior changes

  • URL input no longer auto-clears after a successful download. Re-running for a variant of the same URL is a common pattern and was being thwarted.
  • Logs no longer auto-hide when the session ended in error. Failure traces stay visible so you can copy them.
  • Personalize result rendering switched from .innerHTML (server-payload interpolation) to createElement(code) + textContent — same visual output, defensive even though the field is operator-controlled.

Test plan

  • pytest tests/test_template_a11y.py -v → 14/14 in 0.31s
  • pytest -q233 passed, 2 skipped (was 219 + 2; +14 a11y cases)
  • ruff check tests/test_template_a11y.py kratos_clone/ scripts/ → clean
  • ruff format --check → 12 files already formatted
  • mypy --config-file pyproject.toml → Success, 21 source files
  • bandit --severity-level medium → 0 medium/high (10 low unchanged from baseline)
  • CI green on PR (8 jobs)
  • Manual QA on / and /personalize after merge (test with keyboard-only nav)

Deferred (P1/P2 from audit, separate PRs)

  • Elapsed-time indicator during processing (currently "Processando…" with indeterminate spinner)
  • Reconnect/timeout logic on SSE drop
  • Captures dropdown — GET /api/captures to replace free-text html_dir field on /personalize
  • Step indicator (1/3, 2/3, 3/3) in personalize
  • localStorage persistence of last URL
  • SVG identity / hero polish (P2)

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced screen reader support and keyboard navigation across all pages
    • Improved error messaging with dedicated on-page panels
    • Better visual focus indicators for keyboard users
  • Bug Fixes

    • Replaced alert-based error notifications with accessible in-page messaging
  • Tests

    • Added accessibility regression tests for WCAG compliance validation

Review Change Stack

Closes the P0 batch from the UX audit. Visual identity preserved; this is
strictly assistive-tech, keyboard, and discoverability work.

index.html
- `<input type=url>` now has a real `<label>` (visually hidden via .sr-only)
  instead of relying on placeholder text alone — screen readers were
  announcing "edit" with no context.
- Wrapped fields in `<form id=downloadForm novalidate>` with `type=submit`;
  removed the inline `onclick="startDownload()"` and the manual keypress
  handler. Enter key + assistive-tech "submit" both work natively now and
  the legacy CSP-hostile inline handler is gone.
- Replaced `alert('Por favor, insira uma URL válida')` with an inline
  `#errorMessage` region (`role=alert`, `aria-live=assertive`). Error
  surfaces both for client-side validation AND backend 400/connection
  failures (previously logs hid the failure after 5s).
- Log container: `role=log` + `aria-live=polite` + `aria-label`. SSE
  progress updates are now announced to screen readers as they arrive.
- Success banner: `role=status` + `aria-live=polite`; focus migrates to
  the manual download link on completion (keyboard users land on the
  next actionable element instead of nowhere).
- `<main>` element with `aria-busy` toggled true/false around the
  in-flight worker so AT can announce the long task state change.
- Removed the auto-clear of the URL input after success — re-running for
  a variant is a common pattern. Logs no longer auto-hide when the
  session ended in error (you can still read the failure trace).
- New `<a href="/personalize">` discovery link at the bottom of the card
  (it was previously reachable only via URL bar / docs).
- `button:focus-visible / input:focus-visible / a:focus-visible` gets a
  3px outline + 2px offset so keyboard users see where focus lands.
- Subtitle color darkened from `#666` to `#4a4a4a` to clear WCAG AA
  contrast on the white-glass card.

personalize.html
- Both `.status` divs now have `role=status` + `aria-live=polite`.
- `<main aria-busy>` toggles during the two fetch calls.
- Sections labelled via `aria-labelledby` referencing their `<h2 id>` —
  landmark navigation now identifies each step.
- Focus moves to `#company` after `/api/personalize/structure` succeeds
  (keyboard users follow the flow); focus to `#step-out` after run.
- "← Voltar" link to `/` at the bottom (was stranded before).
- Result rendering switched from `.innerHTML` interpolation to
  `createElement(code) + textContent` — same visual output, no string
  interpolation of server payload into the DOM (defensive even though
  the API field is operator-controlled).
- Status colors hardened: `.error` red darkened `#c1272d` → `#991b1b` and
  bolded; `.success` `#2a7f3a` → `#166534` and bolded.
- Matching `:focus-visible` outline rule on all interactive elements.

tests/test_template_a11y.py (new, +14 cases)
Regression net for the a11y contract. Asserts presence of:
- `<label for=urlInput>` + the label text
- `id=downloadForm` + `type=submit` + ABSENCE of `onclick=`
- `id=errorMessage` + `role=alert` + `aria-live=assertive`
- `id=logContainer` + `role=log` + `aria-live=polite`
- `id=successMessage` + `role=status`
- `id=mainCard` + `aria-busy=false` (initial)
- `href="/personalize"` discovery link
- The specific `alert('Por favor` legacy call is gone
- Both personalize.html status regions are polite live
- All 3 personalize sections have `aria-labelledby`
- `href="/"` back-link on /personalize
- `:focus-visible` style present on both templates

Verifications:
- pytest -q → 233 passed, 2 skipped (was 219 + 2; +14 a11y cases)
- pytest tests/test_template_a11y.py -v → 14/14 in 0.31s
- ruff check kratos_clone/ scripts/ tests/test_template_a11y.py → clean
- ruff format --check → 12 files already formatted
- mypy --config-file pyproject.toml → Success, 21 source files
- bandit --severity-level medium → 0 medium/high (10 low unchanged)

Not in this PR (P1/P2 from the audit, deferred):
- Elapsed-time indicator during processing
- Reconnect/timeout logic on SSE drop
- Captures dropdown (replace free-text `html_dir`)
- Step indicator (1/3, 2/3, 3/3) in personalize
- localStorage persistence of last URL
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 11, 2026

📝 Walkthrough

Walkthrough

This PR improves accessibility across the Website Downloader and Personalize templates by adding ARIA landmarks, semantic error/status regions, keyboard navigation, and focus management. Index errors migrate from alert() to semantic panels; both templates now expose loading state via aria-busy. A comprehensive test suite validates the accessibility contract on both routes.

Changes

Accessibility Improvements for Download and Personalize Templates

Layer / File(s) Summary
Index CSS and Markup Foundation
templates/index.html
Error panel styling, screen-reader utility, contrast enhancement, focus-visible outlines, and accessible <main> wrapper with aria-busy, semantic error/log/success regions, and visible label for URL input.
Index Error Handling and Messaging
templates/index.html
New showError() and clearError() functions replace alert() with on-page error panel rendering. URL validation and fetch errors display via error region; SSE done events with non-complete status log failure and show error UI.
Index Success State and Link Focus
templates/index.html
showSuccess() sets manual-download link href and moves keyboard focus to it. hideSuccess() uses DOM helpers to manage state.
Index Loading and Reset State
templates/index.html
setLoading() updates aria-busy, disables button/input, and changes button label. resetUI() preserves URL input while conditionally hiding logs/success only when no error is active. clearLogs() uses innerHTML = ''.
Index Form Submission
templates/index.html
Form-level submit event listener replaces keypress handling, prevents default submission, and calls startDownload() when button is enabled.
Personalize CSS and Markup Foundation
templates/personalize.html
Status styling updated with new colors and focus-visible outlines. Main container adds id="mainCard" with aria-busy. Steps gain aria-labelledby headings, inputs gain aria-describedby, and status elements use ARIA live-region semantics.
Personalize Extract Request Flow
templates/personalize.html
Extract handler sets busy state before structure request, validates brand brief, clears busy on success/error paths, populates extracted fields, transitions to step 2, and moves focus to the next form section.
Personalize Run Request Flow
templates/personalize.html
Run handler toggles busy state around /api/personalize/run request; on success uses safer DOM construction (textContent + created code node) instead of innerHTML, then focuses result section for assistive tech.
Accessibility Regression Test Suite
tests/test_template_a11y.py
New test module with 14 tests validating index label association, form semantics, alert/log/status roles with aria-live, mainCard aria-busy, navigation links, absence of alert() calls, personalize status regions, labeled sections, bidirectional links, and focus-visible keyboard styling on both routes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 From alerts that startled, now panels that glow,
ARIA attributes help the journey flow,
Focus on links, and tabs that descend,
Two templates made friendlier, from start unto end!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(ui): P0 a11y essentials on index.html + personalize.html' directly and clearly summarizes the main change: adding WCAG accessibility essentials to two template files as part of a P0 priority batch from a UX audit.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/ui-a11y-essentials

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0433164ee0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread templates/index.html
el.classList.add('active');
$('urlInput').setAttribute('aria-invalid', 'true');
// Move focus to input so screen readers + keyboard users find the error
$('urlInput').focus();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Re-enable controls before focusing the error

When showError() is called from /start-download failures or the SSE error branch, urlInput is still disabled because setLoading(false)/resetUI() has not run yet. Calling focus() on a disabled input is ignored, so keyboard and screen-reader users are not actually moved to the error even though this change is meant to add focus management. Re-enable before calling showError() or focus the alert region instead.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request focuses on enhancing the accessibility and user experience of the website downloader and personalization templates by implementing ARIA roles, live regions, focus management, and native form submission. It also introduces a comprehensive suite of accessibility regression tests and improves security by replacing innerHTML with textContent in several locations. The review feedback identifies remaining WCAG AA contrast violations in specific text elements and suggests refining the auto-hide logic for success messages to prevent focus loss for keyboard and screen reader users.

Comment thread templates/index.html
Comment on lines +359 to +365
if (!isError) {
setTimeout(() => {
$('logContainer').classList.remove('active');
hideSuccess();
clearLogs();
}, 5000);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Auto-hiding success messages that contain interactive elements (like the manual download link) can be disorienting for screen reader and keyboard users. If the message disappears while the user is navigating towards it or reading it, focus is lost and the viewport may jump.

Consider checking if the user is currently interacting with the message before hiding it, or removing the auto-hide for success messages entirely as you did for error logs.

Suggested change
if (!isError) {
setTimeout(() => {
$('logContainer').classList.remove('active');
hideSuccess();
clearLogs();
}, 5000);
}
if (!isError) {
setTimeout(() => {
if ($('successMessage').contains(document.activeElement)) return;
$('logContainer').classList.remove('active');
hideSuccess();
clearLogs();
}, 5000);
}

Comment thread templates/index.html
</div>
</div>

<p style="margin-top:1.5rem;font-size:0.85rem;color:#666;text-align:center">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The color #666 on a white background has a contrast ratio of 3.94:1, which fails the WCAG AA requirement of 4.5:1 for normal text. Since you've already updated the .subtitle class to #4a4a4a (9.8:1) for better contrast, you should apply the same color here for consistency and accessibility compliance.

Suggested change
<p style="margin-top:1.5rem;font-size:0.85rem;color:#666;text-align:center">
<p style="margin-top:1.5rem;font-size:0.85rem;color:#4a4a4a;text-align:center">

Comment on lines +54 to +57
button:focus-visible, input:focus-visible, textarea:focus-visible, a:focus-visible {
outline: 3px solid #4a6cf7;
outline-offset: 2px;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The .subtitle class in this file (defined at line 22, outside this diff) still uses #666, which has insufficient contrast (3.94:1). To match the accessibility improvements made in index.html and meet WCAG AA standards, you should override it here with #4a4a4a.

        button:focus-visible, input:focus-visible, textarea:focus-visible, a:focus-visible {
            outline: 3px solid #4a6cf7;
            outline-offset: 2px;
        }
        .subtitle { color: #4a4a4a; }

</section>
</div>

<p style="margin-top:1.5rem;font-size:0.85rem;color:#666;text-align:center">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the footer in index.html, the color #666 here fails WCAG AA contrast requirements. Use #4a4a4a instead to ensure the link text is readable against the white background.

Suggested change
<p style="margin-top:1.5rem;font-size:0.85rem;color:#666;text-align:center">
<p style="margin-top:1.5rem;font-size:0.85rem;color:#4a4a4a;text-align:center">

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
templates/index.html (1)

231-386: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Consolidate into a single script block.

Two separate script blocks violate the coding guideline: "In frontend (templates/index.html), use a single inline <script> block at end of body." Merge the browser logger (393-554) into the main script block (231-386) to comply.

As per coding guidelines: "In frontend (templates/index.html), use a single inline <script> block at end of body — no separate JS files"

Also applies to: 393-554

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@templates/index.html` around lines 231 - 386, The page currently has two
separate inline <script> blocks; merge the second "browser logger" script into
the existing single script block so all front-end JS lives at the end of body.
Copy the functions and variables from the other block (e.g., addLog, clearLogs,
setLoading, resetUI, startDownload, connectSSE, triggerDownload, showSuccess,
hideSuccess, currentSessionId, eventSource and the $ helper) into the main
script, remove duplicates (do not re-declare $ or duplicate function names),
keep the form submit handler ($('downloadForm').addEventListener(...)) and SSE
setup (connectSSE/eventSource) intact, then delete the extra <script> tag so
only one inline script remains. Ensure references to elements like
'downloadBtn', 'urlInput', 'logContainer', 'successMessage' and 'downloadLink'
still resolve after the merge.
🧹 Nitpick comments (2)
templates/index.html (1)

377-379: 💤 Low value

Consider textContent = '' for consistency.

While innerHTML = '' works for clearing the log container, using textContent = '' would be more consistent with the XSS-prevention approach used elsewhere (e.g., personalize.html line 210).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@templates/index.html` around lines 377 - 379, Update the clearLogs function
to assign an empty string to the element's textContent instead of innerHTML:
locate the clearLogs function and the $('logContainer') reference and replace
the innerHTML = '' assignment with textContent = '' to match the XSS-safe
approach used elsewhere (e.g., personalize.html usage pattern).
templates/personalize.html (1)

110-224: 💤 Low value

Consider consolidating script blocks for consistency.

While the coding guideline only mandates a single script block for index.html, consolidating both blocks (main app + logger) into one would improve consistency across templates and reduce the chance of script ordering issues.

Also applies to: 230-326

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@templates/personalize.html` around lines 110 - 224, Consolidate the two
separate script blocks into a single IIFE so the DOM helpers and UI logic (the
var $ helper, setStatus, setBusy, card, extractBtn/runBtn listeners, and
FormData flow in the anonymous function) and the logger initialization are
defined in one place; merge the logger setup into this IIFE before any calls
that may use it, avoid duplicate globals (remove the second var $ or rename if
needed), and ensure all event listeners (extractBtn.addEventListener,
runBtn.addEventListener) and status/update calls keep their original behavior
and ordering so script execution and accessibility focus handling are unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@templates/index.html`:
- Around line 231-386: The page currently has two separate inline <script>
blocks; merge the second "browser logger" script into the existing single script
block so all front-end JS lives at the end of body. Copy the functions and
variables from the other block (e.g., addLog, clearLogs, setLoading, resetUI,
startDownload, connectSSE, triggerDownload, showSuccess, hideSuccess,
currentSessionId, eventSource and the $ helper) into the main script, remove
duplicates (do not re-declare $ or duplicate function names), keep the form
submit handler ($('downloadForm').addEventListener(...)) and SSE setup
(connectSSE/eventSource) intact, then delete the extra <script> tag so only one
inline script remains. Ensure references to elements like 'downloadBtn',
'urlInput', 'logContainer', 'successMessage' and 'downloadLink' still resolve
after the merge.

---

Nitpick comments:
In `@templates/index.html`:
- Around line 377-379: Update the clearLogs function to assign an empty string
to the element's textContent instead of innerHTML: locate the clearLogs function
and the $('logContainer') reference and replace the innerHTML = '' assignment
with textContent = '' to match the XSS-safe approach used elsewhere (e.g.,
personalize.html usage pattern).

In `@templates/personalize.html`:
- Around line 110-224: Consolidate the two separate script blocks into a single
IIFE so the DOM helpers and UI logic (the var $ helper, setStatus, setBusy,
card, extractBtn/runBtn listeners, and FormData flow in the anonymous function)
and the logger initialization are defined in one place; merge the logger setup
into this IIFE before any calls that may use it, avoid duplicate globals (remove
the second var $ or rename if needed), and ensure all event listeners
(extractBtn.addEventListener, runBtn.addEventListener) and status/update calls
keep their original behavior and ordering so script execution and accessibility
focus handling are unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 30160f60-8baa-4bf4-834f-2a09299a9a3e

📥 Commits

Reviewing files that changed from the base of the PR and between 03a19a9 and 0433164.

📒 Files selected for processing (3)
  • templates/index.html
  • templates/personalize.html
  • tests/test_template_a11y.py

@fbmoulin fbmoulin merged commit 59d53b9 into main May 11, 2026
8 checks passed
@fbmoulin fbmoulin deleted the chore/ui-a11y-essentials branch May 11, 2026 04:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant