From 7bf0743852cd52304d7d6754263c4d2af0523ad0 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Thu, 3 Sep 2026 10:54:15 -0700 Subject: [PATCH] The immediate tier moves to the registry, and reaches wasm The design hook's immediate-tier list is the set of rule ids worth fixing at the edit site, and a downstream reviewer wants the same set to decide how loudly a finding is reported. `impeccable-hook` is native-only, so the list moves to `impeccable_core::registry` (the hook re-exports it) and the `detect` feature gains `immediate_tier_rules_json()`. The export is behind `detect`, which the in-page bundle does not build, so the tracked browser asset is unchanged. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY --- crates/foundation/src/registry.rs | 30 ++++++++++++++++++++++++++++++ crates/hook/src/hook_lib.rs | 18 +++--------------- crates/wasm/src/exports_detect.rs | 13 +++++++++++++ docs/ENGINE.md | 7 ++++++- 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/crates/foundation/src/registry.rs b/crates/foundation/src/registry.rs index c3c3051ee..9120bbdc1 100644 --- a/crates/foundation/src/registry.rs +++ b/crates/foundation/src/registry.rs @@ -706,6 +706,36 @@ pub static ANTIPATTERNS: &[Antipattern] = &[ }, ]; +/// The rules the design hook fixes at edit time rather than deferring to a +/// review pass: broken output, objective legibility failures, single-property +/// mechanical slop, and design-system drift. Every one of them is mechanical, +/// unambiguous, and cheap to correct at the edit site. +/// +/// It lives here rather than in `impeccable-hook` because the hook crate is +/// native-only (it reaches for the filesystem and the process environment) +/// while downstream consumers want the same list from wasm. `hook_lib` +/// re-exports it; the `detect` feature of `impeccable-wasm` exports it as +/// JSON. +pub const IMMEDIATE_TIER_RULES: &[&str] = &[ + // Broken output. + "broken-image", + "text-overflow", + "clipped-overflow-container", + "body-text-viewport-edge", + // Objective contrast / legibility failures. + "low-contrast", + "gray-on-color", + "tiny-text", + // Single-property mechanical slop, trivial to fix at the edit site. + "gradient-text", + "dark-glow", + // Design-system drift compounds if not corrected at edit time. + "design-system-font", + "design-system-color", + "design-system-radius", + "design-system-font-size", +]; + /// JS `RULE_ENGINE_SUPPORT`. pub const RULE_ENGINE_SUPPORT: &[(&str, &[&str])] = &[ ("regex", &["source", "page-analyzer"]), diff --git a/crates/hook/src/hook_lib.rs b/crates/hook/src/hook_lib.rs index 128ef3dc8..4e50050dc 100644 --- a/crates/hook/src/hook_lib.rs +++ b/crates/hook/src/hook_lib.rs @@ -100,21 +100,9 @@ pub fn depth_is_set(value: Option<&str>) -> bool { !text.is_empty() && text.bytes().all(|b| b.is_ascii_digit()) && text.bytes().any(|b| b != b'0') } -pub const IMMEDIATE_TIER_RULES: &[&str] = &[ - "broken-image", - "text-overflow", - "clipped-overflow-container", - "body-text-viewport-edge", - "low-contrast", - "gray-on-color", - "tiny-text", - "gradient-text", - "dark-glow", - "design-system-font", - "design-system-color", - "design-system-radius", - "design-system-font-size", -]; +/// The immediate tier, owned by the registry so wasm consumers can read the +/// same list without linking this native-only crate. +pub use impeccable_core::registry::IMMEDIATE_TIER_RULES; pub const ADVISORY_RULES: &[&str] = &["em-dash-overuse"]; diff --git a/crates/wasm/src/exports_detect.rs b/crates/wasm/src/exports_detect.rs index 1b96b9e7f..c618d9497 100644 --- a/crates/wasm/src/exports_detect.rs +++ b/crates/wasm/src/exports_detect.rs @@ -146,3 +146,16 @@ pub fn detect_html_source_json(html: &str, file_path: &str, options_json: &str) ); findings_json(&findings) } + +/// The immediate tier: the rule ids the design hook fixes at edit time, as a +/// JSON array of strings. +/// +/// It rides the `detect` feature because the consumers that need it are the +/// ones scanning files without the binary. A reviewer downstream reads it to +/// decide how loudly a finding is reported, and copying the list into that +/// codebase is how the two drift apart. +#[wasm_bindgen] +pub fn immediate_tier_rules_json() -> String { + serde_json::to_string(impeccable_core::registry::IMMEDIATE_TIER_RULES) + .unwrap_or_else(|_| "[]".into()) +} diff --git a/docs/ENGINE.md b/docs/ENGINE.md index 22ce71cc5..90f2efa39 100644 --- a/docs/ENGINE.md +++ b/docs/ENGINE.md @@ -206,7 +206,12 @@ and return the findings array `impeccable detect --json` prints, same keys and same order. `designSystem` carries the DESIGN.md inputs rather than a normalized object, because the JS API's normalized form used `Set`s and `Map`s that JSON cannot hold. Unparseable options fall back to the defaults. -`antipatterns_json()` lists the built-ins followed by any pack's rows. +`antipatterns_json()` lists the built-ins followed by any pack's rows, and +`immediate_tier_rules_json()` returns the design hook's immediate tier (the +rule ids worth fixing at the edit site). That list lives in +`impeccable_core::registry::IMMEDIATE_TIER_RULES`, which `impeccable-hook` +re-exports, so a wasm consumer reads the same one the hook runs on instead of +keeping a copy. A pack reaches those exports through `impeccable_wasm::set_rule_pack` and `exports_detect::set_static_rule_pack`, both Rust-only: the consumer is a