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

nordisk/gunnar-ui

crates/gunnar-ui-web/src/title.rs

//! **Every page's title: the page, the repository, the site** (GUN-FIELD-TEST, 2026-11-01),
//! on the host side of the wasm boundary. The console's tab said "Gunnar" on every page
//! but the ones the server pre-renders; it now says what is open, in the pre-rendered
//! pages' own order: `Commits · alice/field · Gunnar`.

/// The site's name out of a document title the server wrote (`… · Gunnar`, or
/// `Gunnar — a git server`).
pub fn site_of(title: &str) -> String {
    let last = title.rsplit(" · ").next().unwrap_or(title);
    let name = last.split(" — ").next().unwrap_or(last).trim();
    if name.is_empty() { "Gunnar".to_owned() } else { name.to_owned() }
}

/// Pure: the title of the page at `path` on `site`.
pub fn of(path: &str, site: &str) -> String {
    let parts: Vec<&str> = path.trim_matches('/').split('/').filter(|p| !p.is_empty()).collect();
    let fixed = |what: &str| format!("{what} · {site}");
    match parts.as_slice() {
        [] => site.to_owned(),
        ["repos"] => fixed("Repositories"),
        ["access"] => fixed("Identities"),
        ["account"] => fixed("Account"),
        ["account", "storage"] => fixed("Storage · Account"),
        ["account", "activity"] => fixed("Activity · Account"),
        ["account", "clans"] => fixed("Clans · Account"),
        ["admin"] => fixed("Administration"),
        ["admin", "overview"] | ["overview"] => fixed("Overview · Administration"),
        ["admin", "storage"] | ["storage"] => fixed("Storage · Administration"),
        ["admin", "accounts"] => fixed("Accounts · Administration"),
        ["admin", "audit"] => fixed("Audit log · Administration"),
        ["admin", "identity-provider"] => fixed("Identity provider · Administration"),
        ["usage"] => fixed("Usage"),
        ["usage", ns] => fixed(&format!("{ns} · Usage")),
        ["mirrors"] => fixed("Mirrors"),
        ["mirrors", "import"] => fixed("Import repositories"),
        ["cli"] => fixed("The Gunnar CLI"),
        ["events"] => fixed("Events"),
        ["machine"] => fixed("Machine"),
        ["login"] => fixed("Log in"),
        [ns] => fixed(ns),
        [ns, name, rest @ ..] => {
            let repo = format!("{ns}/{name}");
            let what: Option<String> = match rest {
                [] => None,
                ["tree"] => Some("Files".into()),
                ["tree", path @ ..] | ["blob", path @ ..] => Some(path.join("/")),
                ["commits"] => Some("Commits".into()),
                ["diff"] => Some("Compare".into()),
                ["search"] => Some("Search".into()),
                ["blame"] => Some("Blame".into()),
                ["chain"] => Some("Provenance".into()),
                ["pulls"] => Some("Pull requests".into()),
                ["pulls", n] => Some(format!("Pull request #{n}")),
                ["issues"] => Some("Issues".into()),
                ["issues", n] => Some(format!("Issue #{n}")),
                ["activity"] => Some("Activity".into()),
                ["settings"] => Some("Settings".into()),
                other => Some(other.join("/")),
            };
            match what {
                Some(w) => format!("{w} · {repo} · {site}"),
                None => format!("{repo} · {site}"),
            }
        }
    }
}

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

    /// ★ The page, then the repository, then the site.
    #[test]
    fn every_page_says_what_is_open() {
        let t = |p: &str| of(p, "Gunnar");
        assert_eq!(t("/"), "Gunnar");
        assert_eq!(t("/repos"), "Repositories · Gunnar");
        assert_eq!(t("/alice"), "alice · Gunnar");
        assert_eq!(t("/alice/field"), "alice/field · Gunnar");
        assert_eq!(t("/alice/field/tree"), "Files · alice/field · Gunnar");
        assert_eq!(t("/alice/field/tree/docs"), "docs · alice/field · Gunnar");
        assert_eq!(t("/alice/field/blob/docs/README.md"), "docs/README.md · alice/field · Gunnar");
        assert_eq!(t("/alice/field/commits"), "Commits · alice/field · Gunnar");
        assert_eq!(t("/alice/field/pulls"), "Pull requests · alice/field · Gunnar");
        assert_eq!(t("/alice/field/pulls/3"), "Pull request #3 · alice/field · Gunnar");
        assert_eq!(t("/alice/field/issues/1"), "Issue #1 · alice/field · Gunnar");
        assert_eq!(t("/alice/field/settings"), "Settings · alice/field · Gunnar");
        assert_eq!(t("/admin/storage"), "Storage · Administration · Gunnar");
        assert_eq!(t("/account"), "Account · Gunnar");
        assert_eq!(t("/mirrors/import"), "Import repositories · Gunnar");
        assert_eq!(t("/usage/alice"), "alice · Usage · Gunnar");
        assert_eq!(site_of("Commits · alice/field · Gunnar"), "Gunnar");
        assert_eq!(site_of("Gunnar — a European git server in Rust"), "Gunnar");
        assert_eq!(site_of(""), "Gunnar");
    }

    /// ★ The console sets it on every navigation.
    #[test]
    fn the_console_keeps_the_title() {
        let app = crate::code::of(include_str!("app.rs"));
        assert!(app.contains("crate::title::of(") && app.contains("<TitleKeeper/>"), "the title follows the address");
    }
}

#[cfg(test)]
mod actions_bar {
    /// ★ **The account's one-line note in the top bar is read whole** (GUN-FIELD-TEST,
    /// 2026-11-01): it was cut to "Add your SSH key so you ca" by the bar's other items.
    /// The pill keeps its size (up to its own cap), its line ends in an ellipsis past the
    /// cap, and the whole line is its title.
    #[test]
    fn the_note_keeps_its_size_and_says_itself_whole() {
        let css = include_str!("../styles/console.css");
        let rule = css.split("\n.actions-bar {").nth(1).and_then(|r| r.split('}').next()).unwrap_or("");
        assert!(rule.contains("flex: none"), "the pill is not shrunk by its neighbours: {rule:?}");
        assert!(css.contains(".actions-bar .actions-line {"), "the line ellipsises inside the pill");
        let app = crate::code::of(include_str!("app.rs"));
        assert!(app.contains("class=\"actions-line\"") && app.contains("title=line.clone()"), "the line and its title");
    }
}