frame-host 0.4.0

Frame host server and embedding seam — boots an application's frame-core component tree with an embedded liminal bus, announces the host's real application events on the bus, and serves the built frame page
Documentation
//! The built-in default syntax theme (IRIDIUM-A2 C5, amended 2026-07-21):
//! a highlighter must visibly highlight out of the box, so frame-host
//! ships a default capture-name → `#rrggbb` palette rather than leaving
//! the wire's optional `syntaxTheme` member perpetually absent.
//!
//! The house no-assumed-defaults law forbids inventing numbers, so this
//! palette is not new: it is copied verbatim from the proven `SYNTAX_THEME`
//! constant in `examples/code-view-console/src/fake-feed.ts`, the console's
//! own scripted demo theme. [`crate::doc_binding::DocBinding::boot`] uses
//! this default whenever `[document].syntax_theme` is absent from
//! `frame.toml`; a declared table REPLACES it wholesale (see
//! [`crate::config::DocumentSection::syntax_theme`]).

use std::collections::BTreeMap;

/// The built-in default palette as `(capture name, "#rrggbb")` pairs,
/// copied from `examples/code-view-console/src/fake-feed.ts`'s
/// `SYNTAX_THEME`. Kept as a flat array (rather than a `BTreeMap` literal,
/// which `const`/`static` cannot express) so the single source of truth is
/// visible at a glance and easy to diff against the TypeScript original.
const DEFAULT_PALETTE: [(&str, &str); 4] = [
    ("keyword", "#ff7b72"),
    ("function", "#d2a8ff"),
    ("comment", "#8b949e"),
    ("type", "#ffa657"),
];

/// Builds the built-in default syntax theme: capture-name → `#rrggbb`.
///
/// This is the frame-host binding seat's fallback (IRIDIUM-A2 C5,
/// 2026-07-21 amendment), used whenever `[document].syntax_theme` is not
/// declared in `frame.toml`. The palette values are fixed at compile time
/// and always well-formed `#rrggbb` hex, so this function cannot fail.
#[must_use]
pub fn default_syntax_theme() -> BTreeMap<String, String> {
    DEFAULT_PALETTE
        .iter()
        .map(|(capture, color)| ((*capture).to_owned(), (*color).to_owned()))
        .collect()
}

#[cfg(test)]
mod tests {
    use super::default_syntax_theme;

    /// Every built-in palette value is well-formed `#rrggbb` hex — the same
    /// shape `[document].syntax_theme` config values are validated against
    /// (`config::is_hex_color`) and the wire codec re-validates
    /// (`frame_editor_wire::snapshot`).
    #[test]
    fn default_palette_values_are_hex_colors() {
        let theme = default_syntax_theme();
        assert_eq!(theme.len(), 4);
        for (capture, color) in &theme {
            assert!(!capture.is_empty(), "capture name must be non-empty");
            assert_eq!(color.len(), 7, "{capture}: {color} must be #rrggbb");
            assert!(
                color.starts_with('#'),
                "{capture}: {color} must start with #"
            );
            assert!(
                color[1..].bytes().all(|byte| byte.is_ascii_hexdigit()),
                "{capture}: {color} must be hex after #"
            );
        }
    }

    /// Pinned against the exact TypeScript source
    /// (`examples/code-view-console/src/fake-feed.ts`'s `SYNTAX_THEME`) so a
    /// future edit to either side fails loudly instead of silently drifting.
    #[test]
    fn default_palette_matches_the_fake_feed_source_of_truth() {
        let theme = default_syntax_theme();
        assert_eq!(theme.get("keyword").map(String::as_str), Some("#ff7b72"));
        assert_eq!(theme.get("function").map(String::as_str), Some("#d2a8ff"));
        assert_eq!(theme.get("comment").map(String::as_str), Some("#8b949e"));
        assert_eq!(theme.get("type").map(String::as_str), Some("#ffa657"));
    }
}