Skip to content

MarkdownRenderer structure step 2a: one renderer package, public extension and lifetime API - #105

Merged
zhuowcui merged 7 commits into
codex/markdown-renderer-1-0from
codex/markdown-renderer-api
Oct 28, 2026
Merged

zhuowcui merged 7 commits into
codex/markdown-renderer-1-0from
codex/markdown-renderer-api

Conversation

@Xueyang-Song

Copy link
Copy Markdown
Collaborator

Step 2, part 1 of the MarkdownRenderer structure cleanup. It targets the #103 branch, not main.

Step 2 removes the internals grants and file links that step 1 left as listed exceptions. This PR removes six of them, the obsolete API, and the separate Core package. The rest follows in stacked PRs (see the end).

What changes

One renderer assembly.

  • The WinUI layout walks the parser's Markdig syntax tree. That tree stays out of the public API, and shipping assemblies may not grant each other internals, so Core and the renderer cannot be separate assemblies.
  • Core, Gfm, GitHub, Html and Performance now live in src/MarkdownRenderer, in folders that match their namespaces. Namespaces are unchanged.
  • The package that ships MarkdownRenderer.dll is now called MarkdownRenderer (it was MarkdownRenderer.WinUI), and the dependency-only MarkdownRenderer package is gone.
  • Packages drop from 14 to 8: MarkdownRenderer, the Math, Mermaid, resvg and TextMate packs, the two grammar packs, and MarkdownRenderer.All.
  • Math and Mermaid now pull in the Windows App SDK and Win2D through the renderer. Lock files are regenerated; no resolved version or hash changed.
  • The Core and managed-pack AOT smokes covered the same assembly and are now one MarkdownRenderer.AotSmoke.

Public extension API instead of internals.

  • Math and Mermaid: deferred scenes were an undocumented "renderer.internal.deferred-scene" attribute, copied into six files, plus the internal ShouldDeferOffscreenScenes. They are now public: MarkdownExtensionContext.CanDeferOffscreenScenes, MarkdownExtensionContext.CreateDeferredSceneMarker() and MarkdownContentAttributes.DeferredScene.
  • TextMate reached internal control and builder hooks only from its [Obsolete] overloads, which also found grammar packs by type name through reflection. Those overloads, the parameterless highlighter constructor, TextMateGrammarProviderFactory and the owned-highlighter plumbing are removed.
  • The JitHub app used the internal DisposalCompleted event and DrainRetiredSnapshotsAsync to shut a view down. Views now implement IAsyncDisposable: DisposeAsync() completes when the released Win2D resources are freed, and a public Disposed event replaces the internal one. The library no longer names the app.

Test code in test projects.

  • The conformance harness was an executable in tools/ that called the engine's internal spec-HTML oracle. It now lives in MarkdownRenderer.Conformance.Tests, and its full run over the official corpora is an opt-in test. All 1,322 official CommonMark and GFM examples pass.
  • MarkdownRenderer.PixelTests is now MarkdownRenderer.Pixel.Tests.

Obsolete API removed. Nothing has shipped, so 1.0 starts without [Obsolete] members.

  • MarkdownRendererControl (obsolete, MR1001) is now an abstract base with a private protected constructor; only MarkdownScrollView and MarkdownDocumentView derive from it. MarkdownRendererControlBuilder.Build() is removed.
  • ICodeBlockSyntaxHighlighter, the CodeBlockSyntaxHighlighter property, WithCodeBlockSyntaxHighlighter and their adapter are removed. ICodeHighlighter declares Revision itself.
  • CodeBlockHighlightRequest no longer carries a cancellation token; the token is passed only to HighlightAsync.
  • SafeHtmlFeature.PackageId and SafeHtmlCapabilityDescriptor.PackageId named a package that no longer exists.

Scripts, CI and docs follow the new package set. The docs describe one MarkdownRenderer package plus feature packs, with a decision note on why the renderer is one assembly. The deleted GitHub and Html package READMEs are folded into supported-markdown.md.

Decisions for review

  • Core merged into the renderer, which you chose over a public syntax tree or public Markdig types. There is no WinUI-free package any more; Core already targeted Windows only.
  • Deferred scenes stay an attribute, now public and documented, with a helper that creates valid markers. A builder method that hides the marker would make the progressive-scene tests unable to feed malformed markers, which exercise the renderer's defensive paths.
  • The MarkdownRenderer managed-size budget is the measured DLL plus about 10% (2,475 KiB), the rule the other budgets follow. The summed old budget (2,150 KiB) did not cover the GFM, GitHub, HTML and performance code, which never had a managed budget.
  • DisposeAsync throws synchronously when teardown did not finish, including after an earlier Dispose that failed part way. That is the one case a plain Dispose retry cannot report. Faults while freeing resources come through the returned task.
  • The full conformance run is opt-in (MARKDOWN_RENDERER_CONFORMANCE=1) because it downloads the official corpora. CI runs the harness tests, as it did before.

Still to come in step 2

These grants still carry their exception reasons in MarkdownRenderer.csproj:

  • resvg plugs into internal SVG admission, render-priority, raster-header and budget contracts. It needs a designed public SVG host API.
  • The performance harness, and the GitHub.Tests link to its listener, read internal timing snapshots, parse counters and the performance sink. They will move to EventSource events and counters.
  • The sample is also the host for the correctness UI tests. It reads internal layout and theme snapshots and sets a process-wide theme override. It needs per-view public diagnostics instead.
  • The TextMate grammar adapter is still compiled into both grammar packs from a shared folder.

Also still to come: the RS0026/RS0027 overload fixes, and making non-API types internal (Mermaid's draw-command wire format, Math's scene types, EmbedVisibility).

Verification

Check Result
MarkdownRenderer.slnx Release x64 0 warnings, 0 errors
JitHub.WinUI Release x64 with the security gate passes; 84/84 ReleaseSecurity tests
GitHub.Tests 3,043 (was 3,044: the test of which assembly a renderer lived in is gone)
MarkdownRenderer.Tests 1,397 (+3 deferred-scene API tests, −2 tests of the removed owned-highlighter helper)
Svg.Resvg / Math / Mermaid / Html 808 / 80 / 259 / 10
TextMate 70 (was 74: tests of the removed compatibility members)
Conformance 9 harness tests; the opt-in full run passes 1,322 of 1,322 official examples
PublicApi.Tests 16 (was 21: one row per shipping assembly, now 7)
Sample.Automation.Tests / JitHub.WinUI.Tests Debug 4 / 3,126
NativeAOT project smokes: renderer, Mermaid, resvg, Math, TextMate (win-x64) publish with 0 warnings and run
MarkdownRenderer package consumed by NativeAOT and trimmed single-file x86 and x64 run; ARM64 cross-published
Packed packages 8; size gates pass (931,528 bytes compressed, 2,298,880 managed); 7 managed assemblies
--locked-mode restore every project except JitHub.Web, whose lock does not match this machine's SDK (10.0.14 vs 10.0.11), unchanged here
resvg worker identity, x86/x64/arm64 verified; committed binaries unchanged
Test-ProjectStructure.ps1, engineering and compliance script self-tests pass

Not covered:

  • PixelTests and the external performance-gate subset were not run.
  • No Sandbox UI run: the app's renderer shutdown now goes through DisposeAsync, covered by the app's retirement tests but not by a live run.
  • The per-pack release-evidence scripts were parse-checked, not run. The workflow changes run only on CI.

🤖 Generated with Claude Code

Xueyang-Song and others added 7 commits October 27, 2026 19:08
…assembly

The WinUI layout walks the parser's Markdig syntax tree. With that tree
kept out of the public API, and no internals grants allowed between
shipping assemblies, Core and the renderer could not stay separate
assemblies. Gfm, GitHub, Html and Performance were renderer code in
their own DLLs, plugged in through internal types.

Core, Gfm, GitHub, Html and Performance now live in
src/MarkdownRenderer, in folders that match their namespaces.
Namespaces are unchanged. The package that ships MarkdownRenderer.dll
is now called MarkdownRenderer (it was MarkdownRenderer.WinUI), and the
dependency-only MarkdownRenderer package is gone. Packages drop from
14 to 8: MarkdownRenderer, the Math, Mermaid, resvg and TextMate packs,
the two grammar packs and MarkdownRenderer.All.

Consequences:
- Math and Mermaid reference the renderer package, so they pull in the
  Windows App SDK and Win2D. Their test projects reference the Windows
  App SDK runtime like the other renderer tests, and the Math, Mermaid
  and conformance executables reference the SDK build tools for mt.exe.
- Core.AotSmoke and ManagedPacks.AotSmoke covered the same assembly and
  are now one MarkdownRenderer.AotSmoke that runs both sets of checks.
- The performance harness no longer records a Core DLL hash.
- SafeHtmlFeature.PackageId and SafeHtmlCapabilityDescriptor.PackageId
  named a package that no longer exists and are removed.
- Tests that asserted which assembly a type lived in now assert the
  behavior instead, or are removed where the strict-GFM and GitHub
  registry tests already cover it.

Lock files are regenerated for the new project graph. No resolved
package version or hash changed; the only additions are the Windows App
SDK and Win2D packages, at their pinned versions, in the projects that
used to reference only Core.

The remaining internals grants are listed with reasons in the renderer
project and are removed by the next commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Math and Mermaid read the internal MarkdownExtensionContext.
ShouldDeferOffscreenScenes and tagged their placeholders with an
undocumented "renderer.internal.deferred-scene" attribute. The renderer
kept its own copy of that string in four files, and each pack kept its
own marker counter.

The protocol is now public and documented:
- MarkdownExtensionContext.CanDeferOffscreenScenes says whether this
  render pass may emit a placeholder.
- MarkdownExtensionContext.CreateDeferredSceneMarker() returns a new
  marker, unique across the engine's documents and prefixed with the
  node kind, as the progressive scene plan expects. It throws when the
  pass cannot defer.
- MarkdownContentAttributes.DeferredScene is the attribute key, next to
  the other declarative attribute keys.

The attribute stays a plain attribute, so the progressive-scene tests
can still feed malformed markers to the renderer's defensive paths.
The Math and Mermaid internals grants are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The TextMate pack reached internal control and builder hooks
(SetOwnedCodeHighlighter, WithOwnedCodeHighlighterFactory) only from its
[Obsolete] compatibility overloads, which created and owned a highlighter
per view. Those overloads also found grammar packs by type name through
TextMateGrammarProviderFactory, which needed RequiresUnreferencedCode.

Removed:
- the six obsolete UseTextMateSyntaxHighlighting overloads,
- the parameterless TextMateCodeBlockSyntaxHighlighter constructor,
- TextMateGrammarProviderFactory,
- the control's owned-highlighter fields, transfer and retirement code,
  the builder's owned-highlighter factory, and
  OwnedCodeHighlighterAssignment, which only they used.

The three UseTextMateSyntaxHighlighting overloads that take an explicit
TextMateCodeBlockSyntaxHighlighter stay; the caller owns the highlighter.
Tests that only checked the removed members are removed, and the
highlighter tests construct CommonTextMateGrammarProvider directly. The
TextMate internals grant is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- CI publishes MarkdownRenderer.AotSmoke with NativeAOT in place of the
  Core smoke, restoring it for the target RID because it now consumes
  the Windows App SDK. The package-consumption step tests only the
  MarkdownRenderer package, and the restore and pack lists name the
  eight shipping projects.
- Release, compliance, architecture and performance-gate scripts use
  the new package set. Math's release evidence expects one dependency,
  MarkdownRenderer, and the Math and Mermaid evidence scripts put mt.exe
  on PATH like the resvg script. The size-gate package lookup no longer
  treats every feature pack as a MarkdownRenderer package.
- The MarkdownRenderer managed-size budget is the measured DLL plus
  about 10%, the rule the other budgets follow. The GFM, GitHub, HTML and
  performance code it now holds never had a managed budget, so the
  earlier engine and viewer budget did not cover it.
- Docs describe one MarkdownRenderer package plus feature packs, with a
  decision note on why the renderer is one assembly. The deleted GitHub
  and Html package READMEs are folded into supported-markdown.md, and
  the AOT section no longer describes the removed grammar-pack discovery.
- The line-ending rule for the fingerprinted Primer CSS follows the
  files to their new folder.
- The standards in AGENTS.md describe the new dependency order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both needed internals grants with exceptions:
- The conformance harness was an executable in tools/ that called the
  engine's internal spec-HTML oracle. It is test code, so it now lives in
  MarkdownRenderer.Conformance.Tests. Its command-line run over the
  official corpora is now an opt-in test (MARKDOWN_RENDERER_CONFORMANCE=1,
  Category=Conformance) that writes the same report. The tools folder is
  gone. All 1,322 official CommonMark and GFM examples pass.
- MarkdownRenderer.PixelTests is now MarkdownRenderer.Pixel.Tests, so its
  name ends in .Tests like every other test project.

The renderer grants both test projects internals without an exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… base

Nothing has shipped, so a 1.0 should not start with [Obsolete] surface.

- MarkdownRendererControl was an obsolete class (MR1001) that apps could
  construct, and the base of MarkdownScrollView and MarkdownDocumentView.
  It is now an abstract base with a private protected constructor, so
  only the two views derive from it. MarkdownRendererControlBuilder.Build()
  is removed; BuildScrollView and BuildDocumentView remain. The MR1001
  suppressions in the renderer, tests, sample and app are gone.
- ICodeBlockSyntaxHighlighter, the CodeBlockSyntaxHighlighter property and
  dependency property, WithCodeBlockSyntaxHighlighter and the adapter
  that bridged them are removed. ICodeHighlighter now declares Revision
  itself, and CreateDefault takes an ICodeHighlighter.
- CodeBlockHighlightRequest no longer carries a cancellation token; the
  token is passed only to HighlightAsync. The TextMate highlighter drops
  its single-argument overload and the code that linked the two tokens,
  and still throws ObjectDisposedException after disposal.

The API test that allowed one reviewed inheritable class now checks the
rule itself: an exported class is sealed, static, or has no constructor
that code outside its assembly can call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The JitHub app reached two internal members to shut a view down: the
DisposalCompleted event, to learn that Dispose had finished teardown,
and DrainRetiredSnapshotsAsync, to wait until the released Win2D
resources were freed. The sample used the same event. The library had
to name the app in an internals grant.

MarkdownRendererControl now implements IAsyncDisposable:
- DisposeAsync() disposes the view and returns a task that completes
  when the resources it released are freed, or faults if one could not
  be released. It throws when teardown did not finish, including after
  an earlier Dispose that failed part way, which a plain Dispose retry
  cannot report.
- The public Disposed event replaces the internal DisposalCompleted
  event, for teardown that the view's window starts.

The app feeds DisposeAsync into its existing retirement sequence and no
longer tracks the event; the sample's disposal evidence uses Disposed.
The JitHub.WinUI internals grant is removed, so the library no longer
names the app. One app test that asserts on source text is updated to
the new calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@zhuowcui
zhuowcui merged commit e98e9ca into codex/markdown-renderer-1-0 Oct 28, 2026
8 checks passed
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.

2 participants