From 92b745308da96c1b0bde1608af185550dfe3d023 Mon Sep 17 00:00:00 2001 From: gl0w Date: Thu, 10 Sep 2026 06:39:28 +0100 Subject: [PATCH] m6/tables: scroll containers on all list tables (115 tests) --- src/commits.rs | 2 +- src/forge.rs | 4 +- src/releases.rs | 2 +- static/style.css | 3 + tests/forge_readers.rs | 16 ++-- tests/releases_api.rs | 12 ++- tests/responsive.rs | 164 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 191 insertions(+), 12 deletions(-) diff --git a/src/commits.rs b/src/commits.rs index a98c017..d258631 100644 --- a/src/commits.rs +++ b/src/commits.rs @@ -38,7 +38,7 @@ pub fn render_loaded( body.push_str("
\n\n\n"); for c in log { body.push_str(&format!( - "\n", + "\n", esc(&c.sha), esc(short_sha(&c.sha)), esc(&c.subject), diff --git a/src/forge.rs b/src/forge.rs index 868449e..de99cc1 100644 --- a/src/forge.rs +++ b/src/forge.rs @@ -940,7 +940,7 @@ fn render_issues_index(data: &ForgeData, site_name: &str, nav: &str) -> Result
shasubjectauthordate
{}{}{}{}
{}{}{}{}
\n\n\n"); for issue in &data.issues { body.push_str(&format!( - "\n", + "\n", n = issue.number, id = esc(&issue.id), title = esc(&issue.title), @@ -982,7 +982,7 @@ fn render_pulls_index(prs: &[Pr], site_name: &str, nav: &str) -> String { let lanes = format!("{}→{}", esc(&pr.head), esc(&pr.base)); let draft = if pr.draft { " draft" } else { "" }; body.push_str(&format!( - "\n", + "\n", n = pr.number, id = esc(&pr.id), title = esc(&pr.title), diff --git a/src/releases.rs b/src/releases.rs index e05fe4c..d0de7fd 100644 --- a/src/releases.rs +++ b/src/releases.rs @@ -383,7 +383,7 @@ pub fn render_loaded( body.push_str("
#titlestatecommentscreated
{n}{title}{state}{comments}{created}
{n}{title}{state}{comments}{created}
{n}{title}{draft}{state}{lanes}{created}
{n}{title}{draft}{state}{lanes}{created}
\n\n\n"); for rel in releases { body.push_str(&format!( - "\n", + "\n", v = esc(&rel.version), ve = esc(&rel.version), t = esc(rel.display_title()), diff --git a/static/style.css b/static/style.css index 2bac0f6..32f05a8 100644 --- a/static/style.css +++ b/static/style.css @@ -42,6 +42,9 @@ th,td{text-align:left;padding:.5rem .75rem;border:1px solid #292e42;vertical-ali th{background:#1f2335;color:#7dcfff} tr:nth-child(even) td{background:#1a1b26} td code,th code{word-break:break-all} +/* List tables (m6/tables): long titles/shas wrap inside the scroll box; the # column stays narrow. */ +td.title,td.sha{overflow-wrap:anywhere} +td.num{width:1%;white-space:nowrap} /* Single column on mobile; side-by-side only when it fits (>=768px). */ .cols{display:grid;grid-template-columns:minmax(0,1fr);gap:1.5rem;min-width:0} .cols>section,.cols>div{min-width:0} diff --git a/tests/forge_readers.rs b/tests/forge_readers.rs index f512a39..fda2b8b 100644 --- a/tests/forge_readers.rs +++ b/tests/forge_readers.rs @@ -224,19 +224,19 @@ fn forge_fixture_renders_counts_numbers_and_ci() { // --- issues index: 2 rendered rows (broken one skipped) --- let issues = read("issues/index.html"); - let rows = issues.matches("
versiontitlecreatedartifacts
{ve}{t}{c}{n}
{ve}{t}{c}{n}
").count(); + let rows = issues.matches("
").count(); assert_eq!(rows, 2, "rendered issues match file-count minus broken"); assert!(!issues.contains("No title here"), "broken issue skipped"); // 5-number spot check (1): #1 links the older uuid, #2 the newer uuid. assert!( issues.contains(&format!( - "1" + "1" )), "issue #1 -> older uuid" ); assert!( issues.contains(&format!( - "2" + "2" )), "issue #2 -> newer uuid" ); @@ -262,9 +262,15 @@ fn forge_fixture_renders_counts_numbers_and_ci() { // (4): pulls — same shape + head/base/draft/ci + patch
.
     let pulls = read("pulls/index.html");
-    assert_eq!(pulls.matches("
").count(), 1, "prs match file-count"); + assert_eq!( + pulls.matches("
").count(), + 1, + "prs match file-count" + ); assert!( - pulls.contains(&format!("1")), + pulls.contains(&format!( + "1" + )), "pr #1 -> uuid" ); let pr = read(&format!("pulls/{PR_UUID}.html")); diff --git a/tests/releases_api.rs b/tests/releases_api.rs index 57345e3..9ea9a71 100644 --- a/tests/releases_api.rs +++ b/tests/releases_api.rs @@ -153,9 +153,15 @@ fn releases_artifacts_api_agents_feed() { ); // --- api counts == html counts (same struct pass) --- - let issues_html = read("issues/index.html").matches("
").count(); - let pulls_html = read("pulls/index.html").matches("
").count(); - let commits_html = read("commits.html").matches("
").count(); + let issues_html = read("issues/index.html") + .matches("
") + .count(); + let pulls_html = read("pulls/index.html") + .matches("
") + .count(); + let commits_html = read("commits.html") + .matches("
") + .count(); let releases_html = read("releases/index.html").matches("
").count(); let wiki_html = read("wiki/index.html") .matches("
  • (PathBuf, String) { + let repo = fixture_repo(); + let long_title = "L".repeat(200); + forge_write( + &repo, + "issues/aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa/issue.md", + &format!( + "---\nid: aaaaaaaa-aaaa-4aaa-aaaa-aaaaaaaaaaaa\ntitle: {long_title}\nstate: open\nlabels:\n- kind:scroll-test\nassignees: []\ncreated: 2026-09-10T03:00:00Z\n---\nLong-title body.\n" + ), + ); + forge_write( + &repo, + "prs/bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbbbbbb/pr.md", + &format!( + "---\nid: bbbbbbbb-bbbb-4bbb-bbbb-bbbbbbbbbbbb\ntitle: {long_title}\nstate: open\nbase: main\nhead: long-title-branch\ncreated: 2026-09-10T03:30:00Z\n---\nLong-title PR body.\n" + ), + ); + forge_write( + &repo, + "tasks/cccccccc-cccc-4ccc-cccc-cccccccccccc.md", + "---\nid: cccccccc-cccc-4ccc-cccc-cccccccccccc\ntitle: Scroll task\nstatus: todo\ncreated: 2026-09-10T03:00:00Z\n---\nTask body.\n", + ); + forge_write(&repo, "labels.md", "- name: kind:scroll-test\n"); + forge_write(&repo, "ci/ci.env", "runner: local\n"); + forge_write(&repo, "ci/steps/00_wrap.sh", "#!/bin/sh\nexit 0\n"); + forge_write( + &repo, + "ci/runs/2026-09-10T00-00-00Z/summary.md", + "---\nrun: 2026-09-10T00-00-00Z\nverdict: pass\ncreated: 2026-09-10T00:00:00Z\nsteps:\n - name: 00_wrap.sh\n exit: 0\n duration_ms: 3\n---\nwrap log\n", + ); + forge_write( + &repo, + "releases/v9.9.md", + "---\nversion: v9.9\ntitle: Scroll release\ncreated: 2026-09-10T02:00:00Z\n---\nRelease notes.\n", + ); + git(&repo, &["add", "-A"]); + git(&repo, &["commit", "-m", "long-title forge fixture"]); + let out = fresh("tables"); + let st = Command::new(bin()) + .arg("build") + .arg(&repo) + .arg("-o") + .arg(&out) + .status() + .expect("spawn hu13 build"); + assert!(st.success(), "long-title fixture build exits 0"); + (out, long_title) +} + +#[test] +fn forge_list_tables_scroll_and_wrap_long_titles() { + let (out, long_title) = build_long_title_fixture(); + let read = |rel: &str| std::fs::read_to_string(out.join(rel)).expect(rel); + // Every table-bearing list page wraps its table in .table-scroll and + // leaves no bare outside the scroll container. The wrapper is + // the 360px story: the table scrolls inside instead of forcing the page. + for page in [ + "issues/index.html", + "pulls/index.html", + "commits.html", + "releases/index.html", + "ci/index.html", + ] { + let html = read(page); + assert!(html.contains("table-scroll"), "{page} wraps its table"); + assert!(html.contains(""); + let stripped = html.replace("
    ", ""); + assert!( + !stripped.contains(" outside .table-scroll" + ); + } + // Tasks list renders grouped
      s, never a table. + let tasks = read("tasks/index.html"); + assert!(!tasks.contains(""); + // 200-char titles render in full (wrapped, not cut) inside title cells: + // the cell class is the no-page-widen hook (overflow-wrap:anywhere), + // and the # column keeps its narrow marker class. + for page in ["issues/index.html", "pulls/index.html"] { + let html = read(page); + assert!( + html.contains("
    "), + "{page} marks title cells" + ); + assert!( + html.contains(&long_title), + "{page} keeps the full long title" + ); + assert!(html.contains(""), "{page} keeps # narrow"); + } + let commits = read("commits.html"); + assert!( + commits.contains(""), + "commits marks sha cells" + ); + let releases = read("releases/index.html"); + assert!( + releases.contains(""), + "releases marks title cells" + ); + // Labels from the fixture still list on the issues index. + assert!( + read("issues/index.html").contains("kind:scroll-test"), + "labels listed" + ); + // CSS side: title/sha cells wrap anywhere, # column stays narrow. + let css = css(); + for needle in [ + "td.title", + "td.sha", + "td.num", + "overflow-wrap:anywhere", + "white-space:nowrap", + ] { + assert!(css.contains(needle), "CSS mentions {needle}"); + } +} + +#[test] +fn zero_bare_tables_in_sources() { + // Audit EVERY template (m6, f0rge issue #17): no `` may open + // outside a `.table-scroll` container. All tables are emitted from + // src/*.rs render fns (markdown never emits tables), so grep the + // sources: each line opening a table must carry the scroll wrapper. + let mut checked = 0; + let mut stack = vec![manifest().join("src")]; + while let Some(d) = stack.pop() { + for e in std::fs::read_dir(&d).expect("read src") { + let e = e.expect("entry"); + let p = e.path(); + if p.is_dir() { + stack.push(p); + continue; + } + if p.extension().is_none_or(|x| x != "rs") { + continue; + } + let text = std::fs::read_to_string(&p).expect("read source"); + for (i, line) in text.lines().enumerate() { + if line.contains("", + p.display(), + i + 1 + ); + } + } + } + } + assert!(checked >= 5, "audit saw all list tables, got {checked}"); +} -- 2.55.0