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(", 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