[Bug] `detect` routes only .html/.htm to the DOM engine, so contrast/a11y rules never run on Blade, Vue, Svelte or configured template extensions

Author: bluemediagroupllcCreated Sep 9, 2026Updated Sep 10, 2026
Labelsneeds triage

What happened?

impeccable detect routes a file to the DOM-aware engine only when its extension is exactly .html/.htm. Every other markup-bearing extension — including built-in ones like .blade.php, .vue, .svelte, and any detector.extensions entry such as .html.erb — is sent to the text engine instead.

The files are scanned; they are misrouted. Text-engine rules still fire, so the file looks covered. But DOM-dependent rules (contrast, tap targets, heading hierarchy, ARIA) never run, and detect exits 0 with []. A skipped dimension is indistinguishable from a passing one.

The hook path does not have this bug — it routes the same file correctly. So the same file scanned two ways gives two different answers.

This looks like the same class of path-divergence as #460, but on the detect-vs-hook axis rather than file-vs-directory.

Steps to reproduce

Identical bytes, two extensions, no config needed:

BODY='<a href="#" style="color:#ccc;background:#fff">low contrast</a>'
printf '%s\n' "$BODY" > page.html
printf '%s\n' "$BODY" > page.blade.php   # built-in extension
printf '%s\n' "$BODY" > page.html.erb    # via detector.extensions

impeccable detect --json page.html        # -> low-contrast finding
impeccable detect --json page.blade.php   # -> []
impeccable detect --json page.html.erb    # -> []

Proof the file is scanned but misrouted — a text-engine rule fires on .vue while a DOM rule does not:

printf '<template>\n  <div style="transition: all 0.3s cubic-bezier(0.68,-0.55,0.27,1.55)">y</div>\n</template>\n' > t.vue
cp t.vue t.html
impeccable detect --json t.html   # -> ['bounce-easing']
impeccable detect --json t.vue    # -> ['bounce-easing']   (text rule: fires)
# but the contrast case above returns [] for .vue and a finding for .html

And the hook disagrees with detect on the very same file:

cat > .impeccable/config.json <<'JSON'
{"detector":{"extensions":[{"ext":".html.erb","engine":"html"}]}}
JSON

impeccable detect --json page.html.erb   # -> []
printf '{"tool_name":"Edit","tool_input":{"file_path":"'$PWD'/page.html.erb"},"cwd":"'$PWD'"}' \
  | impeccable hook                      # -> reports the low-contrast finding

impeccable doctor reports "No drift found" throughout.

Expected

detect routes to the DOM engine for any extension that emits markup — the built-in list plus configured engine: "html" entries — and agrees with the hook on the same file.

Likely cause

crates/detect/src/file_system.rs:

pub const HTML_EXTENSIONS: &[&str] = &[".html", ".htm"];

pub fn is_html_path(file_path: &str) -> bool {
    HTML_EXTENSIONS.contains(&to_lower_case(&jsp::extname(file_path)).as_str())
}

extname returns the last segment only, so show.html.erb yields .erb and page.blade.php yields .php. Neither is in HTML_EXTENSIONS. Note has_scannable_extension directly above it already handles multi-part extensions with an ends_with loop — is_html_path appears not to have received the same treatment, and does not consult configured extensions at all.

Impact

Any CI step or script calling impeccable detect on a Rails, Laravel, Vue, or Svelte project reports a clean pass over files whose accessibility rules were never evaluated. The failure is silent and green.

Environment

  • skill 4.3.1, engine 0.1.5
  • macOS arm64 (Darwin 25.5.0)