land #45 code-cols + f0rge state

16735dfgl0w

all commits

Message

land #45 code-cols + f0rge state

Diff

.forge/issues/5df479f5-148a-4008-bad1-3929a370c682/comments/70c1b3b6-ec6e-4382-b628-8f579dac2a6a.md+6 −0
diff --git a/.forge/issues/5df479f5-148a-4008-bad1-3929a370c682/comments/70c1b3b6-ec6e-4382-b628-8f579dac2a6a.md b/.forge/issues/5df479f5-148a-4008-bad1-3929a370c682/comments/70c1b3b6-ec6e-4382-b628-8f579dac2a6a.mdnew file mode 100644index 0000000..048f879--- /dev/null+++ b/.forge/issues/5df479f5-148a-4008-bad1-3929a370c682/comments/70c1b3b6-ec6e-4382-b628-8f579dac2a6a.md@@ -0,0 +1,6 @@1+---2+id: 70c1b3b6-ec6e-4382-b628-8f579dac2a6a3+author: null4+created: 2026-09-10T08:43:27.496611752Z5+---6+Merged PR 489fd4ad (m6/code-cols): files+README side by side >=1100px, stacked below. CI pass. ROUND COMPLETE. Rubric verified.
.forge/issues/5df479f5-148a-4008-bad1-3929a370c682/issue.md+2 −2
diff --git a/.forge/issues/5df479f5-148a-4008-bad1-3929a370c682/issue.md b/.forge/issues/5df479f5-148a-4008-bad1-3929a370c682/issue.mdindex 85fa5da..d92ea27 100644--- a/.forge/issues/5df479f5-148a-4008-bad1-3929a370c682/issue.md+++ b/.forge/issues/5df479f5-148a-4008-bad1-3929a370c682/issue.md@@ -1,13 +1,13 @@11 ---22 id: 5df479f5-148a-4008-bad1-3929a370c68233 title: 'Code tab: files + README side by side on desktop'4-state: open4+state: closed55 labels:66 - area:render77 assignees: []88 milestone: m6-polish99 created: 2026-09-10T08:26:26.654416869Z10-updated: null10+updated: 2026-09-10T08:43:27.502130828Z1111 ---1212 Context: Owner QA — desktop code tab stacks files over README; want side-by-side.1313 Tasks:
.forge/merges/2026-09-10T08-42-20.239300632Z.md+10 −0
diff --git a/.forge/merges/2026-09-10T08-42-20.239300632Z.md b/.forge/merges/2026-09-10T08-42-20.239300632Z.mdnew file mode 100644index 0000000..888c72e--- /dev/null+++ b/.forge/merges/2026-09-10T08-42-20.239300632Z.md@@ -0,0 +1,10 @@1+---2+pr: 489fd4ad-f0ff-4f5a-929d-56ee0594272b3+applied: true4+verdict: pass5+resolved: []6+created: 2026-09-10T08:42:20.239300632Z7+---8+applied: 584d89221dce30deb50f0bdab232429b0e8890ef9+patch: 1663e3e26651106154f9a79958df148bf81c532210+pass.
.forge/prs/489fd4ad-f0ff-4f5a-929d-56ee0594272b/patch.diff+230 −0
diff --git a/.forge/prs/489fd4ad-f0ff-4f5a-929d-56ee0594272b/patch.diff b/.forge/prs/489fd4ad-f0ff-4f5a-929d-56ee0594272b/patch.diffnew file mode 100644index 0000000..c0d1f06--- /dev/null+++ b/.forge/prs/489fd4ad-f0ff-4f5a-929d-56ee0594272b/patch.diff@@ -0,0 +1,230 @@1+From 42731e2e8e17b947efa020db1ac1b4b4245ed040 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+
.forge/prs/489fd4ad-f0ff-4f5a-929d-56ee0594272b/pr.md+12 −0
diff --git a/.forge/prs/489fd4ad-f0ff-4f5a-929d-56ee0594272b/pr.md b/.forge/prs/489fd4ad-f0ff-4f5a-929d-56ee0594272b/pr.mdnew file mode 100644index 0000000..530600d--- /dev/null+++ b/.forge/prs/489fd4ad-f0ff-4f5a-929d-56ee0594272b/pr.md@@ -0,0 +1,12 @@1+---2+id: 489fd4ad-f0ff-4f5a-929d-56ee0594272b3+title: m6/code-cols4+state: merged5+base: master6+head: m6/code-cols7+draft: false8+issues: []9+ci: pass10+created: 2026-09-10T08:42:20.207118000Z11+updated: 2026-09-10T08:42:55.833096030Z12+---