notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 259f12b6292bf3793ba480207e49427559bb2258
parent 42baa19529e8e36f2fb98cc8709282b1b5b1d790
Author: William Casarin <jb55@jb55.com>
Date:   Sun, 15 Feb 2026 21:11:41 -0800

md-stream: add README

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Diffstat:
Acrates/md-stream/README.md | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 71 insertions(+), 0 deletions(-)

diff --git a/crates/md-stream/README.md b/crates/md-stream/README.md @@ -0,0 +1,71 @@ +# md-stream + +Incremental zero-copy markdown parser for streaming LLM output. + +Designed for chat interfaces where markdown arrives token-by-token and +needs to be rendered progressively. Zero dependencies. + +## Design + +All parsed output uses `Span { start, end }` byte indices into the +parser's internal buffer rather than owned `String`s. This means: + +- **Zero heap allocations** in the parsing hot path +- Spans are `Copy` (16 bytes vs String's 24 + heap) +- Buffer is append-only — pushed content never moves, spans stay valid +- Resolve spans to `&str` via `span.resolve(parser.buffer())` + +## Usage + +```rust +use md_stream::{StreamParser, MdElement, Span}; + +let mut parser = StreamParser::new(); + +// Push tokens as they arrive from the LLM +parser.push("# Hello "); +parser.push("World\n\n"); +parser.push("Some **bold** text\n\n"); + +// Read completed elements +for element in parser.parsed() { + match element { + MdElement::Heading { level, content } => { + println!("H{}: {}", level, content.resolve(parser.buffer())); + } + MdElement::Paragraph(inlines) => { + // render inline elements... + } + _ => {} + } +} + +// Call finalize when the stream ends to flush any partial state +parser.finalize(); +``` + +## Supported elements + +- Headings (`# ` through `###### `) +- Paragraphs with inline formatting: + - **Bold** (`**text**`) + - *Italic* (`*text*`) + - ***Bold italic*** (`***text***`) + - ~~Strikethrough~~ (`~~text~~`) + - `Inline code` (`` `code` ``) + - [Links]() (`[text](url)`) + - Images (`![alt](url)`) +- Fenced code blocks (`` ``` `` and `~~~`) with language tags +- Tables (`| header | header |` with separator row) +- Thematic breaks (`---`, `***`, `___`) + +## Streaming behavior + +The parser handles partial input gracefully: + +- **Partial state**: Access `parser.partial()` to speculatively render + in-progress elements (e.g. a code block still receiving content) +- **Ambiguous prefixes**: Single `` ` `` is deferred until the parser + can confirm it's not the start of `` ``` `` +- **Split boundaries**: Double newlines split across `push()` calls + are detected correctly