Skip to content

[fix] Streaming markdown with color directives shows artifact#14468

Merged
lukasmasuch merged 3 commits intodevelopfrom
lukasmasuch/fix-issue-14460
Mar 23, 2026
Merged

[fix] Streaming markdown with color directives shows artifact#14468
lukasmasuch merged 3 commits intodevelopfrom
lukasmasuch/fix-issue-14460

Conversation

@lukasmasuch
Copy link
Copy Markdown
Collaborator

@lukasmasuch lukasmasuch commented Mar 23, 2026

Describe your changes

Fixes streaming markdown completion displaying (streamdown:incomplete-link) when using Streamlit's color directives like :red[text], :small[content], etc.

  • Adds custom remend handler to complete unclosed directive syntax before the link handler runs
  • The handler closes directive brackets (e.g., :red[text:red[text]) so remark-directive can parse them correctly

GitHub Issue Link (if applicable)

Testing Plan

  • Unit Tests (JS and/or Python)
  • E2E Tests

Test files:

  • frontend/lib/src/components/shared/StreamlitMarkdown/StreamlitMarkdown.test.tsx — Tests directive completion during streaming
  • e2e_playwright/st_write_stream_test.py — E2E test for color directives in st.write_stream
Agent metrics
Type Name Count
skill updating-internal-docs 1
subagent fixing-pr 1
subagent general-purpose 4
subagent reviewing-local-changes 1
subagent simplifying-local-changes 1

Add custom remend handler to complete unclosed Streamlit directive
syntax (e.g., :red[text) during streaming. This prevents remend's
link handler from misinterpreting directives as incomplete markdown
links, which was causing "(streamdown:incomplete-link)" artifacts.

Closes #14460

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Copilot AI review requested due to automatic review settings March 23, 2026 10:52
@lukasmasuch lukasmasuch added change:bugfix PR contains bug fix implementation impact:users PR changes affect end users labels Mar 23, 2026
@snyk-io
Copy link
Copy Markdown
Contributor

snyk-io bot commented Mar 23, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Mar 23, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 23, 2026

✅ PR preview is ready!

Name Link
📦 Wheel file https://core-previews.s3-us-west-2.amazonaws.com/pr-14468/streamlit-1.55.0-py3-none-any.whl
📦 @streamlit/component-v2-lib Download from artifacts
🕹️ Preview app pr-14468.streamlit.app (☁️ Deploy here if not accessible)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an artifact ((streamdown:incomplete-link)) that appears during st.write_stream markdown streaming when Streamlit color/size directives (e.g. :red[...], :small[...]) are mid-token, by adding a custom remend handler that completes unterminated directive bracket syntax before link completion runs.

Changes:

  • Add a remend handler that appends missing ] for incomplete directive syntax during streaming markdown parsing.
  • Add frontend unit tests covering incomplete + nested directives under streaming (unterminatedParsing) conditions.
  • Add an E2E regression test and app path to stream directive-containing text via st.write_stream.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
frontend/lib/src/components/shared/StreamlitMarkdown/StreamlitMarkdown.tsx Adds a custom remend handler to close incomplete directive bracket syntax during streaming.
frontend/lib/src/components/shared/StreamlitMarkdown/StreamlitMarkdown.test.tsx Adds unit tests to ensure incomplete/nested directives don’t surface the streamdown artifact while streaming.
e2e_playwright/st_write_stream_test.py Adds an E2E regression check asserting the artifact does not appear for streamed directive content.
e2e_playwright/st_write_stream.py Adds a new stream generator that emits directive syntax via a UI button for the E2E test.

@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Mar 23, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This PR fixes a streaming markdown bug where Streamlit color directives (:red[text], :blue[text], etc.) display the artifact (streamdown:incomplete-link) during streaming. The root cause is that the remend library's built-in link handler (priority 20) misinterprets incomplete directive bracket syntax (e.g., :red[text mid-stream) as an incomplete markdown link.

The fix adds a custom RemendHandler (directiveCompletionHandler) at priority 10 that runs before the link handler and closes any unclosed brackets, allowing remark-directive to parse them correctly.

Changed files:

  • frontend/lib/src/components/shared/StreamlitMarkdown/StreamlitMarkdown.tsx — Core fix: custom remend handler
  • frontend/lib/src/components/shared/StreamlitMarkdown/StreamlitMarkdown.test.tsx — Frontend unit tests
  • e2e_playwright/st_write_stream.py — E2E app script with color directive streaming
  • e2e_playwright/st_write_stream_test.py — E2E regression test

All three reviewers agreed on the core assessment: the fix is well-scoped, follows codebase patterns, has good test coverage, and correctly addresses the reported issue (#14460). The sole disagreement was on whether the bracket-counting algorithm's handling of non-directive brackets constitutes a blocking issue (see Recommendations).

Code Quality

The implementation is clean and follows existing patterns in the codebase:

  • The handler is correctly defined as a module-level constant (per frontend AGENTS.md guidance on static data structures), avoiding re-creation on each render.
  • Uses JSDoc documentation and proper TypeScript typing with the RemendHandler type import.
  • The priority choice (10) is intentional and well-documented — it must run before remend's link handler (priority 20). Priority 10 coincides with remend's built-in htmlTags handler, but they handle orthogonal patterns.

Bracket-counting edge case (2/3 reviewers flagged): One reviewer (gemini-3.1-pro) identified that the bracket-counting algorithm counts all unclosed brackets from the first directive onwards, which means it also closes non-directive brackets (e.g., regular markdown links) that appear after a completed directive. Two reviewers (claude-4.6-opus-high-thinking, gpt-5.3-codex-high) acknowledged this as a low-risk edge case. After independent verification, the behavior in practice is not a regression from the current codebase — before this PR, such cases would show the (streamdown:incomplete-link) artifact, and after this PR they render as plain text with a transient extra ]. The edge case is noted as an improvement opportunity via inline comment rather than a blocking issue.

Test Coverage

All three reviewers agreed test coverage is strong:

  • Frontend unit tests use it.each for parameterized testing of multiple directive variants, cover incomplete directives, complete directives, and nested directives. Each test includes both positive assertions (text renders correctly) and negative assertions (no artifact) — following anti-regression patterns.
  • E2E test uses expect for auto-wait assertions, includes both positive (colored text visible) and negative (artifact absent) checks, and is added to the existing st_write_stream test file rather than creating a new one.
  • Existing test compatibility is preserved — the new button in the E2E app script does not affect existing stMarkdown count assertions.

One reviewer noted the E2E artifact assertion may not reliably catch transient streaming artifacts since it runs after the stream completes. The unit tests are the primary confidence layer.

Backwards Compatibility

No breaking changes. All three reviewers agreed:

  • The remend() call now passes a handlers option, which was previously unused. The remend API accepts this optional parameter.
  • The handler only activates during streaming (unterminatedParsing=true), so static/non-streaming markdown rendering is completely unaffected.
  • No public API changes.

Security & Risk

No security concerns identified by any reviewer:

  • No new dependencies added.
  • No network calls, authentication, or session handling changes.
  • No HTML injection or XSS risk — the handler operates at the raw markdown string level before remark parsing, and only appends ] characters.
  • The change is strictly scoped to streaming markdown pre-processing.

External test recommendation

  • Recommend external_test: No
  • Triggered categories: None
  • Evidence:
    • frontend/lib/src/components/shared/StreamlitMarkdown/StreamlitMarkdown.tsx: Pure frontend rendering fix in markdown pre-processing. No routing, auth, WebSocket, embedding, asset serving, CORS, storage, or security header changes.
    • All other changed files are test-only additions with no infrastructure impact.
  • Suggested external_test focus areas: N/A
  • Confidence: High (all three reviewers agreed unanimously)
  • Assumptions and gaps: None. The change is entirely within the frontend markdown rendering pipeline.

Accessibility

No accessibility impact. All three reviewers agreed. The fix corrects a text rendering artifact during streaming — it does not modify DOM structure, ARIA attributes, keyboard behavior, or focus management.

Recommendations

  1. Consider improving bracket-counting specificity (non-blocking): The current algorithm closes all unclosed brackets from the first directive onwards, including non-directive brackets like regular markdown links. A more precise approach would track directive depth specifically, closing only brackets that are part of directives. This is a valid improvement for a follow-up but does not block this PR since the edge case behavior is not worse than the current (streamdown:incomplete-link) artifact. See inline comment for details.
  2. Consider adding a mixed-content test (non-blocking): Two reviewers suggested adding a unit test that combines a directive with a regular markdown link (e.g., :red[text] and [link) to document the current behavior and guard against future regressions in this edge case.
  3. Minor: remove unnecessary g flag on regex (cosmetic): The g flag on the directivePattern regex (line 938) is unnecessary since exec is only called once.

Verdict

APPROVED: Well-scoped, well-tested bugfix that correctly addresses the reported (streamdown:incomplete-link) artifact during streaming with color directives. All three reviewers agree the fix is sound; the one disagreement (bracket-counting of non-directive brackets) is a valid improvement opportunity but not a blocking regression. The implementation follows codebase patterns, tests are thorough, and there are no backwards compatibility or security concerns.

Reviewer consensus: 3/3 reviews received (claude-4.6-opus-high-thinking: Approved, gemini-3.1-pro: Changes Requested, gpt-5.3-codex-high: Approved). Majority approved. The changes-requested concern was evaluated and determined non-blocking after independent verification.


This is a consolidated AI review by claude-4.6-opus-high-thinking, synthesizing reviews from claude-4.6-opus-high-thinking, gemini-3.1-pro, and gpt-5.3-codex-high. Please verify the feedback and use your judgment.

This review also includes 3 inline comment(s) on specific code lines.

- Fix bracket counting logic to only track directive openings (:<name>[)
  instead of all brackets, preventing unintended closing of markdown links
- Add test for non-directive bracket handling
- Improve E2E test to wait for stream completion before asserting

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@lukasmasuch lukasmasuch added the ai-review If applied to PR or issue will run AI review workflow label Mar 23, 2026
@github-actions github-actions bot removed the ai-review If applied to PR or issue will run AI review workflow label Mar 23, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

This PR fixes a streaming markdown bug (#14460) where Streamlit color directives like :red[text] produce a (streamdown:incomplete-link) artifact during st.write_stream. The root cause is that remend's built-in link handler misinterprets the [ in directive syntax (e.g., :red[text) as an incomplete markdown link.

The fix adds a custom RemendHandler (directiveCompletionHandler) with priority 10 that runs before remend's link handler (priority 20). It detects unclosed Streamlit directive brackets and appends the necessary closing ] characters, preventing the link handler from ever seeing them as incomplete links.

All three reviewers (claude-4.6-opus-high-thinking, gemini-3.1-pro, gpt-5.3-codex-high) unanimously approved this PR.

Code Quality

The implementation is clean and well-structured. The handler is correctly defined at module level as a static constant, following the "Static Data Structures" guideline from frontend/AGENTS.md. The regex /:[a-z][a-z0-9-]*\[/g correctly matches Streamlit directive patterns while avoiding false positives on non-directive colons (URLs, time formats, emoji shortcodes). JSDoc clearly explains the purpose, priority rationale, and links to the issue.

Reviewer consensus: All three reviewers agreed the code is well-organized and maintainable. One reviewer (gemini-3.1-pro) identified an edge case where non-directive [ characters inside an open directive (e.g., :red[text [link) are ignored by the depth tracker, which could cause the artifact to reappear in that specific scenario. This edge case was verified against the code and is valid — see the inline comment on StreamlitMarkdown.tsx line 953 for details and a suggested fix. The other two reviewers did not flag this, but the analysis holds upon inspection.

Test Coverage

Reviewer consensus: All three reviewers agreed that test coverage is strong.

  • Frontend unit tests (StreamlitMarkdown.test.tsx): Parameterized tests via it.each cover incomplete directives (:red[, :blue[, :red-background[, :small[), complete directives, nested directives, and non-directive markdown link brackets. Tests include both positive (text visible) and negative (streamdown:incomplete-link absent) assertions.

  • E2E test (st_write_stream_test.py): A regression test streams text with color directives and verifies colored text appears without the artifact. One reviewer (gemini-3.1-pro) noted that the E2E anti-regression assertion executes after the stream completes, so it would likely pass even without the fix. The unit tests are the primary regression guard here; the E2E test serves as a valuable smoke test for the full streaming path.

  • Optional enhancement (gpt-5.3-codex-high): A unit test for escaped directive-like text (\:red[text) could further harden coverage.

Backwards Compatibility

No breaking changes. The remend() call previously had no options object and now receives { handlers: [directiveCompletionHandler] }. The remend API is additive — the custom handler augments rather than replaces built-in behavior. The handler only activates during streaming (unterminatedParsing=true), so non-streaming markdown rendering is unaffected.

All three reviewers agreed there are no backward compatibility concerns.

Security & Risk

No security concerns. The changes are purely in frontend markdown preprocessing logic. No new network requests, external asset loading, dependencies, eval/Function() usage, or changes to WebSocket, authentication, session management, or server endpoints. The handler only appends ] characters.

Regression risk is low. The handler runs only during streaming and only when directive patterns are detected. The early-return for no-match inputs ensures zero overhead for non-directive content.

All three reviewers were unanimous on this assessment.

External test recommendation

  • Recommend external_test: No
  • Triggered categories: None
  • Evidence: Pure frontend rendering change in markdown preprocessing. No routing, auth, WebSocket, session, cross-origin, embedding, storage, or security header changes.
  • Suggested external_test focus areas: None
  • Confidence: High
  • Assumptions and gaps: None

All three reviewers unanimously agreed no external tests are needed.

Accessibility

No accessibility impact. The change only affects text preprocessing before markdown rendering. The resulting HTML output (colored spans via remark-directive) remains identical to what was already rendered for complete directives. No new interactive elements, ARIA attributes, or focus behavior are introduced.

All three reviewers agreed on this assessment.

Recommendations

  1. Address the nested bracket edge case (medium priority): When a non-directive [ appears inside an open directive (depth > 0), it should increment depth so that the correct number of closing ] characters are appended. Without this, inputs like :red[text [link during streaming will still produce the artifact. See inline comment for the suggested fix.

  2. Extract remend options to a module-level constant (low priority): Move { handlers: [directiveCompletionHandler] } to a module-level REMEND_OPTIONS constant for consistency with other static data structures like BASE_REMARK_PLUGINS.

  3. Add escaped directive test (optional): A unit test for \:red[text would lock in intended behavior for literal, non-directive content during streaming.

Verdict

APPROVED: Well-implemented, targeted fix for the streaming directive artifact bug with comprehensive test coverage and no backward compatibility or security concerns. The nested bracket edge case (recommendation #1) is a valid improvement worth addressing, but it covers a niche scenario (markdown links inside color directives during streaming) and does not block this merge.


This is a consolidated AI review by claude-4.6-opus-high-thinking, synthesizing reviews from claude-4.6-opus-high-thinking, gemini-3.1-pro, and gpt-5.3-codex-high.

This review also includes 3 inline comment(s) on specific code lines.

- Remove unnecessary `g` flag from regex (nitpick)
- Handle nested brackets inside directives to fix cases like `:red[text [link`
  where non-directive brackets need proper balancing (issue)
- Extract REMEND_OPTIONS to module-level constant for consistency (suggestion)
- Add test for nested brackets inside directives

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@lukasmasuch lukasmasuch merged commit 8cd9437 into develop Mar 23, 2026
43 checks passed
@lukasmasuch lukasmasuch deleted the lukasmasuch/fix-issue-14460 branch March 23, 2026 19:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

change:bugfix PR contains bug fix implementation impact:users PR changes affect end users

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Nightly] Streaming Markdown completion displays (streamdown:incomplete-link)

3 participants