fix(graphviz): render comments with template braces (#509)

## Summary

Fixes #508.

This changes Graphviz render preparation so Fabro DOT is normalized
before graph-level style defaults are injected. That keeps leading
comments such as `// ... {{ goal }} ...` from being mistaken for the
graph body opening brace, while continuing to reuse the existing
parser/normalizer path for Fabro-specific syntax like dotted attribute
keys.

## Verification

- `cargo nextest run -p fabro-graphviz`
- `cargo +nightly-2026-04-14 fmt --check --all`

Co-authored-by: Chad Woolley <thewoolleyman@gmail.com>
This commit is contained in:
Bryan Helmkamp 2026-06-15 14:15:01 -05:00 • committed by GitHub
parent a769336c39
commit dbf4829b47
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -21,7 +21,7 @@ const DARK_MODE_STYLE: &str = r##"
}
</style>"##;
/// DOT graph-level defaults injected after the first `{`.
/// DOT graph-level defaults injected after the graph opening `{`.
const DOT_STYLE_DEFAULTS: &str = r##"
bgcolor="transparent"
node [color="#357f9e", fontname="Helvetica", fontsize=12, fontcolor="#1a1a1a"]
@ -48,6 +48,11 @@ pub fn apply_direction<'a>(source: &'a str, direction: &str) -> std::borrow::Cow
/// Inject DOT graph-level style defaults.
#[must_use]
pub fn inject_dot_style_defaults(source: &str) -> String {
let source = normalize_dot_for_graphviz(source);
inject_dot_style_defaults_raw(&source)
}
fn inject_dot_style_defaults_raw(source: &str) -> String {
let Some(pos) = source.find('{') else {
return source.to_string();
};
@ -259,10 +264,8 @@ impl<'a> RenderableDot<'a> {
/// normalizing Fabro-specific syntax such as dotted attribute keys.
#[must_use]
pub fn from_fabro_source(source: &'a str) -> Self {
let styled_source = inject_dot_style_defaults(source);
let render_source = normalize_dot_for_graphviz(&styled_source).into_owned();
Self {
source: Cow::Owned(render_source),
source: Cow::Owned(inject_dot_style_defaults(source)),
}
}
@ -328,6 +331,22 @@ mod tests {
assert!(String::from_utf8(svg).unwrap().contains("<svg"));
}
#[test]
fn render_dot_accepts_leading_comment_with_template_braces() {
let svg = render_dot(
r#"// The goal is rendered into prompts as {{ goal }}.
digraph G {
start [shape=Mdiamond]
exit [shape=Msquare]
a [label="A"]
start -> a -> exit
}"#,
)
.unwrap();
assert!(String::from_utf8(svg).unwrap().contains("<svg"));
}
#[test]
fn render_dot_complex_graph() {
let source = r#"digraph {