#39 m6/code-cols

mergedpassopened
details
branches
master ← m6/code-cols
updated
id
489fd4ad-f0ff-4f5a-929d-56ee0594272b

Patch

src/build.rs+4 −3
diff --git a/src/build.rs b/src/build.rsindex 20ca6f5..6a12e44 100644--- a/src/build.rs+++ b/src/build.rs@@ -1100,7 +1100,8 @@ pub fn run_build(repo: &Path, out: &Path, opts: &BuildOptions) -> Result<BuildSu11001100     ));11011101 11021102     body.push_str("</div>\n");1103-    // Single column on mobile; tree+readme sit side by side only at >=768px.1103+    // Stacked below 1100px; files + README sit side by side only at1104+    // >=1100px via .cols-code (m6/code-cols, f0rge #45, 7/12 + 5/12).11041105     // Code tab file TABLE (m6 code-gitea, f0rge #33, Gitea-style) plus the11051106     // language mix bar (bytes share, Tokyo Night hues, no JS).11061107     // Language math (m6, f0rge #37): tracked files only (via gitx, so@@ -1113,10 +1114,10 @@ pub fn run_build(repo: &Path, out: &Path, opts: &BuildOptions) -> Result<BuildSu11131114         gitx::ignored_paths(repo, &files).unwrap_or_default();11141115     let lang_files: Vec<(String, u64)> = lang_rows(&blobs, &ignored);11151116     let lang_section = language_bar(&lang_files);1116-    body.push_str("<div class=\"cols\"><section><h2>Files</h2>\n");1117+    body.push_str("<div class=\"cols cols-code\"><section class=\"code-files\"><h2>Files</h2>\n");11171118     body.push_str(&file_table_html("", &root_entries, None, &history));11181119     body.push_str(&lang_section);1119-    body.push_str("</section>\n<section><h2>README</h2>\n");1120+    body.push_str("</section>\n<section class=\"code-readme\"><h2>README</h2>\n");11201121     match &readme {11211122         Some((path, blob)) => {11221123             body.push_str(&format!(
static/style.css+9 −0
diff --git a/static/style.css b/static/style.cssindex 17175e3..d645df0 100644--- a/static/style.css+++ b/static/style.css@@ -147,8 +147,17 @@ footer.site-footer .footer-provenance{margin:.5rem 0;font-size:.85rem}147147   .repo-head{margin-top:1.25rem}148148   main{padding:1.5rem 0 3rem}149149 }150+/* Code tab two columns (m6/code-cols, f0rge #45): files + README share151+   the existing .cols grid; the code variant stays stacked below 1100px152+   so 360px-1099px never squeezes the file table. At >=1100px files take153+   7/12 and README 5/12 for a readable line length. Stacked override first154+   so the later 1100px rule wins the cascade on desktop. */155+@media (min-width:768px){156+  .cols-code{grid-template-columns:minmax(0,1fr)}157+}150158 @media (min-width:1100px){151159   .wrap{max-width:72rem;padding:0 1.5rem}160+  .cols-code{grid-template-columns:minmax(0,7fr) minmax(0,5fr)}152161 }153162 /* Code conventions (m6, f0rge #20): breadcrumbs, file meta, line anchors, clone block. */154163 .crumbs{margin:.5rem 0;font-size:.95rem}
tests/code_cols.rs+160 −1
diff --git a/tests/code_cols.rs b/tests/code_cols.rsnew file mode 100644index 0000000..a21ebf3--- /dev/null+++ b/tests/code_cols.rs@@ -0,0 +1,160 @@1+//! Code tab two columns (m6, f0rge issue #45):2+//! files + README share the existing `.cols` grid, side by side only at3+//! >=1100px (files 7/12, README 5/12), stacked below.4+//!5+//! Builds a small fixture repo with the real binary and string-asserts on6+//! the emitted HTML + `static/style.css` (no rendering engine needed).7+8+use std::path::{Path, PathBuf};9+use std::process::Command;10+11+fn bin() -> &'static str {12+    env!("CARGO_BIN_EXE_hu13")13+}14+15+fn manifest() -> PathBuf {16+    PathBuf::from(env!("CARGO_MANIFEST_DIR"))17+}18+19+fn fresh(prefix: &str) -> PathBuf {20+    let dir = std::env::temp_dir().join(format!(21+        "hu13-codecols-{prefix}-{}-{}",22+        std::process::id(),23+        std::time::SystemTime::now()24+            .duration_since(std::time::UNIX_EPOCH)25+            .map(|d| d.as_nanos())26+            .unwrap_or(0)27+    ));28+    std::fs::create_dir_all(&dir).unwrap();29+    dir30+}31+32+fn git(repo: &Path, args: &[&str]) {33+    let st = Command::new("git")34+        .arg("-C")35+        .arg(repo)36+        .args(args)37+        .status()38+        .expect("spawn git");39+    assert!(st.success(), "git {args:?} failed");40+}41+42+fn fixture_repo() -> PathBuf {43+    let dir = fresh("repo");44+    git(&dir, &["init", "-b", "main"]);45+    git(&dir, &["config", "user.name", "hu13-test"]);46+    git(&dir, &["config", "user.email", "[email protected]"]);47+    git(&dir, &["config", "commit.gpgsign", "false"]);48+    std::fs::write(dir.join("README.md"), "# cols fixture\n\nhello readme\n").unwrap();49+    std::fs::write(dir.join("hello.txt"), "hello\n").unwrap();50+    git(&dir, &["add", "-A"]);51+    git(&dir, &["commit", "-m", "init"]);52+    dir53+}54+55+fn build_fixture() -> PathBuf {56+    let repo = fixture_repo();57+    let out = fresh("out");58+    let st = Command::new(bin())59+        .arg("build")60+        .arg(&repo)61+        .arg("-o")62+        .arg(&out)63+        .status()64+        .expect("spawn hu13 build");65+    assert!(st.success(), "fixture build exits 0");66+    out67+}68+69+fn css() -> String {70+    std::fs::read_to_string(manifest().join("static").join("style.css")).expect("read style.css")71+}72+73+#[test]74+fn code_tab_wraps_files_and_readme_in_cols_code() {75+    let out = build_fixture();76+    let index = std::fs::read_to_string(out.join("index.html")).expect("index");77+    // Wrapper reuses the existing .cols pattern with the code variant.78+    assert!(79+        index.contains("<div class=\"cols cols-code\">"),80+        "code tab wraps files + README in .cols.cols-code"81+    );82+    // Two sections in order: files (table) left, README right.83+    let files_pos = index84+        .find("<section class=\"code-files\">")85+        .expect("files section");86+    let readme_pos = index87+        .find("<section class=\"code-readme\">")88+        .expect("readme section");89+    assert!(90+        files_pos < readme_pos,91+        "files section comes before README section"92+    );93+    let files_block = &index[files_pos..readme_pos];94+    assert!(95+        files_block.contains("<h2>Files</h2>"),96+        "files section keeps its heading"97+    );98+    assert!(99+        files_block.contains("file-table"),100+        "file table lives in the files (left) column"101+    );102+    let readme_block = &index[readme_pos..];103+    assert!(104+        readme_block.contains("<h2>README</h2>"),105+        "README section keeps its heading"106+    );107+    assert!(108+        readme_block.contains("cols fixture"),109+        "README body renders in the right column"110+    );111+}112+113+#[test]114+fn code_cols_breakpoint_is_1100px_stacked_below() {115+    let css = css();116+    // Mobile-first base: .cols is a single stacked column.117+    let base = css.split("@media (min-width").next().expect("base css");118+    assert!(119+        base.contains(".cols{display:grid;grid-template-columns:minmax(0,1fr)"),120+        "base .cols stays stacked single-column"121+    );122+    // The code variant stays stacked through the 768px breakpoint123+    // (overrides the generic .cols side-by-side there).124+    assert!(125+        css.contains("@media (min-width:768px)"),126+        "768px breakpoint kept"127+    );128+    assert!(129+        css.contains("@media (min-width:1100px)"),130+        "1100px breakpoint present"131+    );132+    let stacked_rule = ".cols-code{grid-template-columns:minmax(0,1fr)}";133+    assert!(134+        css.contains(stacked_rule),135+        "cols-code stays stacked below 1100px, got no {stacked_rule:?}"136+    );137+    // Desktop: files 7/12 + README 5/12 inside the 1100px block.138+    let wide_rule = ".cols-code{grid-template-columns:minmax(0,7fr) minmax(0,5fr)}";139+    assert!(140+        css.contains(wide_rule),141+        "cols-code is 7/12 + 5/12 at >=1100px, got no {wide_rule:?}"142+    );143+    let block_1100 = css144+        .split("@media (min-width:1100px)")145+        .nth(1)146+        .expect("1100px block");147+    let first_chunk = block_1100.split("@media").next().expect("1100 chunk");148+    assert!(149+        first_chunk.contains(".cols-code"),150+        "7/5 rule lives in the 1100px block"151+    );152+    // Cascade order: the stacked 768px override comes before the 1100px153+    // two-column rule, so desktop wins and 768px–1099px stays stacked.154+    let stacked_pos = css.find(stacked_rule).expect("stacked rule pos");155+    let wide_pos = css.find(wide_rule).expect("wide rule pos");156+    assert!(157+        stacked_pos < wide_pos,158+        "stacked override precedes the 1100px two-column rule"159+    );160+}0-- 2.55.0

Comments (0)

No comments yet.

Reviews (0)

No reviews yet.

all pulls