Repository navigation
MarkdownRenderer structure step 2a: one renderer package, public extension and lifetime API - #105
Merged
zhuowcui merged 7 commits intoOct 28, 2026
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
src/MarkdownRenderer, in folders that match their namespaces. Namespaces are unchanged.MarkdownRenderer.dllis now calledMarkdownRenderer(it wasMarkdownRenderer.WinUI), and the dependency-onlyMarkdownRendererpackage is gone.MarkdownRenderer, the Math, Mermaid, resvg and TextMate packs, the two grammar packs, andMarkdownRenderer.All.MarkdownRenderer.AotSmoke.Public extension API instead of internals.
"renderer.internal.deferred-scene"attribute, copied into six files, plus the internalShouldDeferOffscreenScenes. They are now public:MarkdownExtensionContext.CanDeferOffscreenScenes,MarkdownExtensionContext.CreateDeferredSceneMarker()andMarkdownContentAttributes.DeferredScene.[Obsolete]overloads, which also found grammar packs by type name through reflection. Those overloads, the parameterless highlighter constructor,TextMateGrammarProviderFactoryand the owned-highlighter plumbing are removed.DisposalCompletedevent andDrainRetiredSnapshotsAsyncto shut a view down. Views now implementIAsyncDisposable:DisposeAsync()completes when the released Win2D resources are freed, and a publicDisposedevent replaces the internal one. The library no longer names the app.Test code in test projects.
tools/that called the engine's internal spec-HTML oracle. It now lives inMarkdownRenderer.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.PixelTestsis nowMarkdownRenderer.Pixel.Tests.Obsolete API removed. Nothing has shipped, so 1.0 starts without
[Obsolete]members.MarkdownRendererControl(obsolete, MR1001) is now an abstract base with aprivate protectedconstructor; onlyMarkdownScrollViewandMarkdownDocumentViewderive from it.MarkdownRendererControlBuilder.Build()is removed.ICodeBlockSyntaxHighlighter, theCodeBlockSyntaxHighlighterproperty,WithCodeBlockSyntaxHighlighterand their adapter are removed.ICodeHighlighterdeclaresRevisionitself.CodeBlockHighlightRequestno longer carries a cancellation token; the token is passed only toHighlightAsync.SafeHtmlFeature.PackageIdandSafeHtmlCapabilityDescriptor.PackageIdnamed a package that no longer exists.Scripts, CI and docs follow the new package set. The docs describe one
MarkdownRendererpackage plus feature packs, with a decision note on why the renderer is one assembly. The deleted GitHub and Html package READMEs are folded intosupported-markdown.md.Decisions for review
MarkdownRenderermanaged-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.DisposeAsyncthrows synchronously when teardown did not finish, including after an earlierDisposethat failed part way. That is the one case a plainDisposeretry cannot report. Faults while freeing resources come through the returned task.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:GitHub.Testslink to its listener, read internal timing snapshots, parse counters and the performance sink. They will move to EventSource events and counters.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
MarkdownRenderer.slnxRelease x64JitHub.WinUIRelease x64 with the security gateReleaseSecuritytestsMarkdownRendererpackage consumed by NativeAOT and trimmed single-file--locked-moderestoreJitHub.Web, whose lock does not match this machine's SDK (10.0.14 vs 10.0.11), unchanged hereTest-ProjectStructure.ps1, engineering and compliance script self-testsNot covered:
DisposeAsync, covered by the app's retirement tests but not by a live run.🤖 Generated with Claude Code