Commit a216a45
samples: demo-script-tool (spec 035) + Command tooltip fix (#134)
* samples(demo-script-tool): initial implementation of spec 035
Add the Demo Script Tool sample app — a WinUI 3 / Reactor desktop
application that lets a presenter author a coding demo as a single
demo-script.md file and generate runnable .NET code plus speaker
notes per step via the GitHub Models AI SDK.
Implements phases 0–10 and 14 of
docs/specs/tasks/035-demo-script-tool-implementation.md:
- Domain models: StepModel (event-driven streaming via Changed) and
DemoScriptModel with sentinel-detected single/multi-file mode
- Markdown parse/serialize with RawTail round-trip preservation;
atomic store via temp + File.Move
- Streaming AI pipeline: GhAuth (env -> gh CLI -> interactive login),
GithubModelsClient (OpenAI-compat SSE), GeneratedOutputParser
(state-machine over =STEP=/=CODE=/=DELTA= envelope), StepFileWriter
(single + multi-file with .csproj scaffold fallback)
- DotnetRunner with capture-build, non-blocking-run, and transient
project wrap when file-based-app SDK support is unavailable
- 3-attempt build-and-fix loop in GenerationPipeline; one auth retry
- Filesystem watcher (100 ms debounce, dispatcher marshalled, scaffold
on delete)
- UI: TitleBar with header commands in Content slot, Mica backdrop,
three-column StepCard (prompt / code / actions) with build-state
badges by shape+text, Add/Delete step buttons, fade+slide transitions,
WithKey + PositionInSet for stable reconciliation, accent-button via
Resources() overrides, UseAnnounce for screen-reader progress
- Speaker notes export + per-card Copy Delta via DataPackage
- Inline parse-error banner + non-modal toast surface
- Two seed demos under demo-projects/ (spectre-chat single-file,
aspire-hello multi-file) and a README walkthrough
Build is clean under warnings-as-errors with the three REACTOR_A11Y
analyzers promoted to errors.
Deferred to follow-up PRs: app icon (Phase 11), MSIX packaging
(Phase 12), CI workflow (Phase 13), selftest fixtures (Phase 15).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* samples(demo-script-tool): fix text-box reset, parser tolerance, dev menu
Three follow-on fixes after end-to-end exercise of the sample:
- Text-box reset on every keystroke: DemoPromptPanel and StepCard were
syncing their local UseState buffers from model.Changed in addition
to from the user's typed value. The flow user-type → setLocal →
model.UpdateXyz → Changed fires → handler calls setLocal again →
re-render → reconciler writes TextBox.Text to the same value clears
WinUI's selection / resets the caret to 0. Drop the Changed
subscription for editable fields; the shell mints a NEW
DemoScriptModel instance on Open Folder / file-watcher reload, which
lands as a Props.Model swap and is picked up by the dep-keyed effect
to resync the buffer. StepCard keeps its Changed subscription for
streaming code / build-state mutations — those don't touch the
title/prompt buffers.
- Parser too strict for empty bold titles: a freshly-added step is
serialized as `1. ****` (no title yet), which the previous parser
rejected as malformed. The user couldn't recover — the resulting
banner hid the steps panel including the Add Step button. Empty
bold titles are now accepted; the UI renders the placeholder
"Step title" against the empty value.
- Add a DevtoolsMenu trigger to the title bar with the built-in
"Highlight reconcile changes" / "Show layout cost overlay"
toggles, plus an explorer-reveal action and a model-snapshot
Debug.WriteLine for diagnosis. Only renders in devtools sessions.
- Switch the default GitHub Models id from openai/gpt-5 (which is
not in the public catalog under that exact name) to openai/gpt-4o,
the most reliable evergreen baseline.
- Add Debug.WriteLine traces through GenerationPipeline and
GithubModelsClient covering: payload size, HTTP status, response
body on error, auth retry path, completion / cancellation. Auth-
rejected errors now include actionable copy pointing at
`gh auth login --scopes models:read` so the user has a clear next
step instead of a silent failure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* samples(demo-script-tool): generation works end-to-end
Five fixes after a full Generate → Build → Run debug session against
ReactorDemo:
1. CLI argument: pass a folder path as the first non-flag positional
arg to auto-load on launch — `dotnet run -- C:\dev\my-demo`. The
shell calls the same LoadFolderAsync path used by the picker.
2. Cross-thread setState. Promote every UseState in the shell to
threadSafe:true. Services raise events from Task.Run threads
(file watcher, generation pipeline, dotnet runner) which would
otherwise throw cross-thread on setState. Surface unhandled
exceptions from the Generate Task.Run via SetBanner so silent
pipeline failures are visible.
3. UseAnnounce thread-safety workaround. announce.Announce hits
WinUI's automation peer (`FrameworkElementAutomationPeer.FromElement`)
which is UI-thread-only and throws COMException 0x8001010E
(RPC_E_WRONG_THREAD) when the StatusReporter event raises it from
Task.Run. Marshal explicitly via DispatcherQueue.TryEnqueue.
Followup PR upstream — issue #130 filed for native fix.
4. Self-write loop. The shell's debounced save round-tripped through
the file-system watcher → reload → setModel(loaded) → new model
instance → DemoPromptPanel/StepCard's Props swap → effect re-syncs
local TextField buffer → reconciler writes TextBox.Text to the
matching value → WinUI clears the selection / resets the caret to
0 mid-keystroke. SHA-256 the bytes we're about to write; the
watcher reload reads the file and skips when the disk hash matches.
Seed the hash on initial folder load so the OS's spurious post-read
Changed event doesn't trigger either.
5. Split model.Changed into FieldsChanged (title/demo prompt) and
StepsChanged (Add/Remove/Replace). StepsPanel subscribes to
StepsChanged only — typing in the demo prompt no longer cascades
into a re-render of every step card.
Plus the prompt overhaul that gets generation actually producing
working code:
- System prompt now describes file-based .NET apps explicitly:
`dotnet run step-NN.cs` against net10 with `#:project` /
`#:package` / `#:property` directives at the top of the file.
- Reactor is the default for desktop / WinUI / XAML demos. The prompt
lists the core factories (VStack, FlexRow, Button, TextField,
TitleBar, etc.), the UseState/UseEffect API, and the
`ReactorApp.Run(...)` entry point — so the model produces real
Reactor code instead of inventing raw `Application.Start` /
`OnLaunched` / `<Window>` boilerplate.
- Required directives include WindowsAppSDKSelfContained=true and
RuntimeIdentifier=<rid> — without them the produced exe builds
but fails at startup with "This application could not be started"
because the Windows App SDK runtime can't be located.
- BuildUserPrompt computes the relative `#:project` path from
projectRoot to the local Reactor source by walking up the tree
(handles "demo lives inside the repo" and "demo is a sibling of
the repo"); pins the host RID via RuntimeInformation.OSArchitecture
so the model picks the right runtime layout.
- Default GitHub Models id is openai/gpt-4o (catalog id; spec §AI
Backend names claude-opus-4-6 but the public catalog uses path-
prefixed ids).
- Verified end-to-end: Generate streams a Reactor file with correct
directives → build returns exit 0 → ▶ Run launches the produced
exe and the user sees a Mica-backdropped window titled
"Hello Reactor Demo".
Diagnostic Debug.WriteLine traces are kept in place at structural
boundaries (Pipeline / GithubModels / Parser StepStarted+Completed /
DotnetRunner spawn / BuildFix exit code) so the next time something
silently fails, the path through the system is observable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* samples(demo-script-tool): SDK client, perf, icons, provenance
- Switch to GitHub.Copilot.SDK 0.3.0 (CopilotSdkClient) for first-party
Copilot auth; old GithubModelsClient kept as fallback. IModelClient
exposes ModelId for the per-step provenance footer.
- Pipeline robustness: thread-safe setState in the shell, dispatcher
marshal for UseAnnounce, file-watcher self-write suppression via
SHA-256 of bytes-about-to-write, split model.Changed into
FieldsChanged/StepsChanged so demo-prompt edits don't rebuild step
cards, serialize concurrent dotnet builds, exponential-backoff
retry on file lock, canonicalize step.Code from disk after write
to fix the streamed-buffer scramble.
- UX: SVG action-button icons, provenance footer + stale-since-load
indicator (round-trips via delta-sidecar YAML frontmatter), Add /
Delete step buttons, inline title editing, show-notes/show-code
toggle, devtools menu, CLI auto-load arg, scroll fix on the steps
panel, restore generated code on Open Folder.
- Reconciler perf: replace 18 .Set chains with typed modifiers /
with{} setters so OwnPropsEqual short-circuits when values are
unchanged; one-shot Border.BackgroundSizing moved to .OnMount.
* samples(demo-script-tool): generate steps sequentially with prior-code baseline
Each step now streams + builds + fixes to completion before the next
step starts, and the per-step user prompt carries the previous step's
post-fix on-disk source as a baseline. Steps accrete instead of churning
unrelated code between them.
- GenerationPipeline.GenerateAllAsync: replace the single batch stream
with a sequential loop. Per step: StreamSingleStepAsync (with one
AuthExpired re-auth retry), then await RunBuildAndFixAsync. A failed
step still doesn't halt the run — its broken on-disk code becomes the
baseline for the next step with a "did not build cleanly" hint.
- BuildSingleStepUserPrompt: emits demo context, paths, the prior step's
file body (read from OutputPath, falling back to the standard layout
or in-memory step.Code), and the current step's title + prompt with
an explicit "emit ONLY this step" instruction.
- Status header now reports "Generating / Building / Fixing step N of M
(attempt K)…" so the user can see which phase of which step is live.
- SystemPrompt.txt: rule 1 reworded as ACCRETION — when a previous-code
block is supplied, treat it as the baseline; preserve every line of
prior behavior unless the current prompt explicitly asks otherwise.
New rule 8 forbids previewing other steps. Hint text describes what
to do when the prior code is marked as not-built-cleanly.
* samples(demo-script-tool): coalesce streaming, rich-text diff, tooltip fix
- StepModel.RaiseChanged now coalesces fires to ~60 Hz (16 ms window)
via a single outstanding Task.Delay flush. Streaming-token mutations
raised Changed once per token (hundreds/sec), drowning the UI thread
in setState dispatches and wedging WinUI side-effects (tooltip
dismiss animations, scroll inertia). One frame of smoothing is
invisible for one-shot mutations and dramatic relief for bursts.
- Show-code mode now renders step.Code as a RichTextBlock with each
line that did NOT appear in the previous step rendered bold. Diff
is a cheap line-set difference computed at render time. StepCard
receives the prior StepModel via props and subscribes to its
Changed event so late prior-step updates re-trigger the diff.
Whitespace-only lines are never bolded.
- src/Reactor/Core/CommandBindings.cs: when a Command sets an
Accelerator with no Description, suppress WinUI's auto-generated
bare-chord tooltip ("Ctrl+O") by setting
KeyboardAcceleratorPlacementMode=Hidden on the host control. The
bare tooltip is not very useful on its own and has been observed
to stick on screen during UI-thread bursts. Callers who want a
visible tooltip should set Description.
* samples(demo-script-tool): halt-on-fail, rerun-from-here, --devtools
- Pipeline now halts the moment a step ends in BuildState.Failed (or
produces no code). Continuing past a failure produced cascade-failure
runs in practice — the broken on-disk code became step N+1's baseline
and the model couldn't recover. The banner names the failed step and
points the user at Re-run from here.
- New GenerateFromAsync(model, root, startIndex, ct). GenerateAllAsync
delegates with start=0; the new "Re-run from here" action on each
step card calls it with that step's index. Re-running one step
inevitably changes its prior code for everything downstream, so we
always re-run the chain forward — never just one step in isolation.
- StepCard gets a fifth action button (rerun.svg, blue circular arrow)
with automation name "Re-run step N and every following step".
Disabled while a generation pass is already in flight to keep the
cancellation-token / isGenerating state consistent.
- DotnetRunner.RunAsync now appends `-- --devtools` so the spawned
step app's Reactor devtools overlay is reachable from the demo.
* samples(demo-script-tool): commandify actions, fix wheel + tooltip
- StepCard's five action buttons (Run, Show code/notes, Copy notes,
Re-gen, Delete) are now Command-driven. Run and Re-gen wrap an
ExecuteAsync via UseCommand so the framework tracks IsExecuting and
auto-disables the button while the action is in flight. Run gets a
1.5 s Task.Delay so the visible disable lasts long enough to swallow
rapid double-clicks (the spawn itself returns instantly); Re-gen
gets a 250 ms delay so its IsExecuting window overlaps with the
shell flipping Props.IsGenerating to true on the next render. The
ActionButton helper now takes a Command directly.
- Show-code mode's inner ScrollView no longer eats the mouse wheel.
When a long line forced the horizontal scrollbar to appear, the
ScrollView captured every wheel event — including pure-Y motion —
blocking the parent steps panel from scrolling. Setting
VerticalScrollMode=Disabled on that ScrollView routes vertical
wheel up to the parent; the user navigates long lines via the
visible horizontal scrollbar.
- src/Reactor/Core/CommandBindings.cs: when a Command has an
Accelerator but no Description, fall back to using the command
Label as the explicit ToolTip. WinUI's auto-generated bare-chord
tooltip ("Ctrl+O") only kicks in when ToolTipService.ToolTip is
genuinely unset, so any non-null value defeats it. The earlier
KeyboardAcceleratorPlacementMode=Hidden alone wasn't enough — it
governs the Alt-overlay key tip, not the hover tooltip.
* samples(demo-script-tool): address PR #134 review feedback
- StepFileWriter: refuse model-emitted relative paths that escape the step
directory (path traversal via "..", absolute paths, drive letters).
Resolves and verifies each target is strictly inside stepDir before
writing — model output is untrusted, not a sandbox.
- StepFileWriter: fall back to the first .cs file when multi-file mode
doesn't emit Program.cs, so OutputPath always points at a real file.
Without this, alternate entry-point names left primary as the directory
and downstream File.Exists checks silently no-op'd (canonicalize-on-write
skipped, Open Folder couldn't restore step.Code).
- IModelClient + CopilotSdkClient + GithubModelsClient: new ResetAsync
method so a long-lived SDK client (CopilotSdkClient caches a started
CopilotClient) can be torn down after gh auth refresh. Pipeline calls
it in both auth-retry paths — without it, refreshing gh auth was a
no-op because the CopilotClient session was authenticated under the
old token. GithubModelsClient implements as no-op (it reads the token
per-call already).
- DemoScriptParser: drop section-0 preamble content rather than capturing
it into RawTail. The serializer always writes RawTail at the end, so
capturing preamble silently moved any text between H1 and the first
H2 below the steps section on the next save.
- README: rewrite the auth section to describe the actual Copilot SDK /
Copilot CLI flow (gh auth + Copilot-enabled account) instead of the
obsolete GITHUB_TOKEN / models:read scope description.
Refs: copilot-pull-request-reviewer comments on PR #134.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 9ff621d commit a216a45
38 files changed
Lines changed: 5210 additions & 0 deletions
File tree
- docs/specs/tasks
- samples/apps/demo-script-tool
- App
- Assets/Icons
- Components
- Models
- Resources
- Services
- demo-projects
- aspire-hello
- spectre-chat
- src/Reactor/Core
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
197 | 197 | | |
198 | 198 | | |
199 | 199 | | |
| 200 | + | |
| 201 | + | |
200 | 202 | | |
201 | 203 | | |
202 | 204 | | |
| |||
1345 | 1347 | | |
1346 | 1348 | | |
1347 | 1349 | | |
| 1350 | + | |
| 1351 | + | |
| 1352 | + | |
| 1353 | + | |
| 1354 | + | |
| 1355 | + | |
| 1356 | + | |
| 1357 | + | |
| 1358 | + | |
| 1359 | + | |
| 1360 | + | |
| 1361 | + | |
| 1362 | + | |
| 1363 | + | |
| 1364 | + | |
| 1365 | + | |
1348 | 1366 | | |
1349 | 1367 | | |
1350 | 1368 | | |
| |||
1439 | 1457 | | |
1440 | 1458 | | |
1441 | 1459 | | |
| 1460 | + | |
1442 | 1461 | | |
1443 | 1462 | | |
Large diffs are not rendered by default.
Loading
Loading
Loading
Loading
Loading
Loading
Lines changed: 70 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
Lines changed: 51 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
0 commit comments