#27 m6/table2

mergedpassopened
details
branches
master ← m6/table2
updated
id
019a1079-9f9b-46d9-bbcc-9ffc0d4dffb2

Patch

src/commits.rs+30 −0
diff --git a/src/commits.rs b/src/commits.rsindex 9c3b288..d5fa33e 100644--- a/src/commits.rs+++ b/src/commits.rs@@ -322,6 +322,36 @@ mod tests {322322         assert!(!out.join("commit").join(format!("{}.html", all[2])).exists());323323     }324324 325+    /// m6/table-scroll (f0rge #30): long subject + full sha render inside a326+    /// .table-scroll wrapper with td.title / td.sha cells.327+    #[test]328+    fn long_subject_scrolls_with_title_cell() {329+        let dir = mktemp("long-subject");330+        git(&dir, &["init", "-b", "main"]);331+        git(&dir, &["config", "user.name", "hu13-test"]);332+        git(&dir, &["config", "user.email", "[email protected]"]);333+        git(&dir, &["config", "commit.gpgsign", "false"]);334+        let long_subject = "s".repeat(200);335+        std::fs::write(dir.join("x.txt"), "x\n").unwrap();336+        git(&dir, &["add", "-A"]);337+        git(&dir, &["commit", "-m", &long_subject]);338+        let out = mktemp("out");339+        render(&dir, &out, "main", 100, "test", "<nav>", None).expect("render");340+        let list = std::fs::read_to_string(out.join("commits.html")).expect("list");341+        assert!(342+            list.contains("<div class=\"table-scroll\"><table>"),343+            "wrapper"344+        );345+        assert!(list.contains("<td class=\"title\">"), "title cell");346+        assert!(list.contains("<td class=\"sha\">"), "sha cell");347+        assert!(list.contains(&long_subject), "long subject kept");348+        let sha = shas(&dir)[0].clone();349+        assert!(350+            list.contains(&format!("/commit/{sha}.html")),351+            "full sha link"352+        );353+    }354+325355     #[test]326356     fn empty_log_renders_none_yet() {327357         let dir = mktemp("empty");
src/forge.rs+89 −4
diff --git a/src/forge.rs b/src/forge.rsindex 1f8af3e..3574c8a 100644--- a/src/forge.rs+++ b/src/forge.rs@@ -947,8 +947,8 @@ fn render_issues_index(947947     nav: &str,948948     description: Option<&str>,949949 ) -> Result<String, String> {950-    let mut body = String::from("<h1>Issues</h1>\n");951-    body.push_str("<p><a class=\"btn-new\" href=\"/new-issue.html\">+ New issue</a></p>\n");950+    let mut body =951+        String::from("<div class=\"page-head\"><h1>Issues</h1>\n<a class=\"btn-new\" href=\"/new-issue.html\">+ New issue</a></div>\n");952952     if data.issues.is_empty() {953953         body.push_str("<p class=\"muted\">No issues yet. <a href=\"/new-issue.html\">Suggest one</a> — no account needed.</p>\n");954954     } else {@@ -994,8 +994,8 @@ fn render_issues_index(994994 }995995 996996 fn render_pulls_index(prs: &[Pr], site_name: &str, nav: &str, description: Option<&str>) -> String {997-    let mut body = String::from("<h1>Pulls</h1>\n");998-    body.push_str("<p><a class=\"btn-new\" href=\"/new-pr.html\">+ New PR prompt</a></p>\n");997+    let mut body =998+        String::from("<div class=\"page-head\"><h1>Pulls</h1>\n<a class=\"btn-new\" href=\"/new-pr.html\">+ New PR prompt</a></div>\n");999999     if prs.is_empty() {10001000         body.push_str("<p class=\"muted\">No pull requests yet. <a href=\"/new-pr.html\">Suggest one</a> — no account needed.</p>\n");10011001     } else {@@ -1566,4 +1566,89 @@ mod tests {15661566         assert!(!data.ci.steps.is_empty(), "real ci steps read");15671567         assert!(data.ci.latest.is_some(), "real ci latest run read");15681568     }1569+1570+    /// m6/table-scroll (f0rge #30): CSS keeps natural width + nowrap cells1571+    /// with a wrapping title floor; hover tint via var.1572+    #[test]1573+    fn table_scroll_css_rules() {1574+        let css = include_str!("../static/style.css");1575+        assert!(css.contains("width:max-content"), "natural width");1576+        assert!(css.contains("min-width:100%"), "full-bleed floor");1577+        assert!(1578+            css.contains(".table-scroll th,.table-scroll td") && css.contains("white-space:nowrap"),1579+            "cells nowrap"1580+        );1581+        assert!(1582+            css.contains(".table-scroll td.title") && css.contains("min-width:12rem"),1583+            "title floor"1584+        );1585+        assert!(1586+            css.contains("tr:hover td") && css.contains("var(--bg-th)"),1587+            "hover tint"1588+        );1589+    }1590+1591+    /// m6/table-scroll (f0rge #31): h1 + button share one .page-head row.1592+    #[test]1593+    fn page_head_css_and_markup() {1594+        let css = include_str!("../static/style.css");1595+        assert!(css.contains(".page-head"), "page-head rule");1596+        assert!(css.contains("align-items:center"), "inline row");1597+        let repo = mktemp("page-head");1598+        let out = mktemp("page-head-out");1599+        render_loaded(&load(&repo), &out, "test", "<nav>", None).expect("render");1600+        for (page, title, btn) in [1601+            ("issues/index.html", "<h1>Issues</h1>", "+ New issue"),1602+            ("pulls/index.html", "<h1>Pulls</h1>", "+ New PR prompt"),1603+        ] {1604+            let html = std::fs::read_to_string(out.join(page)).expect(page);1605+            let head = html.find("<div class=\"page-head\">").expect("page-head");1606+            let h1 = html.find(title).expect("h1");1607+            let b = html.find(btn).expect("button");1608+            let close = html[head..].find("</div>").expect("close") + head;1609+            assert!(1610+                h1 > head && b > head && h1 < close && b < close,1611+                "{page}: h1+btn in page-head"1612+            );1613+        }1614+        // Tasks has no submit button (no page-head needed).1615+        let tasks = std::fs::read_to_string(out.join("tasks/index.html")).expect("tasks");1616+        assert!(!tasks.contains("btn-new"), "tasks: no submit button");1617+    }1618+1619+    /// m6/table-scroll (f0rge #30): 200-char title + long sha stay in a1620+    /// .table-scroll wrapper with a td.title cell (scroll, never squash).1621+    #[test]1622+    fn long_title_and_sha_scroll() {1623+        let long_title = "t".repeat(200);1624+        let long_sha = "a".repeat(64);1625+        let repo = mktemp("long-title");1626+        write_forge(1627+            &repo,1628+            "issues/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/issue.md",1629+            &format!(1630+                "---\nid: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\ntitle: {long_title}\nstate: open\ncreated: 2026-09-10T03:00:00Z\n---\nbody\n"1631+            ),1632+        );1633+        write_forge(1634+            &repo,1635+            "prs/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb/pr.md",1636+            &format!(1637+                "---\nid: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb\ntitle: {long_title}\nstate: open\nbase: main\nhead: {long_sha}\ncreated: 2026-09-10T03:00:00Z\n---\ndesc\n"1638+            ),1639+        );1640+        let out = mktemp("long-title-out");1641+        render_loaded(&load(&repo), &out, "test", "<nav>", None).expect("render");1642+        for page in ["issues/index.html", "pulls/index.html"] {1643+            let html = std::fs::read_to_string(out.join(page)).expect(page);1644+            assert!(1645+                html.contains("<div class=\"table-scroll\"><table>"),1646+                "{page}: wrapper"1647+            );1648+            assert!(html.contains("<td class=\"title\">"), "{page}: title cell");1649+            assert!(html.contains(&long_title), "{page}: long title kept");1650+        }1651+        let pulls = std::fs::read_to_string(out.join("pulls/index.html")).expect("pulls");1652+        assert!(pulls.contains(&long_sha), "long head sha kept");1653+    }15691654 }
src/releases.rs+35 −0
diff --git a/src/releases.rs b/src/releases.rsindex 10d39c7..ad238d9 100644--- a/src/releases.rs+++ b/src/releases.rs@@ -462,6 +462,41 @@ mod tests {462462         assert_eq!(r.display_title(), "v1.0");463463     }464464 465+    /// m6/table-scroll (f0rge #30): 200-char release title renders in a466+    /// .table-scroll wrapper with a td.title cell.467+    #[test]468+    fn long_release_title_scrolls_with_title_cell() {469+        let long_title = "r".repeat(200);470+        let rel = Release {471+            version: "v9.9.9".to_string(),472+            title: Some(long_title.clone()),473+            created: "2026-09-10T03:00:00Z".to_string(),474+            sha: Some("a".repeat(64)),475+            body: "notes\n".to_string(),476+            artifacts: Vec::new(),477+        };478+        let repo = std::env::temp_dir().join(format!(479+            "hu13-rel-long-{}-{}",480+            std::process::id(),481+            std::time::SystemTime::now()482+                .duration_since(std::time::UNIX_EPOCH)483+                .map(|d| d.as_nanos())484+                .unwrap_or(0)485+        ));486+        std::fs::create_dir_all(&repo).unwrap();487+        let out = repo.join("out");488+        std::fs::create_dir_all(&out).unwrap();489+        render_loaded(&[rel], &repo, &out, "test", "<nav>", None).expect("render");490+        let index =491+            std::fs::read_to_string(out.join("releases").join("index.html")).expect("index");492+        assert!(493+            index.contains("<div class=\"table-scroll\"><table>"),494+            "wrapper"495+        );496+        assert!(index.contains("<td class=\"title\">"), "title cell");497+        assert!(index.contains(&long_title), "long title kept");498+    }499+465500     #[test]466501     fn missing_dir_is_empty() {467502         let dir = std::env::temp_dir().join(format!(
static/style.css+12 −1
diff --git a/static/style.css b/static/style.cssindex ad2b9da..5234011 100644--- a/static/style.css+++ b/static/style.css@@ -157,3 +157,15 @@ a.ln:hover,a.ln:focus{text-decoration:underline}157157 .clone-block h3{margin:1rem 0 .25rem}158158 .clone-block p{margin:.5rem 0}159159 .clone-cmd code{user-select:all;overflow-wrap:anywhere;word-break:break-all}160+/* List tables scroll, never squash (m6/table-scroll, f0rge #30): natural161+   width inside .table-scroll; cells nowrap except title cells which wrap162+   with a floor so rows scroll horizontally. Hover tint via var. */163+.table-scroll table{width:max-content;min-width:100%}164+.table-scroll th,.table-scroll td{white-space:nowrap}165+.table-scroll td.title{white-space:normal;overflow-wrap:anywhere;min-width:12rem}166+.table-scroll tbody tr:hover td{background:var(--bg-th)}167+/* Page titles with inline buttons (m6/table-scroll, f0rge #31): h1 +168+   button share one flex row; wrap allowed on 360px but stays adjacent. */169+.page-head{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem 1rem}170+.page-head h1{margin:0}171+.page-head .btn-new{flex:none}160-- 2.55.0

Comments (0)

No comments yet.

Reviews (0)

No reviews yet.

all pulls