mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-15 15:46:30 +03:00
The engine no longer lives in a separate repo. `crates/` is a snapshot of the
open crates (foundation, core, common, context, live, hook, skills, comp,
comp-verbs, html, browser, detect, cli) plus `Cargo.lock`, taken as a git
archive of the engine repo at the commit that finished the boundary split.
None of that repo's history comes with it, and none of it should: the closed
half stays private.
The closed half is the rule engine. It ships as a prebuilt native archive per
target, `libimpeccable_detector.a`, published as a `detector-v<X>` GitHub
Release on this repo. `crates/core/build.rs` resolves and links it three ways:
`IMPECCABLE_DETECTOR_LIB=<dir>` for a local detector build, else the
`~/.impeccable/detector/<version>/<target>/` cache, else a download verified
against its `.sha256` sidecar. `crates/core` is a thin shim over a three-symbol
C ABI; nothing above it knows the boundary exists.
What changed versus the engine repo copy:
- Every crate manifest moves from `license-file.workspace` to
`license.workspace` (this workspace declares Apache-2.0), and the workspace
gains the `postcard` dependency the boundary encoding needs.
- The launcher contract test reads `skill/scripts/impeccable{,.cmd}` instead of
a sibling `launcher/` dir, and `engine_binary` downloads from
`github.com/pbakaus/impeccable/releases/download/engine-v<version>/` instead
of the retired dist repo. No oracle golden carried the old URL, so no
re-recording was owed.
- The tests that hunted for a public repo through `IMPECCABLE_PUBLIC_REPO`,
`../impeccable-second` or a hardcoded home directory now resolve the root as
`CARGO_MANIFEST_DIR/../..`, because they are in it. The env var stays as an
override for an out-of-tree checkout.
- The in-page bundle (`detect-antipatterns-browser.js`, 2 MB of generated wasm
glue) is no longer tracked. `crates/core/build.rs` resolves it beside the
archive, hands the path to `impeccable_core::browser::IN_PAGE_BUNDLE_JS`, and
live mode serves that. `scripts/check-detector-release.mjs` now requires it
and its `.sha256` in a detector release.
- The live crate embeds `skill/scripts/live-browser*.js` and
`modern-screenshot.umd.js` directly rather than through vendored copies, so
the binary and the installed skill cannot drift.
- `crates/browser/assets/` (an unused second copy of the bundle) is gone.
- `tests/lib/engine-bin.mjs` also accepts `target/release/impeccable`, so a
plain `cargo build --release -p impeccable` is enough to run `bun run test`.
Verified with the archive from a local detector build: `cargo test --workspace`
267 pass, oracle 795 pass / 0 fail / 0 missing, `bun run build` clean, the
default suite green, and the launcher's `engine-probe` handshake answering
through `skill/scripts/impeccable`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
261 lines
5.7 KiB
Rust
261 lines
5.7 KiB
Rust
//! Port of `cli/engine/shared/constants.mjs`. Sets keep the JS insertion
|
|
//! order as slices so anything that iterates them matches.
|
|
|
|
/// JS `SAFE_TAGS`.
|
|
pub const SAFE_TAGS: &[&str] = &[
|
|
"blockquote",
|
|
"nav",
|
|
"a",
|
|
"input",
|
|
"textarea",
|
|
"select",
|
|
"pre",
|
|
"code",
|
|
"span",
|
|
"th",
|
|
"td",
|
|
"tr",
|
|
"li",
|
|
"label",
|
|
"button",
|
|
"hr",
|
|
"html",
|
|
"head",
|
|
"body",
|
|
"script",
|
|
"style",
|
|
"link",
|
|
"meta",
|
|
"title",
|
|
"br",
|
|
"img",
|
|
"svg",
|
|
"path",
|
|
"circle",
|
|
"rect",
|
|
"line",
|
|
"polyline",
|
|
"polygon",
|
|
"g",
|
|
"defs",
|
|
"use",
|
|
];
|
|
|
|
/// JS `BORDER_SAFE_TAGS`: `SAFE_TAGS` without `label` (card-shaped clickable
|
|
/// labels are a canonical side-tab shape and must stay detectable).
|
|
pub const BORDER_SAFE_TAGS: &[&str] = &[
|
|
"blockquote",
|
|
"nav",
|
|
"a",
|
|
"input",
|
|
"textarea",
|
|
"select",
|
|
"pre",
|
|
"code",
|
|
"span",
|
|
"th",
|
|
"td",
|
|
"tr",
|
|
"li",
|
|
"button",
|
|
"hr",
|
|
"html",
|
|
"head",
|
|
"body",
|
|
"script",
|
|
"style",
|
|
"link",
|
|
"meta",
|
|
"title",
|
|
"br",
|
|
"img",
|
|
"svg",
|
|
"path",
|
|
"circle",
|
|
"rect",
|
|
"line",
|
|
"polyline",
|
|
"polygon",
|
|
"g",
|
|
"defs",
|
|
"use",
|
|
];
|
|
|
|
/// JS `OVERUSED_FONTS`.
|
|
pub const OVERUSED_FONTS: &[&str] = &[
|
|
// Older monoculture (still ubiquitous):
|
|
"inter",
|
|
"roboto",
|
|
"open sans",
|
|
"lato",
|
|
"montserrat",
|
|
"arial",
|
|
"helvetica",
|
|
// Newer monoculture (the Anthropic-skill / Vercel / GitHub default wave):
|
|
"fraunces",
|
|
"instrument sans",
|
|
"instrument serif",
|
|
"geist",
|
|
"geist sans",
|
|
"geist mono",
|
|
"mona sans",
|
|
"plus jakarta sans",
|
|
"space grotesk",
|
|
"recoleta",
|
|
];
|
|
|
|
/// JS `GOOGLE_DOMAINS`.
|
|
pub const GOOGLE_DOMAINS: &[&str] = &[
|
|
"google.com",
|
|
"youtube.com",
|
|
"android.com",
|
|
"chromium.org",
|
|
"chrome.com",
|
|
"web.dev",
|
|
"gstatic.com",
|
|
"firebase.google.com",
|
|
];
|
|
/// JS `VERCEL_DOMAINS`.
|
|
pub const VERCEL_DOMAINS: &[&str] = &["vercel.com", "nextjs.org", "v0.app"];
|
|
/// JS `GITHUB_DOMAINS`.
|
|
pub const GITHUB_DOMAINS: &[&str] = &["github.com", "githubnext.com"];
|
|
|
|
/// JS `BRAND_FONT_DOMAINS`: font name -> hostname suffixes where it is allowed.
|
|
pub const BRAND_FONT_DOMAINS: &[(&str, &[&str])] = &[
|
|
("roboto", GOOGLE_DOMAINS),
|
|
("google sans", GOOGLE_DOMAINS),
|
|
("product sans", GOOGLE_DOMAINS),
|
|
("geist", VERCEL_DOMAINS),
|
|
("geist sans", VERCEL_DOMAINS),
|
|
("geist mono", VERCEL_DOMAINS),
|
|
("mona sans", GITHUB_DOMAINS),
|
|
];
|
|
|
|
/// JS `isBrandFontOnOwnDomain(font)`. The JS reads the global `location`;
|
|
/// here the caller passes `location.hostname` (`None` when there is no
|
|
/// `location`, i.e. outside a browser), which the JS treats as "not on its
|
|
/// own domain".
|
|
pub fn is_brand_font_on_own_domain(font: &str, hostname: Option<&str>) -> bool {
|
|
let Some(hostname) = hostname else {
|
|
return false;
|
|
};
|
|
let Some((_, allowed)) = BRAND_FONT_DOMAINS.iter().find(|(name, _)| *name == font) else {
|
|
return false;
|
|
};
|
|
let host = crate::js::to_lower_case(hostname);
|
|
allowed
|
|
.iter()
|
|
.any(|suffix| host == *suffix || host.ends_with(&format!(".{}", suffix)))
|
|
}
|
|
|
|
/// JS `CSS_GENERIC_FONTS`. Overused-font primary selection skips only CSS
|
|
/// generics so a system stack keeps the system face as primary.
|
|
pub const CSS_GENERIC_FONTS: &[&str] = &[
|
|
"serif",
|
|
"sans-serif",
|
|
"monospace",
|
|
"cursive",
|
|
"fantasy",
|
|
"inherit",
|
|
"initial",
|
|
"unset",
|
|
"revert",
|
|
];
|
|
|
|
/// JS `GENERIC_FONTS`. Includes the CSS generics plus platform faces for
|
|
/// design-system/serif resolution.
|
|
pub const GENERIC_FONTS: &[&str] = &[
|
|
"serif",
|
|
"sans-serif",
|
|
"monospace",
|
|
"cursive",
|
|
"fantasy",
|
|
"inherit",
|
|
"initial",
|
|
"unset",
|
|
"revert",
|
|
"system-ui",
|
|
"ui-serif",
|
|
"ui-sans-serif",
|
|
"ui-monospace",
|
|
"ui-rounded",
|
|
"-apple-system",
|
|
"blinkmacsystemfont",
|
|
"segoe ui",
|
|
];
|
|
|
|
/// JS `WCAG_LARGE_TEXT_PX` = 18 * (96 / 72).
|
|
pub const WCAG_LARGE_TEXT_PX: f64 = 18.0 * (96.0 / 72.0);
|
|
/// JS `WCAG_LARGE_BOLD_TEXT_PX` = 14 * (96 / 72).
|
|
pub const WCAG_LARGE_BOLD_TEXT_PX: f64 = 14.0 * (96.0 / 72.0);
|
|
|
|
/// JS `EM_DASH_FLOOR`.
|
|
pub const EM_DASH_FLOOR: usize = 8;
|
|
/// JS `EM_DASH_CHARS_PER_DASH`.
|
|
pub const EM_DASH_CHARS_PER_DASH: usize = 500;
|
|
|
|
/// JS `KNOWN_SERIF_FONTS`.
|
|
pub const KNOWN_SERIF_FONTS: &[&str] = &[
|
|
"fraunces",
|
|
"recoleta",
|
|
"newsreader",
|
|
"playfair display",
|
|
"playfair",
|
|
"cormorant",
|
|
"cormorant garamond",
|
|
"garamond",
|
|
"eb garamond",
|
|
"tiempos",
|
|
"tiempos headline",
|
|
"tiempos text",
|
|
"lora",
|
|
"vollkorn",
|
|
"spectral",
|
|
"source serif pro",
|
|
"source serif 4",
|
|
"source serif",
|
|
"ibm plex serif",
|
|
"merriweather",
|
|
"libre caslon",
|
|
"libre baskerville",
|
|
"baskerville",
|
|
"georgia",
|
|
"times new roman",
|
|
"times",
|
|
"dm serif display",
|
|
"dm serif text",
|
|
"instrument serif",
|
|
"gt sectra",
|
|
"ogg",
|
|
"canela",
|
|
"freight display",
|
|
"freight text",
|
|
];
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn border_safe_tags_is_safe_tags_minus_label() {
|
|
let expected: Vec<&str> = SAFE_TAGS
|
|
.iter()
|
|
.copied()
|
|
.filter(|t| *t != "label")
|
|
.collect();
|
|
assert_eq!(BORDER_SAFE_TAGS, expected.as_slice());
|
|
}
|
|
|
|
#[test]
|
|
fn brand_font_domains() {
|
|
assert!(is_brand_font_on_own_domain(
|
|
"roboto",
|
|
Some("Fonts.Google.com")
|
|
));
|
|
assert!(is_brand_font_on_own_domain("geist", Some("vercel.com")));
|
|
assert!(!is_brand_font_on_own_domain("geist", Some("notvercel.com")));
|
|
assert!(!is_brand_font_on_own_domain("inter", Some("google.com")));
|
|
assert!(!is_brand_font_on_own_domain("roboto", None));
|
|
}
|
|
}
|