Demo Script Tool is a WinUI 3 desktop sample app built on Reactor. It lets a
presenter author a code demo as a Markdown file (demo-script.md), generate
runnable .NET code and speaker notes for each step via the GitHub Models AI
SDK, and execute individual steps directly from the UI through dotnet run.
The app ships under samples/apps/demo-script-tool/ alongside the existing
sample apps (chat, headtrax, regedit-winui, etc.) and serves as a
medium-complexity exemplar exercising:
- Reactor's SAX-style Markdown parser (read/write of
demo-script.md) - Streaming state updates into a scrollable collection of cards
- File-system watcher integration with Reactor's hook model
- Long-running async work (AI generation, dotnet build, dotnet run) driven from UI commands without blocking the render loop
- Clipboard, file picker, and process-launching interop
All artifacts (the script, generated code files, exported speaker notes) live on disk as plain text in a user-selected project root, so the demo project is fully version-controllable independently of the tool.
- Let a presenter author a demo as a structured list of step prompts in a single Markdown file.
- Generate runnable .NET code per step via AI (GitHub Models — Claude
claude-opus-4-6by default). - Produce a "presenter delta" per step: annotated speaker notes describing what code to type or paste, with precise placement instructions.
- Execute individual steps directly from the tool via
dotnet run. - Store all artifacts as plain files (Markdown + code) that live with the project in version control.
- Demonstrate idiomatic Reactor patterns for streaming, file-watching, and async command pipelines in a real (non-toy) sample.
- Not a XAML sample. The app is pure Reactor C#.
- Not a generic AI client. The model, system prompt, and contract are fixed for v1.
- No per-step regeneration, diff viewer, settings UI, in-app terminal, drag reorder, or cloud sync (see Out of Scope).
All files for a given demo live in a single project root directory opened by the user on launch. The tool itself has no global state — closing and reopening on the same folder reproduces the same UI.
/project-root/
demo-script.md
step-01.cs
step-02.cs
step-03.cs
/project-root/
demo-script.md
step-01/
Program.cs
step-01.csproj
...
step-02/
Program.cs
step-02.csproj
...
The user declares single-file or multi-file intent in the Demo Prompt
section of demo-script.md. The AI and tool both respect this declaration.
If a .csproj is needed, the user notes this in the Demo Prompt and is
responsible for including project setup instructions in the prompt.
The tool reads and writes this file. It is human-editable outside the tool.
# Demo Title
## Demo Prompt
This demo shows how to build a real-time chat UI using .NET 10 top-level
statements and the Spectre.Console library. Single-file mode. Each step
should compile and run standalone with `dotnet run`. Use NuGet inline
references where needed.
## Steps
1. **Hello World baseline**
Start with a minimal top-level statements app that prints a styled
greeting using Spectre.Console. This establishes the file structure and
confirms the NuGet reference works.
2. **Add a message list**
Render a static list of fake chat messages using Spectre.Console's table
component. Show how to style sender names in different colors.
3. **Simulate live updates**
Add a loop that appends a new message every second using
AnsiConsole.Live. The app should run until the user presses a key.| Section | Required | Description |
|---|---|---|
# Title |
Yes | Top-level heading, displayed in the app header |
## Demo Prompt |
Yes | Persistent context passed to the AI for every generation call |
## Steps |
Yes | Numbered list of step prompts; each step can be multiple sentences |
Step entries use a bold title followed by one or more sentences of detail. Both the title and body are passed to the AI as the per-step prompt.
Parsing uses Reactor's built-in SAX-style Markdown parser
(Reactor.Documents.Markdown). The tool subscribes to the parser's section
and list events to extract the three sections above; nothing else in the
file is interpreted. Unknown sections are preserved verbatim on round-trip
write.
Hardcoded in the binary. Governs AI behavior and output contract:
- Role: You are a code demo script generator. Your job is to produce complete, runnable .NET code for each step of a live coding demo, plus presenter notes.
- Per-step output contract: Each step must produce two artifacts: (1) a
full runnable file for that step, and (2) a presenter delta — an
annotated description of what the presenter types or pastes, referencing
specific locations in the code (e.g., "Paste this method after the
BuildLayout()call on line 12"). - Continuity rule: Each step's full code must build naturally on the previous step. The delta should only describe the net-new changes from the prior step.
- Runability rule: Every step's code file must compile and run
independently via
dotnet run. Do not produce incomplete stubs. - Single vs. multi-file: Respect the declaration in the Demo Prompt.
For single-file, output one
.csfile. For multi-file, output all files needed for that step. - Delta format: Use plain English with precise placement instructions.
Example: "Add the following method inside the
Appclass, directly afterOnStartup:" followed by the code block. Avoid vague instructions like "add this somewhere." - Fix mode (see Build-and-Fix Pipeline): When given a build error and the previously generated code, return only the corrected complete file — no explanation, no partial diffs. Preserve all intent from the original generation; fix only what the compiler rejects.
The ## Demo Prompt section of demo-script.md. Passed to the AI on every
generation call as persistent context. Should include:
- The technology/framework being demonstrated
- Single-file vs. multi-file declaration
- Target runtime / NuGet packages
- Any constraints (e.g., "do not use LINQ", "keep each file under 80 lines")
- Audience level (beginner, intermediate, expert)
The UI is a single window divided into three vertical regions.
- App title / current demo title (from
# Titlein the Markdown) - Open Folder button — opens a folder picker; loads
demo-script.mdfrom the selected directory. If the file does not exist, the tool offers to scaffold one with empty Title / Demo Prompt / Steps sections. - Generate All button — triggers full AI generation for all steps (destructive, no confirmation)
- Export Speaker Notes button — exports all presenter deltas to a
single
speaker-notes.txtfile in the project root
A single multi-line text box bound to the ## Demo Prompt section. Edits
are written back to demo-script.md automatically with a 500 ms debounce
after the user stops typing. A FileSystemWatcher on demo-script.md
reloads the UI if the file is modified outside the app (see
Filesystem Watcher).
Each step is rendered as a card with three columns:
┌──────────────────────────────────────────────────────────────────────────┐
│ Step 1 — Hello World baseline │
├─────────────────────┬────────────────────────────┬───────────────────────┤
│ PROMPT │ GENERATED CODE │ ACTIONS │
│ │ │ │
│ [Multi-line │ [Read-only plain text │ [▶ Run] │
│ text box] │ viewer, monospace font] │ [📋 Copy Delta] │
│ │ │ │
└─────────────────────┴────────────────────────────┴───────────────────────┘
- Prompt column: Editable multi-line text box. User edits step prompt
here; changes persist to
demo-script.mdwith the same 500 ms debounce as the Demo Prompt panel. - Generated Code column: Read-only plain text viewer using a monospace/coding font. No syntax highlighting required for v1. Shows an empty/placeholder state before generation.
- Actions column:
- ▶ Run — executes this step's code via
dotnet run. Disabled if no generated file exists for that step. - 📋 Copy Delta — copies this step's presenter delta to the clipboard. Disabled if no delta exists yet.
- ▶ Run — executes this step's code via
When Generate All is triggered:
- Steps are generated sequentially so each step can inform the next.
- Each card animates in as its generation completes (fade or slide transition driven by Reactor's animation hooks; see spec 014).
- A progress indicator shows the current step being processed ("Generating step 2 of 5...").
- Generated code streams into the code viewer token-by-token as it arrives from the API.
- The Generate All button is disabled and shows a cancel affordance during generation.
This sample is also a vehicle for Reactor's Windows 11 design guidance — it
should look and feel like a first-class Windows 11 app. The rules below
restate the relevant pieces of skills/design.md for this app's surfaces.
When in doubt, defer to that skill; this section calls out the choices the
implementing agent must make.
Use Mica as the window backdrop. The app is a long-lived shell with clear chrome, which is the canonical Mica use case.
return Grid([GridSize.Star()], [GridSize.Auto, GridSize.Star()],
titleBar.Grid(row: 0),
mainBody.Grid(row: 1)
).Backdrop(BackdropKind.Mica);For Mica to show through, the root element must not paint an opaque
background. Drop any Theme.SolidBackground from the root. Per-region
backgrounds (cards, the prompt panel) still apply, just not on the root.
Use the TitleBar(...) factory as the window's top region — never a custom
header row. It integrates with the Windows caption (drag region, system
menu, min/max/close) and themes correctly.
var titleBar = (TitleBar("Demo Script Tool") with
{
Subtitle = currentDemoTitle ?? "(no demo open)",
Content = HeaderActions(), // Open Folder / Generate All / Export
RightHeader = TextBlock(generationStatus)
.FontSize(12).Opacity(0.6),
}).Flex(shrink: 0);Place the Open Folder, Generate All, and Export Speaker Notes
commands in the Content slot of the title bar so they live in the
caption strip rather than a redundant second header row. Per-step actions
(▶ Run, 📋 Copy Delta) live on the cards, not in the title bar.
Use semantic text factories — never raw FontSize / FontWeight for
standard UI text:
| Surface | Factory |
|---|---|
Demo title (rendered in TitleBar Subtitle) |
(handled by TitleBar) |
Step card title (Step 1 — Hello World baseline) |
SubHeading(...) (20px, 600) |
Column labels (PROMPT, GENERATED CODE, ACTIONS) |
Caption(...) (12px) |
| Body / prompt text | TextBlock(...) (14px) |
| Inline build error output | TextBlock(...) with monospace font family |
| Empty-state placeholder ("No code generated yet.") | TextBlock(...).Foreground(Theme.SecondaryText) |
The generated-code viewer uses {ThemeResource SymbolThemeFontFamily}-
style override to set a monospace coding font via .Set(tb => tb.FontFamily = ...).
For emphasis use SemiBold (600), not Bold (700) — except Heading(),
which we don't use in this app.
All colors come from Theme.* tokens. No hardcoded hex on themed surfaces.
| Surface | Token |
|---|---|
| Step card background | Theme.CardBackground |
| Step card stroke | Theme.CardStroke |
| Demo Prompt panel background | Theme.LayerFill |
| Body / prompt text | Theme.PrimaryText |
| Empty-state and disabled labels | Theme.SecondaryText / Theme.DisabledText |
| Primary CTA (Generate All) accent | Theme.Accent (resource override on Button) |
| Build-success checkmark | SystemFillColorSuccessBrush (via Theme.Ref) |
| Build-warning amber ("Fixing...") | SystemFillColorCautionBrush |
| Build-failure red | SystemFillColorCriticalBrush |
| Dividers between cards | DividerStrokeColorDefaultBrush |
The Generate All primary button uses .Resources(...) to override
ButtonBackground / ButtonForeground to the accent palette — never
.Background(Theme.Accent) directly, which loses hover/pressed/disabled
states.
The app must work in HC (especially NightSky):
- No opacity tricks on borders or text — encode translucency in alpha for Light/Dark only.
- The step card's outline becomes a
SystemColorHighlightColorborder in HC so the interactive region is visible. - The "build succeeded / fixing / failed" indicators must use shape + text (✓, "Fixing...", "Build failed"), not color alone.
- The streaming code viewer never animates color/gradient — text appears in a single system brush in HC.
- Set
Application.Current.HighContrastAdjustment = ApplicationHighContrastAdjustment.Noneat startup.
Every margin, padding, and gap is a multiple of 4. Concrete values:
- Window edge inset: 16 px on each side, applied at the main body container (not at every card).
- Demo Prompt panel padding: 16 px; bottom margin 12 px before the steps panel.
- Card outer gap (vertical between cards): 12 px.
- Card inner padding: 16 px.
- Card inner column gap: 16 px between Prompt / Code / Actions columns.
- Vertical rhythm inside the actions column: 8 px between buttons.
- Card corner radius: 8 px (overlay-class surface).
- Inline-error / build-output box corner radius: 4 px (control-class).
Use FlexColumn / FlexRow (CSS Flexbox semantics) as the default for
linear layout. Reserve Grid for the card's three-column structure where
the prompt and code columns must respect explicit weights and the code
column needs GridSize.Star() for TextTrimming to fire on long single-
line outputs.
// Card: Grid because we need column weighting + trimmable code
Grid(
columns: [GridSize.Pixels(280), GridSize.Star(), GridSize.Pixels(140)],
rows: [GridSize.Auto, GridSize.Auto],
SubHeading($"Step {n} — {title}").Grid(row: 0, columnSpan: 3),
PromptColumn().Grid(row: 1, column: 0),
CodeColumn().Grid(row: 1, column: 1),
ActionsColumn().Grid(row: 1, column: 2))Use MinHeight not fixed Height on the prompt and code text areas —
text scaling at 200%+ must not clip.
Streaming-token updates into the code viewer must remain smooth at any
display scale and not cause layout thrash. The viewer is a TextView with
IsAppendOnly = true (or equivalent) so the reconciler appends rather
than re-flowing the whole control on each token.
Cards animate in on first generation using:
Border(card).WithTransitions(Transition.Fade, Transition.Slide(Edge.Bottom))No gradient animation, no parallax — both fail HC and add nothing to a demo authoring tool.
The step card is the primary visual unit. Build it as a single bordered surface with shadow lift on hover:
Border(cardContent)
.Background(Theme.CardBackground)
.WithBorder(Theme.CardStroke, 1)
.CornerRadius(8)
.Padding(16)
.Translation(0, 0, isHovered ? 32 : 0)
.Set(b =>
{
b.BackgroundSizing = BackgroundSizing.InnerBorderEdge;
b.Shadow = new ThemeShadow();
})
.ScaleTransition() // smooth elevation lift
.AutomationName($"Step {n} — {title}")The card is interactive (clicking focuses the prompt) so it gets a
Cursor = Hand affordance and a HC-mode SystemColorHighlightColor
border.
| Button | Style | Notes |
|---|---|---|
| Open Folder | Standard (default style) | In TitleBar Content |
| Generate All | Primary (accent override via .Resources) |
Disabled while generating; shows cancel affordance |
| Export Speaker Notes | Standard | Disabled until at least one delta exists |
| ▶ Run | Standard with leading symbol icon | Disabled if no step-NN.cs on disk |
| 📋 Copy Delta | Subtle (per .Resources override pattern) |
Disabled until delta exists |
Buttons use MinHeight(40) only when needed for touch — do not set a
fixed Height(32), which clips at large text scales.
- Every icon-only or symbol-prefixed button has
.AutomationName(...):▶ Run→AutomationName($"Run step {n}")📋 Copy Delta→AutomationName($"Copy speaker notes for step {n}")
- Heading hierarchy: the
TitleBartitle is L1; each card'sSubHeading($"Step n — title")is L2. Don't skip levels. - The steps panel is wrapped in
.Landmark(AutomationLandmarkType.Main); the title-bar action group usesAutomationLandmarkType.Navigation. - The Demo Prompt panel and each step's prompt field use
FormFieldso the label, required state, and error messages are wired into AT automatically. - Generation progress ("Generating step 2 of 5...") is announced via
UseAnnounce(polite) so screen-reader users follow the pipeline. Placeannounce.Regiononce near the root. - Build failure (max retries) is announced assertive.
- Each step card sets
.PositionInSet(n, total)for screen-reader navigation through the list. - Hit-test targets for the per-card hover surface use
Background("#00000000")(visible-to-AT, transparent visually). - Dividers between cards use
DividerStrokeColorDefaultBrush— never a custom brush with opacity (breaks HC). - The three Roslyn analyzers (
REACTOR_A11Y_001..003) are promoted to errors in the sample's.csproj:<WarningsAsErrors>REACTOR_A11Y_001;REACTOR_A11Y_002;REACTOR_A11Y_003</WarningsAsErrors>
- Every step card uses
.WithKey($"step-{n}")so the reconciler keeps identity stable when steps are added/removed by external edits todemo-script.md. - The token stream into the code viewer is bound via
IObservableStream<string>so reconciliation on each token is O(1) per card, not O(N) over all cards.
The sample's selftest fixtures must mount and screenshot in:
- Light / Dark / NightSky (HC) themes
- 100 / 150 / 200 / 250 % display scaling
- Maximum text scaling (Settings > Accessibility > Text size = max)
- An empty demo (no steps) and a 10-step demo (scrolled state)
- User clicks Generate All.
- Tool reads
demo-script.mdand parses the Demo Prompt and all step prompts. - Tool constructs the API request:
- System prompt: Layer 1 (tool system prompt)
- User message: Layer 2 (Demo Prompt) + all step prompts in sequence, requesting all steps be generated together for narrative coherence
- Tool streams the response from GitHub Models (Claude
claude-opus-4-6). - As each step's output arrives, the UI updates that step's card live.
- On completion, generated code files are written to disk
(
step-NN.csorstep-NN/). demo-script.mdis not modified by generation — only code files are written.
If the user cancels mid-generation, the pipeline stops at the current step. Any steps already generated and written to disk are left as-is — the tool makes no attempt to clean up partial state. The step cards reflect whatever was completed before cancellation. Users who want a clean slate should re-run Generate All or use version control.
Re-running Generate All overwrites all existing generated files without confirmation. This is by design. Users who want to preserve prior generations should use version control (git).
Clicking ▶ Run on a step:
- Identifies the file(s) for that step (
step-NN.csorstep-NN/). - Shells out to
dotnet runin the project root (single-file) or the step subdirectory (multi-file). - The demo apps are GUI applications —
dotnet runlaunches the GUI directly; no terminal window is needed or opened. - Build output (stdout/stderr from the build phase) is captured in-app and surfaced only if the build fails (see Build-and-Fix Pipeline).
The ▶ Run button is disabled if no generated file exists for that step, and shows a spinner while a previously launched run is still spawning.
After each step's code is generated and written to disk, the tool automatically attempts a build before moving on. If the build fails, the tool enters an AI-driven fix loop rather than surfacing the error to the user.
- Run
dotnet buildon the step's file(s) and capture stdout/stderr. - If build succeeds, continue to the next step.
- If build fails:
- Send the compiler error output back to the AI with the original generated code and a fix instruction.
- Stream the corrected code into the step's card UI (same streaming animation as initial generation).
- Write the corrected file(s) to disk and attempt the build again.
- Repeat up to 3 fix attempts per step.
- If the build still fails after 3 attempts, mark the step card with an error state and display the final compiler output inline in the card so the user can inspect it.
- Generation continues to subsequent steps regardless — a failed step does not halt the pipeline.
| State | Indicator |
|---|---|
| Not yet built | No indicator |
| Build in progress | Spinner on the card |
| Build succeeded | Subtle green checkmark |
| Build failed (fix in progress) | Amber indicator, "Fixing..." label |
| Build failed (max retries exceeded) | Red indicator, error output shown inline |
- Single-file mode:
dotnet run --project <step-NN.cs>(or, if file- based apps are unavailable on the target SDK, the tool wraps eachstep-NN.csin a transientobj/demo/step-NN/SDK-style project for the build phase). Working directory: project root. - Multi-file mode:
dotnet buildthendotnet run --no-buildinstep-NN/. Working directory: the step subdirectory.
The exact invocation is encapsulated in a DotnetRunner service so the
UI layer never shells out directly.
Clicking Export Speaker Notes:
- Collects the presenter delta for each step as generated by the AI.
- Writes a single
speaker-notes.txtfile to the project root. - Confirms export with a toast notification.
DEMO SCRIPT — [Demo Title]
Generated: [timestamp]
─────────────────────────────────────
STEP 1 — Hello World baseline
─────────────────────────────────────
Add the following NuGet reference at the top of Program.cs:
#r "nuget: Spectre.Console, 0.49.1"
Then replace the body of the file with:
using Spectre.Console;
AnsiConsole.MarkupLine("[bold green]Hello, demo![/]");
─────────────────────────────────────
STEP 2 — Add a message list
─────────────────────────────────────
After the greeting line, add the following table definition...
Speaker notes are also accessible per-step via 📋 Copy Delta, which copies that step's notes to the clipboard without writing a file.
| Setting | Value |
|---|---|
| Provider | GitHub Models |
| SDK | GitHub Models AI SDK (.NET) |
| Default model | claude-opus-4-6 (Claude Opus 4.6 via GitHub Models) |
| Model configurability | Hardcoded for v1 |
| Streaming | Yes — token-by-token streaming to the UI |
| Auth | GitHub token via interactive gh auth login console session |
On first launch (or on any API call that returns an auth error), the
tool spawns an interactive gh auth login session in a console window
to complete GitHub authentication. If an API call fails with an
authentication error after the user appears to be logged in, the tool
triggers the login flow a second time before surfacing an error to the
user. This handles token expiry without requiring the user to manually
intervene.
This sample is also a Reactor exemplar. Implementation should follow the patterns in the specs below; deviations require justification in the PR.
Each step's code viewer binds to an IObservableStream<string> exposed
by the StepCardModel. Token deltas from the GitHub Models SDK are
appended via the model's AppendToken(text) API, which dispatches a
single batched re-render on the next frame (see spec
009 — State and Components). The plain-text viewer is a thin wrapper
over Reactor's TextView component, configured with IsReadOnly = true
and FontFamily = MonospaceCodingFont.
FileSystemWatcher events are marshaled onto the UI dispatcher through
UseEffect cleanup (spec 020 — Async Resources). External edits to
demo-script.md win over in-flight debounced edits — the in-flight
buffer is discarded and the UI reloads from disk. This matches the
existing convention in samples/apps/regedit-winui/ for live registry
keys.
All header buttons (Open Folder, Generate All, Export Speaker
Notes) and per-card actions (▶ Run, 📋 Copy Delta) are
ICommands registered via Reactor's commanding system (spec 012). This
gives them automatic enable/disable wiring against background-task state
and free keyboard-shortcut binding (e.g., Ctrl+G for Generate All).
The generation pipeline, build loop, and dotnet run invocation are
each modeled as AsyncResource<T> instances (spec 020) with
cancellation tokens tied to the lifetime of the owning step card. This
ensures that closing the folder or quitting the app cleanly cancels any
in-flight work.
Reactor.Documents.Markdown (the SAX-style parser referenced in spec
013) handles demo-script.md parsing. Round-tripping unknown sections
verbatim is required — the parser exposes a RawSpan on each event
that the writer concatenates for sections it does not own.
samples/apps/demo-script-tool/
App/
Program.cs ← ReactorApp.Run<DemoScriptShell>(...)
DemoScriptShell.cs ← top-level component (header + panels)
Components/
DemoPromptPanel.cs
StepsPanel.cs
StepCard.cs
HeaderBar.cs
Services/
DemoScriptStore.cs ← read/write demo-script.md
DemoScriptParser.cs ← thin wrapper over Reactor.Documents.Markdown
GenerationPipeline.cs ← drives Layer1 + Layer2 → streamed steps
DotnetRunner.cs ← dotnet build / dotnet run shell-out
GithubModelsClient.cs ← GitHub Models AI SDK wrapper
GhAuth.cs ← gh auth login spawner + retry
SpeakerNotesExporter.cs
Models/
DemoScriptModel.cs ← title + demo prompt + List<StepModel>
StepModel.cs ← prompt, code stream, delta, build state
Resources/
SystemPrompt.txt ← Layer 1 prompt (embedded resource)
DemoScriptTool.csproj
README.md
demo-projects/ ← seeded sample demos for the README walkthrough
spectre-chat/
demo-script.md
aspire-hello/
demo-script.md
The .csproj references Reactor.WinUI, Reactor.Documents.Markdown,
and the GitHub Models AI SDK NuGet package. It is added to
reactor2.sln under the samples/apps solution folder, matching the
other apps in that folder.
The watcher policy is intentionally simple:
- External change wins. If
demo-script.mdis modified on disk while the user has unsaved debounced edits, the in-flight buffer is discarded and the UI reloads from the new disk contents. A toast notifies the user that external changes were picked up. - The watcher also detects deletes (the file being removed out from under the app) and falls back to an empty/scaffolded state.
- Generated
step-NN.csfiles are not watched — the tool is the authoritative writer. Manual edits to those files between runs are silently overwritten on the next regeneration.
| Situation | UX |
|---|---|
gh auth login fails |
Inline banner; pipeline halts |
| Network/API error mid-generation | Step card shows red border, error text inline; pipeline continues to next step |
| Build fails after 3 fix attempts | Red indicator + compiler output inline in card |
dotnet run exits non-zero |
Toast with "Run failed (exit code N) — see output", stderr captured into a per-step run log under obj/demo/step-NN.runlog |
demo-script.md malformed |
Inline banner above the steps panel describing the parse error and pointing at the offending line; the steps panel is hidden until the file parses cleanly |
No modal dialogs anywhere — the app is keyboard-driven and demo-friendly.
The app must support two run modes and a one-command path to a shareable installer.
| Mode | When | Built by |
|---|---|---|
| Unpackaged | Inner-loop / debugging — F5 from VS / VS Code, dotnet run |
WindowsPackageType=None (default .csproj from spec 031 / SKILL.md) |
| Packaged (MSIX) | Sharing with others, demoing on clean machines, validating Mica + capabilities | WindowsPackageType=MSIX via the Package/ project below |
Both modes share the same App assembly and component code — only the
.csproj head changes. There must be no #if PACKAGED divergence in
component code; runtime mode is detected at startup if needed via
Windows.ApplicationModel.Package.Current (wrapped in a try/catch since
unpackaged throws).
samples/apps/demo-script-tool/
App/
DemoScriptTool.csproj ← unpackaged head (debug default)
Package/
DemoScriptTool.Package.wapproj ← MSIX packaging project
Package.appxmanifest
Images/
StoreLogo.png Square44x44Logo.png Square150x150Logo.png
Wide310x150Logo.png SplashScreen.png
build/
sign-cert.ps1 ← creates / installs the self-signed cert
package.ps1 ← one-shot: build → MSIX → sign → output
install.ps1 ← installs the cert + MSIX on a target box
F5 on App/DemoScriptTool.csproj launches the unpackaged app for
debugging. F5 on Package/DemoScriptTool.Package.wapproj launches the
packaged app under the MSIX deployment.
- Identity / PFN —
andersonch.DemoScriptTool(per-user; not Store- bound for v1). - Publisher —
CN=DemoScriptTool Dev(matches the self-signed cert subject below). Update before publishing externally. - MinVersion / MaxVersionTested — Windows 11 22621.
- Visual assets — populated from
Package/Images/so the Start menu tile, taskbar icon, and splash screen render correctly. - Capabilities —
runFullTrust(we shell out todotnet), no broadFileSystemAccess (we use the folder picker instead).
Sharing an MSIX outside the store requires it to be signed with a cert
the target machine trusts. For sample sharing we use a self-signed
cert; recipients install the cert into LocalMachine\TrustedPeople
once.
build/sign-cert.ps1:
- If
build/DemoScriptTool.pfxalready exists, exit (idempotent). - Generate a self-signed code-signing cert:
$cert = New-SelfSignedCertificate ` -Type CodeSigningCert ` -Subject "CN=DemoScriptTool Dev" ` -KeyUsage DigitalSignature ` -FriendlyName "DemoScriptTool Dev Signing" ` -CertStoreLocation "Cert:\CurrentUser\My" ` -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}")
- Export to
build/DemoScriptTool.pfxwith a password read from$env:DEMOSCRIPT_PFX_PASSWORD(or a script-prompted secure string if unset). Also export the public.certobuild/DemoScriptTool.cerfor recipient install. - Print the thumbprint so the wapproj can pick it up.
The .pfx is gitignored. Only build/DemoScriptTool.cer (the
public cert) is checked in, so recipients can install it without
needing the signing key.
# 1. Ensure cert exists
.\build\sign-cert.ps1
# 2. Build + pack
dotnet publish .\App\DemoScriptTool.csproj -c Release -r win-x64 --self-contained
msbuild .\Package\DemoScriptTool.Package.wapproj `
/p:Configuration=Release `
/p:Platform=x64 `
/p:AppxPackageDir=..\out\ `
/p:AppxBundlePlatforms=x64 `
/p:UapAppxPackageBuildMode=SideloadOnly `
/p:AppxPackageSigningEnabled=true `
/p:PackageCertificateKeyFile=..\build\DemoScriptTool.pfx `
/p:PackageCertificatePassword=$env:DEMOSCRIPT_PFX_PASSWORD
# 3. Surface the resulting .msix path
Get-ChildItem .\out\*.msix | Select-Object -ExpandProperty FullNameOutput: out/DemoScriptTool_<version>_x64.msix.
ARM64 is supported via --Platform=ARM64 and a second pack pass; the
package.ps1 script accepts -Arch x64,arm64 and produces both.
# 1. Trust the publisher
Import-Certificate -FilePath .\DemoScriptTool.cer `
-CertStoreLocation Cert:\LocalMachine\TrustedPeople
# 2. Install
Add-AppxPackage .\DemoScriptTool_<version>_x64.msixThe README ships these two commands together with a note that the cert import requires elevation. After install, "Demo Script Tool" appears in Start.
A GitHub Actions job (.github/workflows/sample-demo-script-tool.yml)
on PRs that touch samples/apps/demo-script-tool/**:
- Builds the unpackaged head (
dotnet build App/DemoScriptTool.csproj). - Runs the selftest fixtures.
- Generates an ephemeral CI cert (not the dev cert), runs
build/package.ps1, and uploads the resulting.msixas a build artifact for manual smoke-testing on a clean VM.
samples/apps/demo-script-tool/README.md includes three sections in
this order:
- Run from source —
dotnet runagainst the unpackaged head, the default for development. - Build a shareable MSIX —
pwsh build/package.ps1end-to-end, first-run cert prompt explained. - Install on another machine —
Import-Certificate+Add-AppxPackagefrom the recipient's perspective, including the elevation requirement.
These were carried over from the source product spec; the implementing agent should resolve them during design review.
- Reactor streaming pattern. Confirm the recommended idiom for
binding a stream of tokens into a plain-text viewer in a card
collection. Candidate:
IObservableStream<string>+TextViewwithIsAppendOnly = true. - Reactor Markdown parser API. Confirm the SAX-style event surface covers numbered lists with rich-text bodies (the Steps section).
- Filesystem watcher / edit conflict. Recommended default: external file change wins and reloads the UI, discarding in-flight edits. Confirm or propose alternative.
- Build invocation. Confirm the exact
dotnetCLI invocation for building without running for both single-file and multi-file modes, and the working-directory expectations. - GitHub Models SDK packaging. Confirm the SDK is redistributable
via NuGet under the license terms used elsewhere in
samples/apps/.
- Per-step regeneration (only full regeneration supported)
- In-app diff viewer between steps
- Settings screen or model selection UI
- In-app terminal / output capture beyond build error surfacing
- Step reordering via drag-and-drop
- Collaborative editing or cloud sync
- Syntax highlighting in the code viewer
- Authoring multiple demos within one window (one folder = one demo)
Adapted from the upstream product spec at
C:\Users\andersonch\Downloads\demo-script-tool-spec.md (received
2026-05-02). This spec restates that document's requirements, fits them
to the Reactor sample-app conventions, and adds Reactor-specific
implementation guidance under
Reactor Integration Notes and
Project Layout.