Skip to content

Commit a216a45

Browse files
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

Reactor.sln

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chat.Model", "samples\apps\
197197
EndProject
198198
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Chat.UI", "samples\apps\chat\Chat.UI\Chat.UI.csproj", "{EDDCA324-BB03-4726-8423-902BF4BA9255}"
199199
EndProject
200+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DemoScriptTool", "samples\apps\demo-script-tool\App\DemoScriptTool.csproj", "{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}"
201+
EndProject
200202
Global
201203
GlobalSection(SolutionConfigurationPlatforms) = preSolution
202204
Debug|ARM64 = Debug|ARM64
@@ -1345,6 +1347,22 @@ Global
13451347
{EDDCA324-BB03-4726-8423-902BF4BA9255}.Release|Any CPU.Build.0 = Release|x64
13461348
{EDDCA324-BB03-4726-8423-902BF4BA9255}.Release|x86.ActiveCfg = Release|x64
13471349
{EDDCA324-BB03-4726-8423-902BF4BA9255}.Release|x86.Build.0 = Release|x64
1350+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Debug|ARM64.ActiveCfg = Debug|ARM64
1351+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Debug|ARM64.Build.0 = Debug|ARM64
1352+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Debug|x64.ActiveCfg = Debug|x64
1353+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Debug|x64.Build.0 = Debug|x64
1354+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Debug|Any CPU.ActiveCfg = Debug|x64
1355+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Debug|Any CPU.Build.0 = Debug|x64
1356+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Debug|x86.ActiveCfg = Debug|x64
1357+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Debug|x86.Build.0 = Debug|x64
1358+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Release|ARM64.ActiveCfg = Release|ARM64
1359+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Release|ARM64.Build.0 = Release|ARM64
1360+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Release|x64.ActiveCfg = Release|x64
1361+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Release|x64.Build.0 = Release|x64
1362+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Release|Any CPU.ActiveCfg = Release|x64
1363+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Release|Any CPU.Build.0 = Release|x64
1364+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Release|x86.ActiveCfg = Release|x64
1365+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379}.Release|x86.Build.0 = Release|x64
13481366
EndGlobalSection
13491367
GlobalSection(SolutionProperties) = preSolution
13501368
HideSolutionNode = FALSE
@@ -1439,5 +1457,6 @@ Global
14391457
{7CBB1E3A-B4D4-4CD6-8435-D1C35D171A16} = {E46A2C19-993A-2B88-F889-BAD0F6D10C12}
14401458
{EC3DCAD8-8304-4121-8B56-C0FDB0FCFD86} = {4E734D91-FF91-9C86-E2D6-07A7D63D64AF}
14411459
{EDDCA324-BB03-4726-8423-902BF4BA9255} = {4E734D91-FF91-9C86-E2D6-07A7D63D64AF}
1460+
{A3F705B7-B379-49DB-89D6-4AD9DA5C2379} = {2F1648C1-FA56-3C81-D04A-E07825801750}
14421461
EndGlobalSection
14431462
EndGlobal

docs/specs/tasks/035-demo-script-tool-implementation.md

Lines changed: 450 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using DemoScriptTool.App.Models;
2+
using static Microsoft.UI.Reactor.Factories;
3+
4+
namespace DemoScriptTool.App.Components;
5+
6+
public sealed record DemoPromptPanelProps(
7+
DemoScriptModel Model,
8+
System.Action<string> OnPromptChanged,
9+
System.Action<string> OnTitleChanged);
10+
11+
/// <summary>
12+
/// Top-of-body region binding to <c>## Demo Prompt</c>. The text-area writes
13+
/// into the model on every keystroke; the shell debounces persistent saves to
14+
/// disk (spec §Demo Prompt Panel).
15+
/// </summary>
16+
public sealed class DemoPromptPanel : Component<DemoPromptPanelProps>
17+
{
18+
public override Element Render()
19+
{
20+
// Local typing buffer — the source of truth WHILE the user is editing.
21+
// Keystroke flow: user types → local set → push to model (debounced save).
22+
// We deliberately do NOT subscribe to model.Changed in this panel: that
23+
// would round-trip our own keystrokes back through setState in the same
24+
// frame, which makes WinUI's TextBox clear its selection / reset the
25+
// caret to 0. The shell mints a NEW DemoScriptModel instance on Open
26+
// Folder / file-watcher reload, which lands here as a Props.Model swap
27+
// and is picked up by the dep-keyed effect below.
28+
var (title, setTitle) = UseState(Props.Model.Title);
29+
var (prompt, setPrompt) = UseState(Props.Model.DemoPrompt);
30+
31+
UseEffect(() =>
32+
{
33+
setTitle(Props.Model.Title);
34+
setPrompt(Props.Model.DemoPrompt);
35+
}, Props.Model);
36+
37+
var titleField = (TextField(title, v =>
38+
{
39+
setTitle(v);
40+
Props.OnTitleChanged(v);
41+
}, placeholder: "Demo title (rendered as # heading in demo-script.md)")
42+
with { AcceptsReturn = false })
43+
.FontSize(18)
44+
.FontWeight(Microsoft.UI.Text.FontWeights.SemiBold)
45+
.AutomationName("Demo title");
46+
47+
var promptField = (TextField(prompt, v =>
48+
{
49+
setPrompt(v);
50+
Props.OnPromptChanged(v);
51+
}, placeholder: "Describe the demo: tech stack, single-file vs multi-file, audience level, constraints…")
52+
with { AcceptsReturn = true, TextWrapping = TextWrapping.Wrap })
53+
.MinHeight(96)
54+
.MaxHeight(220)
55+
.AutomationName("Demo prompt — persistent context for AI generation");
56+
57+
return Border(
58+
VStack(8,
59+
Caption("DEMO TITLE").Foreground(Theme.SecondaryText),
60+
titleField,
61+
TextBlock("").Height(4),
62+
Caption("DEMO PROMPT").Foreground(Theme.SecondaryText),
63+
promptField))
64+
.Background(Theme.LayerFill)
65+
.CornerRadius(8)
66+
.Padding(16)
67+
.Margin(0, 0, 0, 12)
68+
.Landmark(Microsoft.UI.Xaml.Automation.Peers.AutomationLandmarkType.Form);
69+
}
70+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using static Microsoft.UI.Reactor.Factories;
2+
3+
namespace DemoScriptTool.App.Components;
4+
5+
/// <summary>
6+
/// Sticky inline banner for auth and parse failures (spec §Error Surfacing).
7+
/// Uses shape (icon) + text — never colour alone — so the message is
8+
/// distinguishable in HighContrast / NightSky.
9+
/// </summary>
10+
public static class InlineBanner
11+
{
12+
public static Element Render(string message, BannerKind kind = BannerKind.Error) =>
13+
Border(
14+
HStack(12,
15+
TextBlock(SymbolFor(kind)).FontSize(16).VAlign(VerticalAlignment.Center),
16+
TextBlock(message)
17+
.Foreground(Theme.PrimaryText)
18+
.VAlign(VerticalAlignment.Center)))
19+
.Padding(16, 12)
20+
.CornerRadius(4)
21+
.Background(BackgroundFor(kind))
22+
.WithBorder(BorderFor(kind), 1)
23+
.AutomationName(message)
24+
.HeadingLevel(Microsoft.UI.Xaml.Automation.Peers.AutomationHeadingLevel.Level2);
25+
26+
static string SymbolFor(BannerKind kind) => kind switch
27+
{
28+
BannerKind.Error => "✕",
29+
BannerKind.Warning => "⚠",
30+
BannerKind.Info => "ⓘ",
31+
_ => "ⓘ",
32+
};
33+
34+
static ThemeRef BackgroundFor(BannerKind kind) => kind switch
35+
{
36+
BannerKind.Error => Theme.SystemCriticalBackground,
37+
BannerKind.Warning => Theme.SystemCautionBackground,
38+
BannerKind.Info => Theme.SystemNeutralBackground,
39+
_ => Theme.SystemNeutralBackground,
40+
};
41+
42+
static ThemeRef BorderFor(BannerKind kind) => kind switch
43+
{
44+
BannerKind.Error => Theme.SystemCritical,
45+
BannerKind.Warning => Theme.SystemCaution,
46+
BannerKind.Info => Theme.SystemNeutral,
47+
_ => Theme.SystemNeutral,
48+
};
49+
}
50+
51+
public enum BannerKind { Info, Warning, Error }

0 commit comments

Comments
 (0)