Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

bug: Missing newlines before paragraphs and code blocks in streamed Markdown rendering

Aperta Adatta ai principianti
#3,257 2 commenti 0 reazioni 0 assegnatari Vedi su GitHub

@nankingjing ci sta già lavorando.

Dal 11/7/2026.

  • #3272 di @nankingjing — aperta

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
78/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
rust
Ambito
cli

Direzione di ricerca

Inizia in rust/crates/rusty-claude-cli/src/render.rs, in TerminalRenderer::render_markdown e MarkdownStreamState::push, quindi confronta il percorso di streaming con flush. Preserva i ritorni a capo dei paragrafi e dei blocchi di codice per i chunk trasmessi, mantenendo invariato il comportamento di trimming dell'output finale.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Describe the bug

When streaming responses from an OpenAI-compatible API (such as Grok or custom proxy gateways), paragraphs and code blocks sometimes lose their leading and trailing newlines, causing them to merge directly into the previous output line without proper line breaks in the terminal.

For example, a response might render as:
最简单的Python入门例子:Hello World!1. 打开记事本 (instead of having double newlines before 1.)
And:
粘贴进去(全部内容就是它):╭─ python (instead of having double newlines before the code block)

Root Cause Analysis

The bug is located in TerminalRenderer::render_markdown inside rust/crates/rusty-claude-cli/src/render.rs:

    #[must_use]
    pub fn render_markdown(&self, markdown: &str) -> String {
        let normalized = normalize_nested_fences(markdown);
        let mut output = String::new();
        // ...
        for event in Parser::new_ext(&normalized, Options::all()) {
            self.render_event(...);
        }

        output.trim_end().to_string() // <--- HERE
    }

During streaming, MarkdownStreamState::push parses intermediate chunks (split at boundaries using find_stream_safe_boundary).

  • The ready chunk (e.g., up to an empty line representing a paragraph break) correctly ends with trailing newlines (e.g., \n\n generated by paragraph boundaries).
  • However, renderer.markdown_to_ansi(&ready) calls render_markdown(), which unconditionally performs .trim_end() on the rendered string.
  • This strips all trailing newlines (including the structural \n\n representing paragraph/block separators).
  • Since these newlines are stripped from the output chunk and the original ready text has been drained from self.pending, the newlines are completely lost from the terminal print.
  • When the next chunk (e.g. list items or code block starts) is printed, it is outputted directly appended to the previous text without any line breaks.
Suggested Fix

Introduce a stream-friendly rendering method in TerminalRenderer that preserves trailing whitespace/newlines during stream chunk generation, and use it inside MarkdownStreamState::push.

For example:

  1. In TerminalRenderer (inside rust/crates/rusty-claude-cli/src/render.rs):
    #[must_use]
    pub fn render_markdown_stream(&self, markdown: &str) -> String {
        let normalized = normalize_nested_fences(markdown);
        let mut output = String::new();
        let mut state = RenderState::default();
        let mut code_language = String::new();
        let mut code_buffer = String::new();
        let mut in_code_block = false;

        for event in Parser::new_ext(&normalized, Options::all()) {
            self.render_event(
                event,
                &mut state,
                &mut output,
                &mut code_buffer,
                &mut code_language,
                &mut in_code_block,
            );
        }

        output // Do not call trim_end() here
    }

    #[must_use]
    pub fn markdown_to_ansi_stream(&self, markdown: &str) -> String {
        self.render_markdown_stream(markdown)
    }
  1. In MarkdownStreamState::push:
    #[must_use]
    pub fn push(&mut self, renderer: &TerminalRenderer, delta: &str) -> Option<String> {
        self.pending.push_str(delta);
        let split = find_stream_safe_boundary(&self.pending)?;
        let ready = self.pending[..split].to_string();
        self.pending.drain(..split);
        Some(renderer.markdown_to_ansi_stream(&ready)) // Use the stream-specific method
    }

This keeps MarkdownStreamState::flush using the standard markdown_to_ansi (which trims trailing newlines at the very end of the stream), while correctly outputting structural paragraph and code block newlines during streaming.

Lingua principale
Rust
Stelle
195k
Fork
108k
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di ultraworkers/claw-code

Tutte le issue di ultraworkers/claw-code

Issue simili

Altre issue su Rust

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.