Skip to content
Merged
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
@@ -0,0 +1,4 @@
## 2024-07-07 - Add definition lists to Fit Report metric cards

**Learning:** Key-value pairs for metrics and statistics in HTML reports must use semantic `<dl>`, `<dt>`, and `<dd>` elements to properly associate the label with its value for screen readers. Using generic `<article>` or `<div>` with `<span>` and `<strong>` separates the semantic relationship. Additionally, default `<dt>` styling varies across browsers, so explicit `font-weight` resets are necessary to maintain cross-browser visual consistency while improving semantics. Also, relying purely on mouse interactions for focused tables (`:focus`) impacts mouse users visually - `:focus-visible` ensures focus outlines appear only during keyboard navigation. Purely visual charting elements (like horizontal bar tracks) inside HTML need `aria-hidden="true"` to prevent redundant, noisy announcements for screen reader users.
**Action:** Always prefer definition lists (`<dl>`) over generic markup for metric pairs, and explicitly reset their font-weight in CSS. Use `:focus-visible` instead of `:focus` for generic focus styles, and add `aria-hidden="true"` to decorative/visual chart tracks.
34 changes: 21 additions & 13 deletions python/fast_mlsirm/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def _render_html(payload: dict[str, Any], report_type: str, title: str, source_n
"<main>",
'<section class="hero">',
'<div class="hero-copy">',
f"<p>fast-mlsirm diagnostics</p>",
"<p>fast-mlsirm diagnostics</p>",
f"<h1>{escape(title)}</h1>",
f"<span>Source: {escape(source_name)}</span>",
"</div>",
Expand Down Expand Up @@ -165,10 +165,10 @@ def _metric_section(heading: str, metrics: dict[str, Any]) -> str | None:
cards.append(
"\n".join(
[
'<article class="metric-card">',
f"<span>{escape(_label(key))}</span>",
f"<strong>{escape(_format_value(value))}</strong>",
"</article>",
'<div class="metric-card">',
f"<dt>{escape(_label(key))}</dt>",
f"<dd>{escape(_format_value(value))}</dd>",
"</div>",
]
)
)
Expand All @@ -179,9 +179,9 @@ def _metric_section(heading: str, metrics: dict[str, Any]) -> str | None:
[
'<section class="report-section">',
f"<h2>{escape(heading)}</h2>",
'<div class="metrics-grid">',
'<dl class="metrics-grid">',
*cards,
"</div>",
"</dl>",
"</section>",
]
)
Expand Down Expand Up @@ -265,7 +265,7 @@ def _bar_chart(rows: list[dict[str, Any]], value_key: str | None) -> str:
[
'<div class="bar-row">',
f'<span class="bar-label">{escape(_row_label(row, index))}</span>',
'<div class="bar-track">',
'<div class="bar-track" aria-hidden="true">',
f'<div class="bar-fill" style="width: {width:.1f}%"></div>',
"</div>",
f'<span class="bar-value">{escape(_format_value(value))}</span>',
Expand All @@ -279,7 +279,7 @@ def _bar_chart(rows: list[dict[str, Any]], value_key: str | None) -> str:

return "\n".join(
[
'<div class="bar-chart" role="img" aria-label="Compact diagnostics bar chart">',
'<div class="bar-chart" aria-hidden="true">',
*chart_rows,
"</div>",
]
Expand Down Expand Up @@ -477,6 +477,7 @@ def _css() -> str:
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
margin: 0;
}

.metric-card {
Expand All @@ -487,16 +488,19 @@ def _css() -> str:
background: #fafafa;
}

.metric-card span {
.metric-card dt {
display: block;
color: var(--muted);
font-size: 0.82rem;
font-weight: normal;
margin: 0;
}

.metric-card strong {
.metric-card dd {
display: block;
margin-top: 8px;
margin: 8px 0 0 0;
font-size: 1.45rem;
font-weight: bold;
overflow-wrap: anywhere;
}

Expand Down Expand Up @@ -574,7 +578,7 @@ def _css() -> str:
border-radius: 8px;
}

.table-wrap:focus {
.table-wrap:focus-visible {
outline: 3px solid #0f766e;
outline-offset: 3px;
}
Expand Down Expand Up @@ -615,6 +619,10 @@ def _css() -> str:
border-bottom: 0;
}

tbody tr:hover {
background: #fbfcfa;
}

.empty-state {
margin: 0;
padding: 14px;
Expand Down
2 changes: 1 addition & 1 deletion scripts/build_benchmark_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ def _report_css() -> str:
border: 1px solid #d8e1e3;
border-radius: 8px;
}
.table-wrap:focus {
.table-wrap:focus-visible {
outline: 3px solid #0f766e;
outline-offset: 3px;
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/build_buyer_packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ def _report_css() -> str:
border: 1px solid #d8e1e3;
border-radius: 8px;
}
.table-wrap:focus {
.table-wrap:focus-visible {
outline: 3px solid #0f766e;
outline-offset: 3px;
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/build_commercial_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ def _report_css() -> str:
border: 1px solid #d8e1e3;
border-radius: 8px;
}
.table-wrap:focus {
.table-wrap:focus-visible {
outline: 3px solid #0f766e;
outline-offset: 3px;
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/build_figma_evidence_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def _report_css() -> str:
.metric-card span { display: block; color: #5e6f76; font-size: 0.8rem; font-weight: 700; text-transform: uppercase; }
.metric-card strong { display: block; margin-top: 8px; overflow-wrap: anywhere; }
.table-wrap { overflow-x: auto; border: 1px solid #d8e1e3; border-radius: 8px; }
.table-wrap:focus { outline: 3px solid #0f766e; outline-offset: 3px; }
.table-wrap:focus-visible { outline: 3px solid #0f766e; outline-offset: 3px; }
table { width: 100%; min-width: 760px; border-collapse: collapse; }
caption { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
th, td { padding: 10px 12px; border-bottom: 1px solid #e8edef; text-align: left; vertical-align: top; }
Expand Down
2 changes: 1 addition & 1 deletion scripts/build_pr_queue_governance.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def _report_css() -> str:
.metric-card span { display: block; color: #5e6f76; font-size: 0.8rem; font-weight: 700; text-transform: uppercase; }
.metric-card strong { display: block; margin-top: 8px; overflow-wrap: anywhere; }
.table-wrap { overflow-x: auto; border: 1px solid #d8e1e3; border-radius: 8px; }
.table-wrap:focus { outline: 3px solid #0f766e; outline-offset: 3px; }
.table-wrap:focus-visible { outline: 3px solid #0f766e; outline-offset: 3px; }
table { width: 100%; min-width: 920px; border-collapse: collapse; }
caption { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
th, td { padding: 10px 12px; border-bottom: 1px solid #e8edef; text-align: left; vertical-align: top; }
Expand Down
2 changes: 1 addition & 1 deletion scripts/build_procurement_due_diligence.py
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ def _report_css() -> str:
.metric-card span { display: block; color: #5e6f76; font-size: 0.8rem; font-weight: 700; text-transform: uppercase; }
.metric-card strong { display: block; margin-top: 8px; overflow-wrap: anywhere; }
.table-wrap { overflow-x: auto; border: 1px solid #d8e1e3; border-radius: 8px; }
.table-wrap:focus { outline: 3px solid #0f766e; outline-offset: 3px; }
.table-wrap:focus-visible { outline: 3px solid #0f766e; outline-offset: 3px; }
table { width: 100%; min-width: 760px; border-collapse: collapse; }
caption { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
th, td { padding: 10px 12px; border-bottom: 1px solid #e8edef; text-align: left; vertical-align: top; }
Expand Down
2 changes: 1 addition & 1 deletion scripts/build_release_evidence_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ def _report_css() -> str:
border: 1px solid #d8e1e3;
border-radius: 8px;
}
.table-wrap:focus {
.table-wrap:focus-visible {
outline: 3px solid #0f766e;
outline-offset: 3px;
}
Expand Down
9 changes: 8 additions & 1 deletion tests/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ def test_render_fit_diagnostics_report_has_sections(tmp_path):
html = out.read_text(encoding="utf-8")
assert "Example Fit" in html
assert "Model Fit" in html
assert '<dl class="metrics-grid">' in html
assert "<dt>Loglik</dt>" in html
assert "<dd>-3.2</dd>" in html
assert "Item Fit" in html
assert "Diagnostics Coverage" in html
assert "No row data" in html
Expand Down Expand Up @@ -225,4 +228,8 @@ def test_render_table_region_has_keyboard_focus_style(tmp_path):
html = out.read_text(encoding="utf-8")
assert 'aria-label="Candidate Comparison diagnostics table"' in html
assert 'tabindex="0"' in html
assert ".table-wrap:focus" in html
assert ".table-wrap:focus-visible" in html
assert ".table-wrap:focus {" not in html
assert "tbody tr:hover" in html
assert '<div class="bar-chart" aria-hidden="true">' in html
assert '<div class="bar-track" aria-hidden="true">' in html
Loading