From 9754c4ee6698ae250236608eb3962b7ca7fd8c19 Mon Sep 17 00:00:00 2001 From: "Dhanji R. Prasanna" Date: Sun, 11 Jan 2026 19:34:46 +0530 Subject: [PATCH] Fix code fence closing without trailing newline When a code block ended without a trailing newline after the closing \`\`\`, two bugs occurred in flush_incomplete(): 1. The closing \`\`\` was included as part of the code block content (displayed with syntax highlighting) 2. The same \`\`\` was then emitted again as literal text because current_line was not cleared after being pushed to block_buffer The fix: - Check if current_line is the closing fence before adding to block_buffer - Always clear current_line after processing in the CodeBlock case Added two tests: - test_code_fence_after_blank_line: code fence with trailing newline - test_code_fence_no_trailing_newline: code fence without trailing newline --- crates/g3-cli/src/streaming_markdown.rs | 11 ++++- .../g3-cli/tests/streaming_markdown_test.rs | 46 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/crates/g3-cli/src/streaming_markdown.rs b/crates/g3-cli/src/streaming_markdown.rs index d46541b..cd7e3a0 100644 --- a/crates/g3-cli/src/streaming_markdown.rs +++ b/crates/g3-cli/src/streaming_markdown.rs @@ -697,7 +697,16 @@ impl StreamingMarkdownFormatter { // Unclosed code block - emit as-is if !self.block_buffer.is_empty() || !self.current_line.is_empty() { if !self.current_line.is_empty() { - self.block_buffer.push(self.current_line.clone()); + // Check if current_line is the closing fence (``` without trailing newline) + let trimmed = self.current_line.trim_start(); + let leading_spaces = self.current_line.len() - trimmed.len(); + if trimmed == "```" && leading_spaces <= 3 { + // This is the closing fence - don't include it in content + // Just clear it and emit the block + } else { + self.block_buffer.push(self.current_line.clone()); + } + self.current_line.clear(); } self.emit_code_block(); } diff --git a/crates/g3-cli/tests/streaming_markdown_test.rs b/crates/g3-cli/tests/streaming_markdown_test.rs index d7ba295..c3dbb3d 100644 --- a/crates/g3-cli/tests/streaming_markdown_test.rs +++ b/crates/g3-cli/tests/streaming_markdown_test.rs @@ -1879,3 +1879,49 @@ fn strip_ansi(s: &str) -> String { let re = regex::Regex::new(r"\x1b\[[0-9;]*m").unwrap(); re.replace_all(s, "").to_string() } + +#[test] +fn test_code_fence_after_blank_line() { + let skin = MadSkin::default(); + let mut fmt = StreamingMarkdownFormatter::new(skin); + + // Simulate the exact input from the bug - text followed by blank line followed by code fence + let input = "Done! The agent mode header now looks like:\n\n```\n>> agent mode | fowler\n```\n"; + + // Process character by character like streaming would + let mut output = String::new(); + for ch in input.chars() { + let chunk = fmt.process(&ch.to_string()); + output.push_str(&chunk); + } + output.push_str(&fmt.finish()); + + println!("Input: {:?}", input); + println!("Output: {:?}", output); + + // Check if backticks appear literally - they shouldn't + assert!(!output.contains("```"), "Literal backticks should not appear in output. Got: {}", output); +} + +#[test] +fn test_code_fence_no_trailing_newline() { + // Test code fence without trailing newline after closing ``` + let skin = MadSkin::default(); + let mut fmt = StreamingMarkdownFormatter::new(skin); + + // Note: no newline after closing ``` + let input = "Done!\n\n```\n>> agent mode | fowler\n-> ~/src/g3\n ✓ README | ✓ AGENTS.md | ✓ Memory\n```"; + + let mut output = String::new(); + for ch in input.chars() { + let chunk = fmt.process(&ch.to_string()); + output.push_str(&chunk); + } + output.push_str(&fmt.finish()); + + println!("Input: {:?}", input); + println!("Output: {:?}", output); + + // The closing ``` should NOT appear literally + assert!(!output.contains("```"), "Literal backticks in output: {}", output); +}