Skip to main content

ferro_json_ui/assets/
mod.rs

1//! Embedded static assets for ferro-json-ui.
2//!
3//! Served by the framework via the automatically-registered
4//! `GET /_ferro/ferro-base.css` route. Embedded at compile time —
5//! no runtime file I/O.
6
7pub(crate) mod quill;
8
9pub mod leaflet;
10
11/// Pre-built Tailwind CSS covering every utility class emitted by
12/// ferro-json-ui components.
13///
14/// Regenerate with `scripts/gen-ferro-base-css.sh` after adding or
15/// modifying components that introduce new utility classes.
16///
17/// In release builds (no `dev-css` feature): static compile-time constant.
18/// In dev builds (`dev-css` feature): use `ferro_base_css()` instead (disk read).
19#[cfg(not(feature = "dev-css"))]
20pub const FERRO_BASE_CSS: &str = include_str!("../../assets/ferro-base.css");
21
22/// Returns the pre-built ferro-json-ui base CSS.
23///
24/// In release builds (no `dev-css` feature): returns the compile-time embedded
25/// constant as a `Cow::Borrowed` — zero allocation, zero I/O.
26///
27/// In dev builds (`dev-css` feature): reads `assets/ferro-base.css` from disk
28/// at call time so that a running Tailwind `--watch` process can update the CSS
29/// without a Rust recompile. Panics if the file is unreadable.
30pub fn ferro_base_css() -> std::borrow::Cow<'static, str> {
31    #[cfg(not(feature = "dev-css"))]
32    {
33        std::borrow::Cow::Borrowed(FERRO_BASE_CSS)
34    }
35    #[cfg(feature = "dev-css")]
36    {
37        let css = std::fs::read_to_string(concat!(
38            env!("CARGO_MANIFEST_DIR"),
39            "/assets/ferro-base.css"
40        ))
41        .expect("ferro-base.css must be readable in dev-css mode");
42        std::borrow::Cow::Owned(css)
43    }
44}
45
46/// Geist Sans Variable woff2. Served at `/_ferro/fonts/Geist-Variable.woff2`.
47///
48/// Source: vercel/geist-font v1.7.2, OFL-1.1 license.
49pub const GEIST_SANS_WOFF2: &[u8] = include_bytes!("../../assets/fonts/Geist-Variable.woff2");
50
51/// Geist Mono Variable woff2. Served at `/_ferro/fonts/GeistMono-Variable.woff2`.
52///
53/// Source: vercel/geist-font v1.7.2, OFL-1.1 license.
54pub const GEIST_MONO_WOFF2: &[u8] = include_bytes!("../../assets/fonts/GeistMono-Variable.woff2");
55
56/// OFL-1.1 license text for the Geist fonts.
57///
58/// Served at `/_ferro/fonts/OFL.txt` alongside the font files.
59pub const GEIST_OFL_TXT: &[u8] = include_bytes!("../../assets/fonts/OFL.txt");
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    #[allow(clippy::const_is_empty)]
67    fn ferro_base_css_non_empty() {
68        let css = ferro_base_css();
69        assert!(!css.is_empty(), "embedded CSS must not be empty");
70        // include_str! guarantees valid UTF-8 (compile error otherwise),
71        // so runtime validation is unnecessary. Smoke-check a class that
72        // every ferro-json-ui page relies on:
73        assert!(
74            css.contains("flex"),
75            "expected `flex` utility in generated CSS"
76        );
77    }
78
79    #[test]
80    #[allow(clippy::const_is_empty)]
81    fn geist_sans_woff2_non_empty() {
82        assert!(
83            !GEIST_SANS_WOFF2.is_empty(),
84            "Geist Sans woff2 must not be empty"
85        );
86    }
87
88    #[test]
89    #[allow(clippy::const_is_empty)]
90    fn geist_mono_woff2_non_empty() {
91        assert!(
92            !GEIST_MONO_WOFF2.is_empty(),
93            "Geist Mono woff2 must not be empty"
94        );
95    }
96
97    #[test]
98    fn geist_sans_woff2_magic_bytes() {
99        // WOFF2 magic number: 'w' 'O' 'F' '2' (0x77 0x4F 0x46 0x32)
100        assert_eq!(
101            &GEIST_SANS_WOFF2[..4],
102            &[0x77, 0x4F, 0x46, 0x32],
103            "Geist Sans woff2 must start with wOF2 magic bytes"
104        );
105    }
106
107    #[test]
108    fn geist_mono_woff2_magic_bytes() {
109        assert_eq!(
110            &GEIST_MONO_WOFF2[..4],
111            &[0x77, 0x4F, 0x46, 0x32],
112            "Geist Mono woff2 must start with wOF2 magic bytes"
113        );
114    }
115
116    #[test]
117    fn ferro_base_css_contains_motion_duration_fallback() {
118        let css = ferro_base_css();
119        // SC1: v1 themes that omit --motion-duration-fast resolve via the fallback.
120        assert!(
121            css.contains("var(--motion-duration-fast,"),
122            "expected motion-duration-fast fallback in generated CSS; run scripts/gen-ferro-base-css.sh"
123        );
124        // The duration utilities must exist as class rules — a theme-layer
125        // variable emission alone satisfies the var() substring above without
126        // any consumable utility being generated.
127        for class in [".duration-fast{", ".duration-base{", ".duration-slow{"] {
128            assert!(
129                css.contains(class),
130                "expected `{class}` utility rule in generated CSS; run scripts/gen-ferro-base-css.sh"
131            );
132        }
133        // SC3: reduced-motion collapse survives regeneration. The collapse
134        // declarations need !important — theme <style> tags are injected
135        // after this stylesheet and redeclare the tokens on :root at equal
136        // specificity, so normal declarations would lose on source order.
137        assert!(
138            css.contains("prefers-reduced-motion"),
139            "expected prefers-reduced-motion block in generated CSS"
140        );
141        assert!(
142            css.contains("--motion-duration-fast:.01ms!important"),
143            "expected !important on the reduced-motion collapse; run scripts/gen-ferro-base-css.sh"
144        );
145    }
146
147    #[test]
148    fn ferro_base_css_ring_falls_back_to_primary() {
149        let css = ferro_base_css();
150        // 23-slot v1 themes never define --color-ring; focus rings must
151        // resolve to the theme's primary color instead of an undefined var.
152        assert!(
153            css.contains("var(--color-ring,var(--color-primary))"),
154            "expected --color-ring fallback to --color-primary in generated CSS; run scripts/gen-ferro-base-css.sh"
155        );
156    }
157
158    // ── SKIN-04: dev-css hot-reload path ──────────────────────────────────────
159
160    /// Release mode (no dev-css): ferro_base_css() must return Cow::Borrowed
161    /// referencing the compile-time embedded constant.
162    ///
163    /// Cow::Borrowed is the only zero-allocation/zero-I/O variant — the test
164    /// uses matches! on the discriminant to distinguish it from Cow::Owned.
165    #[test]
166    #[cfg(not(feature = "dev-css"))]
167    fn ferro_base_css_release_returns_borrowed() {
168        use std::borrow::Cow;
169        let css = ferro_base_css();
170        assert!(
171            matches!(css, Cow::Borrowed(_)),
172            "without dev-css feature, ferro_base_css() must return Cow::Borrowed (zero allocation)"
173        );
174        // Also verify it equals the embedded constant so no divergence can sneak in.
175        assert_eq!(
176            css.as_ref(),
177            FERRO_BASE_CSS,
178            "Cow::Borrowed content must equal the embedded FERRO_BASE_CSS constant"
179        );
180    }
181
182    /// Dev mode (dev-css feature): ferro_base_css() must return Cow::Owned
183    /// whose content equals the on-disk assets/ferro-base.css file.
184    ///
185    /// The file is read independently (via CARGO_MANIFEST_DIR) to prove the
186    /// function actually went to disk rather than returning the embedded bytes.
187    /// This test does NOT mutate the file (parallel-safe).
188    #[test]
189    #[cfg(feature = "dev-css")]
190    fn ferro_base_css_dev_returns_owned_matching_disk() {
191        use std::borrow::Cow;
192        let css = ferro_base_css();
193        assert!(
194            matches!(css, Cow::Owned(_)),
195            "with dev-css feature, ferro_base_css() must return Cow::Owned (disk read)"
196        );
197        // Read the file independently to prove the content matches.
198        let disk_content = std::fs::read_to_string(concat!(
199            env!("CARGO_MANIFEST_DIR"),
200            "/assets/ferro-base.css"
201        ))
202        .expect("assets/ferro-base.css must exist and be readable for the dev-css test");
203        assert_eq!(
204            css.as_ref(),
205            disk_content.as_str(),
206            "Cow::Owned content must equal the on-disk ferro-base.css (disk read proves hot-reload path is wired)"
207        );
208    }
209}