Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/dwarves

dwarves-gui/src/manual.rs

//! 📖 **Manual** tab — dwarves' own documentation, embedded in the binary and rendered
//! live in the tabbed shell.
//!
//! Two variants, one pane, both dwarves' REAL docs:
//!
//! - **Typeset (SVG)** — the **default**: a crisp, book-like typeset render of
//!   dwarves' start-here `.nornir` docs (README + the `*-for-idiots.md` beginner
//!   guides). The SVG pages are prebuilt OFFLINE by `cargo xtask-docs docs` (which
//!   runs dwarves' real markdown through nornir's `docs-export` markdown→typst→SVG
//!   pipeline) and committed under `assets/manual-svg/`; the shipped UI binary just
//!   `include`s them via [`rust_embed`] and renders them with facett-docview's
//!   pure-Rust resvg engine — **no Typst at runtime**, no warehouse touched.
//! - **Markdown** — behind the header toggle: the FULL `.nornir/*.md` doc set (the
//!   idiot guides, the how-to guides, the design notes), rendered live through the
//!   shared markdown renderer, searchable.
//!
//! With the `manual-embed` feature (default) both are baked into the binary at compile
//! time, so the manual travels with the installed binary and renders offline. The pane
//! itself is the SHARED [`facett_manual::ManualPane`] — the SAME manual-pane core korp
//! mounts (L5: one component, not two).
//!
//! Built `--no-default-features` (manual-embed OFF), [`manual_pane`] returns the honest
//! [`ManualPane::not_embedded`] state — the tab says "manual not embedded in this build",
//! never a blank pane.

use facett_manual::ManualPane;

/// dwarves' embedded `.nornir/*.md` doc set (compile-time via rust_embed). Template
/// scaffolds (`_TEMPLATE-*.md`) are excluded here AND again by `ManualPane::from_files`.
/// This is the FULL set — the Markdown variant (toggle).
#[cfg(feature = "manual-embed")]
#[derive(rust_embed::RustEmbed)]
#[folder = "../.nornir/"]
#[include = "*.md"]
#[exclude = "_*.md"]
struct DwarvesDocs;

/// dwarves' prebuilt **typeset SVG book** — the committed output of
/// `cargo xtask-docs docs` (real `.nornir` docs → nornir docs-export → per-page SVG).
/// `page-NNN.svg` are the reading-ordered vector pages (the Typeset variant, default);
/// `toc.tsv` maps each chapter to its 0-based start page. Compile-time via rust_embed.
#[cfg(feature = "manual-embed")]
#[derive(rust_embed::RustEmbed)]
#[folder = "assets/manual-svg/"]
#[include = "*.svg"]
#[include = "toc.tsv"]
struct DwarvesManualSvg;

/// Decode the embedded typeset book: the SVG pages in reading order (sorted by the
/// zero-padded `page-NNN.svg` name) and the chapter TOC parsed from `toc.tsv`
/// (`<0-based page index>\t<title>` per line). Returns `(pages, toc)`; an absent /
/// empty book yields empties, and the pane then degrades to Markdown-only.
#[cfg(feature = "manual-embed")]
fn embedded_typeset_book() -> (Vec<Vec<u8>>, Vec<(String, usize)>) {
    let mut names: Vec<String> = DwarvesManualSvg::iter()
        .filter(|n| n.ends_with(".svg"))
        .map(|n| n.to_string())
        .collect();
    names.sort();
    let pages: Vec<Vec<u8>> = names
        .iter()
        .filter_map(|n| DwarvesManualSvg::get(n).map(|f| f.data.into_owned()))
        .collect();

    let toc: Vec<(String, usize)> = DwarvesManualSvg::get("toc.tsv")
        .and_then(|f| String::from_utf8(f.data.into_owned()).ok())
        .map(|s| {
            s.lines()
                .filter_map(|line| {
                    let (idx, title) = line.split_once('\t')?;
                    Some((title.trim().to_string(), idx.trim().parse::<usize>().ok()?))
                })
                .collect()
        })
        .unwrap_or_default();
    (pages, toc)
}

/// Build the 📖 Manual tab pane. With `manual-embed` (default) it mounts the merged
/// dual-variant pane — **Typeset (SVG) by default** over dwarves' real start-here docs,
/// with the FULL `.nornir/*.md` set behind the Markdown toggle; without it, the honest
/// empty state.
pub fn manual_pane() -> ManualPane {
    #[cfg(feature = "manual-embed")]
    {
        // Decode each embedded markdown file to (name, utf8 body); non-utf8 (none
        // expected) skipped. `iter`/`get` are inherent methods from the RustEmbed derive.
        let files: Vec<(String, String)> = DwarvesDocs::iter()
            .filter_map(|name| {
                let f = DwarvesDocs::get(&name)?;
                let body = String::from_utf8(f.data.into_owned()).ok()?;
                Some((name.to_string(), body))
            })
            .collect();
        let refs: Vec<(&str, &str)> = files
            .iter()
            .map(|(n, b)| (n.as_str(), b.as_str()))
            .collect();
        // The prebuilt typeset book (default variant). If it is absent the pane
        // degrades to Markdown-only — exactly the old `from_files` behavior.
        let (svg_pages, toc) = embedded_typeset_book();
        ManualPane::from_files_with_svgs("dwarves", refs, svg_pages, toc)
    }
    #[cfg(not(feature = "manual-embed"))]
    {
        ManualPane::not_embedded("dwarves")
    }
}

#[cfg(all(test, feature = "manual-embed"))]
mod tests {
    use super::*;

    /// The embedded manual carries dwarves' idiot guides — `dwarves-for-idiots` must be
    /// reachable in the pane (the guide a newcomer starts from).
    #[test]
    fn embeds_the_idiot_guides() {
        let pane = manual_pane();
        assert!(pane.is_embedded(), "manual-embed build must carry docs");
        let slugs = pane.state_json()["docs"].as_array().unwrap().clone();
        let has = |s: &str| slugs.iter().any(|v| v == s);
        assert!(
            has("dwarves-for-idiots"),
            "dwarves-for-idiots.md must be reachable"
        );
        // Templates were excluded.
        assert!(!has("_TEMPLATE-guide"), "template scaffolds are excluded");
    }

    /// The Manual defaults to the **Typeset (SVG)** variant rendered from dwarves' REAL
    /// docs — NOT the generic facett sample, and not a markdown-only regression:
    ///   * the default variant is `typeset` and a typeset book is present with pages;
    ///   * the pane still carries dwarves' actual `.nornir` markdown (the toggle set),
    ///     proven by a known real slug from dwarves' own docs (`dwarves-for-idiots`);
    ///   * the typeset book has the pages the committed `assets/manual-svg/` render
    ///     produced (a real book, not a stub).
    #[test]
    fn default_variant_is_typeset_of_real_dwarves_docs() {
        let pane = manual_pane();
        let st = pane.state_json();

        // Default = typeset (SVG), backed by a real book with pages.
        assert_eq!(
            st["variant"],
            serde_json::json!("typeset"),
            "default variant must be typeset (SVG)"
        );
        assert_eq!(
            st["has_typeset"],
            serde_json::json!(true),
            "a typeset book is embedded"
        );
        assert!(pane.has_typeset(), "typeset book present");
        let pages = st["typeset_page_count"].as_u64().unwrap_or(0);
        assert!(
            pages >= 5,
            "the embedded typeset book has real pages, got {pages}"
        );

        // The content is dwarves' ACTUAL docs (real slug), not the facett sample.
        let slugs = st["docs"].as_array().unwrap().clone();
        assert!(
            slugs.iter().any(|v| v == "dwarves-for-idiots"),
            "typeset default must sit over dwarves' REAL docs (dwarves-for-idiots present), \
             not the generic sample",
        );
        // The facett SAMPLE book's slugs must NOT be what we shipped.
        assert!(
            !slugs.iter().any(|v| v == "manual-for-idiots"),
            "must not regress to facett-manual's embedded SAMPLE content",
        );

        // The embedded SVG book is real (parseable pages + a chapter TOC).
        let (svg_pages, toc) = embedded_typeset_book();
        assert!(
            svg_pages.len() >= 5,
            "committed SVG pages embedded, got {}",
            svg_pages.len()
        );
        assert!(
            svg_pages.iter().all(|p| p.windows(4).any(|w| w == b"<svg")),
            "every embedded page is real SVG",
        );
        assert!(!toc.is_empty(), "the typeset book has a chapter TOC");
    }
}