flutter_markdown_stream 0.5.0
flutter_markdown_stream: ^0.5.0 copied to clipboard
Flicker-free streaming Markdown widget for Flutter. Renders partial LLM token streams progressively, handling unclosed syntax gracefully.
Changelog #
0.5.0 #
New streaming feel, chat-UI helpers, performance, and correctness fixes.
Behaviour changes (defaults now animated) #
MarkdownStreamnow smooths and word-fades by default, for a ChatGPT/Claude-style feel out of the box:- Output smoothing is on when no
configis supplied. wordFadeIndefaults totrue.- Opt back to instant text with
MarkdownStream(wordFadeIn: false, config: StreamingPresets.instant).
- Output smoothing is on when no
StreamingPresets.chatGPT/.claudealso enable smoothing.
Streaming feel #
- Per-word fade-in (
wordFadeIn): the words in the paragraph currently being streamed fade opacity 0→1 as they arrive (the calm, per-word effect used by ChatGPT/Claude — opacity only, no blur). Settled blocks render at full fidelity; everything is opaque once the stream completes. Tune withwordFadeWindow;WordFadeTextis exported for standalone use. - Output smoothing (
OutputSmoother): a paced character-reveal buffer that drains bursty token delivery at a steady rate. Configure viaStreamingTextConfig(smoothingEnabled: …, charsPerSecond: …)orStreamingPresets.smooth. Backlog pastsmoothingMaxBacklogCharsaccelerates so a finished stream never lags. - Trailing fade (
fadeInEnabled) remains available as an opt-in bottom-edge gradient, separate from the per-word fade.
Widgets #
AutoScroll/StickToBottomController: opt-in, runtime-toggleable stick-to-bottom scrolling modelled on Claude's mobile chat — follows the bottom only while the user is already there, disengages the moment they scroll up (never yanks back), and exposes a "jump to latest" state. Disabled by default at the package level;MarkdownStreamnever force-scrolls on its own.- Auto-follows content growth automatically via
ScrollMetricsNotification— notriggerneeded, even while aMarkdownStreamstreams inside it (triggerremains as an optional fallback). - Follows by jumping while content grows (an animated follow lags behind
a fast stream); imperative
animateToBottom()stays available for a user-tapped "jump to latest" button.
- Auto-follows content growth automatically via
CodeBlockView(+CodeBlockView.builder()): a polished default fenced code block with a language label and copy-to-clipboard button, theme-aware and horizontally scrollable. Use asMarkdownStream(codeBuilder: CodeBlockView.builder()). OptionalhighlightBuilder(CodeHighlightBuilder) plugs in your own syntax highlighter without adding a dependency; the copy control carries a proper button + tap semantics.
Performance #
- Incremental parsing (
MarkdownStream(incrementalParsing: true),IncrementalMarkdownBody): settled blocks are parsed once and reused instead of re-parsed every frame, keeping per-frame cost proportional to the active block rather than the whole response. Off by default; settled output is identical.
Sanitizer correctness #
- Emphasis balancing is now flanking-aware: spaced asterisks (
a * b), bullet markers (* one), thematic breaks (***), and arithmetic (2 * 3 = 6) are no longer given spurious trailing markers, while genuinely unclosed emphasis (**bold) is still closed. - Strikethrough (
~~) uses the same flanking discipline (x ~~ yis left alone). - LaTeX currency fix: with
latexEnabled,costs $5 todayis no longer mis-paired into inline math; a$opens math only when followed by non-whitespace, non-digit. - GFM table repair: an incomplete trailing table row is hidden from the projection until it completes, instead of rendering as a broken table.
Accessibility & tooling #
- All 8 cursors are wrapped in
ExcludeSemanticsand accept an optionalsemanticLabel(e.g.'Assistant is typing') for a single announced node. - New
onTextChangedcallback onMarkdownStreamreports the current projected text each frame (drive auto-scroll triggers or counters without a controller). - Bumped
flutter_lintsto^5.0.0.
0.4.0 #
- Re-exported transitive types:
flutter_markdown_streamnow re-exportsflutter_markdown_plusand the relevant symbols frommarkdown(BlockSyntax,InlineSyntax,ExtensionSet) from its main library. Consumers that name types likeMarkdownStyleSheet,MarkdownElementBuilder,SyntaxHighlighter, orBlockSyntaxno longer need to addflutter_markdown_plus/markdownto their own pubspec — a single import ofpackage:flutter_markdown_stream/flutter_markdown_stream.dartis enough.
0.3.0 #
-
StreamingTextController: programmatic playback control forMarkdownStream. Callpause(),resume(),skipToEnd(),stop(), orrestart()(the latter requires astreamFactory). Exposes aStreamingStateenum (idle,streaming,paused,completed,error), livecurrentText/chunkCount, anonStateChangedandonCompletedcallback, and aspeedMultiplierthat compresses or stretches the effective rebuild debounce. -
StreamingTextConfigandStreamingPresets: bundle the rebuild debounce and fade settings into a single value. Named presets:chatGPT,claude,instant,typewriter,gentle,fast. Supply a preset directly:MarkdownStream(stream: s, config: StreamingPresets.claude) -
Trailing-fade effect: opt-in via
config.fadeInEnabled. A bottom-edge gradient softens newly-arriving content while streaming and animates away overfadeInDurationononDone. -
LaTeX support: supply a
latexBuildertoMarkdownStreamto render inline ($…$) and block ($$…$$) math expressions via your preferred renderer (e.g.flutter_math_fork). No math dependency is added to the package — bring your own. IncludesLaTeXBlockSyntax,LaTeXInlineSyntax,LaTeXElementBuilder, and a newlatexEnabledflag onSafeMarkdownParser.sanitize()for delimiter balancing during streaming. -
RTL /
textDirection: wrap rendered output in aDirectionalitywhentextDirectionis set, enabling Arabic, Hebrew, and similar right-to-left scripts without external widgets.
All additions are non-breaking and opt-in. No new pub dependencies.
0.2.0 #
- Breaking: Migrated from the discontinued
flutter_markdowntoflutter_markdown_plus(the official successor maintained by Foresight Mobile).sizedImageBuilderparameter renamed toimageBuilder.MarkdownSizedImageBuildertypedef replaced byMarkdownImageBuilder(signature changed from(Uri, MarkdownImageConfig)to(Uri, String?, String?)).
- Shortened package description to comply with pub.dev 60–180 character guideline.
0.1.0 #
Initial release of flutter_markdown_stream (formerly developed under the working
name flutter_markdown_stream).
MarkdownStreamwidget that rendersStream<String>of Markdown chunks progressively, with a one-frame debounce and optional cursor widget.- Rebuilds are scoped tightly via
ValueNotifier+ValueListenableBuilder; nosetStatein the hot path, so incoming tokens do not invalidate the outer widget subtree or restart unrelated animations. MarkdownStreamis generic over the chunk type:MarkdownStream<T>accepts anyStream<T>with achunkToTextextractor.Stream<String>remains the default and requires no extractor.StreamAdaptersutility with three composable converters for common non-string stream shapes:utf8Bytes(UTF-8 safe across byte splits),serverSentEvents(multi-chunk-safe SSE parser honouring[DONE]), andjsonField(nested JSON string extraction).SafeMarkdownParser.sanitizeutility that repairs unclosed bold, italic, strikethrough, inline code, fenced code blocks, inline links, and autolinks mid-stream.- Full
MarkdownBodypass-through:styleSheet,styleSheetTheme,syntaxHighlighter,onTapLink,onTapText,imageDirectory,blockSyntaxes,inlineSyntaxes,extensionSet,sizedImageBuilder,checkboxBuilder,bulletBuilder,builders,paddingBuilders,listItemCrossAxisAlignment,fitContent,shrinkWrap,softLineBreak,selectable— anything you can do withflutter_markdownyou can do withMarkdownStream. codeBuilderfires only for fenced (block) code, never for inline code. Abuilders['code']entry takes precedence overcodeBuilderif both are supplied.- Stream errors cancel the subscription cleanly (
cancelOnError: true) and are finalized through the sameonDonepath. - Family of eight cursor widgets:
BlinkingCursor,BarCursor,FadingCursor,PulsingCursor,TypingDotsCursor,WaveDotsCursor,SpinnerCursor,ShimmerCursor. All default to the ambient text colour, share the same customization shape (color, size,period), and dispose their tickers cleanly. flutter_lints-clean: zero analyzer issues. Targets Flutter>=3.27.0, Dart>=3.6.0. UsesColor.withValues(alpha:)(thewithOpacitysuccessor) andMarkdownBody.sizedImageBuilder(theimageBuildersuccessor) so the package stays green against current upstream APIs.- Full unit-test coverage of the sanitizer across adversarial inputs.
- Widget tests cover stream swap, cursor lifecycle, partial code fences.
- Example app demonstrating the widget with a simulated token stream and a gallery of every cursor.