Microsoft.UI.Reactor's documentation contains code examples and screenshots that go stale as the framework evolves. When APIs change, docs show code that no longer compiles and screenshots that no longer match reality. Keeping them in sync manually is unsustainable.
A "compilable documentation" system where:
- Every code snippet in the docs is extracted from a real, building, running app
- Every screenshot is captured from a live Reactor app window
- Prose is AI-authored by default, steered by a
goalin each template - Running
mur docs compileproduces final documentation with everything up to date - CI can enforce that docs stay in sync with the framework
docs/
_pipeline/
apps/ ← Doc sample apps (source of truth)
getting-started/
App.cs ← Minimal Reactor app with snippet markers
doc-manifest.yaml ← Screenshot regions, window size, delays
getting-started.csproj
commanding/
App.cs
doc-manifest.yaml
commanding.csproj
templates/ ← Doc templates (human + AI authored)
getting-started.md.dt ← Markdown with snippet/screenshot directives
commanding.md.dt
guide/ ← Compiled docs (generated, not hand-edited)
getting-started.md
commanding.md
images/
getting-started/
hero.png
state-example.png
commanding/
standard-commands.png
Each documentation topic has a dedicated app in docs/_pipeline/apps/. These are minimal Reactor apps whose only purpose is to provide working code examples and screenshot targets.
docs/_pipeline/apps/{topic}/
App.cs ← Single-file Reactor app
doc-manifest.yaml ← Metadata for doc compilation
{topic}.csproj ← Standard Reactor project file
Code that should be extracted into docs is bracketed with comment markers:
// <snippet:creating-a-button>
var button = Button("Click me", () => count++);
// </snippet:creating-a-button>
// <snippet:using-state>
var (count, setCount) = UseState(0);
var (name, setName) = UseState("World");
// </snippet:using-state>Rules:
- Snippet IDs are globally unique across all doc apps (scoped by
{topic}/{id}) - Snippets can be nested (outer snippet includes inner snippet code)
- Snippet markers are stripped from extracted code
- Leading common indentation is trimmed in the extracted output
app:
title: "Getting Started"
width: 800
height: 600
theme: light # light | dark | both (captures both themes)
startup-delay: 2000 # ms to wait after launch before first capture
screenshots:
- id: hero
description: "Full app window"
region: full # full | client | custom
format: png
- id: state-example
description: "Counter after clicking"
region: custom
bounds: { x: 20, y: 100, width: 400, height: 200 }
format: png
setup: # Optional: actions to perform before capture
- click: "Click me" # Future: simple automation DSL
- wait: 500
- id: dark-mode-hero
description: "Full app in dark theme"
region: full
theme: dark
format: png
snippets: # Optional: override snippet extraction settings
trim-namespace-usings: true
max-lines-warning: 30 # Warn if a snippet exceeds this lengthEvery doc app is implicitly a test. The compile pipeline:
- Builds the app (
dotnet build) - Launches it (
dotnet run) - Waits for the startup delay
- Verifies the window appeared and is responsive
- Captures screenshots
- Shuts down the app
If any step fails, the doc compile fails — guaranteeing that every snippet comes from working code.
Templates live in docs/_pipeline/templates/ with a .md.dt extension ("doc template"). They are Markdown files with directives that reference snippets and screenshots.
Injects a code block extracted from a doc app:
```csharp snippet="getting-started/creating-a-button"
```The compiler replaces this with the extracted code:
```csharp
var button = Button("Click me", () => count++);
```Options:
```csharp snippet="getting-started/using-state" title="Managing state with UseState"
```Adds a comment header above the code block in the compiled output.
Injects a screenshot captured from a running doc app:
The compiler replaces the screenshot:// URL with a relative path to the generated image:
The AI writes the entire document by default. The template is a prompt — not a partially-authored draft. The AI is given the full set of available snippets, screenshots, and framework knowledge, and produces the complete doc.
The only marker is ai:lock for content that humans want to control directly:
<!-- ai:lock -->
> **Warning:** The commanding API is experimental and may change before v1.
<!-- /ai:lock -->- Everything outside
ai:lockmarkers is AI-generated and can be regenerated at any time ai:locksections are preserved verbatim through every regeneration- The AI is taught the snippet and screenshot directive syntax and uses them in its output
- The AI can recommend new snippets/apps when existing ones don't cover a topic
Each template starts with a YAML front-matter block that serves as the AI's instructions:
---
title: "Getting Started with Reactor"
app: getting-started
order: 1
audience: beginner
goal: |
Introduce Reactor to a new developer. Show them how to create their first app
with a text box and reactive state. End with next steps toward components.
---The goal field is the core instruction to the AI. It describes what the document should accomplish, what it should cover, and the narrative arc. The AI uses this — along with available snippets and screenshots — to write the entire document.
Invoked via:
duct docs compile [--topic <name>] [--no-ai] [--no-screenshots] [--validate-only]- Parse all templates, extract all snippet and screenshot references
- Scan all doc app source files for snippet markers
- Report errors: missing snippets, duplicate IDs, orphaned snippets (defined but never referenced)
- Report warnings: snippets exceeding
max-lines-warning
dotnet buildall doc apps- Fail fast on any build error (this means a code snippet is broken)
- Launch each doc app that has screenshot definitions
- Use the existing
PreviewCaptureServerinfrastructure (Win32PrintWindow) - Wait for startup delay, execute any setup actions
- Capture screenshots per manifest regions
- Save to
docs/guide/images/{topic}/{id}.{format} - Shut down apps
- Parse snippet markers from all doc app source files
- Trim markers and normalize indentation
- Build a snippet registry:
{topic}/{id}→ extracted code
- For each
.md.dttemplate:- Build the AI prompt (see §5) with front-matter goal, available snippets, screenshot references
- AI generates the full document, using snippet and screenshot directives
ai:locksections from any previous output are preserved verbatim
- Write compiled
.mdfiles todocs/guide/
- Process the AI-generated output:
- Replace
snippet=directives with extracted code - Replace
screenshot://URLs with relative image paths
- Replace
- Write final
.mdfiles todocs/guide/
| Flag | Description |
|---|---|
--topic <name> |
Compile only a specific topic |
--no-screenshots |
Skip screenshot capture (use existing images) |
--no-ai |
Skip AI authoring (use previously generated output) |
--validate-only |
Check references without building or capturing |
--ci |
Strict mode: fail on warnings, verify output matches committed docs |
Reactor already has PreviewCaptureServer which captures WinUI window frames via Win32 PrintWindow and serves JPEG over HTTP. The doc system extends this:
- Launch app with a
--doc-captureflag that enables the capture server - Wait for startup delay
- HTTP GET
/frameto capture the current frame - Crop to the region specified in the manifest
- Save as PNG (higher quality than JPEG for docs)
| Region | Behavior |
|---|---|
full |
Entire window including title bar |
client |
Client area only (no chrome) — default from PreviewCaptureServer |
custom |
Pixel bounds {x, y, width, height} relative to client area |
The manifest's setup array allows simple pre-capture automation:
setup:
- wait: 1000
- click: "Button Text" # Find and click a button by content
- type: "Hello World" # Type text into the focused element
- wait: 500This is aspirational — v1 can simply capture the initial state.
The AI is the primary author of all documentation. Templates provide goals and constraints; the AI produces complete, polished documents. Humans intervene only via ai:lock sections and by editing the template's goal field.
Each document is generated from a structured prompt that teaches the AI how to use the doc system:
You are writing developer documentation for Reactor, a WinUI 3 framework
for building native Windows apps with a React-like declarative component model.
## Document goal
Title: {title from front-matter}
Audience: {audience from front-matter}
Goal: {goal from front-matter}
## How to reference code snippets
You have access to working code snippets extracted from real sample apps.
To include a snippet, use this directive on its own line:
```csharp snippet="{topic}/{snippet-id}"
The build system will replace this with the actual code. Available snippets:
{for each snippet: id, source file, and the actual extracted code}
To include a screenshot from a running app, use this image syntax:
The build system will replace the URL with the generated image. Available screenshots:
{for each screenshot: id, description from manifest}
If the available snippets don't cover what you need to explain, you can recommend a new snippet by writing:
The doc compile pipeline will report these as action items. A developer (or you in a subsequent pass) can then create the sample app and snippet to fill the gap.
The following sections are human-authored and must be included exactly as shown, at the appropriate location in the document:
{for each ai:lock section from previous output: the verbatim content}
- Write concise, practical prose. Developers skim — use short paragraphs.
- Lead with what the code does, not what it is.
- Use the snippets to teach by example. Don't describe code the reader can see.
- After each snippet, briefly explain the "why" or call out non-obvious details.
- Use ## and ### headings to create scannable structure.
- End with a "Next steps" section linking to related topics when applicable.
### AI Capabilities
The AI can:
- **Structure the document** — choose headings, ordering, and narrative flow
- **Select which snippets to include** and in what order
- **Write all prose** around the snippets and screenshots
- **Flag missing content** via `<!-- doc:needs-snippet -->` directives
- **Recommend new sample apps** when existing ones don't cover a topic
The AI cannot:
- Modify `ai:lock` sections
- Invent code (all code must come from real snippets)
- Remove or alter snippet/screenshot directives in locked sections
### Workflow
1. **Developer creates a template** (`.md.dt`) with front-matter: title, audience, goal
2. **Developer creates a doc app** with snippet markers and a manifest
3. **`duct docs compile`** builds apps, captures screenshots, runs AI, assembles output
4. **Developer reviews output** — edits `goal` to steer AI, adds `ai:lock` for critical content
5. **On framework changes**: re-run compile. Apps are rebuilt, screenshots refreshed, AI rewrites prose around the updated snippets.
6. **AI flags gaps**: `<!-- doc:needs-snippet -->` directives appear in output when the AI needs code that doesn't exist yet. Developer creates the snippet, re-compiles.
### `doc:needs-snippet` Directive
When the AI determines it needs a code example that doesn't exist:
```markdown
<!-- doc:needs-snippet id="getting-started/use-effect-timer" description="UseEffect hook with a timer that increments a counter every second" -->
The compile pipeline collects these and reports them:
⚠ Missing snippets requested by AI:
- getting-started/use-effect-timer: "UseEffect hook with a timer that increments a counter every second"
A developer (or the AI in a subsequent agent pass) can then create the doc app code to fill the gap, and the next compile will pick it up.
ai:locksections are always preserved verbatim, in position- All code blocks must reference real snippets (the AI cannot fabricate code)
- The
goalfield in front-matter is the primary steering mechanism - If the AI's output is wrong, fix the
goal— don't hand-edit the output (it will be overwritten)
Add a CI step that runs:
duct docs compile --ci --no-screenshotsThis validates:
- All doc apps build successfully (snippets are valid code)
- All snippet references resolve (no stale
snippet=directives) - All screenshot references have matching manifest entries
Screenshots are excluded from CI validation because they require a graphical environment. Screenshot freshness is enforced by a periodic scheduled build or a manual compile run.
A scheduled pipeline or manual trigger runs the full compile with screenshots:
duct docs compile --ciThis captures fresh screenshots and verifies that the committed docs/guide/ matches the generated output. If they differ, the build fails — signaling that docs need to be recompiled.
| Path | Format | Purpose |
|---|---|---|
docs/_pipeline/apps/{topic}/App.cs |
C# with // <snippet:id> markers |
Source of truth for code examples |
docs/_pipeline/apps/{topic}/doc-manifest.yaml |
YAML | Screenshot regions, app config |
docs/_pipeline/templates/{topic}.md.dt |
YAML front-matter + optional ai:lock sections |
AI instructions (human-edited) |
docs/guide/{topic}.md |
Markdown | AI-generated compiled docs (generated) |
docs/guide/images/{topic}/{id}.png |
PNG | Captured screenshots (generated) |
using Microsoft.UI.Reactor;
using Microsoft.UI.Reactor.Core;
using static Microsoft.UI.Reactor.Factories;
ReactorApp.Run<GettingStartedApp>("Getting Started", width: 600, height: 400);
class GettingStartedApp : Component
{
public override Element Render()
{
// <snippet:hello-world>
var (name, setName) = UseState("World");
return VStack(
Text($"Hello, {name}!").FontSize(24).Bold(),
TextBox(name, setName).Width(200)
);
// </snippet:hello-world>
}
}app:
title: "Getting Started"
width: 600
height: 400
startup-delay: 1500
screenshots:
- id: hello-world
description: "Hello World app running"
region: client
format: png---
title: "Getting Started with Reactor"
app: getting-started
order: 1
audience: beginner
goal: |
Introduce Reactor to a new developer. Show them how to create their first app
using ReactorApp.Run, UseState, and basic elements (Text, TextBox, VStack).
Use the hello-world snippet and screenshot. End with next steps.
---
<!-- ai:lock -->
> **Prerequisites:** .NET 10+ and the Windows App SDK. See the [setup guide](setup.md).
<!-- /ai:lock -->Note: The template is minimal — just front-matter with a goal and any locked sections. The AI writes everything else.
The AI generates the full document, using available snippets and screenshots:
# Getting Started with Reactor
> **Prerequisites:** .NET 10+ and the Windows App SDK. See the [setup guide](setup.md).
Reactor is a declarative UI framework for building native Windows apps.
No XAML, no data binding — you describe your UI in plain C# and Reactor
keeps the screen in sync.
## Your First App
Create a new file called `App.cs` and add this:
```csharp
var (name, setName) = UseState("World");
return VStack(
Text($"Hello, {name}!").FontSize(24).Bold(),
TextBox(name, setName).Width(200)
);
```
Run it and you'll see this:

Type in the text box and the greeting updates instantly. That's
`UseState` — it returns the current value and a setter. When you call
the setter, Reactor re-renders the component with the new state.
## Next Steps
- Learn about [components](components.md) to break your UI into reusable pieces
- Explore [hooks](hooks.md) like `UseEffect` and `UseMemo`Note how the locked prerequisites block appears verbatim. The AI chose to use the hello-world snippet and screenshot, structured the doc with headings, and wrote all the prose.
- Snippet extraction tool (parse markers, build registry)
- Template parser (resolve
snippet=andscreenshot://directives) duct docs compile --validate-onlyand--no-screenshotsmodes- First doc app + template as proof of concept
- Extend
PreviewCaptureServeror build a standalone capture harness - Support
full,client, andcustomregion types - Integrate into compile pipeline
- Prompt engineering for full-document generation
- Teach AI snippet/screenshot directive syntax in prompt
ai:lockmarker preservationdoc:needs-snippetreporting for content gaps- Feedback loop: adjust
goalin front-matter to steer output
- CI validation pipeline
- Scheduled full-compile with screenshots
- Error reporting, diagnostics, and
--verboseoutput - Documentation for the documentation system itself
-
Snippet deduplication: Cross-app references are supported. Any template can reference
snippet="other-topic/id"from any doc app. -
Theme screenshots: When
theme: bothis specified, the pipeline generates two images:{id}-light.pngand{id}-dark.png. The AI can reference either or both. -
Automation DSL scope: Start with simple
click/type/waitactions for pre-capture setup. Extend with richer actions (scroll, hover, drag) only as needed. -
Output format: Compiled output is Markdown. Checked into the repo for browsing.
-
Versioning compiled output:
docs/guide/is committed to the repo. CI verifies that committed output matches a fresh compile. -
AI-created apps: The compile pipeline supports an agent mode where the AI autonomously creates doc apps and snippets to fill
doc:needs-snippetgaps. The developer reviews the generated apps before committing. -
Doc ordering / navigation: The system generates a table of contents for larger documents from the
orderfields in front-matter.