Skip to content

fix(a11y): aria/role 欠落を補完(JanCode/CidrCalculator/RegexMatchTester) - #665

Merged
fumtas1k merged 2 commits into
developfrom
claude/issue-663-sonnet-tlmtf5
Jun 13, 2026
Merged

fix(a11y): aria/role 欠落を補完(JanCode/CidrCalculator/RegexMatchTester)#665
fumtas1k merged 2 commits into
developfrom
claude/issue-663-sonnet-tlmtf5

Conversation

@fumtas1k

Copy link
Copy Markdown
Owner

概要

issue #663 で検出した DADS 準拠監査の aria / role 欠落・不整合を補完する。3 件とも独立した a11y 修正。

Closes #663

変更内容

1. JanCode: バーコード svg に role="img" を付与

  • src/components/tools/JanCode.tsx
  • aria-label はあったが role="img" がなく、<svg> のデフォルト role はブラウザ依存。role="img" を付与して SR が aria-label を確実に accessible name として読めるようにする(ARIA 1.2)。QrCode.tsx が生成 SVG に role="img" を注入済みなのと実装を揃えた。

2. CidrCalculator: 重複検出の行エラー一覧に role="alert" を付与

  • src/components/tools/CidrCalculator.tsx
  • <section aria-live="polite"> は既存だが、section 全体が一度にマウントされると live region の初期コンテンツは SR に確実に通知されない。解析エラーは重要なため、エラーコンテナ <div>role="alert"(暗黙 assertive + atomic)を付与して確実に通知する。pairs テーブルや「重複なし」メッセージには付けない。

3. RegexMatchTester: クリック可能な <mark> をキーボード・SR 操作可能に

  • src/components/tools/RegexMatchTester.tsx
  • 行選択用 onClick を持つ <mark>title のみでマウス専用だった。role="button" / tabIndex={0} / aria-pressed(選択状態)/ aria-label(空マッチは「(空マッチ)」付き)/ onKeyDown(Enter・スペースで選択)を付与。
  • フォーカスリングは global.css:where(..., [role='button'], ...):focus-visible global rule が自動適用するため追加 CSS 不要。outline-none は未使用。

テスト(陽性対照 E2E)

属性・キーボード操作が外れると fail する設計で各 spec に追加:

  • tests/e2e/jan-code.spec.ts: getByRole('img', { name: /バーコード/ })role="img" + accessible name を検証
  • tests/e2e/regex-visualizer.spec.ts: getByRole('button', { name: /マッチ 1/ })role="button" を検証し、Enter キーで aria-pressedfalse → true に変わる(onKeyDown 経路)ことを検証
  • tests/e2e/cidr-calculator.spec.ts: 不正 CIDR(10.0.0.0/99)入力後に getByRole('alert') でエラー一覧コンテナが取得できることを検証

検証結果

検証 結果
node_modules/.bin/astro check 0 errors / 0 warnings / 0 hints
npm run test(unit) 関連テスト pass(失敗はビルド成果物・git 署名サーバ依存のメタテストのみで本変更と無関係)
npm run test:e2e(対象 3 spec + a11y-live-region) 32 passed / 0 failed

関連

https://claude.ai/code/session_01QpTRCLJWorrqTYT5WfdVxQ


Generated by Claude Code

issue #663 で検出した DADS 準拠監査の aria/role 欠落を補完する。

- JanCode: バーコード svg に role="img" を付与(QrCode と揃え、aria-label を
  accessible name として SR が確実に読めるようにする)
- CidrCalculator: 重複検出モードの行エラー一覧に role="alert" を付与(親
  aria-live の初期コンテンツ問題を回避し解析エラーを SR に確実に通知)
- RegexMatchTester: クリック可能な mark に role="button" / tabIndex /
  aria-pressed / aria-label / onKeyDown を付与しキーボード・SR 操作可能にする

各修正に陽性対照 E2E を追加(属性・キーボード操作が外れると fail する設計)。
@github-actions

github-actions Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

🖼️ Visual Regression Test 結果

  • Status: ✅ 全 60 件 pass
  • Workflow run: 27482151255
  • Artifact (diff 画像 / playwright-report): 上記 workflow run の Artifacts セクションから download

diff が 意図的な visual 変更の場合: Update Visual Regression Baseline workflow を本 PR ブランチで workflow_dispatch trigger して baseline を更新。
diff が 意図しない regression の場合: 該当変更を fix。
本 check は required ではないため fail のままでも merge は可能(reviewer 判断)。

@fumtas1k fumtas1k left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

レビュー結果: LGTM(self-PR のため Approve 不可、コメントで記録)✅

issue #663 の 3 項目(JanCode role="img" / CidrCalculator role="alert" / RegexMatchTester の <mark> 操作可能化)を過不足なく実装しており、CI も全 green(test / e2e / VRT 60 件 pass)。多角的観点で評価しました。

セキュリティ

  • 問題なし。aria-label に差し込む result.fullCode / m.value(ユーザー入力)はいずれも React の属性値として render されるためエスケープされ、反射型 XSS の経路にならない。dangerouslySetInnerHTML も不使用。

アーキテクチャ / 一貫性

  • JanCode の role="img" 付与は QrCode.tsx の既存実装(生成 SVG への role="img" 注入)と方針が揃っており、SVG の accessible name 露出パターンが統一された点が good。
  • CidrCalculator は live region 化対象を行エラー一覧のみに限定し、pairs テーブル・「重複なし」メッセージには付けない切り分けが適切。

ロジック / フロントエンド

  • <mark role="button"> + tabIndex + onKeyDown(Enter/Space) + aria-pressed の構成は妥当。Space に preventDefault() を入れてページスクロールを抑止している点も正しい。空マッチに (空マッチ) を付けた aria-label 分岐も適切。

テスト

  • 3 件とも陽性対照(属性を外すと getByRole 取得不能で fail)になっており test-gates の趣旨に合致。特に regex spec が aria-pressed: false → true の遷移で onKeyDown 経路まで観測している点が良い。

任意(非ブロッキング)の指摘

1. role="alert" のキーストロークごとの再読み上げ(CidrCalculator)
overlapResultuseMemo([mode, overlapInput])debounce なしに毎キーストローク再計算される(CidrCalculator.tsx:287)。role="alert" は暗黙 aria-live="assertive" + aria-atomic="true" のため、エラーが残っている間は入力 1 文字ごとにエラー一覧全体が assertive に再読み上げされ、SR ユーザーには割り込みが連続して冗長になり得ます。

  • エラー通知を確実にする意図は理解できますが、複数行 textarea で打鍵頻度が高い UI では role="status"(polite)への変更、もしくは overlap 計算の debounce 化で chattiness を抑える選択肢もあります。判断はお任せしますが、別 issue 化でも良いと思います。

2. <mark> の tab stop 数(RegexMatchTester、軽微)
g フラグ + 長文では全マッチがそれぞれ tab stop になり、キーボードユーザーが多数の focus 位置を踏むことになります。下部の ResultTable でも選択できるため実害は小さいですが、将来的に roving tabindex(コンテナ 1 stop + 矢印キー移動)にすると navigation が軽くなります。これも別 issue 候補。

3. 排他選択の ARIA セマンティクス(RegexMatchTester、設計上の選択肢)
「1 つだけ選択」を複数の aria-pressed トグルボタンで表現しています。WAI-ARIA 的には許容範囲ですが、厳密には radiogroup/radio の方が排他選択の意味に近いです。現状のままで問題はないので参考まで。

いずれも本 PR をブロックする欠陥ではありません。スコープも明確で、このまま merge して良いと判断します。


Generated by Claude Code

issue #663 PR レビュー指摘 #1 への対応。overlapResult は毎キーストローク再計算
されており、行エラー一覧の role="alert"(暗黙 assertive + atomic)がエラー継続中は
入力 1 文字ごとに全体を SR へ割り込み再読み上げしてしまう問題があった。

overlapInput を useDebouncedValue(300ms) で debounce してから計算することで、
打鍵が止まってから 1 度だけエラー一覧を更新する。エラー出現時の確実な通知
(role="alert")は維持しつつ、SR ユーザーへの chattiness を抑制する。
入力欄自体は即時の overlapInput を value に保つため打鍵応答は変わらない。

Copy link
Copy Markdown
Owner Author

レビューありがとうございます。任意指摘 3 点を以下のとおり対応しました。

#1 role="alert" のキーストロークごと再読み上げ → 本 PR で対応(0b411af)

overlapResult の計算を useDebouncedValue(overlapInput, 300) で debounce 化しました。打鍵が止まってから 1 度だけ行エラー一覧を更新するため、エラー継続中の入力 1 文字ごとの assertive 割り込み再読み上げを抑制します。role="alert" によるエラー出現時の確実な通知は維持し、入力欄自体は即時の overlapInputvalue に保つため打鍵応答は変わりません。

  • role="status"(polite)案ではなく debounce を選んだ理由: polite でも変更ごとに announcement がキューされ chattiness は残り、かつ親 aria-live="polite" section の「初期コンテンツが mount 時に確実に通知されない」弱点が再発するため。debounce なら role="alert" の確実性を保ったまま冗長さだけを除けます(副次的に毎キーストロークの O(n) 再計算も解消)。
  • 検証: astro check clean / npm run build OK / cidr-calculator E2E 10 件 pass(role="alert" テスト含む)。

#2 / #3 → follow-up issue 化

いずれも P3 として起票しました。#2#3 は併せて UX を見直す価値があるため相互参照を記載しています。


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

再レビュー: 指摘 3 点すべて解決を確認 ✅

迅速なご対応ありがとうございます。0b411af と follow-up issue を確認しました。

#1 role="alert" の冗長な再読み上げ → 解決(0b411af

  • useDebouncedValue(overlapInput, 300)計算側(overlapResult の入力)にのみ適用し、InputFieldvalue は即時の overlapInput を保持しているため、打鍵応答は変わらず assertive な割り込み再読み上げだけを抑制できています。意図どおりの正しい切り分けです。
  • role="status"(polite)ではなく debounce を選んだ判断にも同意します。polite でも変更ごとに announcement がキューされるうえ、親 aria-live="polite" section の初期コンテンツ未通知問題が再発するため、role="alert" の確実性を保ったまま冗長さだけ除ける debounce が妥当です。副次的に毎キーストロークの O(n) 再計算も解消されています。
  • useDebouncedValueclearTimeout cleanup 付きの標準実装で、debounce 計算と入力 state の分離も適切。コメントに意図が明記されている点も good。

#2 / #3 → follow-up issue 化を確認

結論

当初の LGTM 判断は維持です。本 PR スコープで対応すべき指摘(#1)は解消され、残りは適切に issue 化されました。CI(test / VRT)green、e2e の green 確認後にこのまま merge して問題ありません。


Generated by Claude Code

@fumtas1k
fumtas1k merged commit abf2083 into develop Jun 13, 2026
4 checks passed
@fumtas1k
fumtas1k deleted the claude/issue-663-sonnet-tlmtf5 branch June 13, 2026 23:27
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.

a11y(dads): aria/role 欠落の補完(JanCode svg role=img / CidrCalculator 動的エラー aria-live / RegexMatchTester の mark)

2 participants