Skip to content

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

Description

@bluemediagroupllc

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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs triageNew or reopened issue awaiting maintainer triage

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions