{
  "version": "2.1.0",
  "description": "melta UI 禁止ルール registry。foundations/prohibited.md の machine-readable 版（SSOT）。",
  "schemaVersion": 1,
  "vocabulary": {
    "ruleCategories": [
      "accessibility",
      "ai-pattern",
      "baseline",
      "button",
      "color",
      "datepicker",
      "divider",
      "form",
      "list",
      "modal",
      "motion",
      "philosophy",
      "skeleton",
      "spacing",
      "stepper",
      "table",
      "tag",
      "typography"
    ],
    "componentCategories": [
      "data-display",
      "feedback",
      "form-input",
      "layout",
      "navigation"
    ]
  },
  "rules": [
    {
      "id": "COLOR_NO_TEXT_BLACK",
      "category": "color",
      "severity": "error",
      "description": "純黒はコントラストが強すぎて長時間の利用で目が疲れる",
      "detector": "tailwind-class",
      "pattern": "text-black",
      "alternative": "text-slate-900",
      "contractLint": "enforce"
    },
    {
      "id": "COLOR_NO_ARBITRARY_TEXT_HEX",
      "category": "color",
      "severity": "warn",
      "description": "text-[#...] / text-[rgb(...)] / text-[hsl(...)] / text-[black] 等で色を直書きすると tokens.json のパレット外になりセマンティックカラー原則を回避できてしまう（最頻の検知すり抜け）。フォントサイズの text-[1rem] 等は対象外",
      "detector": "tailwind-class-prefix",
      "pattern": "text-[#",
      "prefixPatterns": [
        "text-[rgb",
        "text-[hsl",
        "text-[oklch",
        "text-[black]",
        "text-[white]"
      ],
      "alternative": "text-slate-900 / text-primary-600 などカラートークンを使う",
      "contractLint": "warn"
    },
    {
      "id": "COLOR_NO_ARBITRARY_BG_HEX",
      "category": "color",
      "severity": "warn",
      "description": "bg-[#...] / bg-[rgb(...)] / bg-[hsl(...)] / bg-[black] 等で背景色を直書きするとパレット外の色が混入する。bg-[url(...)] 等は対象外",
      "detector": "tailwind-class-prefix",
      "pattern": "bg-[#",
      "prefixPatterns": [
        "bg-[rgb",
        "bg-[hsl",
        "bg-[oklch",
        "bg-[black]",
        "bg-[white]"
      ],
      "alternative": "bg-white / bg-slate-50 / bg-primary-500 などカラートークンを使う",
      "contractLint": "warn"
    },
    {
      "id": "COLOR_NO_ARBITRARY_BORDER_HEX",
      "category": "color",
      "severity": "warn",
      "description": "border-[#...] / border-[rgb(...)] / border-[hsl(...)] / border-[black] 等で枠線色を直書きするとパレット外の色が混入する。border-[1px] 等の太さ指定は対象外",
      "detector": "tailwind-class-prefix",
      "pattern": "border-[#",
      "prefixPatterns": [
        "border-[rgb",
        "border-[hsl",
        "border-[oklch",
        "border-[black]",
        "border-[white]"
      ],
      "alternative": "border-slate-200 / border-primary-500 などカラートークンを使う",
      "contractLint": "warn"
    },
    {
      "id": "SPACE_NO_ARBITRARY_SHADOW",
      "category": "spacing",
      "severity": "warn",
      "description": "shadow-[...] で影を直書きすると elevation トークンを回避でき、過剰な影で安っぽく見える",
      "detector": "tailwind-class-prefix",
      "pattern": "shadow-[",
      "alternative": "shadow-sm / shadow-md など elevation トークンを使う",
      "contractLint": "warn"
    },
    {
      "id": "TYPO_NO_ARBITRARY_FONT",
      "category": "typography",
      "severity": "warn",
      "description": "font-[...] で font-weight / font-family を直書きすると typography トークンを回避できる（font-[300] など細すぎる weight の混入経路）",
      "detector": "tailwind-class-prefix",
      "pattern": "font-[",
      "alternative": "font-normal / font-medium / font-semibold などタイポグラフィトークンを使う",
      "contractLint": "warn"
    },
    {
      "id": "COLOR_NO_INLINE_STYLE_HARDCODE",
      "category": "color",
      "severity": "warn",
      "description": "inline style で色をハードコードするとカラールール(class 検査)を迂回できる。var(--...) 経由の CSS 変数は対象外で、# / rgb / hsl の直書きのみ検知する",
      "detector": "html-attr",
      "pattern": null,
      "alternative": "Tailwind カラートークン(bg-primary-600 等) か CSS 変数 var(--text-default) を使う",
      "contractLint": "skip",
      "htmlAttrCheck": {
        "kind": "attr-value-contains",
        "attr": "style",
        "valueRegex": "(?:background|background-color|color|border-color|fill|stroke|box-shadow|outline-color)\\s*:\\s*(?:#|rgb|hsl)"
      }
    },
    {
      "id": "PHILOSOPHY_NO_STYLE_BLOCK",
      "category": "philosophy",
      "severity": "warn",
      "description": "生 CSS の <style> ブロックは Tailwind トークン体系を迂回する escape hatch。CDN 運用では意図的回避でしか発生しない",
      "detector": "html-attr",
      "pattern": null,
      "alternative": "Tailwind ユーティリティ / デザイントークンで表現する。どうしても必要なら @layer で正規化",
      "contractLint": "skip",
      "htmlAttrCheck": {
        "kind": "tag-present",
        "tag": "style"
      }
    },
    {
      "id": "COLOR_NO_DARK_BG_GRAY",
      "category": "color",
      "severity": "error",
      "description": "テキストのコントラスト確保が困難になる",
      "detector": "tailwind-class-prefix",
      "pattern": "bg-gray-[3-9]00",
      "matchPatterns": [
        "bg-gray-300",
        "bg-gray-400",
        "bg-gray-500",
        "bg-gray-600",
        "bg-gray-700",
        "bg-gray-800",
        "bg-gray-900"
      ],
      "alternative": "bg-gray-50 〜 bg-gray-200",
      "contractLint": "enforce"
    },
    {
      "id": "COLOR_NO_PRIMARY_400",
      "category": "color",
      "severity": "warn",
      "description": "CTAとして弱く、目立たない",
      "detector": "tailwind-class",
      "pattern": "bg-primary-400",
      "alternative": "bg-primary-500",
      "contractLint": "enforce"
    },
    {
      "id": "COLOR_NO_GRAY_400_BODY",
      "category": "color",
      "severity": "error",
      "description": "WCAG不適合（コントラスト比不足）",
      "detector": "tailwind-class",
      "pattern": "text-gray-400",
      "alternative": "text-body (#3d4b5f)",
      "contractLint": "enforce"
    },
    {
      "id": "COLOR_NO_GRAY_100_BORDER",
      "category": "color",
      "severity": "error",
      "description": "薄すぎて境界が見えない",
      "detector": "tailwind-class",
      "pattern": "border-gray-100",
      "alternative": "border-slate-200",
      "contractLint": "enforce"
    },
    {
      "id": "COLOR_NO_GREEN",
      "category": "color",
      "severity": "error",
      "description": "emeraldで統一する",
      "detector": "tailwind-class-prefix",
      "pattern": "bg-green-",
      "alternative": "bg-emerald-*",
      "contractLint": "enforce"
    },
    {
      "id": "COLOR_NO_YELLOW",
      "category": "color",
      "severity": "error",
      "description": "amberで統一する",
      "detector": "tailwind-class-prefix",
      "pattern": "bg-yellow-",
      "alternative": "bg-amber-*",
      "contractLint": "enforce"
    },
    {
      "id": "COLOR_NO_ROSE",
      "category": "color",
      "severity": "error",
      "description": "redで統一する",
      "detector": "tailwind-class-prefix",
      "pattern": "bg-rose-",
      "alternative": "bg-red-*",
      "contractLint": "enforce"
    },
    {
      "id": "COLOR_NO_BLUE_BG",
      "category": "color",
      "severity": "error",
      "description": "primaryで統一する",
      "detector": "tailwind-class-prefix",
      "pattern": "bg-blue-",
      "alternative": "bg-primary-*",
      "contractLint": "enforce"
    },
    {
      "id": "COLOR_NO_BLUE_LINKS",
      "category": "color",
      "severity": "error",
      "description": "primaryで統一する",
      "detector": "tailwind-class-prefix",
      "pattern": "text-blue-",
      "alternative": "text-primary-500",
      "contractLint": "enforce"
    },
    {
      "id": "COLOR_NO_INDIGO",
      "category": "color",
      "severity": "error",
      "description": "primaryで統一する",
      "detector": "tailwind-class-prefix",
      "pattern": "bg-indigo-",
      "alternative": "bg-primary-*",
      "contractLint": "enforce"
    },
    {
      "id": "COLOR_ONLY_FORBIDDEN",
      "category": "color",
      "severity": "error",
      "description": "色だけで情報伝達すると色覚多様性への非対応になる",
      "detector": "manual",
      "pattern": null,
      "alternative": "アイコン/テキストを必ず併用",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "PHILOSOPHY_NO_EXCESSIVE_ANIMATION",
      "category": "philosophy",
      "severity": "error",
      "description": "操作の邪魔になり、認知コストを増加させる",
      "detector": "manual",
      "pattern": null,
      "alternative": "150〜300ms の状態変化フィードバックに限定",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "MOTION_NO_LONG_DURATION",
      "category": "motion",
      "severity": "error",
      "description": "操作が鈍く感じる（Progress フィルバーは例外）",
      "detector": "tailwind-class-prefix",
      "pattern": "duration-[5-9]00|duration-1000",
      "matchPatterns": [
        "duration-500",
        "duration-700",
        "duration-1000"
      ],
      "alternative": "duration-300 以下",
      "contractLint": "skip",
      "requiresContext": true
    },
    {
      "id": "MOTION_REDUCED_MOTION_REQUIRED",
      "category": "motion",
      "severity": "error",
      "description": "prefers-reduced-motion 無視はアクセシビリティ違反",
      "detector": "manual",
      "pattern": null,
      "alternative": "メディアクエリで対応",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "TYPO_NO_TRACKING_TIGHT",
      "category": "typography",
      "severity": "error",
      "description": "日本語の可読性が低下する",
      "detector": "tailwind-class",
      "pattern": "tracking-tight",
      "alternative": "tracking-normal 以上（本文2%、見出し1%）",
      "contractLint": "enforce"
    },
    {
      "id": "TYPO_NO_XS_BODY",
      "category": "typography",
      "severity": "error",
      "description": "本文には小さすぎて読みづらい",
      "detector": "manual",
      "pattern": null,
      "alternative": "text-base（16px）",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "TYPO_NO_FONT_LIGHT",
      "category": "typography",
      "severity": "error",
      "description": "細すぎて可読性が低い",
      "detector": "tailwind-class",
      "pattern": "font-light",
      "alternative": "font-normal（400）以上",
      "contractLint": "enforce"
    },
    {
      "id": "TYPO_NO_PLACEHOLDER_ONLY",
      "category": "typography",
      "severity": "error",
      "description": "入力開始で消え、目的がわからなくなる",
      "detector": "manual",
      "pattern": null,
      "alternative": "<label> を必ず使用",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "SPACE_NO_ROUNDED_NONE_CARDS",
      "category": "spacing",
      "severity": "error",
      "description": "UIの統一感を損なう",
      "detector": "tailwind-class",
      "pattern": "rounded-none",
      "alternative": "rounded-xl（12px）",
      "contractLint": "skip",
      "requiresContext": true
    },
    {
      "id": "SPACE_NO_SHADOW_LG",
      "category": "spacing",
      "severity": "error",
      "description": "影が強すぎてノイズになる",
      "detector": "tailwind-class",
      "pattern": "shadow-lg",
      "alternative": "shadow-sm 〜 shadow-md（オーバーレイ: shadow-xl）",
      "contractLint": "enforce"
    },
    {
      "id": "SPACE_NO_SHADOW_2XL",
      "category": "spacing",
      "severity": "error",
      "description": "影が強すぎてノイズになる",
      "detector": "tailwind-class",
      "pattern": "shadow-2xl",
      "alternative": "shadow-sm 〜 shadow-md（オーバーレイ: shadow-xl）",
      "contractLint": "enforce"
    },
    {
      "id": "SPACE_NO_PY_05_BTN",
      "category": "spacing",
      "severity": "error",
      "description": "タップターゲットが小さすぎる",
      "detector": "tailwind-class",
      "pattern": "py-0.5",
      "alternative": "h-8 以上（S: h-8 / M: h-10 / L: h-12）",
      "contractLint": "skip",
      "requiresContext": true
    },
    {
      "id": "SPACE_NO_P0_CARDS",
      "category": "spacing",
      "severity": "error",
      "description": "コンテンツが窮屈になる",
      "detector": "tailwind-class",
      "pattern": "p-0",
      "alternative": "p-5 以上",
      "contractLint": "skip",
      "requiresContext": true
    },
    {
      "id": "SPACE_NO_GAP0_SECTIONS",
      "category": "spacing",
      "severity": "warn",
      "description": "セクションの区切りが不明瞭",
      "detector": "tailwind-class",
      "pattern": "gap-0",
      "alternative": "gap-6 以上",
      "contractLint": "skip",
      "requiresContext": true
    },
    {
      "id": "SPACE_NO_M0_PAGE",
      "category": "spacing",
      "severity": "warn",
      "description": "ページ端にコンテンツが張り付く",
      "detector": "manual",
      "pattern": null,
      "alternative": "px-6 py-8 以上",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "SPACE_NO_DARK_SIDEBAR_BG",
      "category": "spacing",
      "severity": "error",
      "description": "メインコンテンツとのコントラストが強すぎる",
      "detector": "manual",
      "pattern": null,
      "alternative": "bg-white + ボーダー",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "SPACE_NO_NONSTANDARD_SIDEBAR_WIDTH",
      "category": "spacing",
      "severity": "error",
      "description": "実装ごとにバラつきが出る",
      "detector": "tailwind-class",
      "pattern": "w-60",
      "alternative": "w-64（標準）or w-16（コンパクト）",
      "contractLint": "skip",
      "requiresContext": true
    },
    {
      "id": "SPACE_NO_ROUNDED_XL_NAV",
      "category": "spacing",
      "severity": "error",
      "description": "ボタン等他コンポーネントとの一貫性を損なう",
      "detector": "manual",
      "pattern": null,
      "alternative": "rounded-lg",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "SPACE_NO_LARGE_NAV_ICON",
      "category": "spacing",
      "severity": "error",
      "description": "DS標準アイコンサイズからの逸脱",
      "detector": "manual",
      "pattern": null,
      "alternative": "w-5 h-5",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "SPACE_NO_DRAWER_NO_FOCUS_TRAP",
      "category": "spacing",
      "severity": "error",
      "description": "キーボードユーザーが背面要素を操作してしまう",
      "detector": "manual",
      "pattern": null,
      "alternative": "フォーカストラップを実装",
      "contractLint": "skip",
      "automationStatus": "human-only"
    },
    {
      "id": "SPACE_NO_MISSING_ARIA_CURRENT",
      "category": "spacing",
      "severity": "error",
      "description": "スクリーンリーダーが現在ページを識別できない",
      "detector": "html-attr",
      "pattern": null,
      "alternative": "Active ナビアイテムに aria-current=\"page\" を付与",
      "contractLint": "skip",
      "automationStatus": "impossible-static"
    },
    {
      "id": "SPACE_NO_3COL_LAYOUT",
      "category": "spacing",
      "severity": "warn",
      "description": "認知負荷が高い",
      "detector": "manual",
      "pattern": null,
      "alternative": "サイドバー + メインの2列",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "AI_NO_CARD_COLOR_BAR_TOP",
      "category": "ai-pattern",
      "severity": "error",
      "description": "AI生成UIの典型パターン。装飾過剰で汎用性が低い",
      "detector": "tailwind-class",
      "pattern": "border-t-4",
      "alternative": "border border-slate-200 のみでカードを構成",
      "contractLint": "enforce"
    },
    {
      "id": "AI_NO_CARD_COLOR_BAR_LEFT",
      "category": "ai-pattern",
      "severity": "error",
      "description": "Alert含め全コンポーネントで禁止",
      "detector": "tailwind-class",
      "pattern": "border-l-4",
      "alternative": "border border-*-200 rounded-lg で全周ボーダー",
      "contractLint": "enforce"
    },
    {
      "id": "AI_NO_GRADIENT_BG",
      "category": "ai-pattern",
      "severity": "error",
      "description": "装飾的なグラデーション背景はAI生成UIの典型tell。安っぽく見える",
      "detector": "tailwind-class-prefix",
      "pattern": "bg-gradient-",
      "alternative": "単色の bg-primary-500 / bg-white / bg-slate-50 を使う",
      "contractLint": "enforce"
    },
    {
      "id": "AI_NO_DECORATIVE_PURPLE",
      "category": "ai-pattern",
      "severity": "error",
      "description": "purple/violet/fuchsia 系の装飾カラーはAI生成UIの典型tell。brand colorを使う",
      "detector": "tailwind-class-segment",
      "pattern": null,
      "matchPatterns": [
        "purple",
        "violet",
        "fuchsia"
      ],
      "alternative": "bg-primary-* / text-primary-* （brand color）を使う",
      "contractLint": "enforce"
    },
    {
      "id": "BTN_NO_SAME_STYLE_PARALLEL",
      "category": "button",
      "severity": "warn",
      "description": "重要度の区別がつかない（Neutralのみ例外）",
      "detector": "manual",
      "pattern": null,
      "alternative": "階層の異なるボタンを組み合わせる",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "BTN_NO_LIGHTED_SOLO",
      "category": "button",
      "severity": "warn",
      "description": "トグル状態の対比がないと意味不明",
      "detector": "manual",
      "pattern": null,
      "alternative": "Neutralとペアで使用",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "BTN_ICON_ONLY_ARIA_REQUIRED",
      "category": "button",
      "severity": "error",
      "description": "操作内容がスクリーンリーダーに伝わらない",
      "detector": "composition",
      "pattern": null,
      "alternative": "aria-label=\"閉じる\" 等を付与",
      "contractLint": "skip",
      "compositionCheck": {
        "kind": "dom-attr-required",
        "selector": "button",
        "requireAnyAttr": [
          "aria-label",
          "aria-labelledby",
          "title"
        ],
        "when": "icon-only"
      },
      "automationStatus": "auto"
    },
    {
      "id": "BTN_MIN_TAP_TARGET",
      "category": "button",
      "severity": "error",
      "description": "高さ 44px 未満のボタン（h-6〜h-10）は当たり判定拡張が無いとモバイルで操作困難（WCAG 2.2 SC 2.5.5 Target Size (Enhanced) = AAA の 44px 水準。AA の SC 2.5.8 は 24px だが melta は 44px を採る。インラインテキストリンクを除く）。見た目を変えず after: 擬似要素でタップ領域だけ広げる。icon-only ボタンは幅拡張が別式のため第一弾対象外（2026-07-19 に manual から composition 自動検出へ昇格）。横断方針は A11Y_MIN_TAP_TARGET_44 が正。本ルールはその web（button）実装形",
      "detector": "composition",
      "pattern": null,
      "alternative": "relative after:absolute after:inset-x-0 after:top-1/2 after:h-11 after:-translate-y-1/2 を付与（button recipe 準拠なら自動で付く）",
      "contractLint": "skip",
      "compositionCheck": {
        "kind": "dom-class-required",
        "selector": "button.h-6, button.h-7, button.h-8, button.h-9, button.h-10",
        "requireAnyClass": [
          "after:h-11",
          "min-h-11"
        ],
        "excludeWhen": "icon-only"
      },
      "automationStatus": "auto"
    },
    {
      "id": "FORM_SELECT_APPEARANCE_NONE",
      "category": "form",
      "severity": "error",
      "description": "ネイティブ矢印はブラウザ間で位置・余白が不安定",
      "detector": "manual",
      "pattern": null,
      "alternative": "appearance-none + pr-10 + カスタムSVGシェブロン",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "FORM_NO_LABEL_OMIT",
      "category": "form",
      "severity": "error",
      "description": "スクリーンリーダーが目的を読み上げられない",
      "detector": "manual",
      "pattern": null,
      "alternative": "<label> を for 属性で関連付け",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "FORM_NO_SIDE_LABEL",
      "category": "form",
      "severity": "warn",
      "description": "モバイルで破綻し、視線移動が増える",
      "detector": "manual",
      "pattern": null,
      "alternative": "ラベルは入力欄の上",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "FORM_FIELDSET_LEGEND_REQUIRED",
      "category": "form",
      "severity": "error",
      "description": "グループの目的が伝わらない",
      "detector": "manual",
      "pattern": null,
      "alternative": "グループ時は <fieldset> / <legend> を使用",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "FORM_NO_AUTO_HIDE_ERROR",
      "category": "form",
      "severity": "error",
      "description": "ユーザーが読む前に消える",
      "detector": "manual",
      "pattern": null,
      "alternative": "修正されるまで表示",
      "contractLint": "skip",
      "automationStatus": "human-only"
    },
    {
      "id": "FORM_NO_COLOR_ONLY_ERROR",
      "category": "form",
      "severity": "error",
      "description": "色覚多様性への非対応",
      "detector": "manual",
      "pattern": null,
      "alternative": "ボーダー色 + エラーアイコン + テキスト",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "FORM_NO_CARD_FIELDSET_LEGEND",
      "category": "form",
      "severity": "error",
      "description": "<legend> のブラウザデフォルト描画がカードの border と干渉する",
      "detector": "manual",
      "pattern": null,
      "alternative": "カードレベルは <div> + <h2>。<fieldset>/<legend> はカード内部のフォームグループに限定",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "FORM_NO_DATE_EQUAL_WIDTH",
      "category": "form",
      "severity": "warn",
      "description": "月・日セレクトが不必要に広くなり選択しにくい",
      "detector": "manual",
      "pattern": null,
      "alternative": "flex レイアウトで年 w-28、月・日 w-20 に固定",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "FORM_NO_CHECK_ONLY_CONFIRM",
      "category": "form",
      "severity": "error",
      "description": "意図しない確定が起きる",
      "detector": "manual",
      "pattern": null,
      "alternative": "確認ボタンを必ず配置",
      "contractLint": "skip",
      "automationStatus": "human-only"
    },
    {
      "id": "FORM_NO_AUTO_FOCUS_MOVE",
      "category": "form",
      "severity": "error",
      "description": "ユーザーを混乱させる",
      "detector": "manual",
      "pattern": null,
      "alternative": "ユーザー操作に任せる",
      "contractLint": "skip",
      "automationStatus": "human-only"
    },
    {
      "id": "FORM_NO_DISTANT_ERROR",
      "category": "form",
      "severity": "error",
      "description": "フィールドとの対応がわからない",
      "detector": "manual",
      "pattern": null,
      "alternative": "フィールド直下に表示",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "MODAL_FOCUS_TRAP_REQUIRED",
      "category": "modal",
      "severity": "error",
      "description": "キーボードユーザーがモーダル外に出てしまう",
      "detector": "manual",
      "pattern": null,
      "alternative": "Tab/Shift+Tabがモーダル内を循環",
      "contractLint": "skip",
      "automationStatus": "covered-by-test"
    },
    {
      "id": "MODAL_ESC_CLOSE_REQUIRED",
      "category": "modal",
      "severity": "error",
      "description": "ユーザーが脱出できない",
      "detector": "manual",
      "pattern": null,
      "alternative": "Escキーで閉じる",
      "contractLint": "skip",
      "automationStatus": "covered-by-test"
    },
    {
      "id": "MODAL_CLOSE_REQUIRED",
      "category": "modal",
      "severity": "error",
      "description": "ユーザーが操作を中断できない",
      "detector": "manual",
      "pattern": null,
      "alternative": "閉じるボタン + Esc + オーバーレイクリック",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "MODAL_ROLE_DIALOG_REQUIRED",
      "category": "modal",
      "severity": "error",
      "description": "スクリーンリーダーがモーダルを認識できない",
      "detector": "html-attr",
      "pattern": null,
      "alternative": "role=\"dialog\" を必ず付与",
      "contractLint": "skip",
      "automationStatus": "covered-by-test"
    },
    {
      "id": "MODAL_OVERLAY_REQUIRED",
      "category": "modal",
      "severity": "error",
      "description": "背景との分離が不明確",
      "detector": "manual",
      "pattern": null,
      "alternative": "bg-black/50 のオーバーレイ",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "MODAL_NO_NESTED",
      "category": "modal",
      "severity": "error",
      "description": "モーダルの中にモーダルを開くとUXが複雑になりすぎる。合法な部品だけで構成しても合成として崩壊する",
      "detector": "composition",
      "pattern": null,
      "alternative": "設計を見直す（role=dialog を2階層以上ネストしない）",
      "contractLint": "skip",
      "compositionCheck": {
        "kind": "nested-selector",
        "selector": "[role=\"dialog\"]"
      }
    },
    {
      "id": "A11Y_NO_NESTED_INTERACTIVE",
      "category": "accessibility",
      "severity": "error",
      "description": "インタラクティブ要素の入れ子（button/a/role=button の中に別のインタラクティブ要素）は HTML 仕様上不正で、キーボード操作・スクリーンリーダー・クリック判定が壊れる。合法な部品でも組み方で崩壊する典型",
      "detector": "composition",
      "pattern": null,
      "alternative": "入れ子を解消する。クリック領域を分ける、外側を div 等の非インタラクティブ要素にする、または stretched-link パターンを使う",
      "contractLint": "skip",
      "compositionCheck": {
        "kind": "nested-selector",
        "selector": "a[href], button, [role=\"button\"]"
      }
    },
    {
      "id": "LIST_NO_FIXED_HEIGHT_MISMATCH",
      "category": "list",
      "severity": "error",
      "description": "テキストが切れる or 余白が不均等",
      "detector": "manual",
      "pattern": null,
      "alternative": "可変高さにする",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "LIST_NO_COLOR_ONLY_SELECTION",
      "category": "list",
      "severity": "error",
      "description": "色覚多様性への非対応",
      "detector": "manual",
      "pattern": null,
      "alternative": "ボーダー太さ + 背景スタイルを併用",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "LIST_NO_GESTURE_ONLY",
      "category": "list",
      "severity": "error",
      "description": "キーボード/スクリーンリーダーで操作不可",
      "detector": "manual",
      "pattern": null,
      "alternative": "ボタンによる代替操作を提供",
      "contractLint": "skip",
      "automationStatus": "human-only"
    },
    {
      "id": "LIST_NO_ICON_BORDER",
      "category": "list",
      "severity": "warn",
      "description": "情報の断絶を誤解させる",
      "detector": "manual",
      "pattern": null,
      "alternative": "ボーダーはアイテム間のみ",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "TABLE_NO_LAYOUT_TABLE",
      "category": "table",
      "severity": "error",
      "description": "セマンティクス違反",
      "detector": "manual",
      "pattern": null,
      "alternative": "flex / grid を使う",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "TABLE_TH_SCOPE_REQUIRED",
      "category": "table",
      "severity": "error",
      "description": "ヘッダーとデータの関係が不明確",
      "detector": "html-attr",
      "pattern": null,
      "alternative": "scope=\"col\" を付与",
      "contractLint": "skip",
      "htmlAttrCheck": {
        "kind": "tag-missing-attr",
        "tag": "th",
        "requiredAttr": "scope"
      }
    },
    {
      "id": "TAG_REMOVABLE_X_REQUIRED",
      "category": "tag",
      "severity": "error",
      "description": "キーボードのみでは削除操作を発見できない",
      "detector": "manual",
      "pattern": null,
      "alternative": "×ボタンを必ず表示",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "TAG_X_MIN_TAP_TARGET",
      "category": "tag",
      "severity": "error",
      "description": "モバイルで誤タップが頻発する。横断方針は A11Y_MIN_TAP_TARGET_44 が正。本ルールはその web（tag の × ボタン）実装形",
      "detector": "manual",
      "pattern": null,
      "alternative": "p-0.5 + SVG w-3 h-3（実質24px以上）",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "TAG_X_ARIA_LABEL_REQUIRED",
      "category": "tag",
      "severity": "error",
      "description": "スクリーンリーダーがボタンの目的を読み上げられない",
      "detector": "composition",
      "pattern": null,
      "alternative": "aria-label=\"{タグ名}を削除\" を付与",
      "contractLint": "skip",
      "compositionCheck": {
        "kind": "dom-attr-required",
        "selector": "button",
        "requireAnyAttr": [
          "aria-label",
          "aria-labelledby"
        ],
        "when": "text-glyph",
        "glyphs": [
          "×",
          "✕",
          "✖",
          "╳",
          "⨯"
        ]
      },
      "automationStatus": "auto"
    },
    {
      "id": "TAG_FILTER_ARIA_SELECTED_REQUIRED",
      "category": "tag",
      "severity": "error",
      "description": "選択状態がスクリーンリーダーに伝わらない",
      "detector": "html-attr",
      "pattern": null,
      "alternative": "aria-selected=\"true\" / \"false\" を付与",
      "contractLint": "skip",
      "automationStatus": "impossible-static"
    },
    {
      "id": "TAG_BADGE_CONFUSION",
      "category": "tag",
      "severity": "warn",
      "description": "Badge はステータス表示、Tag はユーザー管理メタデータ",
      "detector": "manual",
      "pattern": null,
      "alternative": "用途に応じて badge / tag を使い分ける",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "DIVIDER_NO_DIV_BORDER_B",
      "category": "divider",
      "severity": "error",
      "description": "セマンティクス違反。支援技術が区切りを認識できない",
      "detector": "manual",
      "pattern": null,
      "alternative": "<hr> or role=\"separator\"",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "DIVIDER_NO_GRAY_100",
      "category": "divider",
      "severity": "error",
      "description": "薄すぎて境界が見えない",
      "detector": "tailwind-class",
      "pattern": "border-gray-100",
      "alternative": "border-slate-200",
      "contractLint": "enforce"
    },
    {
      "id": "DIVIDER_NO_SLATE_400_PLUS",
      "category": "divider",
      "severity": "warn",
      "description": "線が強すぎてノイズになる",
      "detector": "manual",
      "pattern": null,
      "alternative": "border-slate-200（標準）/ border-slate-300（強調）",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "STEPPER_NO_COLOR_ONLY_STATE",
      "category": "stepper",
      "severity": "error",
      "description": "色覚多様性への非対応",
      "detector": "manual",
      "pattern": null,
      "alternative": "アイコン + ボーダー + 背景色を併用",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "STEPPER_ARIA_CURRENT_REQUIRED",
      "category": "stepper",
      "severity": "error",
      "description": "スクリーンリーダーが現在ステップを識別できない",
      "detector": "html-attr",
      "pattern": null,
      "alternative": "Active ステップに aria-current=\"step\" を付与",
      "contractLint": "skip",
      "automationStatus": "impossible-static"
    },
    {
      "id": "STEPPER_MIN_INDICATOR_SIZE",
      "category": "stepper",
      "severity": "error",
      "description": "タップターゲットが小さすぎる",
      "detector": "manual",
      "pattern": null,
      "alternative": "w-8 h-8（標準）/ w-6 h-6（コンパクト最小）",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "STEPPER_NO_DARK_CONNECTOR",
      "category": "stepper",
      "severity": "warn",
      "description": "線が強すぎてノイズになる",
      "detector": "manual",
      "pattern": null,
      "alternative": "bg-primary-500（完了区間）/ bg-slate-200（未着手区間）",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "DATEPICKER_NO_NATIVE_INPUT",
      "category": "datepicker",
      "severity": "warn",
      "description": "ブラウザ間で表示が不統一",
      "detector": "html-attr",
      "pattern": null,
      "alternative": "カスタム Date Picker を使用",
      "contractLint": "skip",
      "htmlAttrCheck": {
        "kind": "element-present",
        "tag": "input",
        "attr": "type",
        "attrValue": "date"
      }
    },
    {
      "id": "DATEPICKER_NO_SHADOW_LG",
      "category": "datepicker",
      "severity": "error",
      "description": "影が強すぎてノイズになる（datepicker コンテキスト。汎用は SPACE_NO_SHADOW_LG で検出）",
      "detector": "manual",
      "pattern": null,
      "alternative": "shadow-md",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "DATEPICKER_Z_INDEX_20",
      "category": "datepicker",
      "severity": "error",
      "description": "Dropdown レイヤーの統一が崩れる",
      "detector": "manual",
      "pattern": null,
      "alternative": "z-20（Dropdown レイヤー）",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "DATEPICKER_NO_COLOR_ONLY_TODAY",
      "category": "datepicker",
      "severity": "error",
      "description": "色覚多様性への非対応",
      "detector": "manual",
      "pattern": null,
      "alternative": "font-semibold を併用",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "DATEPICKER_KEYBOARD_NAV_REQUIRED",
      "category": "datepicker",
      "severity": "error",
      "description": "キーボードユーザーが操作不可",
      "detector": "manual",
      "pattern": null,
      "alternative": "矢印 + Enter + Escape を実装",
      "contractLint": "skip",
      "automationStatus": "human-only"
    },
    {
      "id": "DATEPICKER_WEEKDAY_HEADER_REQUIRED",
      "category": "datepicker",
      "severity": "error",
      "description": "日付の曜日が判別できない",
      "detector": "manual",
      "pattern": null,
      "alternative": "日〜土のラベルを必ず表示",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "SKELETON_BG_SLATE_200_ONLY",
      "category": "skeleton",
      "severity": "error",
      "description": "DS統一から外れる",
      "detector": "manual",
      "pattern": null,
      "alternative": "bg-slate-200 固定",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "SKELETON_NO_SPINNER_ONLY",
      "category": "skeleton",
      "severity": "warn",
      "description": "進捗が伝わらず不安を与える",
      "detector": "manual",
      "pattern": null,
      "alternative": "スケルトン or プログレスバーを併用",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "SKELETON_ARIA_BUSY_REQUIRED",
      "category": "skeleton",
      "severity": "error",
      "description": "スクリーンリーダーがローディング状態を認識できない",
      "detector": "composition",
      "pattern": null,
      "alternative": "コンテナに aria-busy=\"true\" + role=\"status\"",
      "contractLint": "skip",
      "compositionCheck": {
        "kind": "dom-attr-required",
        "selector": ".skeleton-pulse",
        "requireAnyAttr": [
          "aria-busy"
        ],
        "scope": "ancestor-or-self"
      },
      "automationStatus": "auto"
    },
    {
      "id": "SKELETON_ARIA_BUSY_RELEASE",
      "category": "skeleton",
      "severity": "error",
      "description": "ローディング完了が伝わらない",
      "detector": "manual",
      "pattern": null,
      "alternative": "aria-busy=\"false\" に変更",
      "contractLint": "skip",
      "automationStatus": "human-only"
    },
    {
      "id": "A11Y_NO_OUTLINE_NONE_WITHOUT_RING",
      "category": "accessibility",
      "severity": "error",
      "description": "フォーカスインジケーターが消える",
      "detector": "manual",
      "pattern": null,
      "alternative": "focus:ring-2 focus:ring-primary-500/50",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "A11Y_NO_TABINDEX_POSITIVE",
      "category": "accessibility",
      "severity": "error",
      "description": "フォーカス順序が混乱する",
      "detector": "html-attr",
      "pattern": null,
      "alternative": "DOM順に従い、tabindex=\"0\" or \"-1\" のみ使用",
      "contractLint": "skip",
      "htmlAttrCheck": {
        "kind": "attr-value-forbidden",
        "attr": "tabindex",
        "valueRegex": "^[1-9][0-9]*$"
      }
    },
    {
      "id": "A11Y_NO_TIME_LIMIT",
      "category": "accessibility",
      "severity": "error",
      "description": "ユーザーが操作を完了できない可能性",
      "detector": "manual",
      "pattern": null,
      "alternative": "時間制限を設けない",
      "contractLint": "skip",
      "automationStatus": "human-only"
    },
    {
      "id": "A11Y_NO_DECORATIVE_ANIMATION",
      "category": "accessibility",
      "severity": "warn",
      "description": "動きに敏感なユーザーへの配慮不足",
      "detector": "manual",
      "pattern": null,
      "alternative": "状態変化のフィードバックのみ",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "A11Y_NO_TEXT_TRUNCATION_200",
      "category": "accessibility",
      "severity": "error",
      "description": "コンテンツにアクセスできなくなる",
      "detector": "manual",
      "pattern": null,
      "alternative": "レスポンシブに対応",
      "contractLint": "skip",
      "automationStatus": "human-only"
    },
    {
      "id": "A11Y_DISABLED_REQUIRES_ARIA",
      "category": "accessibility",
      "severity": "warn",
      "description": "disabled 属性単独では支援技術への状態通知が実装依存になる。契約（button stateSpecs.disabled）の規範どおり aria-disabled=\"true\" を併記し、視覚状態クラス（opacity-50 cursor-not-allowed）とセットにする。タブ順維持が要る文脈（ツールバー/ページネーション）では aria-disabled 単独 + JS click ガードも可（DESIGN.md 注記参照）",
      "detector": "composition",
      "pattern": null,
      "alternative": "disabled aria-disabled=\"true\" class=\"opacity-50 cursor-not-allowed ...\"（契約 stateSpecs.disabled の tailwind 準拠）",
      "contractLint": "skip",
      "compositionCheck": {
        "kind": "dom-attr-required",
        "selector": "button[disabled]",
        "requireAnyAttr": [
          "aria-disabled"
        ]
      }
    },
    {
      "id": "A11Y_MIN_TAP_TARGET_44",
      "category": "accessibility",
      "severity": "error",
      "description": "すべての操作要素は実効タップ標的 44pt（web は 44px）を下回らない — melta の横断方針（WCAG 2.2 SC 2.5.5 Target Size (Enhanced) = AAA の 44px 水準を採る。AA の SC 2.5.8 は 24px だが melta はそこで止めない。インラインテキストリンクは例外）。視覚寸法は契約どおり据え置き、当たり判定だけを広げる: web は h-8/h-10 のまま after: 擬似要素で 44px（または min-h-11）、app は視覚 24pt + hitSlop 10 の正典パターンか minHeight で下限を保証する（height 固定は fontScale でクリップするので使わない）。コンポーネント別の BTN_MIN_TAP_TARGET / TAG_X_MIN_TAP_TARGET は本方針の実装形であり、方針そのものの正本は本ルール",
      "detector": "manual",
      "pattern": null,
      "alternative": "視覚寸法は変えずタップ領域だけ拡張する。web: relative after:absolute after:inset-x-0 after:top-1/2 after:h-11 after:-translate-y-1/2（または min-h-11）/ app: hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} か minHeight: 44",
      "contractLint": "skip",
      "automationStatus": "llm-judge-candidate"
    },
    {
      "id": "A11Y_NAV_ARIA_LABEL_REQUIRED",
      "category": "accessibility",
      "severity": "error",
      "description": "<nav>（および role=\"navigation\"）にアクセシブルネームが無いと、スクリーンリーダーのランドマーク一覧で複数のナビゲーションを区別できない。nav が 1 つしか無い画面でも、後から増えたときに無名ランドマークが並ぶため常に付与する",
      "detector": "composition",
      "pattern": null,
      "alternative": "<nav aria-label=\"メインナビゲーション\"> のように aria-label を付与する（見出し要素を指す aria-labelledby でもよい）。空文字・空白のみの値はアクセシブルネームにならないので不可。aria-labelledby の参照先の存在は静的検査しない（fragment 検査では参照先が文書外にあるのが常態のため。design-review の目視で確認する）",
      "contractLint": "skip",
      "compositionCheck": {
        "kind": "dom-attr-required",
        "selector": "nav, [role~=\"navigation\" i]",
        "requireAnyAttr": [
          "aria-label",
          "aria-labelledby"
        ]
      },
      "automationStatus": "auto"
    },
    {
      "id": "BASELINE_NO_ANCHOR_POSITIONING",
      "category": "baseline",
      "severity": "warn",
      "description": "CSS Anchor Positioning は Baseline Limited（2026-07 時点, webstatus.dev）。fallback なしでは非対応ブラウザで配置が崩壊する。Baseline Widely available 未達機能は fallback 併記 + コメント明示宣言がない限り使わない",
      "detector": "tailwind-class-prefix",
      "pattern": "[anchor-name:",
      "prefixPatterns": [
        "[position-anchor:",
        "[position-area:",
        "[position-try"
      ],
      "alternative": "absolute + top/left トークン系ユーティリティによる従来配置（DESIGN.md の配置パターン準拠）",
      "contractLint": "warn"
    },
    {
      "id": "BASELINE_NO_INVOKER_COMMANDS",
      "category": "baseline",
      "severity": "warn",
      "description": "Invoker Commands（commandfor / command 属性）は Baseline Limited（2026-07 時点）。非対応ブラウザではボタンが完全に無反応になる",
      "detector": "html-attr",
      "pattern": null,
      "alternative": "明示的な JS イベントリスナー、または dialog は showModal() 呼び出しで開閉する",
      "contractLint": "skip",
      "htmlAttrCheck": {
        "kind": "attr-present",
        "attr": "(?:commandfor|command)"
      }
    },
    {
      "id": "BASELINE_NO_POPOVER_ATTR",
      "category": "baseline",
      "severity": "warn",
      "description": "popover 属性は Baseline Newly → Widely 境界（2026-07 時点）。polyfill なしの生成では古い環境で開閉不能になる。使う場合は fallback かコメント明示宣言を伴うこと",
      "detector": "html-attr",
      "pattern": null,
      "alternative": "dropdown/tooltip contract の実装パターン（絶対配置 + JS toggle）を使う",
      "contractLint": "skip",
      "htmlAttrCheck": {
        "kind": "attr-present",
        "attr": "popover"
      }
    },
    {
      "id": "BASELINE_NO_VIEW_TRANSITION",
      "category": "baseline",
      "severity": "warn",
      "description": "View Transitions は same-document が Baseline Newly / cross-document が Limited（2026-07 時点）。演出前提の UI 構造にすると非対応環境で意味が壊れる",
      "detector": "tailwind-class-prefix",
      "pattern": "[view-transition-name:",
      "prefixPatterns": [
        "[view-transition-class:"
      ],
      "alternative": "motion トークン（duration/easing）による CSS transition で代替する",
      "contractLint": "warn"
    },
    {
      "id": "BASELINE_NO_TEXT_BOX_TRIM",
      "category": "baseline",
      "severity": "warn",
      "description": "text-box-trim / text-box-edge は Baseline Newly（2026-07 時点）。行ボックスの光学調整に依存したレイアウトは非対応環境で余白がズレる",
      "detector": "tailwind-class-prefix",
      "pattern": "[text-box-trim:",
      "prefixPatterns": [
        "[text-box-edge:",
        "[text-box:"
      ],
      "alternative": "spacing トークン（py-* / leading-*）で余白を明示指定する",
      "contractLint": "warn"
    }
  ]
}
