Skip to content

Enable speculative rewrite for streamed markdown snapshots - #180

Open
Vincent Peng (vincent-peng) wants to merge 3 commits into
microsoft:mainfrom
vincent-peng:streamed-speculative-rewrite
Open

Vincent Peng (vincent-peng) wants to merge 3 commits into
microsoft:mainfrom
vincent-peng:streamed-speculative-rewrite

Conversation

@vincent-peng

Copy link
Copy Markdown
Contributor

Summary

speculativeRewrite and its post-parse rewriters (PartialEmphasisRewriter, PartialTableRewriter) already exist, but no shipped call site enabled them — StreamedMarkdownController parsed every snapshot with speculation off, so trailing **/*/` and incomplete table headers rendered as literal text mid-stream.

Changes:

  • StreamedMarkdownController parses each incoming snapshot with speculativeRewrite: true.
  • When the stream completes, the final snapshot is re-parsed without speculative rewriting so a deliberately trailing literal marker (e.g. 2 * 3) is restored rather than eaten by the speculated emphasis.
  • parse(text:config:) gains an optional speculativeRewrite: parameter (defaults false; source-compatible) so streaming callers don't have to construct MarkdownParseOption by hand.
  • Cancellation is checked before and after each render/publish, including the settle pass.
  • StreamedMarkdownController.task is widened from private to internal so tests can await completion deterministically (no polling).
  • The sample app's LLMChatInteractor uses the same flag — speculating only on incomplete chunks — which also demonstrates the intended usage.

Resolves #178

Validation

  • make lint — 0 violations (strict)
  • make test — full package suite passes, including a new StreamedMarkdownControllerTests suite covering:
    • trailing ** speculatively closed mid-stream, then correct render on completion
    • literal trailing * restored once the stream finishes
    • incomplete table header hidden mid-stream, rendered once the separator row arrives
    • no republish after end() cancels the task
  • make build-sample — builds clean

OSS readiness

  • Source-compatible: the new parameter is optional; parse(text:option:) and the non-streaming MarkdownView path are untouched.

The partial-emphasis and partial-table rewriters were built for the
streaming path but never wired up: the parse(text:config:) convenience
hardcodes speculativeRewrite: false, so streamed snapshots ending in
an unclosed emphasis marker or a partial table header rendered literal
jitter text.

StreamedMarkdownController now parses each snapshot with
speculativeRewrite: true, matching the rewriters' intended use.
MarkdownView (complete documents) keeps the literal behavior so
trailing markers in static text are not eaten.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Review feedback: speculative rewriting also applied to the last
emission, so a completed stream ending in a deliberate trailing
marker ("2 * 3", a literal "|" line) stayed munged forever. After the
source finishes, re-render the final snapshot literally.

Also expose speculativeRewrite on parse(text:config:) so both call
sites share the option bridging, and opt the sample chat demo's
streaming path in (literal on the final chunk).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Bail before the settle parse when the task was cancelled so a torn-down
  view does not pay for a render it will never publish.
- Cover the cancellation invariant with a test asserting no republish
  after end().
- Replace the implicitly-unwrapped continuation with
  AsyncStream.makeStream in the test helper (force_unwrapping is a
  strict-mode lint rule).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@vincent-peng
Vincent Peng (vincent-peng) requested review from a team and a balanced review from Copilot October 8, 2026 13:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@vincent-peng

Copy link
Copy Markdown
Contributor Author

Independent verification: PASS (verified against the real artifact by an agent that did not write the code, on detached worktree 9eb861e)

  • swiftlint --strict: 0 violations, 0 serious in 141 files
  • Package tests: 123 executed, 0 failures — all 4 StreamedMarkdownControllerTests pass, including the cancellation/settle cases
  • make build-sample: ** BUILD SUCCEEDED **
  • Notes: pre-existing warnings only, in files untouched by this diff

Vincent Peng (vincent-peng) added a commit to vincent-peng/SwiftStreamingMarkdown that referenced this pull request Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

speculativeRewrite is implemented but never used in streaming views

2 participants