#28 m6/logo-text
Patch
src/build.rs+8 −4--- a/src/build.rs+++ b/src/build.rs@@ -370,11 +370,15 @@ fn write_style(out: &Path) -> Result<(), String> {370370 std::fs::write(out.join("style.css"), css).map_err(|e| format!("hu13: write style.css: {e}"))371371 }372372 373-/// Write `out/favicon.svg` + `out/manifest.webmanifest` from the embedded374-/// static files (m6 favicon, f0rge issue #16). SVG-first, zero binary375-/// blobs: the favicon is the hand-written logo mark, and the manifest376-/// points at it (`icons` svg, `short_name` hu13, `display` minimal-ui).373+/// Write `out/logo.svg` + `out/favicon.svg` + `out/manifest.webmanifest`374+/// from the embedded static files (m6 favicon #16; logo-text #27 + #28).375+/// SVG-first, zero binary blobs: the header lockup is `logo.svg`376+/// (diamond-hub + hu13 as <path>, no <text>/fonts), the favicon is the377+/// simplified mark-only variant for 16px, and the manifest points at the378+/// favicon (`icons` svg, `short_name` hu13, `display` minimal-ui).377379 fn write_favicon_and_manifest(out: &Path) -> Result<(), String> {380+ let logo = include_str!("../static/logo.svg");381+ std::fs::write(out.join("logo.svg"), logo).map_err(|e| format!("hu13: write logo.svg: {e}"))?;378382 let icon = include_str!("../static/favicon.svg");379383 std::fs::write(out.join("favicon.svg"), icon)380384 .map_err(|e| format!("hu13: write favicon.svg: {e}"))?;
src/layout.rs+38 −18--- a/src/layout.rs+++ b/src/layout.rs@@ -87,12 +87,15 @@ pub fn page(title: &str, nav: &str, body: &str) -> String {8787 }8888 8989 /// Render a full HTML page with site identity (m6 header config, f0rge90-/// issues #21 + #24): the header brand shows `site_name`, `<title>` is90+/// issues #21 + #24; minimal header f0rge issues #27 + #28): the header91+/// carries zero names (logo-with-text art only + nav), `<title>` is9192 /// the escaped page `title` (callers build it as `"{page} \u2014 {site}"`9293 /// so the repo name is in every tab), `og:title` mirrors the page title,9394 /// and `og:description` / `meta[name=description]` carry `description`94-/// when present. `site_name` and `description` are escaped (hostile95-/// `<script>` renders as text). `nav`/`body` are inlined as-is like [`page`].95+/// when present. `site_name` still names the title/og surfaces (and the96+/// feed title + code-page repo-head via `build`); it is never rendered in97+/// the header. `description` is escaped (hostile `<script>` renders as98+/// text). `nav`/`body` are inlined as-is like [`page`].9699 ///97100 /// Head standards (m6 favicon, f0rge issue #16) ride along on every page:98101 /// SVG-first favicon (`rel=icon` + `apple-touch-icon` both point at@@ -192,10 +195,12 @@ pub fn page_site(192195 description: Option<&str>,193196 ) -> String {194197 let title_e = esc(title);195- let brand = esc(site_name);196- // One-bar header (m6 navbar, f0rge issue #18): the active link (if197- // any) gets `aria-current="page"` before the nav is inlined. The198- // favicon doubles as the logo mark (byte-identical to `logo.svg`).198+ // One-bar minimal header (m6 navbar #18, logo-text #27 + #28): the199+ // active link (if any) gets `aria-current="page"` before the nav is200+ // inlined. Logo art is `logo.svg` (diamond-hub + hu13 as <path>, no201+ // <text>/fonts); `favicon.svg` is the simplified mark-only variant202+ // for 16px (see `logo.rs`). Header is logo + nav only: exactly one203+ // `•` separator, no `hu13-word` / `brand` name nodes.199204 let mut head = format!(200205 "<meta charset=\"utf-8\">\n\201206 <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\@@ -227,7 +232,7 @@ pub fn page_site(227232 <html lang=\"en\">\n\228233 <head>\n{head}</head>\n\229234 <body>\n\230-<header class=\"site-header\"><div class=\"wrap header-bar\"><a class=\"brand-lockup\" href=\"/\"><img class=\"site-logo\" src=\"/favicon.svg\" alt=\"hu13 logo\" width=\"24\" height=\"24\"><span class=\"hu13-word\">hu13</span></a><span class=\"sep\" aria-hidden=\"true\">•</span><nav class=\"site-nav nav-scroll\" aria-label=\"Site\"><div class=\"nav-scroll-row\">{nav_active}</div></nav><span class=\"sep\" aria-hidden=\"true\">•</span><span class=\"brand\">{brand}</span></div></header>\n\235+<header class=\"site-header\"><div class=\"wrap header-bar\"><a class=\"brand-lockup\" href=\"/\"><img class=\"site-logo\" src=\"/logo.svg\" alt=\"hu13 logo\" width=\"96\" height=\"32\"></a><span class=\"sep\" aria-hidden=\"true\">•</span><nav class=\"site-nav nav-scroll\" aria-label=\"Site\"><div class=\"nav-scroll-row\">{nav_active}</div></nav></div></header>\n\231236 <main class=\"wrap\">\n{body}\n</main>\n\232237 </body>\n\233238 </html>\n"@@ -301,25 +306,40 @@ mod tests {301306 }302307 303308 #[test]304- fn header_is_one_bar_with_logo_seps_brand_and_scroll_nav() {305- // m6 navbar (f0rge issue #18): a single <header> holds logo +306- // hu13 • scrollable nav • repo brand; no second top-level nav.309+ fn header_is_one_bar_with_logo_sep_and_scroll_nav() {310+ // m6 navbar (#18) + logo-text (#27 + #28): a single <header>311+ // holds logo-with-text • scrollable nav only; no hu13-word or312+ // brand name nodes. The repo name still lives in <title>/og.307313 use super::{active_href, apply_active, page_site};308314 let nav = "<a href=\"/\">code</a> · <a href=\"/commits.html\">commits</a>";309315 let html = page_site("demo", nav, "<p>b</p>", "demo", None);310316 assert_eq!(html.matches("<header").count(), 1, "single header bar");311317 assert!(312- html.contains("<img class=\"site-logo\" src=\"/favicon.svg\" alt=\"hu13 logo\""),313- "logo img with alt text"318+ html.contains("<img class=\"site-logo\" src=\"/logo.svg\" alt=\"hu13 logo\""),319+ "logo lockup img with alt text"314320 );315321 assert!(316- html.contains("<span class=\"hu13-word\">hu13</span>"),317- "hu13 word"322+ !html.contains("hu13-word"),323+ "no hu13 word node (letters live in logo.svg paths)"318324 );319- assert!(html.matches("•").count() >= 2, "bullet separators");320325 assert!(321- html.contains("<span class=\"brand\">demo</span>"),322- "repo brand kept"326+ !html.contains("<span class=\"brand\">"),327+ "no repo brand node in header"328+ );329+ let h0 = html.find("<header").expect("header open");330+ let h1 = html.find("</header>").expect("header close");331+ let header = &html[h0..h1];332+ assert!(!header.contains(">demo<"), "header carries zero names");333+ assert_eq!(334+ header.matches("•").count(),335+ 1,336+ "exactly one sep (logo • nav)"337+ );338+ // Repo name still lives in title + og:title.339+ assert!(html.contains("<title>demo</title>"), "title keeps name");340+ assert!(341+ html.contains("<meta property=\"og:title\" content=\"demo\">"),342+ "og:title keeps name"323343 );324344 assert!(html.contains("nav-scroll"), "nav has scroll class");325345 assert!(html.contains("<nav"), "nav lives inside the header");
src/logo.rs+34 −9--- a/src/logo.rs+++ b/src/logo.rs@@ -1,12 +1,15 @@1-//! hu13 logo + ASCII banner (m6, f0rge issue #15).1+//! hu13 logo + ASCII banner (m6, f0rge issues #15 + #27 + #28).22 //!3-//! The mark lives hand-written in `static/logo.svg` (square icon, reads at4-//! 16px) plus `static/wordmark.svg` (horizontal lockup for the navbar); the5-//! text banner lives in `static/ascii.txt`.3+//! The lockup lives hand-written in `static/logo.svg` (diamond-hub mark +4+//! hu13 lettered as <path>, no <text>/fonts; reads at 24px header size)5+//! plus `static/wordmark.svg` (legacy horizontal lockup); the simplified6+//! 16px favicon variant lives in `static/favicon.svg` (mark only, no7+//! letters — small sizes stay legible, full lettering stays in logo.svg);8+//! the text banner lives in `static/ascii.txt`.69 //!7-//! [`ASCII_ART`], [`LOGO_SVG`] and [`WORDMARK_SVG`] re-expose those static8-//! files for later issues (navbar #16, favicon #17, wiki README #19, agents9-//! page) with no build step.10+//! [`ASCII_ART`], [`LOGO_SVG`], [`FAVICON_SVG`] and [`WORDMARK_SVG`]11+//! re-expose those static files for later issues (navbar #16, favicon #17,12+//! wiki README #19, agents page) with no build step.1013 //!1114 //! [`ascii_pre`] renders the banner as an escaped `<pre>` block (see1215 //! [`crate::layout::esc`]): the art itself is plain `|/-_` plus spaces so@@ -22,9 +25,14 @@ use crate::layout::esc;2225 pub const ASCII_ART: &str = include_str!("../static/ascii.txt");2326 2427 #[allow(dead_code)]25-/// Square mark, byte-for-byte `static/logo.svg`.28+/// Header lockup, byte-for-byte `static/logo.svg` (mark + hu13 as <path>).2629 pub const LOGO_SVG: &str = include_str!("../static/logo.svg");2730 31+#[allow(dead_code)]32+/// Simplified 16px favicon variant, byte-for-byte `static/favicon.svg`33+/// (mark only, no letters — documented choice for small-size legibility).34+pub const FAVICON_SVG: &str = include_str!("../static/favicon.svg");35+2836 #[allow(dead_code)]2937 /// Horizontal lockup, byte-for-byte `static/wordmark.svg`.3038 pub const WORDMARK_SVG: &str = include_str!("../static/wordmark.svg");@@ -74,9 +82,26 @@ mod tests {7482 7583 #[test]7684 fn svg_consts_match_tiny_marks() {77- for (name, svg) in [("logo", LOGO_SVG), ("wordmark", WORDMARK_SVG)] {85+ for (name, svg) in [86+ ("logo", LOGO_SVG),87+ ("favicon", FAVICON_SVG),88+ ("wordmark", WORDMARK_SVG),89+ ] {7890 assert!(svg.contains("<svg"), "{name} const holds svg");7991 assert!(svg.len() < 2048, "{name} const stays tiny");8092 }93+ // Logo-text (#27): lockup letters are <path>, never <text>/fonts.94+ assert!(LOGO_SVG.contains("<path"), "logo letters are paths");95+ assert!(!LOGO_SVG.contains("<text"), "logo has no <text>");96+ assert!(97+ !LOGO_SVG.to_lowercase().contains("font"),98+ "logo has no font refs"99+ );100+ // Simplified favicon variant (#27): mark only, no letters.101+ assert!(!FAVICON_SVG.contains("<text"), "favicon has no <text>");102+ assert!(103+ !FAVICON_SVG.contains("<path"),104+ "favicon is mark-only (no letter paths)"105+ );81106 }82107 }
static/favicon.svg+1 −0--- a/static/favicon.svg+++ b/static/favicon.svg@@ -1,4 +1,5 @@11 <?xml version="1.0" encoding="UTF-8"?>2+<!-- hu13 favicon: simplified mark-only variant for 16px legibility. Full lettering lives in logo.svg. -->23 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" role="img" aria-label="hu13 logo">34 <title>hu13</title>45 <rect x="0" y="0" width="32" height="32" fill="#1a1b26"/>
static/logo.svg+9 −4--- a/static/logo.svg+++ b/static/logo.svg@@ -1,8 +1,13 @@11 <?xml version="1.0" encoding="UTF-8"?>2-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32" role="img" aria-label="hu13 logo">2+<!-- hu13 lockup: diamond-hub mark + hu13 lettering as hand-drawn vectors. Reads at 24px header size. -->3+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 32" width="96" height="32" role="img" aria-label="hu13 logo">34 <title>hu13</title>4- <rect x="0" y="0" width="32" height="32" fill="#1a1b26"/>5- <rect x="0.5" y="0.5" width="31" height="31" fill="none" stroke="#292e42"/>6- <polygon points="16,6 26,16 16,26 6,16" fill="none" stroke="#7aa2f7" stroke-width="3"/>5+ <rect x="0" y="0" width="96" height="32" fill="#1a1b26"/>6+ <rect x="0.5" y="0.5" width="95" height="31" fill="none" stroke="#292e42"/>7+ <polygon points="16,4 28,16 16,28 4,16" fill="none" stroke="#7aa2f7" stroke-width="3"/>78 <rect x="13" y="13" width="6" height="6" fill="#7dcfff"/>9+ <path d="M40 8 V24 M40 15 H46 V24" fill="none" stroke="#c0caf5" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>10+ <path d="M52 8 V21 H58 V8" fill="none" stroke="#c0caf5" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>11+ <path d="M64 10 L66 8 V24 M62 24 H70" fill="none" stroke="#c0caf5" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>12+ <path d="M74 8 H80 V14 H74 M74 14 H80 V24 H74" fill="none" stroke="#c0caf5" stroke-width="2" stroke-linecap="square" stroke-linejoin="miter"/>813 </svg>
tests/favicon.rs+15 −4--- a/tests/favicon.rs+++ b/tests/favicon.rs@@ -158,10 +158,21 @@ fn favicon_emitted_svg_only_no_binary_blobs() {158158 let text = String::from_utf8(raw).expect("favicon is valid UTF-8 text");159159 assert!(text.contains("<svg"), "favicon is an SVG document");160160 assert!(text.contains("viewBox"), "scales via viewBox");161- // Mirrors the logo mark (same diamond-hub geometry, same palette).162- let logo =163- std::fs::read_to_string(manifest_dir().join("static").join("logo.svg")).expect("logo.svg");164- assert_eq!(text, logo, "favicon adapts logo.svg exactly");161+ // Simplified mark-only 16px variant (#27): same diamond-hub palette162+ // as the lockup, but no letter <path>s and no <text>/fonts.163+ let fav_src = std::fs::read_to_string(manifest_dir().join("static").join("favicon.svg"))164+ .expect("favicon.svg");165+ assert_eq!(text, fav_src, "emitted favicon mirrors static source");166+ assert!(!text.contains("<text"), "favicon has no <text>");167+ assert!(!text.contains("<path"), "favicon is mark-only");168+ assert!(!text.to_lowercase().contains("font"), "no font refs");169+ // Header lockup (#27 + #28) is emitted too and carries the letters.170+ let logo_raw = std::fs::read(out.join("logo.svg")).expect("build emits logo.svg");171+ let logo = String::from_utf8(logo_raw).expect("logo is valid UTF-8 text");172+ assert!(logo.contains("<svg"), "logo is an SVG document");173+ assert!(logo.contains("<path"), "logo letters are <path>");174+ assert!(!logo.contains("<text"), "logo has no <text>");175+ assert_ne!(text, logo, "favicon is the simplified variant, not a copy");165176 // No binary icon formats anywhere in static/ or the build output.166177 for dir in [manifest_dir().join("static"), out.clone()] {167178 let mut stack = vec![dir.clone()];
tests/header_config.rs+59 −10--- a/tests/header_config.rs+++ b/tests/header_config.rs@@ -1,7 +1,8 @@1-//! Header config (m6, f0rge issues #21 + #24):1+//! Header config (m6, f0rge issues #21 + #24 + #27 + #28):22 //! `--repo-name`/`HU13_REPO_NAME` (flag > env > dirname, never blank) +33 //! `--description`/`HU13_DESCRIPTION` (flag > env > hidden, no empty `<p>`),4-//! threaded into header brand, `<title>`, og tags, and feed title/subtitle.4+//! threaded into `<title>`, og tags, feed title/subtitle, and the code-page5+//! repo-head — never into the minimal header (logo • nav only).56 //! Hostile `<script>` input renders as escaped text everywhere.67 78 use std::path::{Path, PathBuf};@@ -68,6 +69,12 @@ fn read(rel: &Path, join: &str) -> String {6869 std::fs::read_to_string(rel.join(join)).expect(join)6970 }7071 72+fn header_of(html: &str) -> &str {73+ let start = html.find("<header").expect("header open");74+ let end = html.find("</header>").expect("header close");75+ &html[start..end]76+}77+7178 fn set_env(k: &str, v: Option<&str>) {7279 unsafe {7380 match v {@@ -93,9 +100,25 @@ fn repo_name_chain_flag_beats_env_beats_dirname() {93100 "env name in title"94101 );95102 assert!(96- index.contains("<span class=\"brand\">Env Name</span>"),97- "env name in brand"103+ index.contains("og:title\" content=\"Env Name\""),104+ "env name in og:title"98105 );106+ assert!(107+ index.contains("<div class=\"repo-head\"><h1>Env Name</h1>"),108+ "env name in code-page repo-head"109+ );110+ assert!(111+ read(&out, "feed.atom").contains("<title>Env Name</title>"),112+ "env name in feed title"113+ );114+ let header = header_of(&index).to_string();115+ assert!(116+ !header.contains("<span class=\"brand\">"),117+ "no brand node in header"118+ );119+ assert!(!header.contains("hu13-word"), "no word node in header");120+ assert_eq!(header.matches("•").count(), 1, "logo • nav only");121+ assert!(!header.contains("Env Name"), "header carries zero names");99122 100123 // Flag beats env.101124 set_env("HU13_REPO_NAME", Some("Env Name"));@@ -103,6 +126,10 @@ fn repo_name_chain_flag_beats_env_beats_dirname() {103126 let index = read(&out, "index.html");104127 assert!(index.contains("<title>Flag Name</title>"), "flag wins");105128 assert!(!index.contains("Env Name"), "env suppressed by flag");129+ assert!(130+ index.contains("og:title\" content=\"Flag Name\""),131+ "og:title follows flag"132+ );106133 107134 // Neither -> dirname fallback, never blank.108135 set_env("HU13_REPO_NAME", None);@@ -113,8 +140,12 @@ fn repo_name_chain_flag_beats_env_beats_dirname() {113140 "dirname fallback"114141 );115142 assert!(116- index.contains(&format!("<span class=\"brand\">{dirname}</span>")),117- "brand fallback"143+ index.contains(&format!("<div class=\"repo-head\"><h1>{dirname}</h1>")),144+ "repo-head fallback"145+ );146+ assert!(147+ !header_of(&index).contains(&dirname),148+ "header stays nameless"118149 );119150 assert!(!index.contains("<h1></h1>"), "header never blank");120151 set_env("HU13_REPO_NAME", None);@@ -178,11 +209,18 @@ fn hostile_name_and_description_escape_everywhere() {178209 }179210 assert!(!feed.contains("<script>alert"), "feed escapes too");180211 181- // Escaped forms present in every surface.212+ // Escaped forms present in every naming surface; the header stays nameless.182213 let name_e = "Evil <script>alert(1)</script> Hub";183214 let desc_e = "Desc <script>alert(2)</script> & "quoted"";184- assert!(index.contains(name_e), "header/brand escapes name");185- assert!(index.contains(desc_e), "header escapes description");215+ assert!(216+ !header_of(&index).contains(name_e),217+ "header carries zero names"218+ );219+ assert!(220+ !header_of(&index).contains("<span class=\"brand\">"),221+ "no brand node to escape into"222+ );223+ assert!(index.contains(desc_e), "body escapes description");186224 assert!(187225 index.contains(&format!("<title>{name_e}</title>")),188226 "title escapes name"@@ -195,7 +233,18 @@ fn hostile_name_and_description_escape_everywhere() {195233 index.contains(&format!("og:description\" content=\"{desc_e}\"")),196234 "og:description matches"197235 );198- assert!(commits.contains(name_e), "subpage brand escapes name");236+ assert!(237+ commits.contains(&format!("<title>commit")),238+ "subpage title kept"239+ );240+ assert!(241+ !header_of(&commits).contains(name_e),242+ "subpage header stays nameless"243+ );244+ assert!(245+ index.contains(&format!("<div class=\"repo-head\"><h1>{name_e}</h1>")),246+ "code-page repo-head escapes name"247+ );199248 assert!(200249 feed.contains(&format!("<title>{name_e}</title>")),201250 "feed title escapes name"
tests/logo.rs+57 −3--- a/tests/logo.rs+++ b/tests/logo.rs@@ -1,6 +1,7 @@1-//! Logo suite (m6, f0rge issue #15):2-//! `static/logo.svg` exists + valid XML + <2KB, wordmark variant,3-//! ASCII banner limits, `<pre>` rendering invariant.1+//! Logo suite (m6, f0rge issues #15 + #27 + #28):2+//! `static/logo.svg` (lockup with hu13 as <path>) + `static/favicon.svg`3+//! (simplified mark-only 16px variant) exist + valid XML + <2KB total,4+//! wordmark variant, ASCII banner limits, `<pre>` rendering invariant.45 //!56 //! File-level suite (hu13 is a binary crate, so `src/logo.rs` unit tests67 //! cover the `ascii_pre()` helper itself; here we assert on disk).@@ -138,6 +139,59 @@ fn logo_svg_exists_valid_tiny() {138139 assert!(svg.contains(color), "Tokyo Night palette keeps {color}");139140 }140141 assert_sharp_no_gradient("logo.svg", &svg);142+ // Logo-text (#27): hu13 lettering is hand-written <path>, never143+ // <text>/fonts, so the header lockup has no font dependency.144+ assert!(svg.contains("<path"), "logo letters are <path>");145+ assert!(!svg.contains("<text"), "logo has no <text>");146+ assert!(147+ !svg.to_lowercase().contains("font"),148+ "logo has no font refs"149+ );150+ assert!(151+ svg.matches("<path").count() >= 2,152+ "logo carries mark + letter paths"153+ );154+}155+156+#[test]157+fn favicon_variant_is_simplified_mark_only() {158+ // Logo-text (#27): the 16px favicon is the simplified mark-only159+ // variant (no letters) so it stays legible; full lettering lives in160+ // logo.svg. Both stay valid XML, sharp, tiny, with <2KB combined.161+ let fav_path = manifest().join("static").join("favicon.svg");162+ let fav_len = std::fs::metadata(&fav_path)163+ .expect("static/favicon.svg exists")164+ .len();165+ assert!(166+ fav_len < 2048,167+ "favicon.svg is {fav_len} bytes, budget is 2KB"168+ );169+ let fav = read_static("favicon.svg");170+ check_well_formed(&fav);171+ assert!(fav.contains("<svg"), "favicon has an <svg> root");172+ assert!(fav.contains("viewBox"), "favicon scales via viewBox");173+ assert!(!fav.contains("<text"), "favicon has no <text>");174+ assert!(175+ !fav.to_lowercase().contains("font"),176+ "favicon has no font refs"177+ );178+ assert!(179+ !fav.contains("<path"),180+ "favicon is mark-only (no letter <path>s)"181+ );182+ assert_sharp_no_gradient("favicon.svg", &fav);183+ for color in ["#1a1b26", "#7aa2f7", "#7dcfff"] {184+ assert!(fav.contains(color), "favicon keeps {color}");185+ }186+ let logo_len = std::fs::metadata(manifest().join("static").join("logo.svg"))187+ .expect("static/logo.svg exists")188+ .len();189+ assert!(190+ logo_len + fav_len < 2048,191+ "logo ({logo_len}B) + favicon ({fav_len}B) stay under 2KB total"192+ );193+ let logo = read_static("logo.svg");194+ assert_ne!(logo, fav, "favicon is a simplified variant, not a copy");141195 }142196 143197 #[test]
tests/navbar.rs+30 −13--- a/tests/navbar.rs+++ b/tests/navbar.rs@@ -1,5 +1,5 @@1-//! Navbar one-bar (m6, f0rge issue #18):2-//! single `<header>` (`logo hu13 • nav • repo`), scrollable nav strip1+//! Navbar one-bar (m6, f0rge issues #18 + #27 + #28):2+//! single `<header>` (logo-with-text • nav only), scrollable nav strip33 //! (own `nav-scroll` class: `overflow-x:auto` + nowrap, snap off, visible44 //! focus), logo img alt text, `aria-current` on the active tab.55 //!@@ -72,7 +72,7 @@ fn css() -> String {7272 }7373 7474 #[test]75-fn one_header_bar_with_logo_seps_brand_and_scroll_nav() {75+fn one_header_bar_with_logo_sep_and_scroll_nav() {7676 let out = build_fixture();7777 for page in ["index.html", "commits.html", "issues/index.html"] {7878 let html = std::fs::read_to_string(out.join(page)).expect(page);@@ -86,24 +86,28 @@ fn one_header_bar_with_logo_seps_brand_and_scroll_nav() {8686 html.contains("<nav"),8787 "{page} keeps a <nav> inside the header"8888 );89- // Brand cluster: logo img (alt text) + hu13 • … • repo brand.89+ // Minimal lockup (#27 + #28): logo-with-text art • nav only.9090 assert!(91- html.contains("<img class=\"site-logo\""),92- "{page} shows the logo img"91+ html.contains("<img class=\"site-logo\" src=\"/logo.svg\""),92+ "{page} shows the logo lockup img"9393 );9494 assert!(9595 html.contains("alt=\"hu13 logo\""),9696 "{page} logo carries alt text"9797 );98- assert!(html.contains(">hu13<"), "{page} names hu13");99- assert!(html.contains("•"), "{page} has • separators");10098 assert!(101- html.matches("•").count() >= 2,102- "{page} separates brand/nav/repo with •"99+ !html.contains("hu13-word"),100+ "{page} drops the hu13 word node (letters live in logo.svg)"103101 );104102 assert!(105- html.contains("<span class=\"brand\">"),106- "{page} keeps the repo brand"103+ !html.contains("<span class=\"brand\">"),104+ "{page} drops the repo brand node"105+ );106+ let header = html.split("</header>").next().expect("header close");107+ assert_eq!(108+ header.matches("•").count(),109+ 1,110+ "{page} header is logo • nav (exactly one sep)"107111 );108112 // Scrollable nav strip under its own class.109113 assert!(@@ -111,6 +115,20 @@ fn one_header_bar_with_logo_seps_brand_and_scroll_nav() {111115 "{page} nav has the scroll class"112116 );113117 }118+ // Repo name still lives outside the header (#28): title, og:title,119+ // code-page repo-head, and feed title.120+ let index = std::fs::read_to_string(out.join("index.html")).expect("index");121+ assert!(index.contains("<title>"), "title kept");122+ assert!(123+ index.contains("<meta property=\"og:title\""),124+ "og:title kept"125+ );126+ assert!(127+ index.contains("<div class=\"repo-head\"><h1>"),128+ "code-page repo-head title kept"129+ );130+ let feed = std::fs::read_to_string(out.join("feed.atom")).expect("feed");131+ assert!(feed.contains("<title>"), "feed title kept");114132 }115133 116134 #[test]117--
Comments (0)
No comments yet.
Reviews (0)
No reviews yet.