From e8d1601ad7161d3ad131b9572943ddff7b96c8dc Mon Sep 17 00:00:00 2001 From: gl0w Date: Thu, 10 Sep 2026 09:33:29 +0100 Subject: [PATCH] m6/code-bits: CI badge colors + details gap fix --- src/build.rs | 63 +++++++++++++++++++++++++++++++++++++++++++++--- static/style.css | 15 +++++++++++- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/build.rs b/src/build.rs index 5d176ba..ffd8e86 100644 --- a/src/build.rs +++ b/src/build.rs @@ -669,7 +669,7 @@ pub fn clone_block( }; let mut out = format!( "
\n\ - Clone & download {} @ {}\n", + Clone & download {} @ {}\n
\n", esc(branch), esc(short_sha) ); @@ -696,7 +696,7 @@ pub fn clone_block( esc(url) )); } - out.push_str("
\n"); + out.push_str("\n\n"); return out; } if let Some(url) = clone_url { @@ -727,7 +727,7 @@ pub fn clone_block( served from this server: ask the host for its address.

\n", ); } - out.push_str("\n"); + out.push_str("\n\n"); out } @@ -1996,6 +1996,63 @@ mod tests { assert!(bundle_pos < tar_pos, "bundle before tarball"); } + #[test] + fn code_head_ci_badge_green_red() { + // f0rge #43: code repo-head CI badge is green/red via + // `.badge.pass` / `.badge.fail` (running/cancelled keep tones). + for verdict in ["pass", "fail"] { + let repo = fixture_repo(); + let run_dir = repo.join(".forge").join("ci/runs/2026-09-10T00-00-00Z"); + std::fs::create_dir_all(&run_dir).unwrap(); + std::fs::write( + run_dir.join("summary.md"), + format!( + "---\nrun: 2026-09-10T00-00-00Z\nverdict: {verdict}\ncreated: 2026-09-10T00:00:00Z\n---\nlog\n" + ), + ) + .unwrap(); + git(&repo, &["add", "-A"]); + git(&repo, &["commit", "-m", &format!("ci {verdict}")]); + let out = mktemp(&format!("out-ci-{verdict}")); + run_build(&repo, &out, &opts()).expect("build"); + let index = std::fs::read_to_string(out.join("index.html")).expect("index"); + let head_start = index.find("
").expect("repo-head"); + let head_end = index[head_start..].find("
").expect("repo-head end") + head_start; + let head = &index[head_start..head_end]; + assert!( + head.contains(&format!("badge {verdict}")), + "code head carries badge {verdict}" + ); + } + } + + #[test] + fn clone_summary_followed_by_body_no_gap() { + // f0rge #44: summary immediately followed by content wrapper — + // no empty

, no margin-collapse gap when collapsed. + let (_repo, out) = build_fixture(); + let index = std::fs::read_to_string(out.join("index.html")).expect("index"); + assert!( + index.contains("\n

"), + "summary immediately followed by clone-body wrapper" + ); + let s = index.find("").expect("summary end"); + let after = &index[s..s + 200]; + assert!( + !after.contains("

"), + "no empty paragraph after summary" + ); + assert!( + !after.contains("

"), + "no blank paragraph after summary" + ); + // Wrapper closes before the details closes (structure intact). + assert!( + index.contains("
\n"), + "clone-body closes inside details" + ); + } + #[test] fn is_binary_sniffs_first_8kib() { // f0rge #37: NUL in the first 8KiB means binary for math. diff --git a/static/style.css b/static/style.css index fa1b70d..386ad62 100644 --- a/static/style.css +++ b/static/style.css @@ -189,9 +189,22 @@ a.ln:hover,a.ln:focus{text-decoration:underline} .lang-legend li{display:inline-flex;align-items:center;gap:.35rem} .lang-dot{display:inline-block;width:.75rem;height:.75rem;flex:none} details.clonebox{margin:1rem 0;padding:.25rem 1rem 1rem;background:var(--bg-alt);border:1px solid var(--border)} -details.clonebox>summary{cursor:pointer;min-height:40px;font-weight:700;color:var(--cyan)} +details.clonebox>summary{cursor:pointer;min-height:40px;font-weight:700;color:var(--cyan);margin:0} details.clonebox>summary:focus-visible{outline:2px solid var(--blue);outline-offset:2px} +/* Clone gap (m6/code-bits, f0rge #44): no blank line under summary when + collapsed — collapsed padding stays tight; open gap lives on the + wrapper's first child so margins never collapse through summary. */ +details.clonebox:not([open]){padding-bottom:.25rem} +.clonebox .clone-body{margin:0} +.clonebox .clone-body>:first-child{margin-top:.75rem} .clone-hint,.clonebox summary .muted{font-weight:400} +/* CI verdict badges (m6/code-bits, f0rge #43): GitHub-style green/red on + the code repo-head; running/cancelled keep muted tones. */ +.badge{display:inline-block;padding:.1em .5em;border:1px solid var(--border);font-weight:700;font-size:.85rem} +.badge.pass{color:var(--green);border-color:var(--green)} +.badge.fail{color:var(--red);border-color:var(--red)} +.badge.running{color:var(--yellow);border-color:var(--yellow)} +.badge.cancelled{color:var(--muted);border-color:var(--muted)} /* Stacked cards on narrow (m6/no-scroll, f0rge #39): Gitea-style reflow. Desktop (>=768px) keeps .table-scroll horizontal scroll untouched above. Narrow: no scroll (overflow visible), thead clipped for SR only, each -- 2.55.0