docs(comments): rewrite comments across all crates to the guidelines

Sweep every first-party crate source (1956 .rs files) to the project comment
guidelines: delete redundant restatements, decorative banners, change
narration, and end-of-line comments; keep and tighten the crucial ones
(invariants, bug rationale, SAFETY blocks, ported-source attribution).

No functional code changed. Every edit is proven comment-only against the
prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a
separate doctest-fence check. Where removing a comment made rustfmt or clippy
want to re-lay-out adjacent code, the minimal triggering comment is restored so
code tokens stay byte-identical.

Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy
--workspace --all-targets (0 warnings).

Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for
these guidelines (flags banners, end-of-line comments, change narration, and
commented-out code).
This commit is contained in:
2026-07-23 16:55:39 -04:00
parent ff0fb56c67
commit a02b555e66
1458 changed files with 10729 additions and 21750 deletions
+2 -1
View File
@@ -215,7 +215,8 @@ mod tests {
called: Default::default(),
outcome: ok_diagram,
};
let src = "12345678"; // exactly 8 bytes
// exactly 8 bytes
let src = "12345678";
let limits = RenderLimits {
max_source_bytes: 8,
};
+17 -40
View File
@@ -1,20 +1,11 @@
//! Render [Mermaid](https://mermaid.js.org/) diagram source to a rasterized PNG,
//! behind a swappable [`MermaidEngine`] trait.
//!
//! This crate is a self-contained, pure-library building block: it turns Mermaid
//! diagram text into PNG bytes with no Node, no headless browser, and no network.
//! It isolates the layout engine and the SVG raster stack behind our own audited
//! boundary so the rest of the CLI can swap engines or fall back to a code block
//! without caring how a diagram is produced.
//!
//! # Pipeline
//!
//! 1. A [`MermaidEngine`] turns Mermaid source into an SVG and rasterizes it.
//! The default engine ([`PureRustEngine`]) uses the vendored, dagre-based
//! `mermaid-to-svg` for layout, then [`rasterize`].
//! 2. [`rasterize`] converts SVG to PNG with `resvg`/`usvg`/`tiny-skia`,
//! configured with **no remote/file resolvers** and a **bundled font** so it
//! is safe over untrusted input and deterministic across machines.
//! Rendering needs no Node, no headless browser, and no network: a
//! [`MermaidEngine`] lays a diagram out as SVG, then [`rasterize`] converts it
//! to PNG with `resvg`/`usvg`/`tiny-skia`, configured with **no remote/file
//! resolvers** and a **bundled font** so it is safe over untrusted input and
//! deterministic across machines.
//!
//! # Untrusted input and crash isolation
//!
@@ -27,8 +18,7 @@
//! pager renders each diagram in a short-lived child process (see
//! [`run_with_timeout`] and the pager's `mermaid_worker`), so a panic or runaway
//! render is contained to the child and the wall-clock timeout is a real,
//! killable process kill. This crate provides both the in-process engine and the
//! subprocess spawn/timeout/reap building blocks that child uses.
//! killable process kill.
//!
//! # Example
//!
@@ -58,10 +48,8 @@ pub use subprocess::{SubprocessError, run_with_timeout};
use std::sync::Arc;
/// Which color scheme a diagram should be rendered for.
///
/// Mapped from the pager's theme by the caller; only the light/dark split is
/// relevant to diagram rendering.
/// Which color scheme a diagram should be rendered for. Callers collapse the
/// pager's theme down to this light/dark split.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum MermaidTheme {
/// Light surfaces with dark text (e.g. `KigiDay`).
@@ -71,18 +59,17 @@ pub enum MermaidTheme {
Dark,
}
/// Default opaque surface colors. Single source of truth, shared by the raster
/// Single source of truth for the surface colors, shared by the raster
/// background ([`MermaidTheme::surface_background`]) and the vendored engine's
/// theme background (`pure::theme_for`, via [`Rgba::to_hex`]).
pub(crate) const LIGHT_SURFACE: Rgba = Rgba::new(0xFA, 0xFA, 0xFA, 0xFF);
pub(crate) const DARK_SURFACE: Rgba = Rgba::new(0x18, 0x18, 0x1B, 0xFF);
impl MermaidTheme {
/// The default opaque surface color a diagram blends into for this theme.
///
/// Used as the raster background when the caller does not supply an explicit
/// [`RenderParams::background`]; chosen to approximate a typical terminal
/// scrollback surface so the PNG sits flush with the grid.
/// The default opaque surface color a diagram blends into for this theme,
/// used when the caller supplies no explicit [`RenderParams::background`].
/// Chosen to approximate a typical terminal scrollback surface so the PNG
/// sits flush with the grid.
pub fn surface_background(self) -> Rgba {
match self {
MermaidTheme::Light => LIGHT_SURFACE,
@@ -118,14 +105,10 @@ impl Rgba {
/// Parameters controlling a single diagram render.
///
/// Mirrors the sizing model: [`target_width_px`](Self::target_width_px)
/// is the primary size driver (already HiDPI-oversampled by the caller),
/// [`max_height_px`](Self::max_height_px) clamps tall diagrams, and
/// [`scale`](Self::scale) is the fallback oversample used only when
/// `target_width_px == 0`. [`min_width_px`](Self::min_width_px) raises the scale
/// so small diagrams still rasterize wide enough for OS viewers. The default
/// config is **target-width-driven** (`target_width_px` non-zero), so the default
/// `scale` is inert.
/// [`target_width_px`](Self::target_width_px) is the primary size driver
/// (already HiDPI-oversampled by the caller) and [`scale`](Self::scale) is the
/// fallback oversample consulted only when it is `0`. The default config is
/// target-width-driven, so the default `scale` is inert.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RenderParams {
/// Color scheme to render for.
@@ -211,7 +194,6 @@ mod tests {
let light = MermaidTheme::Light.surface_background();
let dark = MermaidTheme::Dark.surface_background();
assert_ne!(light, dark, "light and dark must map to different surfaces");
// Light surface is brighter than dark on every channel; both opaque.
assert!(light.r > dark.r && light.g > dark.g && light.b > dark.b);
assert_eq!(light.a, 0xFF);
assert_eq!(dark.a, 0xFF);
@@ -220,16 +202,12 @@ mod tests {
#[test]
fn rgba_to_hex_is_opaque_rrggbb() {
assert_eq!(Rgba::new(0x12, 0xAB, 0xCD, 0xFF).to_hex(), "#12ABCD");
// Alpha is ignored.
assert_eq!(Rgba::new(0, 0, 0, 0).to_hex(), "#000000");
// The shared dark surface const renders to the hex the dark theme uses.
assert_eq!(DARK_SURFACE.to_hex(), "#18181B");
}
#[test]
fn default_params_are_target_width_driven() {
// Exercises the real default path: target_width_px (1024) drives output
// width regardless of `scale`. Engine-agnostic, runs in the default build.
let svg = r##"<svg xmlns="http://www.w3.org/2000/svg" width="100" height="50" viewBox="0 0 100 50"><rect width="10" height="10" fill="#0000ff"/></svg>"##;
let out = rasterize(svg, &RenderParams::default()).expect("render");
assert_eq!(
@@ -245,7 +223,6 @@ mod tests {
assert_send_sync(&engine);
}
/// The default engine renders a real PNG and never panics on valid input.
#[test]
fn default_engine_renders_valid_input() {
let engine = default_engine();
+6 -11
View File
@@ -63,9 +63,9 @@ impl MmdcEngine {
impl MermaidEngine for MmdcEngine {
fn render(&self, source: &str, params: &RenderParams) -> Result<RenderedDiagram, MermaidError> {
// Environment/IO failures below are `Rasterize` (a render-pipeline
// failure), not `Unsupported` (which connotes "this input/engine isn't
// supported"); spawn failure stays `Unsupported` (engine unavailable).
// Environment/IO failures below are `Rasterize` (the render pipeline
// broke), not `Unsupported` — that arm is reserved for "no usable
// engine", which is what a spawn failure means.
let dir = tempfile::Builder::new()
.prefix("xai-mermaid-")
.tempdir()
@@ -73,8 +73,6 @@ impl MermaidEngine for MmdcEngine {
let input = dir.path().join("diagram.mmd");
let output = dir.path().join("diagram.svg");
// Create atomically with 0600 (no umask/chmod TOCTOU window). The parent
// tempdir is already 0700.
write_private(&input, source)
.map_err(|e| MermaidError::Rasterize(format!("could not write source: {e}")))?;
@@ -94,7 +92,7 @@ impl MermaidEngine for MmdcEngine {
// setsid/console detach via the sanctioned helper (never a raw pre_exec).
kigi_tty_utils::detach_std_command(&mut cmd);
// Source goes via the temp file, so no stdin payload.
// No stdin payload: the source travels via the temp file.
run_with_timeout(cmd, None, self.timeout).map_err(map_subprocess_error)?;
let svg = std::fs::read_to_string(&output).map_err(|e| {
@@ -220,7 +218,6 @@ mod tests {
#[cfg(unix)]
#[test]
fn fake_mmdc_zero_exit_without_output_is_layout_error() {
// Exits 0 but writes nothing → the "no readable SVG output" Layout error.
let (_dir, bin) = fake_mmdc("exit 0");
let err = MmdcEngine::new(bin)
.render("flowchart LR; A-->B", &RenderParams::default())
@@ -231,8 +228,6 @@ mod tests {
#[cfg(unix)]
#[test]
fn fake_mmdc_nonzero_exit_maps_to_layout() {
// A non-zero exit from mmdc surfaces as a Layout error, distinct from a
// timeout or a missing binary.
let (_dir, bin) = fake_mmdc("exit 3");
let err = MmdcEngine::new(bin)
.render("flowchart LR; A-->B", &RenderParams::default())
@@ -243,8 +238,8 @@ mod tests {
#[cfg(unix)]
#[test]
fn with_timeout_is_honored() {
// A fake that sleeps far longer than the configured timeout must time out
// (and be reaped) quickly — proving with_timeout feeds run_with_timeout.
// Returning well before the 30s sleep proves `with_timeout` reaches
// `run_with_timeout` rather than being ignored.
let (_dir, bin) = fake_mmdc("sleep 30");
let start = Instant::now();
let err = MmdcEngine::new(bin)
+15 -33
View File
@@ -6,9 +6,6 @@ use mermaid_to_svg::{MermaidTheme as EngineTheme, render_mermaid_to_svg};
use crate::{MermaidEngine, MermaidError, MermaidTheme, RenderParams, RenderedDiagram};
/// The default, offline, pure-Rust engine.
///
/// Uses the vendored dagre-based layout engine to produce an SVG, then
/// rasterizes it with the crate's hardened [`crate::rasterize`] pipeline.
#[derive(Debug, Default, Clone, Copy)]
pub struct PureRustEngine;
@@ -26,18 +23,17 @@ impl MermaidEngine for PureRustEngine {
}
}
/// Mermaid source -> SVG (the layout half). A free function (no engine state) so
/// the SVG can be tested directly and reused by [`MermaidEngine::render`].
/// Mermaid source -> SVG (the layout half). A free function, not a method, so
/// the SVG can be asserted on directly in tests.
///
/// The engine returns an error for unparseable or unsupported diagram types; the
/// caller degrades any error to the code-block fallback (see
/// [`crate::render_checked`]).
/// Errors here (unparseable source, unsupported diagram type) are not fatal: the
/// caller degrades them to the code-block fallback via [`crate::render_checked`].
fn build_svg(source: &str, theme: MermaidTheme) -> Result<String, MermaidError> {
let engine_theme = theme_for(theme);
render_mermaid_to_svg(source, Some(&engine_theme)).map_err(map_engine_error)
}
/// Map the vendored engine's error taxonomy onto ours, preserving the
/// Map the vendored engine's errors onto ours, preserving the
/// parse/layout/unsupported split so observability stays honest.
fn map_engine_error(e: mermaid_to_svg::MermaidError) -> MermaidError {
use mermaid_to_svg::MermaidError as E;
@@ -52,10 +48,10 @@ fn map_engine_error(e: mermaid_to_svg::MermaidError) -> MermaidError {
/// Map [`MermaidTheme`] to a vendored-engine [`EngineTheme`].
///
/// Only the diagram surface is overridden, to the crate's single-source-of-truth
/// surface color ([`crate::LIGHT_SURFACE`] / [`crate::DARK_SURFACE`]) so the
/// painted SVG background blends with the terminal scrollback surface the PNG
/// sits on; the rest of each preset's palette is used as-is.
/// Only the surface is overridden to the crate's single-source-of-truth color
/// ([`crate::LIGHT_SURFACE`] / [`crate::DARK_SURFACE`]) so the painted SVG
/// background blends with the terminal scrollback the PNG sits on. The rest of
/// each preset's palette is used as-is.
fn theme_for(theme: MermaidTheme) -> EngineTheme {
match theme {
MermaidTheme::Light => {
@@ -128,13 +124,10 @@ mod tests {
);
}
/// A cyclic flowchart whose back-edge (`Attempts -->|No| Enter`) routes back
/// up into the cycle — the tricky case for flowchart edge routing. Every one
/// of the eight edges must keep its arrowhead, and no node may be dropped by
/// the cycle.
/// The back-edge (`Attempts -->|No| Enter`) routes back up into the cycle,
/// the tricky case for flowchart edge routing.
#[test]
fn cyclic_login_flow_renders_with_arrowheads() {
// Eight directed edges; each must emit exactly one arrowhead marker.
const EDGE_COUNT: usize = 8;
let source = "flowchart TD\n\
Start([User visits login page]) --> Enter[Enter username & password]\n\
@@ -146,15 +139,13 @@ mod tests {
Attempts -->|No| Enter\n\
Validate -->|Yes| Session[Create session]";
let svg = build_svg(source, MermaidTheme::Light).expect("cyclic flow renders");
// Pin the invariant to the edges: exactly one `marker-end="url(#arrowhead)"`
// per edge, so a dropped/detached back-edge arrowhead fails (a whole-doc
// "contains arrow" substring check would pass even with one missing).
// Count rather than substring-match: a whole-document "contains an
// arrowhead" check would still pass with the back-edge arrowhead gone.
let arrowheads = svg.matches(r#"marker-end="url(#arrowhead)""#).count();
assert_eq!(
arrowheads, EDGE_COUNT,
"every flowchart edge must carry an arrowhead marker",
);
// All node labels survive layout (no node dropped by the cycle).
for label in [
"Enter username",
"Submit credentials",
@@ -169,8 +160,6 @@ mod tests {
#[test]
fn light_and_dark_render_to_different_pixels() {
// Stronger than an SVG-string diff: render both themes at identical
// params and assert the encoded pixels actually differ.
let engine = PureRustEngine::new();
let light = engine
.render(
@@ -203,8 +192,6 @@ mod tests {
#[test]
fn theme_for_overrides_surface_per_theme() {
// The diagram background is the crate's surface single-source-of-truth so
// the PNG blends with the terminal scrollback surface.
assert_eq!(
theme_for(MermaidTheme::Light).background,
crate::LIGHT_SURFACE.to_hex()
@@ -219,10 +206,8 @@ mod tests {
);
}
/// Untrusted input must never panic — `render_checked` would surface a panic
/// as `MermaidError::Panic`, which we assert against. Unparseable input may
/// legitimately return other errors (which degrade to the code-block
/// fallback), but never a panic.
/// Only `MermaidError::Panic` is forbidden: garbage input may legitimately
/// return other errors, which degrade to the code-block fallback.
#[test]
fn garbage_input_never_panics() {
let engine = PureRustEngine::new();
@@ -251,7 +236,6 @@ mod tests {
#[test]
fn engine_error_taxonomy_maps_every_arm() {
use mermaid_to_svg::MermaidError as E;
// Parse family: malformed source, bad direction, bad node shape.
for parse in [
E::ParseError {
line: 1,
@@ -265,7 +249,6 @@ mod tests {
"expected Parse mapping",
);
}
// Layout family: dot generation + SVG render failures.
for layout in [
E::DotGenerationError("x".into()),
E::RenderError("x".into()),
@@ -275,7 +258,6 @@ mod tests {
"expected Layout mapping",
);
}
// Unsupported diagram type is its own category.
assert!(matches!(
map_engine_error(E::UnsupportedDiagramType("x".into())),
MermaidError::Unsupported(_)
+6 -3
View File
@@ -320,7 +320,8 @@ mod tests {
#[test]
fn opaque_background_fills_empty_region() {
let mut p = params(0, 10_000); // 1x => 100x50 so pixel coords are exact
// 1x => 100x50 so pixel coords are exact
let mut p = params(0, 10_000);
p.background = Some(Rgba::new(255, 0, 0, 255));
let out = rasterize(SVG_100X50, &p).expect("rasterize");
let img = image::load_from_memory(&out.png)
@@ -440,7 +441,8 @@ mod tests {
#[test]
fn non_finite_or_non_positive_scale_clamps_to_valid_png() {
for bad_scale in [0.0_f32, -1.0, f32::NAN, f32::INFINITY] {
let mut p = params(0, 10_000); // target_width_px == 0 => scale path
// target_width_px == 0 => scale path
let mut p = params(0, 10_000);
p.scale = bad_scale;
let out = rasterize(SVG_100X50, &p)
.unwrap_or_else(|e| panic!("scale {bad_scale} should clamp to 1.0, got {e:?}"));
@@ -490,7 +492,8 @@ mod tests {
// point at the bundled face the glyphs are silently dropped and node
// labels render blank. Black text on white: assert dark pixels exist.
let svg = r##"<svg xmlns="http://www.w3.org/2000/svg" width="200" height="60" viewBox="0 0 200 60"><text x="10" y="38" font-family="Inter, ui-sans-serif, system-ui, -apple-system, &quot;Segoe UI&quot;, sans-serif" font-size="28" fill="#000000">Hello</text></svg>"##;
let mut p = params(0, 10_000); // scale 1.0 => 200x60
// scale 1.0 => 200x60
let mut p = params(0, 10_000);
p.background = Some(Rgba::new(255, 255, 255, 255));
let out = rasterize(svg, &p).expect("rasterize");
let img = image::load_from_memory(&out.png)
+24 -36
View File
@@ -2,8 +2,7 @@
//! to a wall-clock budget, and reap the whole process group on a breach.
//!
//! Used by both the optional [`crate::MmdcEngine`] (which shells out to
//! `mmdc`/headless-Chromium) and the pager's out-of-process render child (a
//! short-lived re-exec of the pager that renders one diagram in isolation). The
//! `mmdc`/headless-Chromium) and the pager's out-of-process render child. The
//! timeout is a *real* process kill, not a soft signal: a panic under
//! `panic = "abort"` or a runaway render in the child is contained because the
//! parent kills and reaps it.
@@ -57,14 +56,10 @@ pub fn run_with_timeout(
) -> Result<(), SubprocessError> {
let mut child = spawn_with_etxtbsy_retry(&mut cmd).map_err(SubprocessError::Spawn)?;
// Feed stdin from a scoped thread so a child that stops reading can't wedge
// a `write_all` of a large (up to the source-size cap) payload and deadlock
// the wait below. On timeout we kill the child, which EOF/EPIPEs the writer.
let stdin = child.stdin.take();
// A payload with no piped stdin would be silently dropped (the caller forgot
// `cmd.stdin(Stdio::piped())`). Both in-tree callers pipe correctly; flag the
// foot-gun loudly in debug and at least log it in release.
// A payload with no piped stdin is silently dropped, so flag that caller
// mistake loudly in debug and at least log it in release.
if stdin_payload.is_some() && stdin.is_none() {
tracing::warn!(
target: "mermaid",
@@ -75,11 +70,14 @@ pub fn run_with_timeout(
"run_with_timeout: stdin_payload supplied but cmd.stdin is not piped (payload dropped)"
);
}
// `scope` joins the writer before returning, so the writer must always be
// able to finish: a child that stops reading is killed by `wait_and_reap`,
// which EOF/EPIPEs the pending `write_all`.
std::thread::scope(|scope| {
if let (Some(mut sink), Some(payload)) = (stdin, stdin_payload) {
scope.spawn(move || {
use std::io::Write as _;
// Errors are expected if the child exits/dies first; ignore them.
// Expected to fail if the child exits first; nothing to report.
let _ = sink.write_all(payload);
// Dropping `sink` closes the pipe so the child observes EOF.
});
@@ -96,8 +94,7 @@ pub fn run_with_timeout(
/// is close-on-exec but only closes at the child's own `execve`, leaving a
/// fork→execve window during which our `execve` of a freshly-written binary can
/// race. It is transient and clears within milliseconds, so retry a few times
/// with a short backoff. (No-op on the steady-state path; only the failing
/// transient case changes behaviour.)
/// with a short backoff.
fn spawn_with_etxtbsy_retry(cmd: &mut Command) -> std::io::Result<Child> {
const MAX_ATTEMPTS: u32 = 5;
let mut attempt = 0;
@@ -121,14 +118,13 @@ fn spawn_with_etxtbsy_retry(cmd: &mut Command) -> std::io::Result<Child> {
/// that spawned grandchildren can't orphan them.
fn wait_and_reap(child: &mut Child, timeout: Duration) -> Result<(), SubprocessError> {
match child.wait_timeout(timeout) {
// `wait_timeout` already reaped the direct child on these two branches,
// but it was its own detached group leader: SIGKILL the group's pgid so
// any grandchildren (e.g. an opt-in MmdcEngine's headless Chromium) are
// torn down regardless of exit code. The pgid stays valid while a
// grandchild is alive (the case that matters); for the grandchild-less
// render child the leader is already gone, so killpg is a harmless no-op
// (ESRCH). The full `reap()` is unneeded — the direct child is already
// reaped, so `child.kill()`/`wait()` would be redundant.
// On these two branches `wait_timeout` already reaped the direct child,
// so `reap()` would be redundant — but the child was its own detached
// group leader, so still SIGKILL the pgid to tear down grandchildren
// (e.g. an opt-in MmdcEngine's headless Chromium) regardless of exit
// code. The pgid stays valid while a grandchild is alive, the case that
// matters; with no grandchildren the leader is gone and killpg is a
// harmless ESRCH no-op.
Ok(Some(status)) if status.success() => {
reap_process_group(child);
Ok(())
@@ -141,8 +137,8 @@ fn wait_and_reap(child: &mut Child, timeout: Duration) -> Result<(), SubprocessE
reap(child);
Err(SubprocessError::Timeout)
}
// waitpid failed; the child may still be running, so reap it too — the
// same teardown as the timeout branch (don't leak the child tree).
// waitpid failed, so the child may still be running: same teardown as
// the timeout branch rather than leaking the child tree.
Err(e) => {
reap(child);
Err(SubprocessError::Wait(e))
@@ -227,11 +223,9 @@ mod tests {
);
}
/// A large stdin payload (bigger than any OS pipe buffer) must be delivered
/// through `run_with_timeout`'s own scoped writer without deadlocking the
/// wait — the whole reason the writer is a scoped thread. We point `cat` at a
/// file (its stdout) so we can prove every byte was consumed, and assert the
/// call returns `Ok(())` promptly rather than hitting the timeout.
/// A payload larger than any OS pipe buffer, the case the scoped writer
/// exists for. `cat`'s stdout goes to a file so the drained byte count
/// proves the whole payload was delivered and consumed.
#[cfg(unix)]
#[test]
fn large_stdin_payload_is_delivered_without_deadlock() {
@@ -256,8 +250,6 @@ mod tests {
start.elapsed() < Duration::from_secs(5),
"must return after the drain, not after the full timeout",
);
// `cat` copies all of stdin to the file → proves the whole payload was
// both delivered and consumed via the real scoped writer.
let drained = std::fs::metadata(&sink).expect("sink metadata").len();
assert_eq!(
drained,
@@ -266,7 +258,6 @@ mod tests {
);
}
/// `reap` actually terminates the spawned process group.
#[cfg(unix)]
#[test]
fn reap_terminates_the_process() {
@@ -278,7 +269,7 @@ mod tests {
reap(&mut child);
// After SIGKILL + wait, the pid no longer names a live process.
// Signal 0 is an existence probe: ESRCH means the pid is gone.
assert_eq!(unsafe { libc::kill(pid, 0) }, -1);
assert_eq!(
std::io::Error::last_os_error().raw_os_error(),
@@ -294,12 +285,9 @@ mod tests {
assert!(matches!(r, Err(SubprocessError::Spawn(_))), "got {r:?}");
}
/// The dropped-stdin-payload guard fires when a caller passes a payload but
/// forgets `cmd.stdin(Stdio::piped())`: the `debug_assert!` turns that silent
/// foot-gun into a hard failure. Gated on `debug_assertions` because that is
/// exactly when the assert is active (release keeps only the `warn`). `true`
/// exits at once; `detached` sets stdin to null (not piped), so the payload
/// would be dropped and the guard must catch it.
/// `detached` sets stdin to null rather than piped, so the payload would be
/// silently dropped and the `debug_assert!` guard must catch it. Gated on
/// `debug_assertions` because release builds keep only the `warn`.
#[cfg(all(unix, debug_assertions))]
#[test]
#[should_panic(expected = "stdin_payload supplied but cmd.stdin is not piped")]
@@ -76,7 +76,7 @@ fn sequence_diagram_with_activations_renders_to_png() {
/// Regression: a class diagram using quoted cardinalities, stereotypes,
/// generics, and the full relation set must render instead of erroring
/// (previously failed with "Unrecognized classDiagram line" on
/// (earlier failed with "Unrecognized classDiagram line" on
/// `Owner "1" o-- "0..*" Animal`).
#[test]
fn class_diagram_with_cardinalities_renders() {
@@ -149,7 +149,7 @@ fn long_identifier_node_labels_survive_intact_in_svg() {
/// A categorical-x-axis xychart with two `line` series must render to a decodable
/// PNG through the full `[Open Image]` path (source -> SVG -> raster), on both
/// themes. The categorical x-axis (no `-->`) previously failed to open.
/// themes. The categorical x-axis (no `-->`) earlier failed to open.
#[test]
fn categorical_xychart_with_two_series_renders_to_png() {
const SOURCE: &str = "xychart-beta\n \