.forge/prs/39589eba-2219-4c35-9b17-0527275b24be/patch.diff 23.3 KiB 490 lines Raw
1 From dacca5a820d372be31d9058ab209055921e63e70 Mon Sep 17 00:00:00 20012 From: gl0w <gl0w@world>3 Date: Thu, 10 Sep 2026 08:39:46 +01004 Subject: [PATCH] m6/no-scroll: stacked cards on mobile, tables on desktop5 6 ---7  src/build.rs           |  39 +++++++++++++---8  src/commits.rs         |  34 ++++++++++++--9  src/forge.rs           | 100 +++++++++++++++++++++++++++++++++++++++--10  src/releases.rs        |  46 ++++++++++++++++++-11  static/style.css       |  21 +++++++++12  tests/forge_readers.rs |  10 ++---13  tests/releases_api.rs  |  10 ++---14  tests/responsive.rs    |   8 ++--15  8 files changed, 238 insertions(+), 30 deletions(-)16 17 diff --git a/src/build.rs b/src/build.rs18 index d70dc3f..b0b69ca 10064419 --- a/src/build.rs20 +++ b/src/build.rs21 @@ -584,8 +584,8 @@ pub fn file_table_html(22      );23      if let Some((href, label)) = parent {24          out.push_str(&format!(25 -            "<tr class=\"parent\"><td><a href=\"{}\">{}</a></td>\26 -             <td></td><td></td></tr>\n",27 +            "<tr class=\"parent\"><td data-label=\"Name\"><a href=\"{}\">{}</a></td>\28 +             <td data-label=\"Last commit\"></td><td data-label=\"Age\"></td></tr>\n",29              esc(href),30              esc(label)31          ));32 @@ -600,8 +600,8 @@ pub fn file_table_html(33          let msg_short = truncate_msg(&msg);34          if e.is_dir {35              out.push_str(&format!(36 -                "<tr class=\"dir\"><td><a href=\"/files/{}.html\">{}/</a></td>\37 -                 <td class=\"title\">{}</td><td>{}</td></tr>\n",38 +                "<tr class=\"dir\"><td data-label=\"Name\"><a href=\"/files/{}.html\">{}/</a></td>\39 +                 <td class=\"title\" data-label=\"Last commit\">{}</td><td data-label=\"Age\">{}</td></tr>\n",40                  href_esc(&full),41                  esc(&e.name),42                  esc(&msg_short),43 @@ -609,8 +609,8 @@ pub fn file_table_html(44              ));45          } else {46              out.push_str(&format!(47 -                "<tr class=\"file\"><td><a href=\"/files/{}.html\">{}</a></td>\48 -                 <td class=\"title\">{}</td><td>{}</td></tr>\n",49 +                "<tr class=\"file\"><td data-label=\"Name\"><a href=\"/files/{}.html\">{}</a></td>\50 +                 <td class=\"title\" data-label=\"Last commit\">{}</td><td data-label=\"Age\">{}</td></tr>\n",51                  href_esc(&full),52                  esc(&e.name),53                  esc(&msg_short),54 @@ -1651,7 +1651,32 @@ mod tests {55              "breadcrumb kept on subdir"56          );57          // Message + age columns exist even when history is empty-ish.58 -        assert!(sub.contains("<td class=\"title\">"), "message cell");59 +        assert!(sub.contains("<td class=\"title\""), "message cell");60 +    }61 +62 +    /// m6/no-scroll (f0rge #39): file-table rows carry data-labels for cards.63 +    #[test]64 +    fn file_table_cells_have_data_labels() {65 +        let css = include_str!("../static/style.css");66 +        assert!(css.contains("content:attr(data-label)"), "label rule");67 +        assert!(css.contains("@media (max-width:767px)"), "narrow query");68 +        let (_repo, out) = build_fixture();69 +        for page in ["index.html", "files/sub.html"] {70 +            let html = std::fs::read_to_string(out.join(page)).expect(page);71 +            let tds = html.matches("<td").count();72 +            assert!(tds > 0, "{page} has cells");73 +            assert_eq!(74 +                html.matches("data-label=").count(),75 +                tds,76 +                "{page}: every td labeled"77 +            );78 +            for label in ["Name", "Last commit", "Age"] {79 +                assert!(80 +                    html.contains(&format!("data-label=\"{label}\"")),81 +                    "{page}: label {label}"82 +                );83 +            }84 +        }85      }86  87      #[test]88 diff --git a/src/commits.rs b/src/commits.rs89 index d5fa33e..7075113 10064490 --- a/src/commits.rs91 +++ b/src/commits.rs92 @@ -40,7 +40,7 @@ pub fn render_loaded(93          body.push_str("<div class=\"table-scroll\"><table>\n<thead><tr><th>sha</th><th>subject</th><th>author</th><th>date</th></tr></thead>\n<tbody>\n");94          for c in log {95              body.push_str(&format!(96 -                "<tr><td class=\"sha\"><a href=\"/commit/{}.html\"><code>{}</code></a></td><td class=\"title\">{}</td><td>{}</td><td>{}</td></tr>\n",97 +                "<tr><td class=\"sha\" data-label=\"sha\"><a href=\"/commit/{}.html\"><code>{}</code></a></td><td class=\"title\" data-label=\"subject\">{}</td><td data-label=\"author\">{}</td><td data-label=\"date\">{}</td></tr>\n",98                  esc(&c.sha),99                  esc(short_sha(&c.sha)),100                  esc(&c.subject),101 @@ -342,8 +342,8 @@ mod tests {102              list.contains("<div class=\"table-scroll\"><table>"),103              "wrapper"104          );105 -        assert!(list.contains("<td class=\"title\">"), "title cell");106 -        assert!(list.contains("<td class=\"sha\">"), "sha cell");107 +        assert!(list.contains("<td class=\"title\""), "title cell");108 +        assert!(list.contains("<td class=\"sha\""), "sha cell");109          assert!(list.contains(&long_subject), "long subject kept");110          let sha = shas(&dir)[0].clone();111          assert!(112 @@ -368,4 +368,32 @@ mod tests {113          let list = std::fs::read_to_string(out.join("commits.html")).expect("list");114          assert!(list.contains("No commits yet."));115      }116 +117 +    /// m6/no-scroll (f0rge #39): commit rows carry data-labels for cards.118 +    #[test]119 +    fn commit_cells_have_data_labels() {120 +        let css = include_str!("../static/style.css");121 +        assert!(css.contains("content:attr(data-label)"), "label rule");122 +        assert!(css.contains("@media (max-width:767px)"), "narrow query");123 +        let dir = mktemp("labels");124 +        git(&dir, &["init", "-b", "main"]);125 +        git(&dir, &["config", "user.name", "hu13-test"]);126 +        git(&dir, &["config", "user.email", "[email protected]"]);127 +        git(&dir, &["config", "commit.gpgsign", "false"]);128 +        std::fs::write(dir.join("x.txt"), "x\n").unwrap();129 +        git(&dir, &["add", "-A"]);130 +        git(&dir, &["commit", "-m", "labeled subject"]);131 +        let out = mktemp("out");132 +        render(&dir, &out, "main", 100, "test", "<nav>", None).expect("render");133 +        let list = std::fs::read_to_string(out.join("commits.html")).expect("list");134 +        let tds = list.matches("<td").count();135 +        assert!(tds > 0, "has cells");136 +        assert_eq!(list.matches("data-label=").count(), tds, "every td labeled");137 +        for label in ["sha", "subject", "author", "date"] {138 +            assert!(139 +                list.contains(&format!("data-label=\"{label}\"")),140 +                "label {label}"141 +            );142 +        }143 +    }144  }145 diff --git a/src/forge.rs b/src/forge.rs146 index fe2c0e4..b41ed7a 100644147 --- a/src/forge.rs148 +++ b/src/forge.rs149 @@ -986,7 +986,7 @@ fn render_issues_index(150          body.push_str("<div class=\"table-scroll\"><table>\n<thead><tr><th>#</th><th>title</th><th>state</th><th>comments</th><th>created</th></tr></thead>\n<tbody>\n");151          for issue in &data.issues {152              body.push_str(&format!(153 -                "<tr><td class=\"num\">{n}</td><td class=\"title\"><a href=\"/issues/{id}.html\">{title}</a></td><td>{state}</td><td>{comments}</td><td>{created}</td></tr>\n",154 +                "<tr><td class=\"num\" data-label=\"#\">{n}</td><td class=\"title\" data-label=\"title\"><a href=\"/issues/{id}.html\">{title}</a></td><td data-label=\"state\">{state}</td><td data-label=\"comments\">{comments}</td><td data-label=\"created\">{created}</td></tr>\n",155                  n = issue.number,156                  id = esc(&issue.id),157                  title = esc(&issue.title),158 @@ -1045,7 +1045,7 @@ fn render_pulls_index(159              let lanes = format!("{}→{}", esc(&pr.head), esc(&pr.base));160              let draft = if pr.draft { " draft" } else { "" };161              body.push_str(&format!(162 -                "<tr><td class=\"num\">{n}</td><td class=\"title\"><a href=\"/pulls/{id}.html\">{title}</a>{draft}</td><td>{state}</td><td><code>{lanes}</code></td><td>{created}</td></tr>\n",163 +                "<tr><td class=\"num\" data-label=\"#\">{n}</td><td class=\"title\" data-label=\"title\"><a href=\"/pulls/{id}.html\">{title}</a>{draft}</td><td data-label=\"state\">{state}</td><td data-label=\"head→base\"><code>{lanes}</code></td><td data-label=\"created\">{created}</td></tr>\n",164                  n = pr.number,165                  id = esc(&pr.id),166                  title = esc(&pr.title),167 @@ -1143,7 +1143,7 @@ fn render_ci_index(ci: &CiInfo, site_name: &str, nav: &str, description: Option<168                  body.push_str("<div class=\"table-scroll\"><table>\n<thead><tr><th>step</th><th>exit</th><th>duration_ms</th></tr></thead>\n<tbody>\n");169                  for s in &run.steps {170                      body.push_str(&format!(171 -                        "<tr><td><code>{}</code></td><td>{}</td><td>{}</td></tr>\n",172 +                        "<tr><td data-label=\"step\"><code>{}</code></td><td data-label=\"exit\">{}</td><td data-label=\"duration_ms\">{}</td></tr>\n",173                          esc(&s.name),174                          s.exit,175                          s.duration_ms,176 @@ -1690,7 +1690,7 @@ mod tests {177                  html.contains("<div class=\"table-scroll\"><table>"),178                  "{page}: wrapper"179              );180 -            assert!(html.contains("<td class=\"title\">"), "{page}: title cell");181 +            assert!(html.contains("<td class=\"title\""), "{page}: title cell");182              assert!(html.contains(&long_title), "{page}: long title kept");183          }184          let pulls = std::fs::read_to_string(out.join("pulls/index.html")).expect("pulls");185 @@ -1750,4 +1750,96 @@ mod tests {186          assert!(pulls.contains("1 closed"), "pulls closed total");187          assert!(pulls.contains(" draft"), "draft stays marked");188      }189 +190 +    /// m6/no-scroll (f0rge #39): stacked-card reflow on narrow, desktop untouched.191 +    /// Research: from knowledge of Gitea mobile (issue/PR lists collapse to192 +    /// title-led cards; file table stacks name + meta) — no live fetch here.193 +    #[test]194 +    fn stacked_cards_no_scroll() {195 +        let css = include_str!("../static/style.css");196 +        assert!(css.contains("@media (max-width:767px)"), "narrow query");197 +        assert!(css.contains("overflow-x:visible"), "no scroll on narrow");198 +        assert!(199 +            css.contains("content:attr(data-label)"),200 +            "label via ::before"201 +        );202 +        assert!(css.contains(".table-scroll td::before"), "label rule");203 +        assert!(204 +            css.contains(".table-scroll td.title::before"),205 +            "title leads card"206 +        );207 +        assert!(208 +            css.contains("display:block") && css.contains("display:flex"),209 +            "card layout"210 +        );211 +        assert!(css.contains("width:max-content"), "desktop width kept");212 +        assert!(213 +            css.contains(".table-scroll th,.table-scroll td") && css.contains("white-space:nowrap"),214 +            "desktop nowrap kept"215 +        );216 +        let repo = mktemp("cards");217 +        write_forge(218 +            &repo,219 +            "issues/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/issue.md",220 +            "---\nid: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\ntitle: Card issue\nstate: open\ncreated: 2026-09-10T03:00:00Z\n---\nbody\n",221 +        );222 +        write_forge(223 +            &repo,224 +            "prs/bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb/pr.md",225 +            "---\nid: bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb\ntitle: Card pr\nstate: open\nbase: main\nhead: feat\ncreated: 2026-09-10T03:00:00Z\n---\ndesc\n",226 +        );227 +        write_forge(228 +            &repo,229 +            "tasks/cccccccc-cccc-cccc-cccc-cccccccccccc.md",230 +            "---\nid: cccccccc-cccc-cccc-cccc-cccccccccccc\ntitle: Card task\nstatus: todo\ncreated: 2026-09-10T03:00:00Z\n---\nwork\n",231 +        );232 +        write_forge(&repo, "ci/ci.env", "runner: local\n");233 +        std::fs::create_dir_all(repo.join(".forge").join("ci/steps")).unwrap();234 +        std::fs::write(235 +            repo.join(".forge").join("ci/steps/00_test.sh"),236 +            "#!/bin/sh\n",237 +        )238 +        .unwrap();239 +        write_forge(240 +            &repo,241 +            "ci/runs/2026-09-10T00-00-00Z/summary.md",242 +            "---\nrun: 2026-09-10T00-00-00Z\nverdict: pass\ncreated: 2026-09-10T00:00:00Z\nsteps:\n  - name: 00_test.sh\n    exit: 0\n    duration_ms: 7\n---\nok\n",243 +        );244 +        let out = mktemp("cards-out");245 +        render_loaded(&load(&repo), &out, "test", "<nav>", None).expect("render");246 +        for (page, labels) in [247 +            (248 +                "issues/index.html",249 +                vec!["#", "title", "state", "comments", "created"],250 +            ),251 +            (252 +                "pulls/index.html",253 +                vec!["#", "title", "state", "head→base", "created"],254 +            ),255 +            ("ci/index.html", vec!["step", "exit", "duration_ms"]),256 +        ] {257 +            let html = std::fs::read_to_string(out.join(page)).expect(page);258 +            let tds = html.matches("<td").count();259 +            assert!(tds > 0, "{page} has cells");260 +            assert_eq!(261 +                html.matches("data-label=").count(),262 +                tds,263 +                "{page}: every td labeled"264 +            );265 +            for label in labels {266 +                assert!(267 +                    html.contains(&format!("data-label=\"{label}\"")),268 +                    "{page}: label {label}"269 +                );270 +            }271 +        }272 +        let tasks = std::fs::read_to_string(out.join("tasks/index.html")).expect("tasks");273 +        assert!(!tasks.contains("table-scroll"), "tasks: no scroll wrapper");274 +        assert!(tasks.contains("todo (1)"), "tasks group kept");275 +        let issues = std::fs::read_to_string(out.join("issues/index.html")).expect("issues");276 +        assert!(277 +            issues.contains("open") && issues.contains("2026-09-10"),278 +            "issues data kept in DOM"279 +        );280 +    }281  }282 diff --git a/src/releases.rs b/src/releases.rs283 index ad238d9..87493bb 100644284 --- a/src/releases.rs285 +++ b/src/releases.rs286 @@ -391,7 +391,7 @@ pub fn render_loaded(287          body.push_str("<div class=\"table-scroll\"><table>\n<thead><tr><th>version</th><th>title</th><th>created</th><th>artifacts</th></tr></thead>\n<tbody>\n");288          for rel in releases {289              body.push_str(&format!(290 -                "<tr><td><a href=\"/releases/{v}.html\">{ve}</a></td><td class=\"title\">{t}</td><td>{c}</td><td>{n}</td></tr>\n",291 +                "<tr><td data-label=\"version\"><a href=\"/releases/{v}.html\">{ve}</a></td><td class=\"title\" data-label=\"title\">{t}</td><td data-label=\"created\">{c}</td><td data-label=\"artifacts\">{n}</td></tr>\n",292                  v = esc(&rel.version),293                  ve = esc(&rel.version),294                  t = esc(rel.display_title()),295 @@ -493,7 +493,7 @@ mod tests {296              index.contains("<div class=\"table-scroll\"><table>"),297              "wrapper"298          );299 -        assert!(index.contains("<td class=\"title\">"), "title cell");300 +        assert!(index.contains("<td class=\"title\""), "title cell");301          assert!(index.contains(&long_title), "long title kept");302      }303  304 @@ -510,4 +510,46 @@ mod tests {305          std::fs::create_dir_all(&dir).unwrap();306          assert!(load(&dir).is_empty());307      }308 +309 +    /// m6/no-scroll (f0rge #39): release rows carry data-labels for cards.310 +    #[test]311 +    fn release_cells_have_data_labels() {312 +        let css = include_str!("../static/style.css");313 +        assert!(css.contains("content:attr(data-label)"), "label rule");314 +        let rel = Release {315 +            version: "v1.2.3".to_string(),316 +            title: Some("Labeled".to_string()),317 +            created: "2026-09-10T03:00:00Z".to_string(),318 +            sha: None,319 +            body: "notes\n".to_string(),320 +            artifacts: Vec::new(),321 +        };322 +        let repo = std::env::temp_dir().join(format!(323 +            "hu13-rel-labels-{}-{}",324 +            std::process::id(),325 +            std::time::SystemTime::now()326 +                .duration_since(std::time::UNIX_EPOCH)327 +                .map(|d| d.as_nanos())328 +                .unwrap_or(0)329 +        ));330 +        std::fs::create_dir_all(&repo).unwrap();331 +        let out = repo.join("out");332 +        std::fs::create_dir_all(&out).unwrap();333 +        render_loaded(&[rel], &repo, &out, "test", "<nav>", None).expect("render");334 +        let index =335 +            std::fs::read_to_string(out.join("releases").join("index.html")).expect("index");336 +        let tds = index.matches("<td").count();337 +        assert!(tds > 0, "has cells");338 +        assert_eq!(339 +            index.matches("data-label=").count(),340 +            tds,341 +            "every td labeled"342 +        );343 +        for label in ["version", "title", "created", "artifacts"] {344 +            assert!(345 +                index.contains(&format!("data-label=\"{label}\"")),346 +                "label {label}"347 +            );348 +        }349 +    }350  }351 diff --git a/static/style.css b/static/style.css352 index a09ecf2..2efee6d 100644353 --- a/static/style.css354 +++ b/static/style.css355 @@ -183,3 +183,24 @@ details.clonebox{margin:1rem 0;padding:.25rem 1rem 1rem;background:var(--bg-alt)356  details.clonebox>summary{cursor:pointer;min-height:40px;font-weight:700;color:var(--cyan)}357  details.clonebox>summary:focus-visible{outline:2px solid var(--blue);outline-offset:2px}358  .clone-hint,.clonebox summary .muted{font-weight:400}359 +/* Stacked cards on narrow (m6/no-scroll, f0rge #39): Gitea-style reflow.360 +   Desktop (>=768px) keeps .table-scroll horizontal scroll untouched above.361 +   Narrow: no scroll (overflow visible), thead clipped for SR only, each362 +   tr becomes a bordered card, each td a labeled flex row via data-label.363 +   Title cells lead bold without a repeated label; nothing is deleted. */364 +@media (max-width:767px){365 +  .table-scroll{overflow-x:visible}366 +  .table-scroll table,.file-table table{width:100%;min-width:0}367 +  .table-scroll thead{position:absolute;max-width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}368 +  .table-scroll tbody,.table-scroll tr{display:block;width:auto}369 +  .table-scroll tr{border:1px solid var(--border);background:var(--bg-alt);margin:0 0 .75rem;padding:.25rem 0}370 +  .table-scroll th,.table-scroll td,.file-table th:nth-child(3),.file-table td:nth-child(3){white-space:normal}371 +  .table-scroll td{display:flex;gap:.5rem;align-items:baseline;padding:.4rem .75rem;border:none;border-bottom:1px solid var(--border);min-width:0;overflow-wrap:anywhere}372 +  .table-scroll td:last-child{border-bottom:none}373 +  .table-scroll td::before{content:attr(data-label);flex:0 0 6rem;color:var(--muted);font-weight:600;font-size:.8rem;overflow-wrap:anywhere}374 +  .table-scroll td.title::before{display:none}375 +  .table-scroll td.title{font-weight:700;font-size:1.02rem;min-width:0}376 +  .table-scroll td.num,.table-scroll td.sha{width:auto}377 +  .table-scroll td code,.table-scroll td a{overflow-wrap:anywhere;word-break:break-all;min-width:0}378 +  .table-scroll tbody tr:hover td{background:none}379 +}380 diff --git a/tests/forge_readers.rs b/tests/forge_readers.rs381 index d695600..b3aa723 100644382 --- a/tests/forge_readers.rs383 +++ b/tests/forge_readers.rs384 @@ -224,19 +224,19 @@ fn forge_fixture_renders_counts_numbers_and_ci() {385  386      // --- issues index: 2 rendered rows (broken one skipped) ---387      let issues = read("issues/index.html");388 -    let rows = issues.matches("<tr><td class=\"num\">").count();389 +    let rows = issues.matches("<tr><td class=\"num\"").count();390      assert_eq!(rows, 2, "rendered issues match file-count minus broken");391      assert!(!issues.contains("No title here"), "broken issue skipped");392      // 5-number spot check (1): #1 links the older uuid, #2 the newer uuid.393      assert!(394          issues.contains(&format!(395 -            "<td class=\"num\">1</td><td class=\"title\"><a href=\"/issues/{OLDER_UUID}.html\">"396 +            "<td class=\"num\" data-label=\"#\">1</td><td class=\"title\" data-label=\"title\"><a href=\"/issues/{OLDER_UUID}.html\">"397          )),398          "issue #1 -> older uuid"399      );400      assert!(401          issues.contains(&format!(402 -            "<td class=\"num\">2</td><td class=\"title\"><a href=\"/issues/{NEWER_UUID}.html\">"403 +            "<td class=\"num\" data-label=\"#\">2</td><td class=\"title\" data-label=\"title\"><a href=\"/issues/{NEWER_UUID}.html\">"404          )),405          "issue #2 -> newer uuid"406      );407 @@ -263,13 +263,13 @@ fn forge_fixture_renders_counts_numbers_and_ci() {408      // (4): pulls — same shape + head/base/draft/ci + patch <pre>.409      let pulls = read("pulls/index.html");410      assert_eq!(411 -        pulls.matches("<tr><td class=\"num\">").count(),412 +        pulls.matches("<tr><td class=\"num\"").count(),413          1,414          "prs match file-count"415      );416      assert!(417          pulls.contains(&format!(418 -            "<td class=\"num\">1</td><td class=\"title\"><a href=\"/pulls/{PR_UUID}.html\">"419 +            "<td class=\"num\" data-label=\"#\">1</td><td class=\"title\" data-label=\"title\"><a href=\"/pulls/{PR_UUID}.html\">"420          )),421          "pr #1 -> uuid"422      );423 diff --git a/tests/releases_api.rs b/tests/releases_api.rs424 index 9ea9a71..ea32b2c 100644425 --- a/tests/releases_api.rs426 +++ b/tests/releases_api.rs427 @@ -110,7 +110,7 @@ fn releases_artifacts_api_agents_feed() {428  429      // --- releases: index + both detail pages ---430      let index = read("releases/index.html");431 -    assert_eq!(index.matches("<tr><td>").count(), 2, "two release rows");432 +    assert_eq!(index.matches("<tr><td").count(), 2, "two release rows");433      assert!(index.contains("v1.0"), "v1.0 listed");434      assert!(index.contains("v2.0"), "v2.0 listed");435  436 @@ -154,15 +154,15 @@ fn releases_artifacts_api_agents_feed() {437  438      // --- api counts == html counts (same struct pass) ---439      let issues_html = read("issues/index.html")440 -        .matches("<tr><td class=\"num\">")441 +        .matches("<tr><td class=\"num\"")442          .count();443      let pulls_html = read("pulls/index.html")444 -        .matches("<tr><td class=\"num\">")445 +        .matches("<tr><td class=\"num\"")446          .count();447      let commits_html = read("commits.html")448 -        .matches("<tr><td class=\"sha\">")449 +        .matches("<tr><td class=\"sha\"")450          .count();451 -    let releases_html = read("releases/index.html").matches("<tr><td>").count();452 +    let releases_html = read("releases/index.html").matches("<tr><td").count();453      let wiki_html = read("wiki/index.html")454          .matches("<li><a href=\"/wiki/")455          .count();456 diff --git a/tests/responsive.rs b/tests/responsive.rs457 index 37b536e..f0c4b2d 100644458 --- a/tests/responsive.rs459 +++ b/tests/responsive.rs460 @@ -377,23 +377,23 @@ fn forge_list_tables_scroll_and_wrap_long_titles() {461      for page in ["issues/index.html", "pulls/index.html"] {462          let html = read(page);463          assert!(464 -            html.contains("<td class=\"title\">"),465 +            html.contains("<td class=\"title\""),466              "{page} marks title cells"467          );468          assert!(469              html.contains(&long_title),470              "{page} keeps the full long title"471          );472 -        assert!(html.contains("<td class=\"num\">"), "{page} keeps # narrow");473 +        assert!(html.contains("<td class=\"num\""), "{page} keeps # narrow");474      }475      let commits = read("commits.html");476      assert!(477 -        commits.contains("<td class=\"sha\">"),478 +        commits.contains("<td class=\"sha\""),479          "commits marks sha cells"480      );481      let releases = read("releases/index.html");482      assert!(483 -        releases.contains("<td class=\"title\">"),484 +        releases.contains("<td class=\"title\""),485          "releases marks title cells"486      );487      // Labels from the fixture still list on the issues index.488 -- 489 2.55.0490