`를 사용하도록 변경했습니다.
- 이를 통해 스크린 리더 사용자가 페이지를 동적으로 탐색하거나 로딩될 때 데이터가 없음을 명확하게 인지할 수 있도록 접근성을 개선했습니다.
- 관련된 테스트와 `.jules/palette.md`의 학습 일지도 함께 업데이트했습니다.
---
.jules/palette.md | 4 ++++
.../scoring/essay/validation_report_html.py | 15 +++++--------
...st_scoring_essay_validation_report_html.py | 22 ++++++++++++-------
3 files changed, 24 insertions(+), 17 deletions(-)
diff --git a/.jules/palette.md b/.jules/palette.md
index ee48ab0ea..1463b6968 100644
--- a/.jules/palette.md
+++ b/.jules/palette.md
@@ -47,3 +47,7 @@
## 2025-02-12 - Exposing Unrounded Numeric Representations for Formatted Floats
**Learning:** In data-heavy HTML reports, formatting floats to a fixed number of significant digits can obscure the original Python float representation. A native `title` tooltip can help pointer users inspect that unrounded representation, but it is not a reliable keyboard, touch, or assistive-technology disclosure mechanism.
**Action:** Add the unrounded Python float representation as a supplemental native `title` tooltip on formatted values. Preserve the report's accessible exact-value disclosure and JSON/CSV exports as the authoritative non-hover paths; never claim that `title` alone provides accessibility.
+
+## 2025-02-13 - Empty States ARIA Live Regions
+**Learning:** In HTML reports, simply using generic tags like `
` for empty states (such as an empty list of triggers) fails to proactively announce the absence of data to screen reader users navigating dynamically.
+**Action:** Always use `
` for empty states to ensure they act as ARIA live regions and are reliably announced by assistive technologies when content is evaluated or loaded.
diff --git a/python/fast_mlsirm/scoring/essay/validation_report_html.py b/python/fast_mlsirm/scoring/essay/validation_report_html.py
index 3c8eea6d3..ee3e1b148 100644
--- a/python/fast_mlsirm/scoring/essay/validation_report_html.py
+++ b/python/fast_mlsirm/scoring/essay/validation_report_html.py
@@ -74,8 +74,7 @@ def _validated_report(
category_count=report.category_count,
paired_observation_count=report.paired_observation_count,
metrics=tuple(
- _replay_metric(metric, index)
- for index, metric in enumerate(report.metrics)
+ _replay_metric(metric, index) for index, metric in enumerate(report.metrics)
),
review_trigger_ids=report.review_trigger_ids,
metadata=report.metadata,
@@ -108,7 +107,7 @@ def _identifier_list(
) -> str:
"""Render identifier evidence as a semantic list or explicit empty state."""
if not identifiers:
- return f'
{escape(empty_message)}
'
+ return f'
{escape(empty_message)}
'
items = "".join(
f"
{escape(identifier)}" for identifier in identifiers
)
@@ -191,9 +190,9 @@ def _render_html(report: EssayValidationEvidenceReport, title: str) -> str:
'
',
'Human interpretation required
',
f'{escape(_VALIDITY_NOTICE)}
',
- 'Review triggers
',
+ "Review triggers
",
triggers,
- 'Interpretation boundaries
',
+ "Interpretation boundaries
",
boundaries,
"",
'
',
@@ -206,7 +205,7 @@ def _render_html(report: EssayValidationEvidenceReport, title: str) -> str:
"",
'
',
'Canonical JSON
',
- 'The complete deterministic evidence payload is available below for audit reconstruction.
',
+ "The complete deterministic evidence payload is available below for audit reconstruction.
",
'',
_canonical_json(report),
"",
@@ -236,9 +235,7 @@ def render_essay_validation_evidence_report_html(
if output.suffix.lower() != ".html":
raise ValueError("essay validation evidence output path must end with .html")
if title is not None and (not isinstance(title, str) or not title.strip()):
- raise ValueError(
- "essay validation evidence title must be a non-empty string"
- )
+ raise ValueError("essay validation evidence title must be a non-empty string")
resolved_title = _DEFAULT_TITLE if title is None else title
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(_render_html(validated, resolved_title), encoding="utf-8")
diff --git a/tests/test_scoring_essay_validation_report_html.py b/tests/test_scoring_essay_validation_report_html.py
index 27a5739c2..97e12ac88 100644
--- a/tests/test_scoring_essay_validation_report_html.py
+++ b/tests/test_scoring_essay_validation_report_html.py
@@ -80,18 +80,24 @@ def test_custom_title_and_identifiers_are_escaped(tmp_path: Path) -> None:
assert "<img src=x onerror="alert(1)">" in html
assert "
",),
- empty_message="No review trigger.",
- ) == ''
+ assert (
+ validation_report_html._identifier_list(
+ ("review_",),
+ empty_message="No review trigger.",
+ )
+ == ''
+ )
def test_empty_identifier_list_renders_explicit_state() -> None:
"""An empty evidence list remains understandable without hidden state."""
- assert validation_report_html._identifier_list(
- (),
- empty_message="No boundary is available.",
- ) == 'No boundary is available.
'
+ assert (
+ validation_report_html._identifier_list(
+ (),
+ empty_message="No boundary is available.",
+ )
+ == 'No boundary is available.
'
+ )
def test_renderer_rejects_wrong_type_and_wrong_suffix(tmp_path: Path) -> None: