Migration to 1.3
The 1.3 release keeps 1.2.x APIs compiling, but the recommended rendering path has changed. Review the deprecated parser switches and behavior changes before upgrading production chat screens. Removal of the deprecated APIs is planned for 2.0.0.
Requirements
gpt_markdown 1.3 requires Flutter 3.32 or newer and Dart 3.7 or newer. There is no new runtime dependency. Existing 1.2.x code should compile, but output, selection, semantics, and golden images can change.
Leave the legacy parser
plusparse is now the default parser. Delete incremental rather than setting it to true. Do not pass components or inlineComponents, even as empty lists: any of those arguments selects the legacy regex parser. That path does not use the incremental segment cache, span-level reveal, block components, or lazy sliver rendering.
1// 1.3 uses plusparse by default.
2// Remove the deprecated routing switches:
3GptMarkdown(
4 reply,
5 // incremental: true, // delete this
6 // components: [...], // use blockComponents instead
7 // inlineComponents: [...], // use inlinePatterns or inlineDirectives
8)
9
10// These deprecated arguments still compile until 2.0.0, but they select
11// the legacy regex parser and disable segment caching and lazy rendering.Migrate custom blocks
Replace BlockMd and the old global component list with MarkdownBlockComponent in blockComponents. Use FencedBlockSyntax for fenced containers or implement MarkdownBlockSyntax for another block grammar. The syntax parser stays pure and the builder receives an immutable node, so the parsed document can be cached.
1// Before: a regex component selected the legacy parser.
2class CalloutMd extends BlockMd {
3
4 String get expString => r':::callout\\n([\\s\\S]*?)\\n:::';
5 // ...
6}
7
8GptMarkdown(text, components: [CalloutMd(), ...MarkdownComponent.globalComponents]);
9
10// After: syntax is pure Dart and rendering is a separate builder.
11GptMarkdown(
12 text,
13 blockComponents: [
14 MarkdownBlockComponent(
15 syntax: const FencedBlockSyntax(
16 type: 'callout',
17 opening: ':::callout',
18 ),
19 builder: (context, node, config) => CalloutBox(body: node.body),
20 ),
21 ],
22)Migrate custom inline syntax
Replace InlineMd with InlinePattern for text matched in the parsed document. Use InlineDirective when the parser must not inspect a delimited region, such as a server-provided JSON payload. Patterns match the whole document, so prefer lookarounds over ^ and $. Keep pattern and component instances stable across rebuilds.
1// Before: InlineMd receives only the matched text.
2class ShoutMd extends InlineMd {
3
4 RegExp get exp => RegExp(r'!![A-Za-z]+!!');
5 // ...
6}
7
8// After: InlinePattern keeps the match and resolved surrounding style.
9GptMarkdown(
10 text,
11 inlinePatterns: [
12 InlinePattern(
13 pattern: RegExp(r'!![A-Za-z]+!!'),
14 builder: (context, match, style) => TextSpan(
15 text: match[0]!.replaceAll('!!', '').toUpperCase(),
16 style: style.copyWith(fontWeight: FontWeight.bold),
17 ),
18 ),
19 ],
20)
21
22// For a region the parser must not inspect, use InlineDirective instead.Update inline builders
Prefer span-returning builders for inline content. inlineLinkBuilder receivesLinkBuildDetails, while inlineSourceTagBuilder receivesSourceTagBuildDetails. The deprecated widget builders remain available for compatibility, but WidgetSpan output cannot wrap across lines, is less selectable, and can disappear when nested inside a link label on iOS.
1// New span-returning builders keep inline content wrappable,
2// selectable, and aligned with the surrounding baseline.
3GptMarkdown(
4 reply,
5 inlineLinkBuilder: (details) => details.defaultSpan(),
6 inlineSourceTagBuilder: (details) => details.defaultSpan(),
7 inlineCodeBuilder: (context, code, style, codeStyle) => CodeTextSpan(
8 text: code,
9 style: style,
10 codeStyle: codeStyle,
11 ),
12)
13
14// linkBuilder, sourceTagBuilder, and highlightBuilder still compile,
15// but return WidgetSpan-based output and are deprecated until 2.0.0.Use SliverGptMarkdown for long documents
Ordinary GptMarkdown remains the compact, eager widget and is the only path with character reveal. For a long answer in its own scrollable viewport, move to SliverGptMarkdown. It eagerly segments the source but delays built-in AST, span, and widget construction until the viewport requests each segment. It composes with other slivers and has no independent scroll controller.
1// Before: the whole long document is built inside one scroll child.
2SingleChildScrollView(
3 child: GptMarkdown(document),
4)
5
6// After: offscreen segments wait for the viewport.
7SelectionArea(
8 child: CustomScrollView(
9 slivers: [
10 SliverGptMarkdown(
11 document,
12 config: const GptMarkdownConfig(
13 style: TextStyle(fontSize: 16),
14 ),
15 ),
16 ],
17 ),
18)
19
20// SliverGptMarkdown shows source updates immediately. Character reveal stays
21// on GptMarkdown. Selection covers mounted content, and a very large table,
22// list, paragraph, or fence remains one segment.Passing deprecated component lists forces a single SliverToBoxAdapter and disables viewport laziness. Keep modern component lists stable, and remember that selection includes only mounted content.
Complete deprecation migration reference
Nothing below is removed in 1.3.0; removal is planned for 2.0.0. This is the complete migration map for the deprecated public surface.
Widget arguments
| Deprecated | Use instead | Migration note |
|---|---|---|
| incremental | Delete it | plusparse is already the default; false selects the legacy parser. |
| components | blockComponents | Move inline entries to inlinePatterns or inlineDirectives. |
| inlineComponents | inlinePatterns / inlineDirectives | Use directives for opaque delimited payloads. |
Extension points and registration
| Deprecated | Use instead | Migration note |
|---|---|---|
| InlineMd | InlinePattern / InlineDirective | Choose InlineDirective when Markdown parsing must be blocked. |
| BlockMd | MarkdownBlockComponent + MarkdownBlockSyntax | FencedBlockSyntax handles :::name containers. |
| MarkdownComponent.globalComponents | blockComponents | Do not spread the legacy registry. |
| MarkdownComponent.inlineComponents | inlinePatterns | Do not spread the legacy registry. |
| MdWidget | GptMarkdown | Direct use loses segment caching, span reveal, and lazy sliver rendering. |
Builders, typedefs, and link widget
| Deprecated | Use instead | Migration note |
|---|---|---|
| linkBuilder / LinkBuilder | inlineLinkBuilder / InlineLinkBuilder | Return an InlineSpan from LinkBuildDetails. |
| sourceTagBuilder / SourceTagBuilder | inlineSourceTagBuilder / InlineSourceTagBuilder | Use SourceTagBuildDetails. |
| highlightBuilder / HighlightBuilder | inlineCodeBuilder / InlineCodeBuilder | Use inlineCodeStyle when only appearance changes. |
| LinkButton | inlineLinkBuilder or LinkTextSpan | The package no longer builds LinkButton. |
| LinkSpanBuilder | No replacement | Only deprecated LinkButton used it. |
Built-in regex components: no direct replacement
These were internals of the deprecated regex pipeline. Remove direct references; the modern parser handles each construct itself.
InlineDirectiveMdInline directives
IndentMdIndented content
HTagHeadings
NewLinesBlank-line separation
HrLineHorizontal rules
CheckBoxMdTask-list items
RadioButtonMdRadio options
BlockQuoteBlock quotes
UnOrderedListUnordered lists
OrderedListOrdered lists
HighlightedTextInline code
BoldMdBold
StrikeMdStrikethrough
ItalicMdItalic
LatexMathMultiLineDisplay math
LatexMathInline math
SourceTagCitation chips
ATagMdLinks
ImageMdImages
TableMdTables
CodeBlockMdFenced code
UnderLineMdUnderline
Not deprecated: MarkdownComponent, MarkdownComponent.generate, MarkdownScope, and AutolinkMd.
Behavior changes to review
Links are text spans
Links now wrap mid-label, remain selectable and copyable, reveal per character, and align to the text baseline. Plain-text snapshots and link goldens change because toPlainText now includes link labels.
RTL and text scaling are corrected
Block alignment follows text direction. Paragraph and widget scaling is resolved once, fixing double-scaled nested lists, quotes, tables, checkboxes, and code blocks.
Malformed syntax stays visible
Malformed links and unclaimed custom matches render as literal source text instead of disappearing. Case-insensitive patterns and top-level alternation are also handled correctly.
Modern blocks can render lazily
GptMarkdown still renders the full document at mount. Use SliverGptMarkdown in a CustomScrollView when viewport-lazy block rendering is required.
Streaming has two independent axes
Character reveal and block entrance animation are separate. blockAnimation can use none, fadeIn, growIn, slideUp, or scaleIn; isStreaming must become false for completed, failed, and historical messages.
Accessibility settles after streaming
While a live reply is rebuilding, semantics intentionally collapse to one node. Structure reopens after a quiet period and reveal completion; selection is stable after the content settles.
Update tests and release checks
Update link goldens and assertions that expect LinkButton or a WidgetSpan. Test both parser paths when maintaining legacy compatibility, but make the modern path the release path. Pump streaming state changes rather than relying only onpumpAndSettle, and assert semantics and selection after the quiet period.
1// Link labels are now LinkTextSpan, not LinkButton widgets.
2expect(find.byType(LinkButton), findsNothing);
3
4// RichText subclasses need a widget predicate.
5final richText = find.byWidgetPredicate((widget) => widget is RichText);
6
7// Test the streaming lifecycle explicitly.
8await tester.pumpWidget(GptMarkdown(reply, isStreaming: true));
9await tester.pump(const Duration(milliseconds: 16));
10await tester.pumpWidget(GptMarkdown(reply, isStreaming: false));
11// Then pump until the reveal settles before asserting selection or semantics.Run package tests
Include docs snippets, example tests, parser tests, streaming tests, and Linux goldens.
Check app lifecycle
Set isStreaming: false on completion, errors, and loaded history messages.
Measure before launch
The published branch benchmarks are macOS profile measurements, not universal FPS or latency guarantees.
Performance evidence is qualified
The branch documentation reports lower median UI and raster work for several cold and streaming fixtures, plus lower first-viewport work with SliverGptMarkdown. Those results compare complete package revisions and were recorded on macOS. Mobile, web, RTL, scroll-through, selection, and very large individual blocks still need representative measurements before they become launch acceptance criteria.
