なぜ画面だけ毎回人の手が要るのか
コード品質には既に自動の仕掛けがある。lint が規則違反を止め、型検査が形を揃え、テストが挙動を守る。この三つは人が見ていなくても回り、破ればマージが止まる。
画面はそうではない。コントラストが崩れていないか、カードが二重になっていないか、モバイルで文字が切れないか。たいていはレビュアーが目で判断する。だからレビュアーが忙しければ通り、人が替われば基準も変わる。同じ指摘が数か月後にまた上がってくる。
画面を AI に任せはじめると、この問題は大きくなる。人が一日でつくっていた画面をエージェントが一時間でつくる一方、レビューは相変わらず人の速度だからだ。自動で回せる部分と人が見るべき部分を分けておかないと、ボトルネックはそのまま残る。
機械が見られる項目は思ったより広い。コントラスト比、行間、タップ領域の大きさ、見出し段階の飛ばし、カードの入れ子、色の組み合わせ——値で判定できるものだ。こうした項目を CI に下ろし、人は階層と意味の伝達に集中すればよい。
終了コード一つがそのままゲートになる
Impeccable の detect は結果を終了コードで知らせる。指摘があればゼロ以外で終わり、なければ 0 で終わる。CI はコマンドがゼロ以外で終わればその手順を失敗とみなすので、ワークフローにコマンドを一行入れるだけで、間に何のアクションもプラグインも挟まずゲートになる。
実際に確かめるとこうだ。よくある癖を詰め込んだ HTML 一枚に走らせると 17 件、終了コードは 2。指摘された箇所だけ直して再度走らせると 0 件、終了コード 0。この差ひとつでパイプラインが分かれる。
掛ける場所は二つがよい。一つはソースのフォルダを対象にする手順、もう一つはプレビュー配信の URL を対象にする手順だ。次節で述べるとおり、両者は掴む項目が違う。
LLM を呼ばない点は CI で特に効く。API キーを秘密値として置く必要がなく、同じ入力に同じ結果が出て、実行時間と費用が読める。モデルの応答で判定が揺れる検査はゲートには使えない。
- name: Design check
run: npx impeccable detect src/
# プレビュー配信も併せて見るなら
- name: Design check (deployed)
run: npx impeccable detect ${{ steps.deploy.outputs.preview-url }}対象によって検査のしかたが分かれる
同じコマンドでも、何を渡すかで中の仕事が違う。HTML ファイルはリンクされた CSS まで一緒に読んで静的に解析する。JSX、TSX、CSS といったそれ以外のファイルはパターンマッチで洗う。URL を渡すと実際のブラウザでページを開き、レンダリングが終わった画面を見る。
この違いが実務で混乱を生む。コンポーネントのソースだけを走査すれば、値として現れる項目は掴めるが、実際に組み合わさったときだけ現れる項目は素通りする。たとえば見出しのすぐ上に付いた小さなラベルや最終的なコントラスト値は、画面が組み上がってからでないと判定できない。ソース走査が 0 件でも配信 URL の走査で指摘が出るのはこのためだ。
だからゲートを一つだけ置くなら、プレビュー URL のほうが多く掴む。ただし URL 検査はブラウザを起動するぶんソース走査より遅い。ソース走査は全プッシュに、URL 走査はプレビューが用意できてから掛ける組み合わせが無難だ。
選択肢は四つ知っていれば足りる。json は機械可読の出力で、レポート生成や PR コメントへ渡すのに向く。scope は型やレイアウトのように特定の領域だけを見させる。viewport は URL 検査で画面幅を変え、モバイルでもう一度回す。no-advisory は参考として分類された指摘をそもそも隠す。
npx impeccable detect --json src/ > findings.json # レポート用
npx impeccable detect --scope type,layout src/ # 領域を限定
npx impeccable detect --viewport 390x844 <URL> # モバイル幅で再検査
npx impeccable detect --no-advisory src/ # 参考項目を隠す例外は理由とともにコードに残す
ゲートを掛ければ必ず例外が要る。ブランドが決めた書体がありふれた一覧に入っていたり、特定のページだけ意図して規則を外れたりする。そのとき検査を丸ごと切るのではなく、二つの方法で記録する。
一つ目はファイル内のコメントだ。impeccable-disable のあとに規則名と理由を書けば、そのファイルでだけ免除される。一行だけ免除する書き方もある。例外がコードの隣にあるので、ファイルを移したり消したりすれば例外も一緒に消える。
二つ目はリポジトリの設定だ。ignores コマンドで規則、ファイルパス、特定の値を例外として登録するとリポジトリ全体に効き、理由も併せて残せるので、なぜ開けておいたかを後から辿れる。
どちらにも共通点がある。例外が会話ではなくファイルに残るということだ。レビューで口頭合意した例外は次の人へ伝わらないが、リポジトリに書かれた例外は伝わる。
<!-- impeccable-disable overused-font -- ブランド指定の書体 -->
/* impeccable-disable-line dark-glow */
npx impeccable ignores add-value overused-font Inter --reason "Brand font"
npx impeccable ignores listコミットの前に引っかけたいなら
CI は最後の網だ。作り終えてから引っかかると戻す手間がかかる。そこで対応するツールには、編集の瞬間に走るフックが併せて入る。UI ファイルを直した瞬間に検出器が回り、結果をエージェント側へ返してくれる。
ツールごとに介入の時点が違う。Cursor は誤った書き込みが反映される前に止め、それ以外は修正が終わったあとに知らせる。どちらにせよ、人がレビューを開く前に一度濾されるという点は同じだ。
フックを使うかはインストールの過程で尋ねられる。自動化が編集の流れを断つのが嫌なら、フックは外して CI だけ掛ければよい。逆に画面づくりを多くエージェントに任せるチームほどフックが効く。作った直後に指摘が返ってこそ、エージェントが同じ文脈のまま直せるからだ。
