diff --git a/Cargo.lock b/Cargo.lock
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -659,6 +659,10 @@ dependencies = [
  "toml_edit",
 ]
 
+[[package]]
+name = "craft-testkit"
+version = "0.1.0"
+
 [[package]]
 name = "craft-ui"
 version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,7 @@ craft-docpath = { path = "crates/docpath" }
 craft-print = { path = "crates/print" }
 craft-quit = { path = "crates/quit" }
 craft-settings = { path = "crates/settings" }
+craft-testkit = { path = "crates/testkit" }
 craft-ui = { path = "crates/ui" }
 egui = { version = "0.36", default-features = false }
 rayon = "1"
diff --git a/crates/testkit/Cargo.toml b/crates/testkit/Cargo.toml
new file mode 100644
--- /dev/null
+++ b/crates/testkit/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "craft-testkit"
+version.workspace = true
+edition.workspace = true
+license.workspace = true
+rust-version.workspace = true
+repository.workspace = true
+description = "Source-level platform regression guards and verification utilities for the Craft application suite"
+
+[dependencies]
+
+[lints]
+workspace = true
diff --git a/crates/testkit/README.md b/crates/testkit/README.md
new file mode 100644
--- /dev/null
+++ b/crates/testkit/README.md
@@ -0,0 +1,79 @@
+# craft-testkit
+
+Source-level platform regression guards and verification utilities for the Craft application suite.
+
+## Overview
+
+`craft-testkit` provides lightweight, non-panicking source guards that prevent applications in the Craft ecosystem from repeating platform defects that were previously diagnosed and resolved in peer applications:
+
+1. **`predefined_quit`**: Flags direct calls to `muda`'s `PredefinedMenuItem::quit` outside comments. On macOS, this predefined menu item routes directly to `NSApplication terminate:`, completely bypassing application dirty document checks and unsaved changes save prompts. Applications should instead wire Quit through `craft-quit` lifecycle interceptors.
+2. **`wgsl_float_literals` / `wgsl_float_literals_in`**: Scans WGSL shader sources using PhotoCraft's decimal float scanner and flags literals whose absolute value exceeds `f32::MAX` (such as `3.40282347e38`). While native compilers (`naga`) round these down, browser WebGPU compilers (`Tint` in Chromium) reject them with fatal pipeline compilation errors, forcing applications into CPU fallback rendering. Exact values equal to `f32::MAX` must be written as exact hex float literals (`0x1.fffffep+127f`).
+3. **`std_instant`**: Flags lines naming `std::time::Instant` outside comments without `web_time`. On `wasm32-unknown-unknown`, `std::time::Instant::now()` panics at runtime with `"time not implemented on this platform"`. Code targeting the web must use `web_time::Instant`.
+4. **`rust_sources`**: Recursively discovers a crate's `.rs` source files while excluding integration tests (`tests/`), test modules (`tests.rs`, `tests_*.rs`, `*_tests.rs`, `*_test.rs`), and build artifacts (`target/`, `.git/`).
+
+## Epistemic Scope: Syntactic Guards vs Runtime Validation
+
+Source-level guards provide deterministic syntactic evidence about codebase structure:
+
+### What They Establish
+- Checked source files do not contain forbidden symbol invocations, known out-of-range literal representations, or platform-hazardous imports.
+- Reusable, deterministic checks that run in milliseconds as regular Cargo tests without external runners.
+
+### What They Cannot Establish
+- **Runtime execution safety**: Syntactic checks do not verify whether code branches actually execute at runtime.
+- **Semantic type resolution**: The scanners do not resolve arbitrary type aliases (e.g. `use std::time as t; t::Instant::now()`) or expand procedural macros.
+- **Complex target predicates**: While `std_instant` recognizes direct item-level non-wasm target conditions (e.g. `#[cfg(not(target_arch = "wasm32"))]`), it does not evaluate arbitrary multi-crate boolean expressions. Applications should scope `std_instant` tests to the specific crates that compile into their web distribution.
+- **Driver and host OS behavior**: Syntactic checks do not verify WebGPU driver compatibility or host window manager event dispatch.
+
+Source guards serve as fast, zero-dependency regression gates in application test suites, complementing platform integration tests and CI workflows.
+
+## Consumer Adoption Examples
+
+### 1. PhotoCraft: Adopt `wgsl_float_literals` and `predefined_quit`
+
+In `photocraft/crates/gpu/src/lib.rs`, replace the in-tree `decimal_float_literals` helper with `craft_testkit::wgsl_float_literals`:
+
+```rust
+#[test]
+fn shader_float_literals_fit_f32() {
+    let bad = craft_testkit::wgsl_float_literals(SHADER);
+    assert!(
+        bad.is_empty(),
+        "float literals outside f32 finite range reject on WebGPU (Tint):\n{}",
+        bad.iter().map(|f| f.to_string()).collect::<Vec<_>>().join("\n")
+    );
+}
+```
+
+In `photocraft/apps/photocraft/tests/native_menu_tests.rs`:
+
+```rust
+#[test]
+fn no_unintercepted_predefined_quit_in_menu() {
+    let sources = craft_testkit::rust_sources("src").expect("scan sources");
+    let findings = craft_testkit::predefined_quit(&sources);
+    assert!(
+        findings.is_empty(),
+        "predefined muda Quit bypasses dirty document save prompts:\n{}",
+        findings.iter().map(|f| f.to_string()).collect::<Vec<_>>().join("\n")
+    );
+}
+```
+
+### 2. FilmCraft: Adopt `std_instant` and `wgsl_float_literals`
+
+In `filmcraft/crates/engine/src/wasm_clock_tests.rs`, replace in-tree textual search with `craft_testkit::std_instant`:
+
+```rust
+#[test]
+fn no_std_instant_in_code_the_web_build_runs() {
+    let sources = craft_testkit::rust_sources(Path::new(env!("CARGO_MANIFEST_DIR")).join("src"))
+        .expect("scan engine sources");
+    let findings = craft_testkit::std_instant(&sources);
+    assert!(
+        findings.is_empty(),
+        "std::time::Instant panics on wasm32; use web_time::Instant:\n{}",
+        findings.iter().map(|f| f.to_string()).collect::<Vec<_>>().join("\n")
+    );
+}
+```
diff --git a/crates/testkit/src/guards.rs b/crates/testkit/src/guards.rs
new file mode 100644
--- /dev/null
+++ b/crates/testkit/src/guards.rs
@@ -0,0 +1,465 @@
+//! Platform regression guards checking source code for known defects and lifecycle hazards.
+
+use std::fmt;
+#[cfg(not(target_arch = "wasm32"))]
+use std::path::Path;
+
+/// A diagnostic finding reported by a source guard.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct Finding {
+    /// File path where the violation was detected.
+    pub path: String,
+    /// 1-based line number of the violation.
+    pub line: usize,
+    /// Diagnostic context (the offending code snippet or float literal).
+    pub text: String,
+}
+
+impl Finding {
+    /// Create a new diagnostic finding.
+    pub fn new(path: impl Into<String>, line: usize, text: impl Into<String>) -> Self {
+        Self { path: path.into(), line, text: text.into() }
+    }
+}
+
+impl fmt::Display for Finding {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        if self.path.is_empty() { write!(f, "{}: {}", self.line, self.text) } else { write!(f, "{}:{}: {}", self.path, self.line, self.text) }
+    }
+}
+
+/// Abstraction over a source file path and its text content.
+pub trait AsFileSource {
+    /// Path of the source file.
+    fn path(&self) -> &str;
+    /// Text content of the source file.
+    fn content(&self) -> &str;
+}
+
+impl AsFileSource for (String, String) {
+    fn path(&self) -> &str {
+        &self.0
+    }
+    fn content(&self) -> &str {
+        &self.1
+    }
+}
+
+impl AsFileSource for &(String, String) {
+    fn path(&self) -> &str {
+        &self.0
+    }
+    fn content(&self) -> &str {
+        &self.1
+    }
+}
+
+impl AsFileSource for (&str, &str) {
+    fn path(&self) -> &str {
+        self.0
+    }
+    fn content(&self) -> &str {
+        self.1
+    }
+}
+
+impl AsFileSource for &(&str, &str) {
+    fn path(&self) -> &str {
+        self.0
+    }
+    fn content(&self) -> &str {
+        self.1
+    }
+}
+
+impl AsFileSource for (String, &str) {
+    fn path(&self) -> &str {
+        &self.0
+    }
+    fn content(&self) -> &str {
+        self.1
+    }
+}
+
+impl AsFileSource for &(String, &str) {
+    fn path(&self) -> &str {
+        &self.0
+    }
+    fn content(&self) -> &str {
+        self.1
+    }
+}
+
+impl AsFileSource for (&str, String) {
+    fn path(&self) -> &str {
+        self.0
+    }
+    fn content(&self) -> &str {
+        &self.1
+    }
+}
+
+impl AsFileSource for &(&str, String) {
+    fn path(&self) -> &str {
+        self.0
+    }
+    fn content(&self) -> &str {
+        &self.1
+    }
+}
+
+/// Replace code comments and string literals with whitespace while preserving newlines and line counts.
+///
+/// Handles single-line comments (`//`), block comments (`/* ... */`, including nested comments),
+/// normal string literals (`"..."`), and raw string literals (`r#"..."#`).
+fn mask_comments_and_strings(code: &str) -> String {
+    let mut out = String::with_capacity(code.len());
+    let chars: Vec<char> = code.chars().collect();
+    let len = chars.len();
+    let mut i = 0;
+    let mut in_line_comment = false;
+    let mut block_comment_depth: usize = 0;
+    let mut in_normal_string = false;
+    let mut raw_string_hashes: Option<usize> = None;
+    let mut escape = false;
+
+    while i < len {
+        let c = chars[i];
+        if in_line_comment {
+            if c == '\n' {
+                in_line_comment = false;
+                out.push('\n');
+            } else {
+                out.push(' ');
+            }
+            i += 1;
+        } else if block_comment_depth > 0 {
+            if c == '*' && i + 1 < len && chars[i + 1] == '/' {
+                block_comment_depth = block_comment_depth.saturating_sub(1);
+                out.push(' ');
+                out.push(' ');
+                i += 2;
+            } else if c == '/' && i + 1 < len && chars[i + 1] == '*' {
+                block_comment_depth = block_comment_depth.saturating_add(1);
+                out.push(' ');
+                out.push(' ');
+                i += 2;
+            } else if c == '\n' {
+                out.push('\n');
+                i += 1;
+            } else {
+                out.push(' ');
+                i += 1;
+            }
+        } else if let Some(hashes) = raw_string_hashes {
+            if c == '"' {
+                let mut match_hashes = true;
+                if i + hashes < len {
+                    for h in 0..hashes {
+                        if chars[i + 1 + h] != '#' {
+                            match_hashes = false;
+                            break;
+                        }
+                    }
+                } else {
+                    match_hashes = false;
+                }
+                if match_hashes {
+                    raw_string_hashes = None;
+                    for _ in 0..=hashes {
+                        out.push(' ');
+                    }
+                    i += 1 + hashes;
+                    continue;
+                }
+            }
+            if c == '\n' {
+                out.push('\n');
+            } else {
+                out.push(' ');
+            }
+            i += 1;
+        } else if in_normal_string {
+            if escape {
+                escape = false;
+                out.push(' ');
+            } else if c == '\\' {
+                escape = true;
+                out.push(' ');
+            } else if c == '"' {
+                in_normal_string = false;
+                out.push(' ');
+            } else if c == '\n' {
+                out.push('\n');
+            } else {
+                out.push(' ');
+            }
+            i += 1;
+        } else {
+            if c == '/' && i + 1 < len && chars[i + 1] == '/' {
+                in_line_comment = true;
+                out.push(' ');
+                out.push(' ');
+                i += 2;
+            } else if c == '/' && i + 1 < len && chars[i + 1] == '*' {
+                block_comment_depth = 1;
+                out.push(' ');
+                out.push(' ');
+                i += 2;
+            } else if c == '"' {
+                in_normal_string = true;
+                out.push(' ');
+                i += 1;
+            } else if c == 'r' && i + 1 < len && (chars[i + 1] == '"' || chars[i + 1] == '#') {
+                let mut j = i + 1;
+                let mut hashes = 0;
+                while j < len && chars[j] == '#' {
+                    hashes += 1;
+                    j += 1;
+                }
+                if j < len && chars[j] == '"' {
+                    raw_string_hashes = Some(hashes);
+                    for _ in i..=j {
+                        out.push(' ');
+                    }
+                    i = j + 1;
+                } else {
+                    out.push(c);
+                    i += 1;
+                }
+            } else {
+                out.push(c);
+                i += 1;
+            }
+        }
+    }
+    out
+}
+
+/// Guard: detects direct calls to `muda`'s predefined Quit menu item.
+///
+/// Using `PredefinedMenuItem::quit` connects the macOS menu directly to `NSApplication terminate:`,
+/// which bypasses dirty document checks, unsaved changes save prompts, and lifecycle cleanup.
+/// Applications should route Quit through custom menu items or interceptors connected to `craft-quit`.
+pub fn predefined_quit<I>(files: I) -> Vec<Finding>
+where
+    I: IntoIterator,
+    I::Item: AsFileSource,
+{
+    let mut findings = Vec::new();
+    for file in files {
+        let path = file.path();
+        let content = file.content();
+        let masked = mask_comments_and_strings(content);
+
+        for (orig_line, (idx, masked_line)) in content.lines().zip(masked.lines().enumerate()) {
+            let normalized = masked_line.replace(' ', "");
+            if normalized.contains("PredefinedMenuItem::quit") || normalized.contains("PredefinedMenuItem::Quit") {
+                findings.push(Finding::new(path, idx + 1, orig_line.trim()));
+            }
+        }
+    }
+    findings
+}
+
+/// Guard: detects uses of `std::time::Instant` outside comments in code paths running on the web.
+///
+/// On `wasm32-unknown-unknown`, `std::time::Instant::now()` panics with "time not implemented
+/// on this platform". Crates running in the browser should use `web_time::Instant`, which maps to
+/// `std::time::Instant` on native platforms and `performance.now()` on the web.
+///
+/// ### Syntactic Scope and Limitations
+///
+/// * **Targeted files**: Callers should pass files from crates that compile into the application's web build.
+/// * **Allowed patterns**: Comments, string literals, lines containing `web_time`, and items decorated with
+///   explicit non-wasm target conditions (e.g. `#[cfg(not(target_arch = "wasm32"))]` or `#[cfg(target_os = ...)]`)
+///   are skipped.
+/// * **Limitations**: This is a lexical source guard. It does not perform semantic type resolution, macro
+///   expansion, or track arbitrary module aliases (e.g. `use std::time as t; t::Instant`).
+pub fn std_instant<I>(files: I) -> Vec<Finding>
+where
+    I: IntoIterator,
+    I::Item: AsFileSource,
+{
+    let mut findings = Vec::new();
+    for file in files {
+        let path = file.path();
+        let content = file.content();
+        let masked = mask_comments_and_strings(content);
+
+        let mut in_cfg_non_wasm = false;
+        let mut cfg_brace_depth: usize = 0;
+
+        for (orig_line, (idx, masked_line)) in content.lines().zip(masked.lines().enumerate()) {
+            let is_comment_only = masked_line.trim().is_empty();
+            let line_has_cfg_non_wasm = !is_comment_only
+                && (orig_line.contains("cfg(not(target_arch = \"wasm32\"))")
+                    || orig_line.contains("cfg(target_arch = \"x86_64\")")
+                    || orig_line.contains("cfg(target_arch = \"aarch64\")")
+                    || orig_line.contains("cfg(target_os ="));
+
+            if line_has_cfg_non_wasm {
+                in_cfg_non_wasm = true;
+            }
+
+            let names_std_instant =
+                masked_line.contains("time::Instant") || (masked_line.contains("use std::time::") && masked_line.contains("Instant"));
+
+            if names_std_instant && !masked_line.contains("web_time") && !in_cfg_non_wasm {
+                findings.push(Finding::new(path, idx + 1, orig_line.trim()));
+            }
+
+            if in_cfg_non_wasm && !is_comment_only {
+                let opens = masked_line.chars().filter(|&c| c == '{').count();
+                let closes = masked_line.chars().filter(|&c| c == '}').count();
+                if cfg_brace_depth == 0 {
+                    if opens > 0 {
+                        cfg_brace_depth = cfg_brace_depth.saturating_add(opens).saturating_sub(closes);
+                        if cfg_brace_depth == 0 {
+                            in_cfg_non_wasm = false;
+                        }
+                    } else if masked_line.contains(';') {
+                        in_cfg_non_wasm = false;
+                    }
+                } else {
+                    cfg_brace_depth = cfg_brace_depth.saturating_add(opens).saturating_sub(closes);
+                    if cfg_brace_depth == 0 {
+                        in_cfg_non_wasm = false;
+                    }
+                }
+            }
+        }
+    }
+    findings
+}
+
+/// Guard: scans a single WGSL shader string and detects decimal float literals outside `f32`'s finite range.
+///
+/// Browser WebGPU compilers (such as Chrome's Tint) reject float literals exceeding `f32::MAX`
+/// (e.g. `3.40282347e38`), triggering fatal pipeline build errors and fallback to CPU rendering.
+/// Exact values equal to `f32::MAX` should be written as hex float literals (e.g. `0x1.fffffep+127f`).
+///
+/// ### Policy and Limitations
+///
+/// * **Conservative mathematical bound**: The guard evaluates the parsed mathematical magnitude against
+///   `f64::from(f32::MAX)`. Any decimal literal exceeding this threshold is flagged, even if native
+///   compilers like `naga` would round it down to `f32::MAX`.
+/// * **Hexadecimal floats**: Exact hex float literals (`0x...p...`) are skipped as they represent exact bit patterns.
+/// * **Comments and strings**: Comments (`//` and `/* ... */`) and string contents are masked out prior to scanning.
+pub fn wgsl_float_literals(wgsl: &str) -> Vec<Finding> {
+    wgsl_float_literals_in([("", wgsl)])
+}
+
+/// Guard: scans multiple WGSL shader files and detects decimal float literals outside `f32`'s finite range.
+pub fn wgsl_float_literals_in<I>(files: I) -> Vec<Finding>
+where
+    I: IntoIterator,
+    I::Item: AsFileSource,
+{
+    let mut findings = Vec::new();
+    for file in files {
+        let path = file.path();
+        let content = file.content();
+        let masked = mask_comments_and_strings(content);
+
+        for (n, line) in masked.lines().enumerate() {
+            let code = line.as_bytes();
+            let mut i = 0;
+            while i < code.len() {
+                let digit_or_dot = code[i].is_ascii_digit() || (code[i] == b'.' && code.get(i + 1).is_some_and(u8::is_ascii_digit));
+                let word_before = i > 0 && (code[i - 1].is_ascii_alphanumeric() || code[i - 1] == b'_' || code[i - 1] == b'.');
+                if !digit_or_dot || word_before {
+                    i += 1;
+                    continue;
+                }
+                // Skip hex float literals (e.g. 0x1.fffffep+127f, 0x1.0p0h)
+                if code[i] == b'0' && matches!(code.get(i + 1), Some(b'x' | b'X')) {
+                    i += 2;
+                    while i < code.len() && (code[i].is_ascii_hexdigit() || matches!(code[i], b'.' | b'p' | b'P' | b'+' | b'-' | b'f' | b'h')) {
+                        i += 1;
+                    }
+                    continue;
+                }
+                let start = i;
+                while i < code.len() && (code[i].is_ascii_digit() || code[i] == b'.') {
+                    i += 1;
+                }
+                if i < code.len() && matches!(code[i], b'e' | b'E') {
+                    let mut j = i + 1;
+                    if j < code.len() && matches!(code[j], b'+' | b'-') {
+                        j += 1;
+                    }
+                    if j < code.len() && code[j].is_ascii_digit() {
+                        i = j;
+                        while i < code.len() && code[i].is_ascii_digit() {
+                            i += 1;
+                        }
+                    }
+                }
+                let literal = String::from_utf8_lossy(&code[start..i]);
+                if i < code.len() && matches!(code[i], b'f' | b'h') {
+                    i += 1;
+                }
+                let is_overflow = match literal.parse::<f64>() {
+                    Ok(v) => !v.is_finite() || v.abs() > f64::from(f32::MAX),
+                    Err(_) => true,
+                };
+                if is_overflow {
+                    findings.push(Finding::new(path, n + 1, literal.into_owned()));
+                }
+            }
+        }
+    }
+    findings
+}
+
+/// Recursively collect all Rust source files under `dir`, excluding test files and test directories.
+///
+/// Excluded files:
+/// * `tests.rs`
+/// * `tests_*.rs`
+/// * `*_tests.rs`
+/// * `*_test.rs`
+/// * `test.rs`
+/// * Any file within a directory component named `tests`, `benches`, `examples`, `.git`, or `target`.
+///
+/// Results are sorted lexicographically by file path for deterministic behavior across platforms.
+#[cfg(not(target_arch = "wasm32"))]
+pub fn rust_sources(dir: impl AsRef<Path>) -> std::io::Result<Vec<(String, String)>> {
+    let root = dir.as_ref();
+    let mut files = Vec::new();
+    let mut dirs = vec![root.to_path_buf()];
+
+    while let Some(d) = dirs.pop() {
+        let entries = std::fs::read_dir(&d)?;
+        for entry in entries {
+            let entry = entry?;
+            let p = entry.path();
+            let metadata = std::fs::metadata(&p)?;
+            if metadata.is_dir() {
+                let dirname = p.file_name().and_then(|n| n.to_str()).unwrap_or_default();
+                if dirname.starts_with('.') || dirname == "target" || dirname == "tests" || dirname == "benches" || dirname == "examples" {
+                    continue;
+                }
+                dirs.push(p);
+            } else if metadata.is_file() {
+                let name = p.file_name().and_then(|n| n.to_str()).unwrap_or_default();
+                if !name.ends_with(".rs") {
+                    continue;
+                }
+                if name == "tests.rs" || name.starts_with("tests_") || name.ends_with("_tests.rs") || name.ends_with("_test.rs") || name == "test.rs"
+                {
+                    continue;
+                }
+                if p.components().any(|c| c.as_os_str() == "tests") {
+                    continue;
+                }
+                let content = std::fs::read_to_string(&p)?;
+                files.push((p.display().to_string(), content));
+            }
+        }
+    }
+
+    files.sort_by(|a, b| a.0.cmp(&b.0));
+    Ok(files)
+}
diff --git a/crates/testkit/src/lib.rs b/crates/testkit/src/lib.rs
new file mode 100644
--- /dev/null
+++ b/crates/testkit/src/lib.rs
@@ -0,0 +1,32 @@
+#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
+#![forbid(unsafe_code)]
+
+//! Source-level platform regression guards and verification utilities for the Craft application suite.
+//!
+//! This crate provides non-panicking source analysis guards that detect known platform hazards
+//! and lifecycle regressions across Craft applications:
+//!
+//! * [`guards::predefined_quit`]: flags direct calls to `muda`'s predefined Quit item that bypass
+//!   dirty document save prompts and window teardown on macOS.
+//! * [`guards::wgsl_float_literals`]: flags decimal float literals exceeding `f32`'s finite range,
+//!   which break browser WebGPU compilers (Tint) and trigger CPU fallbacks.
+//! * [`guards::std_instant`]: flags uses of `std::time::Instant` in code paths targeting the web,
+//!   where `std::time::Instant::now()` panics on `wasm32`.
+//! * [`guards::rust_sources`]: directory crawler collecting `.rs` sources while excluding tests.
+//!
+//! ### Epistemic Scope: What Source Guards Establish vs What They Cannot
+//!
+//! Source-level guards provide deterministic syntactic evidence about codebase structure.
+//! They establish that checked source files do not contain forbidden symbol names, known invalid
+//! literal patterns, or unsafe platform invocations.
+//!
+//! They do **not** establish runtime correctness, protocol concurrency safety, or platform behavior
+//! under foreign host environments. They are lightweight syntactic regression gates designed to complement,
+//! rather than replace, platform integration tests and CI execution.
+
+pub mod guards;
+
+pub use guards::{Finding, predefined_quit, std_instant, wgsl_float_literals, wgsl_float_literals_in};
+
+#[cfg(not(target_arch = "wasm32"))]
+pub use guards::rust_sources;
diff --git a/crates/testkit/tests/guards.rs b/crates/testkit/tests/guards.rs
new file mode 100644
--- /dev/null
+++ b/crates/testkit/tests/guards.rs
@@ -0,0 +1,330 @@
+use craft_testkit::{Finding, predefined_quit, std_instant, wgsl_float_literals, wgsl_float_literals_in};
+
+#[test]
+fn test_finding_display() {
+    let f1 = Finding::new("src/menu.rs", 42, "PredefinedMenuItem::quit()");
+    assert_eq!(f1.to_string(), "src/menu.rs:42: PredefinedMenuItem::quit()");
+
+    let f2 = Finding::new("", 156, "3.40282347e38");
+    assert_eq!(f2.to_string(), "156: 3.40282347e38");
+}
+
+#[test]
+fn test_predefined_quit_positive() {
+    let source = r#"
+fn setup_menu() {
+    let quit = PredefinedMenuItem::quit(Some("Quit"));
+    bar.add(quit);
+}
+"#;
+    let findings = predefined_quit([("src/menu.rs", source)]);
+    assert_eq!(findings.len(), 1);
+    assert_eq!(findings[0].path, "src/menu.rs");
+    assert_eq!(findings[0].line, 3);
+    assert_eq!(findings[0].text, "let quit = PredefinedMenuItem::quit(Some(\"Quit\"));");
+    assert_eq!(findings[0].to_string(), "src/menu.rs:3: let quit = PredefinedMenuItem::quit(Some(\"Quit\"));");
+}
+
+#[test]
+fn test_predefined_quit_negative_and_comments() {
+    let source = r#"
+// PredefinedMenuItem::quit(None) - do not use this!
+/*
+ * PredefinedMenuItem::quit is hazardous
+ */
+fn setup_menu() {
+    let sep = PredefinedMenuItem::separator();
+    let hide = PredefinedMenuItem::hide(Some("Hide"));
+    let note = "PredefinedMenuItem::quit in string should not trigger";
+}
+"#;
+    let findings = predefined_quit([("src/safe_menu.rs", source)]);
+    assert!(findings.is_empty(), "expected zero findings for comments, separators, and strings: {findings:?}");
+}
+
+/// Historical failure: FilmCraft `apps/filmcraft/src/native_menu.rs:56`
+/// used `PredefinedMenuItem::quit`, routing directly to `NSApplication terminate:` and bypassing save prompts.
+#[test]
+fn test_historical_filmcraft_native_menu_quit_regression() {
+    let problematic_source = r#"
+    let predefined = [
+        (hide_label, PredefinedMenuItem::hide(Some(app.ui.language.tr(hide_label)))),
+        (hide_others_label, PredefinedMenuItem::hide_others(Some(app.ui.language.tr(hide_others_label)))),
+        (quit_label, PredefinedMenuItem::quit(Some(app.ui.language.tr(quit_label)))),
+    ];
+"#;
+    let findings = predefined_quit([("apps/filmcraft/src/native_menu.rs", problematic_source)]);
+    assert_eq!(findings.len(), 1);
+    assert_eq!(findings[0].line, 5);
+    assert_eq!(findings[0].text, "(quit_label, PredefinedMenuItem::quit(Some(app.ui.language.tr(quit_label)))),");
+
+    // Corrected source wires Quit via MenuItem::with_id and craft-quit lifecycle
+    let corrected_source = r#"
+    let quit = MenuItem::with_id("app.quit", label("app.quit", "Quit FilmCraft"), true, None);
+    let predefined = [
+        (hide_label, PredefinedMenuItem::hide(Some(app.ui.language.tr(hide_label)))),
+        (hide_others_label, PredefinedMenuItem::hide_others(Some(app.ui.language.tr(hide_others_label)))),
+    ];
+"#;
+    let corrected_findings = predefined_quit([("apps/filmcraft/src/native_menu.rs", corrected_source)]);
+    assert!(corrected_findings.is_empty());
+}
+
+#[test]
+fn test_std_instant_positive() {
+    let source = r#"
+use std::time::Instant;
+use std::time::{Duration, Instant as StdInstant};
+
+fn measure() {
+    let t0 = std::time::Instant::now();
+}
+"#;
+    let findings = std_instant([("src/clock.rs", source)]);
+    assert_eq!(findings.len(), 3);
+    assert_eq!(findings[0].line, 2);
+    assert_eq!(findings[1].line, 3);
+    assert_eq!(findings[2].line, 6);
+}
+
+#[test]
+fn test_std_instant_negative_and_web_time() {
+    let source = r#"
+// std::time::Instant::now() causes wasm panic
+/*
+   use std::time::Instant;
+*/
+use web_time::Instant;
+use std::time::Duration;
+
+fn measure() {
+    let t0 = web_time::Instant::now();
+    let d = Duration::from_millis(100);
+}
+"#;
+    let findings = std_instant([("src/safe_clock.rs", source)]);
+    assert!(findings.is_empty(), "expected zero findings for web_time, duration, and comments: {findings:?}");
+}
+
+/// Historical failure: FilmCraft `crates/engine/src/wasm_clock_tests.rs`
+/// jobs running synchronously on wasm32 called `std::time::Instant::now()`, panicking at runtime.
+#[test]
+fn test_historical_filmcraft_wasm_clock_instant_regression() {
+    let problematic_source = r#"
+    use std::time::Instant;
+
+    pub fn run_job() {
+        let t0 = std::time::Instant::now();
+    }
+"#;
+    let findings = std_instant([("crates/engine/src/job.rs", problematic_source)]);
+    assert_eq!(findings.len(), 2);
+
+    let corrected_source = r#"
+    use web_time::Instant;
+
+    pub fn run_job() {
+        let t0 = web_time::Instant::now();
+    }
+"#;
+    let corrected_findings = std_instant([("crates/engine/src/job.rs", corrected_source)]);
+    assert!(corrected_findings.is_empty());
+
+    // User's specific conditional compilation test case:
+    let native_guarded_inline = r#"
+#[cfg(not(target_arch = "wasm32"))] fn timestamp() -> std::time::Instant { std::time::Instant::now() }
+"#;
+    assert!(std_instant([("src/native.rs", native_guarded_inline)]).is_empty());
+
+    let native_guarded_multiline = r#"
+#[cfg(not(target_arch = "wasm32"))]
+fn timestamp() -> std::time::Instant {
+    std::time::Instant::now()
+}
+"#;
+    assert!(std_instant([("src/native.rs", native_guarded_multiline)]).is_empty());
+}
+
+/// Historical failure: PhotoCraft PR #1722 / #1982 `crates/gpu/src/compose.wgsl:156`
+/// `const F32_MAX: f32 = 3.40282347e38;` is slightly above f32::MAX. Naga rounds it natively,
+/// but Chromium WebGPU compiler (Tint) rejects it, disabling the GPU compositor.
+#[test]
+fn test_historical_photocraft_compose_wgsl_regression() {
+    let problematic_shader = r#"
+// line 156 of compose.wgsl
+const F32_MAX: f32 = 3.40282347e38;
+"#;
+    let findings = wgsl_float_literals(problematic_shader);
+    assert_eq!(findings.len(), 1);
+    assert_eq!(findings[0].line, 3);
+    assert_eq!(findings[0].text, "3.40282347e38");
+
+    // Corrected shader uses exact hexadecimal float representation
+    let corrected_shader = r#"
+// exact f32::MAX bit for bit
+const F32_MAX: f32 = 0x1.fffffep+127f;
+"#;
+    let corrected_findings = wgsl_float_literals(corrected_shader);
+    assert!(corrected_findings.is_empty());
+}
+
+#[test]
+fn test_wgsl_float_literals_controls() {
+    // 1. Scientific notation and signed values
+    let signed_and_sci = r#"
+const OVER_POS: f32 = 1e39;
+const OVER_NEG: f32 = -1e39;
+const VALID_POS: f32 = 1.5e10;
+const VALID_NEG: f32 = -1.5e10;
+const VALID_TINY: f32 = 1e-40;
+"#;
+    let findings = wgsl_float_literals(signed_and_sci);
+    assert_eq!(findings.len(), 2);
+    assert_eq!(findings[0].text, "1e39");
+    assert_eq!(findings[1].text, "1e39"); // Scanned magnitude of -1e39
+
+    // 2. Hexadecimal floating-point representations
+    let hex_floats = r#"
+const EXACT_MAX: f32 = 0x1.fffffep+127f;
+const ONE_P0: f32 = 0x1.0p0;
+const HALF_HEX: f32 = 0x1.0p-1h;
+"#;
+    assert!(wgsl_float_literals(hex_floats).is_empty());
+
+    // 3. Literals appearing inside comments
+    let in_comments = r#"
+// const F32_MAX = 3.40282347e38;
+/*
+   const OVERFLOW = 1e40;
+*/
+"#;
+    assert!(wgsl_float_literals(in_comments).is_empty());
+
+    // 4. Numeric suffixes (f, h)
+    let suffixes = r#"
+const BAD_F: f32 = 3.40282347e38f;
+const GOOD_F: f32 = 1.0f;
+const GOOD_H: f16 = 0.5h;
+"#;
+    let suffix_findings = wgsl_float_literals(suffixes);
+    assert_eq!(suffix_findings.len(), 1);
+    assert_eq!(suffix_findings[0].text, "3.40282347e38");
+
+    // 5. Embedded in identifiers or other tokens
+    let idents = r#"
+let var_1 = 1.0;
+let layer2 = 2.0;
+let coord_0 = 0.0;
+"#;
+    assert!(wgsl_float_literals(idents).is_empty());
+}
+
+#[test]
+fn test_wgsl_float_literals_in_multiple_files() {
+    let s1 = "const A: f32 = 1.0;\nconst B: f32 = 3.40282347e38;\n";
+    let s2 = "const OK: f32 = 0x1.fffffep+127f;\n";
+    let s3 = "const BAD: f32 = 9.99e38;\n";
+
+    let files = [("shaders/compose.wgsl", s1), ("shaders/clean.wgsl", s2), ("shaders/fx.wgsl", s3)];
+    let findings = wgsl_float_literals_in(files);
+    assert_eq!(findings.len(), 2);
+    assert_eq!(findings[0].path, "shaders/compose.wgsl");
+    assert_eq!(findings[0].line, 2);
+    assert_eq!(findings[0].text, "3.40282347e38");
+    assert_eq!(findings[1].path, "shaders/fx.wgsl");
+    assert_eq!(findings[1].line, 1);
+    assert_eq!(findings[1].text, "9.99e38");
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+#[test]
+fn test_predefined_quit_qualified_and_whitespace() {
+    let source = r#"
+fn setup() {
+    let q1 = muda::PredefinedMenuItem::quit(None);
+    let q2 = PredefinedMenuItem :: quit(Some("Quit"));
+    let q3 = crate::menu::PredefinedMenuItem::Quit;
+}
+"#;
+    let findings = predefined_quit([("src/menu.rs", source)]);
+    assert_eq!(findings.len(), 3);
+    assert_eq!(findings[0].line, 3);
+    assert_eq!(findings[1].line, 4);
+    assert_eq!(findings[2].line, 5);
+}
+
+#[test]
+fn test_std_instant_module_alias() {
+    let source = r#"
+use std::time;
+
+fn run() {
+    let t0 = time::Instant::now();
+}
+"#;
+    let findings = std_instant([("src/worker.rs", source)]);
+    assert_eq!(findings.len(), 1);
+    assert_eq!(findings[0].line, 5);
+}
+
+#[test]
+fn test_wgsl_exact_f32_max_boundary() {
+    // 3.4028234663852886e38 is mathematical f32::MAX bit for bit
+    let exact_valid = r#"
+const EXACT_MAX: f32 = 3.4028234663852886e38;
+const NEG_EXACT_MAX: f32 = -3.4028234663852886e38;
+"#;
+    assert!(wgsl_float_literals(exact_valid).is_empty());
+
+    // 3.40282347e38 is slightly above f32::MAX
+    let just_over = r#"
+const OVER_POS: f32 = 3.40282347e38;
+const OVER_NEG: f32 = -3.40282347e38;
+"#;
+    let findings = wgsl_float_literals(just_over);
+    assert_eq!(findings.len(), 2);
+    assert_eq!(findings[0].text, "3.40282347e38");
+    assert_eq!(findings[1].text, "3.40282347e38");
+}
+
+#[cfg(not(target_arch = "wasm32"))]
+#[test]
+fn test_rust_sources_filtering() {
+    use craft_testkit::rust_sources;
+    use std::fs::{self, File};
+    use std::io::Write;
+
+    let temp_dir = std::env::temp_dir().join(format!("craft_testkit_test_{}", std::process::id()));
+    let _ = fs::remove_dir_all(&temp_dir);
+    fs::create_dir_all(temp_dir.join("src").join("sub")).expect("create temp sub");
+    fs::create_dir_all(temp_dir.join("tests")).expect("create temp tests");
+    fs::create_dir_all(temp_dir.join("target")).expect("create temp target");
+    fs::create_dir_all(temp_dir.join(".github")).expect("create temp dot-dir");
+
+    let create_file = |path: std::path::PathBuf, content: &str| {
+        let mut f = File::create(path).expect("create file");
+        f.write_all(content.as_bytes()).expect("write content");
+    };
+
+    create_file(temp_dir.join("src").join("lib.rs"), "pub fn lib() {}");
+    create_file(temp_dir.join("src").join("sub").join("mod.rs"), "pub fn sub() {}");
+    create_file(temp_dir.join("src").join("tests.rs"), "// should exclude");
+    create_file(temp_dir.join("src").join("tests_foo.rs"), "// should exclude");
+    create_file(temp_dir.join("src").join("foo_tests.rs"), "// should exclude");
+    create_file(temp_dir.join("src").join("foo_test.rs"), "// should exclude");
+    create_file(temp_dir.join("tests").join("integ.rs"), "// should exclude");
+    create_file(temp_dir.join("target").join("gen.rs"), "// should exclude");
+    create_file(temp_dir.join(".github").join("action.rs"), "// should exclude dot-dir");
+    create_file(temp_dir.join("src").join("README.md"), "# Not a rust source");
+
+    let sources = rust_sources(&temp_dir).expect("rust_sources");
+    let names: Vec<String> = sources.iter().map(|(p, _)| std::path::Path::new(p).file_name().unwrap().to_string_lossy().into_owned()).collect();
+
+    assert_eq!(names, vec!["lib.rs", "mod.rs"]);
+
+    // Test passing &[(&str, &str)] to guards
+    let findings = predefined_quit(&sources);
+    assert!(findings.is_empty());
+
+    let _ = fs::remove_dir_all(&temp_dir);
+}
