record #75 merge

b52463cgl0w

all commits

Message

record #75 merge

Diff

.forge/merges/2026-10-27T16-33-02.781317430Z.md+9 −0
diff --git a/.forge/merges/2026-10-27T16-33-02.781317430Z.md b/.forge/merges/2026-10-27T16-33-02.781317430Z.mdnew file mode 100644index 0000000..aef2454--- /dev/null+++ b/.forge/merges/2026-10-27T16-33-02.781317430Z.md@@ -0,0 +1,9 @@1+---2+pr: 5cfbd5b6-7bdf-45bb-8420-629541cc40f23+applied: true4+verdict: pass5+resolved: []6+created: 2026-10-27T16:33:02.781317430Z7+---8+clean apply9+patch: 34c843261e3673937726be19131e04f1e292f5b5
.forge/prs/5cfbd5b6-7bdf-45bb-8420-629541cc40f2/patch.diff+272 −0
diff --git a/.forge/prs/5cfbd5b6-7bdf-45bb-8420-629541cc40f2/patch.diff b/.forge/prs/5cfbd5b6-7bdf-45bb-8420-629541cc40f2/patch.diffnew file mode 100644index 0000000..2a423b4--- /dev/null+++ b/.forge/prs/5cfbd5b6-7bdf-45bb-8420-629541cc40f2/patch.diff@@ -0,0 +1,272 @@1+From fa0bf99b4807d48a67ac85f71d6731ee78ebac47 Mon Sep 17 00:00:00 20012+From: hu13-test <[email protected]>3+Date: Tue, 27 Oct 2026 16:32:09 +00004+Subject: [PATCH] responsive: nav wraps, file table drops commit column, clone5+ summary segments (#75)6+7+- <=767px: header wraps into brand row + nav row; nav links wrap inside8+  the row (overflow visible) instead of being clipped in a scroll strip,9+  so all 8 destinations are reachable at 320/390px. The stray sep dot10+  is hidden when the nav gets its own row.11+- File table hides 'Last commit' on narrow (Name + Age stay, both12+  links) instead of wrapping subjects into four lines and clipping Age.13+- Clone summary is three .cb-seg spans (Clone & download / branch @ sha14+  / CI badge) with white-space:nowrap + a CSS bullet between them, so15+  the badge never splits from 'CI' or wraps mid-token.16+- Meta chips/pills get max-width:100% + overflow-wrap:anywhere.17+- Tests: new responsive assertion block; existing summary finders18+  updated for the segmented markup; light_theme CSS helpers are now19+  UTF-8-safe (they sliced bytes as chars).20+---21+ src/build.rs           | 19 ++++++++++-------22+ static/style.css       | 20 +++++++++++++++++-23+ tests/code_space.rs    |  4 +++-24+ tests/forge_readers.rs |  4 +++-25+ tests/light_theme.rs   | 26 +++++++++++------------26+ tests/responsive.rs    | 47 ++++++++++++++++++++++++++++++++++++++++++27+ 6 files changed, 96 insertions(+), 24 deletions(-)28+29+diff --git a/src/build.rs b/src/build.rs30+index a813a50..352fc43 10064431+--- a/src/build.rs32++++ b/src/build.rs33+@@ -925,7 +925,7 @@ pub fn clone_block(34+     let badge = crate::forge::verdict_badge(ci_verdict);35+     let mut out = format!(36+         "<details class=\"clonebox clone-block\" aria-label=\"Clone and download\">\n\37+-         <summary>Clone &amp; download \u{b7} {} @ {} \u{b7} CI {}</summary>\n<div class=\"clone-body\">\n",38++         <summary><span class=\"cb-seg\">Clone &amp; download</span><span class=\"cb-seg\">{} @ {}</span><span class=\"cb-seg\">CI {}</span></summary>\n<div class=\"clone-body\">\n",39+         esc(branch),40+         esc(short_sha),41+         badge42+@@ -1651,7 +1651,9 @@ mod tests {43+         assert!(!index.contains("repo-meta"), "no dupe repo-meta line");44+         assert!(!index.contains("<p>CI:"), "no dupe CI line");45+         assert!(index.contains("badge"), "CI verdict badge in summary");46+-        let summ = index.find("<summary>Clone").expect("summary");47++        let summ = index48++            .find("<summary><span class=\"cb-seg\">Clone")49++            .expect("summary");50+         let send = index[summ..].find("</summary>").expect("summary end") + summ;51+         let summary = &index[summ..send];52+         assert!(summary.contains("main"), "branch in summary");53+@@ -2393,10 +2395,7 @@ mod tests {54+         assert!(index.contains("<details"), "details present");55+         assert!(index.contains("class=\"clonebox"), "clonebox class");56+         assert!(index.contains("clone-block"), "clone-block class kept");57+-        assert!(58+-            index.contains("<summary>Clone &amp; download"),59+-            "summary label"60+-        );61++        assert!(index.contains("Clone &amp; download"), "summary label");62+         assert!(index.contains("main"), "branch hint in summary");63+         // Closed by default: no `open` attr on the clone details.64+         let d0 = index65+@@ -2475,7 +2474,9 @@ mod tests {66+             let out = mktemp(&format!("out-ci-{verdict}"));67+             run_build(&repo, &out, &opts()).expect("build");68+             let index = std::fs::read_to_string(out.join("index.html")).expect("index");69+-            let summ = index.find("<summary>Clone").expect("summary");70++            let summ = index71++                .find("<summary><span class=\"cb-seg\">Clone")72++                .expect("summary");73+             let send = index[summ..].find("</summary>").expect("summary end") + summ;74+             let summary = &index[summ..send];75+             assert!(76+@@ -2696,7 +2697,9 @@ mod tests {77+         // Code tab: repo-head with h1, summary carries branch @ sha + CI.78+         let head_pos = index.find("<div class=\"repo-head\">").expect("repo-head");79+         assert!(index[head_pos..].contains("<h1>"), "code h1 in repo-head");80+-        let s0 = index.find("<summary>Clone").expect("summary");81++        let s0 = index82++            .find("<summary><span class=\"cb-seg\">Clone")83++            .expect("summary");84+         let s1 = index[s0..].find("</summary>").expect("summary end") + s0;85+         let summary = &index[s0..s1];86+         assert!(summary.contains("Clone &amp; download"), "summary label");87+diff --git a/static/style.css b/static/style.css88+index ff5d619..95af580 10064489+--- a/static/style.css90++++ b/static/style.css91+@@ -279,7 +279,9 @@ main>h1:first-child{margin-top:var(--gap-head);margin-bottom:var(--gap-content)}92+ .lang-legend li{display:inline-flex;align-items:center;gap:.35rem}93+ .lang-dot{display:inline-block;width:.75rem;height:.75rem;flex:none}94+ details.clonebox{margin:1rem 0;padding:.25rem 1rem 1rem;background:var(--bg-alt);border:1px solid var(--border)}95+-details.clonebox>summary{cursor:pointer;min-height:40px;font-weight:700;color:var(--cyan);margin:0}96++details.clonebox>summary{cursor:pointer;min-height:40px;font-weight:700;color:var(--cyan);margin:0;display:flex;flex-wrap:wrap;align-items:center;gap:.15rem .5rem}97++.cb-seg{white-space:nowrap}98++.cb-seg+.cb-seg::before{content:"·";margin-right:.5rem;color:var(--fg-dim)}99+ details.clonebox>summary:focus-visible{outline:2px solid var(--blue);outline-offset:2px}100+ /* Clone gap (m6/code-bits, f0rge #44): no blank line under summary when101+    collapsed — collapsed padding stays tight; open gap lives on the102+@@ -367,3 +369,19 @@ pre.diff{margin:0;border:0;background:var(--surface)}103+ .diff-del-row{background:var(--diff-del-bg)}104+ .diff-file{color:var(--diff-file);font-weight:600}105+ .df-meta{color:var(--fg-dim)}106++107++/* Responsive fixes (f0rge #75): every nav destination visible at 390px,108++   file table keeps Name + Age, meta chips never overflow. */109++@media (max-width:767px){110++  .site-header .wrap.header-bar{flex-wrap:wrap}111++  .site-header .sep{display:none}112++  .site-header .site-nav.nav-scroll{flex:1 1 100%;overflow-x:visible;border-top:1px solid var(--border);margin-top:var(--s1);padding-top:var(--s1)}113++  .nav-scroll-row{flex-wrap:wrap;width:auto;min-width:0;white-space:normal;column-gap:.9rem;row-gap:.1rem}114++  /* Dense file table: drop "Last commit" below 768px instead of wrapping115++     commit subjects into four lines or clipping the age column. */116++  .file-table th:nth-child(2),.file-table td:nth-child(2){display:none}117++  .file-table td.title{white-space:nowrap}118++  .file-scroll{overflow-x:auto}119++  .meta .chip,.meta .pill{max-width:100%;overflow-wrap:anywhere}120++  .clonebox>summary{font-weight:700}121++}122+diff --git a/tests/code_space.rs b/tests/code_space.rs123+index 02e1aab..d8e406c 100644124+--- a/tests/code_space.rs125++++ b/tests/code_space.rs126+@@ -113,7 +113,9 @@ fn repo_head_spacing_is_css_margin_not_br_tags() {127+     );128+     assert!(!index.contains("repo-meta"), "no dupe repo-meta line");129+     assert!(!index.contains("<p>CI:"), "no dupe CI line");130+-    let s0 = index.find("<summary>Clone").expect("summary");131++    let s0 = index132++        .find("<summary><span class=\"cb-seg\">Clone")133++        .expect("summary");134+     let s1 = index[s0..].find("</summary>").expect("summary end") + s0;135+     let summary = &index[s0..s1];136+     assert!(summary.contains("main"), "branch in summary");137+diff --git a/tests/forge_readers.rs b/tests/forge_readers.rs138+index f0b7baa..276d52d 100644139+--- a/tests/forge_readers.rs140++++ b/tests/forge_readers.rs141+@@ -326,7 +326,9 @@ fn forge_fixture_renders_counts_numbers_and_ci() {142+         "no dupe repo-meta (f0rge #53)"143+     );144+     assert!(!index.contains("<p>CI:"), "no dupe CI line (f0rge #53)");145+-    let summ = index.find("<summary>Clone").expect("summary");146++    let summ = index147++        .find("<summary><span class=\"cb-seg\">Clone")148++        .expect("summary");149+     let send = index[summ..].find("</summary>").expect("summary end") + summ;150+     let summary = &index[summ..send];151+     assert!(summary.contains("CI"), "CI in summary");152+diff --git a/tests/light_theme.rs b/tests/light_theme.rs153+index d7cb27c..42f309b 100644154+--- a/tests/light_theme.rs155++++ b/tests/light_theme.rs156+@@ -17,18 +17,18 @@ fn css() -> String {157+ /// Strip `/* ... */` comments so palette hex in prose is not mistaken158+ /// for hardcoded rule colors.159+ fn without_comments(s: &str) -> String {160++    let chars: Vec<char> = s.chars().collect();161+     let mut out = String::with_capacity(s.len());162+-    let bytes = s.as_bytes();163+     let mut i = 0;164+-    while i < bytes.len() {165+-        if i + 1 < bytes.len() && bytes[i] == b'/' && bytes[i + 1] == b'*' {166++    while i < chars.len() {167++        if i + 1 < chars.len() && chars[i] == '/' && chars[i + 1] == '*' {168+             i += 2;169+-            while i + 1 < bytes.len() && !(bytes[i] == b'*' && bytes[i + 1] == b'/') {170++            while i + 1 < chars.len() && !(chars[i] == '*' && chars[i + 1] == '/') {171+                 i += 1;172+             }173+             i += 2;174+         } else {175+-            out.push(bytes[i] as char);176++            out.push(chars[i]);177+             i += 1;178+         }179+     }180+@@ -38,18 +38,18 @@ fn without_comments(s: &str) -> String {181+ /// Remove every `:root { ... }` block (brace-matched) so var definitions182+ /// may carry palette hex; what remains must reference vars, not hex.183+ fn without_root_blocks(s: &str) -> String {184++    let chars: Vec<char> = s.chars().collect();185+     let mut out = String::with_capacity(s.len());186+-    let bytes = s.as_bytes();187+     let mut i = 0;188+-    while i < bytes.len() {189+-        if s[i..].starts_with(":root") {190+-            if let Some(open) = s[i..].find('{') {191++    while i < chars.len() {192++        if chars[i..].iter().take(5).collect::<String>() == ":root" {193++            if let Some(open) = chars[i..].iter().position(|c| *c == '{') {194+                 let mut depth = 0;195+                 let mut j = i + open;196+-                while j < bytes.len() {197+-                    if bytes[j] == b'{' {198++                while j < chars.len() {199++                    if chars[j] == '{' {200+                         depth += 1;201+-                    } else if bytes[j] == b'}' {202++                    } else if chars[j] == '}' {203+                         depth -= 1;204+                         if depth == 0 {205+                             break;206+@@ -61,7 +61,7 @@ fn without_root_blocks(s: &str) -> String {207+                 continue;208+             }209+         }210+-        out.push(bytes[i] as char);211++        out.push(chars[i]);212+         i += 1;213+     }214+     out215+diff --git a/tests/responsive.rs b/tests/responsive.rs216+index 9c333b7..3a62e8c 100644217+--- a/tests/responsive.rs218++++ b/tests/responsive.rs219+@@ -464,3 +464,50 @@ fn zero_bare_tables_in_sources() {220+     }221+     assert!(checked >= 5, "audit saw all list tables, got {checked}");222+ }223++224++#[test]225++fn mobile_nav_wraps_and_file_table_keeps_name_and_age() {226++    // f0rge #75: no destination may be clipped at 390px; the dense file227++    // table drops "Last commit" instead of wrapping/clipping.228++    let css = css();229++    let at = css.find("@media (max-width:767px)").expect("narrow block");230++    let narrow = &css[at..];231++    assert!(232++        narrow.contains(".nav-scroll-row{flex-wrap:wrap"),233++        "nav wraps into multiple rows on narrow: {narrow}"234++    );235++    assert!(236++        narrow.contains(".site-nav.nav-scroll{flex:1 1 100%;overflow-x:visible"),237++        "nav gets its own row and is not clipped"238++    );239++    assert!(240++        narrow.contains(".file-table th:nth-child(2),.file-table td:nth-child(2){display:none}"),241++        "file table drops Last commit on mobile"242++    );243++    assert!(244++        css.contains(".cb-seg{white-space:nowrap}"),245++        "clone summary segments never wrap mid-token"246++    );247++    let out = build_fixture();248++    let index = std::fs::read_to_string(out.join("index.html")).expect("index");249++    assert_eq!(250++        index.matches("class=\"cb-seg\"").count(),251++        3,252++        "clone summary has three no-wrap segments"253++    );254++    assert!(255++        index.contains("class=\"cb-seg\">CI <span class=\"badge"),256++        "CI badge stays inside its segment"257++    );258++    // All nav destinations are present in the DOM (wrapping is CSS-only).259++    for tab in [260++        "/",261++        "/commits.html",262++        "/issues/index.html",263++        "/pulls/index.html",264++        "/ci/index.html",265++        "/agents.html",266++    ] {267++        assert!(index.contains(&format!("href=\"{tab}\"")), "nav has {tab}");268++    }269++}270+-- 271+2.56.0272+
.forge/prs/5cfbd5b6-7bdf-45bb-8420-629541cc40f2/pr.md+13 −0
diff --git a/.forge/prs/5cfbd5b6-7bdf-45bb-8420-629541cc40f2/pr.md b/.forge/prs/5cfbd5b6-7bdf-45bb-8420-629541cc40f2/pr.mdnew file mode 100644index 0000000..fbb0272--- /dev/null+++ b/.forge/prs/5cfbd5b6-7bdf-45bb-8420-629541cc40f2/pr.md@@ -0,0 +1,13 @@1+---2+id: 5cfbd5b6-7bdf-45bb-8420-629541cc40f23+title: 'Responsive: nav wraps, file table drops commit column, clone summary segments (#75)'4+state: merged5+base: master6+head: issue-75-responsive7+draft: false8+issues:9+- 1489b1c1-1014-4bb1-b28d-3eca6f81126710+ci: pass11+created: 2026-10-27T16:32:09.341021787Z12+updated: 2026-10-27T16:33:02.781317430Z13+---