.forge/prs/489fd4ad-f0ff-4f5a-929d-56ee0594272b/patch.diff 8.3 KiB 230 lines Raw
1 From 9153d223284229667d59d9afb9d4696fc6c839f7 Mon Sep 17 00:00:00 20012 From: gl0w <gl0w@world>3 Date: Thu, 10 Sep 2026 09:42:20 +01004 Subject: [PATCH] m6/code-cols: files+README side by side on desktop5 6 ---7  src/build.rs       |   7 +-8  static/style.css   |   9 +++9  tests/code_cols.rs | 160 +++++++++++++++++++++++++++++++++++++++++++++10  3 files changed, 173 insertions(+), 3 deletions(-)11  create mode 100644 tests/code_cols.rs12 13 diff --git a/src/build.rs b/src/build.rs14 index 20ca6f5..6a12e44 10064415 --- a/src/build.rs16 +++ b/src/build.rs17 @@ -1100,7 +1100,8 @@ pub fn run_build(repo: &Path, out: &Path, opts: &BuildOptions) -> Result<BuildSu18      ));19  20      body.push_str("</div>\n");21 -    // Single column on mobile; tree+readme sit side by side only at >=768px.22 +    // Stacked below 1100px; files + README sit side by side only at23 +    // >=1100px via .cols-code (m6/code-cols, f0rge #45, 7/12 + 5/12).24      // Code tab file TABLE (m6 code-gitea, f0rge #33, Gitea-style) plus the25      // language mix bar (bytes share, Tokyo Night hues, no JS).26      // Language math (m6, f0rge #37): tracked files only (via gitx, so27 @@ -1113,10 +1114,10 @@ pub fn run_build(repo: &Path, out: &Path, opts: &BuildOptions) -> Result<BuildSu28          gitx::ignored_paths(repo, &files).unwrap_or_default();29      let lang_files: Vec<(String, u64)> = lang_rows(&blobs, &ignored);30      let lang_section = language_bar(&lang_files);31 -    body.push_str("<div class=\"cols\"><section><h2>Files</h2>\n");32 +    body.push_str("<div class=\"cols cols-code\"><section class=\"code-files\"><h2>Files</h2>\n");33      body.push_str(&file_table_html("", &root_entries, None, &history));34      body.push_str(&lang_section);35 -    body.push_str("</section>\n<section><h2>README</h2>\n");36 +    body.push_str("</section>\n<section class=\"code-readme\"><h2>README</h2>\n");37      match &readme {38          Some((path, blob)) => {39              body.push_str(&format!(40 diff --git a/static/style.css b/static/style.css41 index 17175e3..d645df0 10064442 --- a/static/style.css43 +++ b/static/style.css44 @@ -147,8 +147,17 @@ footer.site-footer .footer-provenance{margin:.5rem 0;font-size:.85rem}45    .repo-head{margin-top:1.25rem}46    main{padding:1.5rem 0 3rem}47  }48 +/* Code tab two columns (m6/code-cols, f0rge #45): files + README share49 +   the existing .cols grid; the code variant stays stacked below 1100px50 +   so 360px-1099px never squeezes the file table. At >=1100px files take51 +   7/12 and README 5/12 for a readable line length. Stacked override first52 +   so the later 1100px rule wins the cascade on desktop. */53 +@media (min-width:768px){54 +  .cols-code{grid-template-columns:minmax(0,1fr)}55 +}56  @media (min-width:1100px){57    .wrap{max-width:72rem;padding:0 1.5rem}58 +  .cols-code{grid-template-columns:minmax(0,7fr) minmax(0,5fr)}59  }60  /* Code conventions (m6, f0rge #20): breadcrumbs, file meta, line anchors, clone block. */61  .crumbs{margin:.5rem 0;font-size:.95rem}62 diff --git a/tests/code_cols.rs b/tests/code_cols.rs63 new file mode 10064464 index 0000000..a21ebf365 --- /dev/null66 +++ b/tests/code_cols.rs67 @@ -0,0 +1,160 @@68 +//! Code tab two columns (m6, f0rge issue #45):69 +//! files + README share the existing `.cols` grid, side by side only at70 +//! >=1100px (files 7/12, README 5/12), stacked below.71 +//!72 +//! Builds a small fixture repo with the real binary and string-asserts on73 +//! the emitted HTML + `static/style.css` (no rendering engine needed).74 +75 +use std::path::{Path, PathBuf};76 +use std::process::Command;77 +78 +fn bin() -> &'static str {79 +    env!("CARGO_BIN_EXE_hu13")80 +}81 +82 +fn manifest() -> PathBuf {83 +    PathBuf::from(env!("CARGO_MANIFEST_DIR"))84 +}85 +86 +fn fresh(prefix: &str) -> PathBuf {87 +    let dir = std::env::temp_dir().join(format!(88 +        "hu13-codecols-{prefix}-{}-{}",89 +        std::process::id(),90 +        std::time::SystemTime::now()91 +            .duration_since(std::time::UNIX_EPOCH)92 +            .map(|d| d.as_nanos())93 +            .unwrap_or(0)94 +    ));95 +    std::fs::create_dir_all(&dir).unwrap();96 +    dir97 +}98 +99 +fn git(repo: &Path, args: &[&str]) {100 +    let st = Command::new("git")101 +        .arg("-C")102 +        .arg(repo)103 +        .args(args)104 +        .status()105 +        .expect("spawn git");106 +    assert!(st.success(), "git {args:?} failed");107 +}108 +109 +fn fixture_repo() -> PathBuf {110 +    let dir = fresh("repo");111 +    git(&dir, &["init", "-b", "main"]);112 +    git(&dir, &["config", "user.name", "hu13-test"]);113 +    git(&dir, &["config", "user.email", "[email protected]"]);114 +    git(&dir, &["config", "commit.gpgsign", "false"]);115 +    std::fs::write(dir.join("README.md"), "# cols fixture\n\nhello readme\n").unwrap();116 +    std::fs::write(dir.join("hello.txt"), "hello\n").unwrap();117 +    git(&dir, &["add", "-A"]);118 +    git(&dir, &["commit", "-m", "init"]);119 +    dir120 +}121 +122 +fn build_fixture() -> PathBuf {123 +    let repo = fixture_repo();124 +    let out = fresh("out");125 +    let st = Command::new(bin())126 +        .arg("build")127 +        .arg(&repo)128 +        .arg("-o")129 +        .arg(&out)130 +        .status()131 +        .expect("spawn hu13 build");132 +    assert!(st.success(), "fixture build exits 0");133 +    out134 +}135 +136 +fn css() -> String {137 +    std::fs::read_to_string(manifest().join("static").join("style.css")).expect("read style.css")138 +}139 +140 +#[test]141 +fn code_tab_wraps_files_and_readme_in_cols_code() {142 +    let out = build_fixture();143 +    let index = std::fs::read_to_string(out.join("index.html")).expect("index");144 +    // Wrapper reuses the existing .cols pattern with the code variant.145 +    assert!(146 +        index.contains("<div class=\"cols cols-code\">"),147 +        "code tab wraps files + README in .cols.cols-code"148 +    );149 +    // Two sections in order: files (table) left, README right.150 +    let files_pos = index151 +        .find("<section class=\"code-files\">")152 +        .expect("files section");153 +    let readme_pos = index154 +        .find("<section class=\"code-readme\">")155 +        .expect("readme section");156 +    assert!(157 +        files_pos < readme_pos,158 +        "files section comes before README section"159 +    );160 +    let files_block = &index[files_pos..readme_pos];161 +    assert!(162 +        files_block.contains("<h2>Files</h2>"),163 +        "files section keeps its heading"164 +    );165 +    assert!(166 +        files_block.contains("file-table"),167 +        "file table lives in the files (left) column"168 +    );169 +    let readme_block = &index[readme_pos..];170 +    assert!(171 +        readme_block.contains("<h2>README</h2>"),172 +        "README section keeps its heading"173 +    );174 +    assert!(175 +        readme_block.contains("cols fixture"),176 +        "README body renders in the right column"177 +    );178 +}179 +180 +#[test]181 +fn code_cols_breakpoint_is_1100px_stacked_below() {182 +    let css = css();183 +    // Mobile-first base: .cols is a single stacked column.184 +    let base = css.split("@media (min-width").next().expect("base css");185 +    assert!(186 +        base.contains(".cols{display:grid;grid-template-columns:minmax(0,1fr)"),187 +        "base .cols stays stacked single-column"188 +    );189 +    // The code variant stays stacked through the 768px breakpoint190 +    // (overrides the generic .cols side-by-side there).191 +    assert!(192 +        css.contains("@media (min-width:768px)"),193 +        "768px breakpoint kept"194 +    );195 +    assert!(196 +        css.contains("@media (min-width:1100px)"),197 +        "1100px breakpoint present"198 +    );199 +    let stacked_rule = ".cols-code{grid-template-columns:minmax(0,1fr)}";200 +    assert!(201 +        css.contains(stacked_rule),202 +        "cols-code stays stacked below 1100px, got no {stacked_rule:?}"203 +    );204 +    // Desktop: files 7/12 + README 5/12 inside the 1100px block.205 +    let wide_rule = ".cols-code{grid-template-columns:minmax(0,7fr) minmax(0,5fr)}";206 +    assert!(207 +        css.contains(wide_rule),208 +        "cols-code is 7/12 + 5/12 at >=1100px, got no {wide_rule:?}"209 +    );210 +    let block_1100 = css211 +        .split("@media (min-width:1100px)")212 +        .nth(1)213 +        .expect("1100px block");214 +    let first_chunk = block_1100.split("@media").next().expect("1100 chunk");215 +    assert!(216 +        first_chunk.contains(".cols-code"),217 +        "7/5 rule lives in the 1100px block"218 +    );219 +    // Cascade order: the stacked 768px override comes before the 1100px220 +    // two-column rule, so desktop wins and 768px–1099px stays stacked.221 +    let stacked_pos = css.find(stacked_rule).expect("stacked rule pos");222 +    let wide_pos = css.find(wide_rule).expect("wide rule pos");223 +    assert!(224 +        stacked_pos < wide_pos,225 +        "stacked override precedes the 1100px two-column rule"226 +    );227 +}228 -- 229 2.55.0230