bubbleteandmd

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 11, 2026 License: MIT Imports: 5 Imported by: 0

README

bubbleteandmd

Streaming Markdown for terminal programs built on bubbletea. A Go port of the ideas in llx-markdown (MIT; see NOTICE), for the terminal. First user: dun.

Status: layer 1 (streaming core: NewStream, Frame, Final) in v0.1.0. Layers 2-3 in progress.

Problem

An LLM reply arrives token by token. Rendering the whole document again per frame costs more as the reply grows, and half-written syntax (**bold, an open code fence, a link without its )) shows raw until it closes.

Design (three layers, each usable alone)

  1. Streaming core. A line scanner splits the source into blocks only where a full parse would split. Blocks before the last two are final: rendered once, cached by (source, width, style). The tail is healed while streaming only — the final text renders as written — and rendered again. Lists, blockquotes, footnotes and link definitions force a wider re-render where needed. Block rendering uses glamour.
  2. Directive blocks. llx-markdown's syntax, unchanged, so the same model output renders in both: > [!NOTE]-style callouts, > [!STEPS], > [!TABS] + #### panels, > [!STATS], > [!PROS]/> [!CONS], > [!DETAILS], > [!SOURCES], ```tree fences, title="…" on code fences, GFM task lists. Drawn with lipgloss. Mermaid and math show as code.
  3. Interactive blocks. A bubbletea component that holds fold and tab state per block id across re-renders: fold [!DETAILS] and trees, switch tabs. While a block streams, tabs show stacked.

Plus a short format prompt that teaches a model the directive forms (like llx-markdown's answerFormatPrompt).

Rules

  • Healed text never reaches the stored source.
  • Joined blocks must equal glamour's whole-document spacing: the final frame must not move when the stream ends (checked by a test that compares streamed and whole renders at every chunk boundary).

Documentation

Overview

Package bubbleteandmd renders streaming LLM Markdown in a terminal, for bubbletea programs. Finished blocks are rendered once and cached; only the open tail is rendered again as tokens arrive, and that tail is healed (an open fence, `**`, a half-typed link) so raw syntax does not show mid-stream. It reads llx-markdown's directive syntax (`> [!STEPS]`, `> [!TABS]`, ```tree fences …). See README.md.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Cache

type Cache struct {
	// contains filtered or unexported fields
}

Cache holds rendered stable blocks, keyed by (Stream key, block source). One Cache can serve many Streams; it is safe for concurrent use.

func NewCache

func NewCache(max int) *Cache

NewCache returns a cache of at most max rendered blocks.

type Renderer

type Renderer interface {
	Render(string) (string, error)
}

Renderer renders one whole Markdown document. *glamour.TermRenderer satisfies it. A Renderer is used by one goroutine at a time.

type Stream

type Stream struct {
	// contains filtered or unexported fields
}

Stream renders one document that grows at its end. Blocks before the last two are rendered once and cached; the last two (the tail) are rendered on every call. Frame heals the tail; Final does not. The result equals r.Render of the whole (healed, for Frame) text.

A Stream is not safe for concurrent use.

func NewStream

func NewStream(r Renderer, key string, c *Cache) *Stream

NewStream returns a Stream that renders with r and caches stable blocks in c under key. key must name everything that changes r's output (dun: width and style). c may be shared by Streams; nil means no cache.

func (*Stream) Final

func (s *Stream) Final(src string) (string, error)

Final renders src as written, with no healing.

func (*Stream) Frame

func (s *Stream) Frame(src string) (string, error)

Frame renders src while it is still streaming: the tail is healed (heal.Close), so half-written syntax does not show raw.

Directories

Path Synopsis
internal
blocks
Package blocks cuts a Markdown document into top-level blocks that can be rendered, and cached, one by one.
Package blocks cuts a Markdown document into top-level blocks that can be rendered, and cached, one by one.
corpus
Package corpus holds the streaming test corpora, the seeded reply generator and the chunking helpers ported from llx-markdown, for invariant tests.
Package corpus holds the streaming test corpora, the seeded reply generator and the chunking helpers ported from llx-markdown, for invariant tests.
heal
Package heal stabilizes the tail of a markdown text that is still streaming.
Package heal stabilizes the tail of a markdown text that is still streaming.
join
Package join puts separately rendered glamour blocks together so that the result equals glamour's render of the whole document.
Package join puts separately rendered glamour blocks together so that the result equals glamour's render of the whole document.
lru
Package lru is a small bounded least-recently-used cache.
Package lru is a small bounded least-recently-used cache.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL