From 4ec92884350ed193b2e13a01c85b346a9f7f857b Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 16 Aug 2026 07:23:15 +0000 Subject: [PATCH 01/10] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Prevent=20jarri?= =?UTF-8?q?ng=20focus=20outline=20on=20mouse=20click?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `outline: none;` on `:focus` to interactive `` and scrollable `
` elements in HTML reports to remove default browser outlines on mouse clicks, while retaining accessibility outlines via `:focus-visible` for keyboard users.
---
 .Jules/palette.md                               | 1 +
 python/fast_mlsirm/report.py                    | 5 +++++
 python/fast_mlsirm/scoring/essay/report_html.py | 1 +
 3 files changed, 7 insertions(+)

diff --git a/.Jules/palette.md b/.Jules/palette.md
index d8f0c25d5..2dd35289e 100644
--- a/.Jules/palette.md
+++ b/.Jules/palette.md
@@ -23,3 +23,4 @@
 ## 2026-08-04 - Status Semantics and Numeric Alignment for Reports
 **Learning:** Explicit status semantics can make conditionally rendered empty states easier to discover with assistive technology, while tabular numerals improve visual comparison of metric columns. Focus-reveal behavior must not depend only on `:focus-visible`, and hover styling must not reduce the contrast of unrelated rows.
 **Action:** Use `role="status"` for genuine conditionally rendered status messages, apply `font-variant-numeric: tabular-nums` to numeric report tables, reveal skip links on `:focus`, retain a visible `:focus-visible` indicator, and avoid opacity-based dimming of non-hovered content.
+## 2026-08-05 - Suppress Default Focus Outline for Mouse Users\n**Learning:** Relying solely on `:focus-visible` to style focus outlines is insufficient because browsers still apply their default, jarring outline on standard `:focus` (e.g., when a user clicks an element like `` or `
` with a mouse). Keyboard-accessible elements can appear visually unpolished for pointing-device users.\n**Action:** When adding `:focus-visible` styles to generic focusable containers or interactive elements in HTML reports, always explicitly remove the default outline using `:focus { outline: none; }` to maintain a smooth experience for mouse clicks.
diff --git a/python/fast_mlsirm/report.py b/python/fast_mlsirm/report.py
index 753f915ce..aedb132cb 100644
--- a/python/fast_mlsirm/report.py
+++ b/python/fast_mlsirm/report.py
@@ -860,6 +860,11 @@ def _css() -> str:
   color: var(--teal);
 }
 
+.exact-values > summary:focus,
+.export-block > summary:focus {
+  outline: none;
+}
+
 .exact-values > summary:focus-visible,
 .export-block > summary:focus-visible {
   outline: 3px solid var(--teal);
diff --git a/python/fast_mlsirm/scoring/essay/report_html.py b/python/fast_mlsirm/scoring/essay/report_html.py
index d4a9896ef..e250befc5 100644
--- a/python/fast_mlsirm/scoring/essay/report_html.py
+++ b/python/fast_mlsirm/scoring/essay/report_html.py
@@ -219,6 +219,7 @@ def _css() -> str:
 .details-grid dt { font-weight: 700; }
 .details-grid dd { margin: 0; overflow-wrap: anywhere; }
 .table-scroll { overflow-x: auto; }
+.table-scroll:focus, pre:focus { outline: none; }
 .table-scroll:focus-visible, pre:focus-visible { outline: 3px solid Highlight; outline-offset: 3px; }
 table { width: 100%; border-collapse: collapse; }
 caption { text-align: left; font-weight: 700; margin-bottom: 8px; }

From c1531ba153e44022f02d4dba8ff9b2414b0cebfa Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Sun, 16 Aug 2026 07:56:57 +0000
Subject: [PATCH 02/10] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Prevent=20jarri?=
 =?UTF-8?q?ng=20focus=20outline=20on=20mouse=20click?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Adds `outline: none;` on `:focus` to interactive `` and scrollable `
` elements in HTML reports to remove default browser outlines on mouse clicks, while retaining accessibility outlines via `:focus-visible` for keyboard users.
---
 .Jules/palette.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.Jules/palette.md b/.Jules/palette.md
index 2dd35289e..d8f0c25d5 100644
--- a/.Jules/palette.md
+++ b/.Jules/palette.md
@@ -23,4 +23,3 @@
 ## 2026-08-04 - Status Semantics and Numeric Alignment for Reports
 **Learning:** Explicit status semantics can make conditionally rendered empty states easier to discover with assistive technology, while tabular numerals improve visual comparison of metric columns. Focus-reveal behavior must not depend only on `:focus-visible`, and hover styling must not reduce the contrast of unrelated rows.
 **Action:** Use `role="status"` for genuine conditionally rendered status messages, apply `font-variant-numeric: tabular-nums` to numeric report tables, reveal skip links on `:focus`, retain a visible `:focus-visible` indicator, and avoid opacity-based dimming of non-hovered content.
-## 2026-08-05 - Suppress Default Focus Outline for Mouse Users\n**Learning:** Relying solely on `:focus-visible` to style focus outlines is insufficient because browsers still apply their default, jarring outline on standard `:focus` (e.g., when a user clicks an element like `` or `
` with a mouse). Keyboard-accessible elements can appear visually unpolished for pointing-device users.\n**Action:** When adding `:focus-visible` styles to generic focusable containers or interactive elements in HTML reports, always explicitly remove the default outline using `:focus { outline: none; }` to maintain a smooth experience for mouse clicks.

From cecd5410b0ba6a9f9e92211c4b811beee416e1bb Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Sun, 16 Aug 2026 08:10:08 +0000
Subject: [PATCH 03/10] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Prevent=20jarri?=
 =?UTF-8?q?ng=20focus=20outline=20on=20mouse=20click?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Adds `outline: none;` on `:focus` to interactive `` elements in HTML reports to remove default browser outlines on mouse clicks, while retaining accessibility outlines via `:focus-visible` for keyboard users.
---
 python/fast_mlsirm/scoring/essay/report_html.py | 1 -
 tests/test_report_focus_contrast.py             | 1 +
 2 files changed, 1 insertion(+), 1 deletion(-)

diff --git a/python/fast_mlsirm/scoring/essay/report_html.py b/python/fast_mlsirm/scoring/essay/report_html.py
index e250befc5..d4a9896ef 100644
--- a/python/fast_mlsirm/scoring/essay/report_html.py
+++ b/python/fast_mlsirm/scoring/essay/report_html.py
@@ -219,7 +219,6 @@ def _css() -> str:
 .details-grid dt { font-weight: 700; }
 .details-grid dd { margin: 0; overflow-wrap: anywhere; }
 .table-scroll { overflow-x: auto; }
-.table-scroll:focus, pre:focus { outline: none; }
 .table-scroll:focus-visible, pre:focus-visible { outline: 3px solid Highlight; outline-offset: 3px; }
 table { width: 100%; border-collapse: collapse; }
 caption { text-align: left; font-weight: 700; margin-bottom: 8px; }
diff --git a/tests/test_report_focus_contrast.py b/tests/test_report_focus_contrast.py
index 12a32bfb0..6743d23c0 100644
--- a/tests/test_report_focus_contrast.py
+++ b/tests/test_report_focus_contrast.py
@@ -54,3 +54,4 @@ def test_focus_containers_suppress_mouse_click_outlines(tmp_path: Path) -> None:
     html = _render_report(tmp_path)
     assert ".table-wrap:focus {\n  outline: none;\n}" in html
     assert ".export-block pre:focus {\n  outline: none;\n}" in html
+    assert ".exact-values > summary:focus,\n.export-block > summary:focus {\n  outline: none;\n}" in html

From e212f28cf5a32bcb6bdb68e23cc4863cf3b7f9d5 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Sun, 16 Aug 2026 09:29:04 +0000
Subject: [PATCH 04/10] =?UTF-8?q?=F0=9F=8E=A8=20Palette:=20Fail-safe=20foc?=
 =?UTF-8?q?us=20outline=20suppression=20for=20pointer=20clicks?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Replaces bare `:focus { outline: none; }` rules with `:focus:not(:focus-visible)` for interactive ``, `.table-wrap`, and `
` elements in HTML reports. This prevents jarring browser outlines on mouse clicks while safely falling back to user-agent defaults if `:focus-visible` is unsupported.
---
 .Jules/palette.md                   |  7 +++++++
 CHANGELOG.md                        |  1 +
 python/fast_mlsirm/report.py        |  8 ++++----
 tests/test_report.py                |  2 +-
 tests/test_report_focus_contrast.py | 20 ++++++++++++++++----
 5 files changed, 29 insertions(+), 9 deletions(-)

diff --git a/.Jules/palette.md b/.Jules/palette.md
index d8f0c25d5..af3bc2574 100644
--- a/.Jules/palette.md
+++ b/.Jules/palette.md
@@ -23,3 +23,10 @@
 ## 2026-08-04 - Status Semantics and Numeric Alignment for Reports
 **Learning:** Explicit status semantics can make conditionally rendered empty states easier to discover with assistive technology, while tabular numerals improve visual comparison of metric columns. Focus-reveal behavior must not depend only on `:focus-visible`, and hover styling must not reduce the contrast of unrelated rows.
 **Action:** Use `role="status"` for genuine conditionally rendered status messages, apply `font-variant-numeric: tabular-nums` to numeric report tables, reveal skip links on `:focus`, retain a visible `:focus-visible` indicator, and avoid opacity-based dimming of non-hovered content.
+## 2026-08-05 - Fail-Safe Pointer Focus Outline Suppression
+**Learning:** Globally suppressing focus outlines for mouse users with `:focus { outline: none; }` destroys keyboard accessibility on user agents that do not support `:focus-visible`. A fail-safe selector approach uses `:focus:not(:focus-visible)` to explicitly target non-keyboard interaction, preserving the user agent default when `:focus-visible` is unsupported. Pointer-only ring suppression is a visual polish feature, not a complete solution for whole-product WCAG conformance; pointer users may still benefit from focus indication.
+**Action:** When suppressing pointer focus rings, always use `:focus:not(:focus-visible) { outline: none; }`.
+References:
+- W3C. (2025). *Understanding Success Criterion 2.4.7: Focus Visible*.
+- W3C. (2025). *C45: Using CSS :focus-visible to provide keyboard focus indication*.
+- W3C. (2025). *Selectors Level 4*.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d08845599..cac994a73 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 ### Fixed
 
+- 단독 실행 HTML 리포트의 focus 표시기에서 마우스 클릭 시 기본 브라우저 윤곽선이 나타나는 문제를 방지하기 위해, fail-safe CSS 선택자 `:focus:not(:focus-visible)`를 적용했습니다.
 - Public conditional-Rasch M2 fails closed without the compiled Rust core and delegates every result field to the native `m2_cmle_rasch_stat` entrypoint.
 - Reject ambiguous LLM-judge JSON with duplicate keys or unexpected top-level fields; require the exact mode-specific schema including advisory `accepted`.
 - Require compiled Rust ownership for public `s_x2` and `person_fit`, including prior-mean S-X² dispatch, with fail-closed errors when the core is missing.
diff --git a/python/fast_mlsirm/report.py b/python/fast_mlsirm/report.py
index aedb132cb..be406ac81 100644
--- a/python/fast_mlsirm/report.py
+++ b/python/fast_mlsirm/report.py
@@ -771,7 +771,7 @@ def _css() -> str:
   border-radius: 8px;
 }
 
-.table-wrap:focus {
+.table-wrap:focus:not(:focus-visible) {
   outline: none;
 }
 
@@ -860,8 +860,8 @@ def _css() -> str:
   color: var(--teal);
 }
 
-.exact-values > summary:focus,
-.export-block > summary:focus {
+.exact-values > summary:focus:not(:focus-visible),
+.export-block > summary:focus:not(:focus-visible) {
   outline: none;
 }
 
@@ -891,7 +891,7 @@ def _css() -> str:
   white-space: pre;
 }
 
-.export-block pre:focus {
+.export-block pre:focus:not(:focus-visible) {
   outline: none;
 }
 
diff --git a/tests/test_report.py b/tests/test_report.py
index 0fee77e2b..1004bce23 100644
--- a/tests/test_report.py
+++ b/tests/test_report.py
@@ -265,7 +265,7 @@ def test_render_table_region_has_keyboard_focus_style(tmp_path):
     assert 'aria-label="Candidate Comparison diagnostics table"' in html
     assert 'tabindex="0"' in html
     assert ".table-wrap:focus-visible" in html
-    assert ".table-wrap:focus {" in html
+    assert ".table-wrap:focus:not(:focus-visible) {" in html
     assert "tbody tr:hover" in html
     assert '