Files
Javier Calvarro Nelson 198e58c983 Add blazor skills to dotnet-blazor plugin (#357)
* Add dotnet-blazor plugin and convert-blazor-server-to-webapp skill

Add the dotnet-blazor plugin with 9 skills covering Blazor Web App development:
- plan-ui-change: Plan and scaffold UI features in Blazor Web Apps
- create-blazor-project: Create new Blazor projects with proper render mode setup
- author-component: Author Razor components with parameters, events, lifecycle
- coordinate-components: Share state across components using CascadingValueSource/scoped services
- use-js-interop: Call JavaScript from Blazor and vice versa
- fetch-and-send-data: HTTP data access with proper service patterns
- configure-auth: Set up ASP.NET Core Identity and authorization in Blazor
- collect-user-input: Build forms with EditForm, validation, and file uploads
- support-prerendering: Handle prerendering lifecycle and state persistence

Add convert-blazor-server-to-webapp skill to the dotnet-aspnet plugin for
migrating .NET 7 Blazor Server apps to .NET 8+ Blazor Web App architecture.

All 10 skills pass evaluation with quality improvements ranging from 12-50%
and overfitting scores within acceptable thresholds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Update eng/allowed-external-deps.txt

* Update .gitignore

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 16:25:27 +00:00

159 lines
8.7 KiB
YAML

scenarios:
- name: "Auto-saving notepad that survives page reloads"
prompt: |
I need a `Notepad` component for my Blazor Web App.
Requirements:
- A textarea where the user can type notes
- Content is automatically saved to the browser every 30 seconds
- When the user returns to the page (or reloads), their last saved content is restored
- A "Last saved at HH:MM:SS" label that updates after each save
- If the user has unsaved changes and tries to close the browser tab, show the browser's
built-in "Leave site?" confirmation dialog
- A "Clear" button that erases both the textarea and the saved data
- A "Save now" button for manual saves
setup: &blazor_project
commands:
- "dotnet new blazor --interactivity Server --no-https"
assertions:
- type: "output_contains"
value: "OnAfterRenderAsync"
- type: "output_contains"
value: "IAsyncDisposable"
- type: "output_matches"
pattern: "(export function|export \\{)"
rubric:
- "Uses a collocated .razor.js file with exported functions — not a global script tag or window-level globals"
- "Initializes interop in OnAfterRenderAsync(firstRender), not in OnInitializedAsync — JS is unavailable during prerendering"
- "Restores saved content on first interactive render so the user sees their previous notes immediately"
- "Batches related browser operations into single JS calls where possible — e.g., save content + timestamp together instead of separate interop calls for each"
- "Registers and removes the beforeunload listener properly — adds on setup, removes on dispose"
- "Manages the auto-save interval in JS and clears it on dispose — no dangling timers"
- "Implements IAsyncDisposable and catches JSDisconnectedException so disposal doesn't throw when the circuit is already gone"
- "Uses InvokeVoidAsync when no return value is needed"
- "Does not use fire-and-forget (unawaited ValueTask) — all JS calls are properly awaited"
- "The clear function removes stored data and resets the beforeunload guard in one operation"
- "Uses sessionStorage (or localStorage) for persistence, the beforeunload event for the unsaved-changes prompt, and setInterval/clearInterval for the auto-save timer"
timeout: 600
- name: "User activity tracker that detects idle timeout"
prompt: |
I need a `UserActivityTracker` component for my Blazor Web App.
Requirements:
- Monitors user activity on the page (mouse movement, key presses, clicks, scrolling)
- After a configurable period of inactivity, triggers an `EventCallback OnIdle` to notify the parent
- When the user becomes active again after being idle, triggers `EventCallback OnActive`
- The idle timeout duration is set via `[Parameter] int TimeoutSeconds` (default 300)
- Wraps child content — can be placed around any section of the app
- After the component is removed from the page, no leftover timers or event handlers should remain
setup: *blazor_project
assertions:
- type: "output_contains"
value: "OnAfterRenderAsync"
- type: "output_contains"
value: "DotNetObjectReference"
- type: "output_contains"
value: "IAsyncDisposable"
- type: "output_matches"
pattern: "(export function|export \\{)"
rubric:
- "Uses a collocated .razor.js file with exported functions — not global window functions or inline scripts"
- "Registers activity event listeners (mousemove, keydown, click, scroll) in OnAfterRenderAsync(firstRender) — not during prerendering"
- "Uses DotNetObjectReference so JS can call back into .NET for idle/active transitions and disposes it in DisposeAsync"
- "Implements IAsyncDisposable and catches JSDisconnectedException in DisposeAsync"
- "Manages the idle timeout timer in JavaScript using setTimeout/clearTimeout — not via .NET Task.Delay"
- "Cleans up all event listeners and timers in the JS dispose function — no dangling handlers after component removal"
timeout: 600
- name: "Responsive layout that adapts to screen size"
prompt: |
My Blazor Web App needs to adapt its layout based on the browser viewport width. I need:
1. A `ScreenSizeProvider` component that wraps child content and detects the current
viewport category:
- `Mobile` (width < 768px)
- `Tablet` (768px ≤ width < 1024px)
- `Desktop` (width ≥ 1024px)
2. The current category should be available to all child components as a cascading value
3. When the user resizes their browser, the category should update in real time
4. The default category (before detection runs) should be `Desktop`
Usage:
```razor
<ScreenSizeProvider>
<MyNavigation /> @* can read the cascading ScreenSize value *@
<MyContent />
</ScreenSizeProvider>
```
setup: *blazor_project
assertions:
- type: "output_contains"
value: "CascadingValue"
- type: "output_contains"
value: "OnAfterRenderAsync"
- type: "output_contains"
value: "IAsyncDisposable"
- type: "output_matches"
pattern: "(export function|export \\{)"
rubric:
- "Provides the screen size category to children via CascadingValue — child components can consume it with [CascadingParameter]"
- "Uses a collocated .razor.js module with exported functions — not global scripts"
- "Sets up matchMedia listeners or resize listener in OnAfterRenderAsync(firstRender) — not in OnInitializedAsync"
- "Defaults to Desktop (or a sensible fallback) during prerendering before JavaScript is available"
- "Updates the category in real time when the browser is resized — uses event-based detection, not polling"
- "Uses DotNetObjectReference for the JS resize/matchMedia callback and disposes it in DisposeAsync"
- "The [JSInvokable] callback wraps state changes in InvokeAsync(StateHasChanged)"
- "Implements IAsyncDisposable and catches JSDisconnectedException in DisposeAsync"
- "Removes matchMedia/resize event listeners in the JS dispose function — no dangling listeners"
- "Does not call JS interop during prerendering — guards all JS calls behind firstRender or a flag"
- "Uses window.matchMedia or the resize event for viewport detection — not polling or server-side user-agent sniffing"
timeout: 600
- name: "Infinite scroll list using IntersectionObserver"
prompt: |
I need an `InfiniteScrollList` component for my Blazor Web App that loads more items
as the user scrolls.
Requirements:
- Displays items in a scrollable container
- When the user scrolls near the bottom, automatically triggers loading of more items
- Shows a loading spinner while fetching
- Stops triggering when there are no more items (parent signals this via a parameter)
- The component is generic — it accepts a `RenderFragment<TItem>` for rendering each item
- Uses an efficient browser API for scroll detection (not scroll event listeners)
Usage:
```razor
<InfiniteScrollList Items="products" HasMore="hasMore"
OnLoadMore="LoadNextPage" Context="product">
<div class="product-card">@product.Name — @product.Price</div>
</InfiniteScrollList>
```
setup: *blazor_project
assertions:
- type: "output_contains"
value: "IntersectionObserver"
- type: "output_contains"
value: "OnAfterRenderAsync"
- type: "output_contains"
value: "IAsyncDisposable"
- type: "output_contains"
value: "DotNetObjectReference"
rubric:
- "Uses a collocated .razor.js file with exported functions — not a global script"
- "Creates IntersectionObserver in OnAfterRenderAsync(firstRender) — not during prerendering"
- "Uses a sentinel element (observed by IntersectionObserver) at the bottom of the list to detect when to load more"
- "Uses DotNetObjectReference to receive the intersection callback from JS and disposes it in DisposeAsync"
- "The [JSInvokable] callback is public and wraps state changes in InvokeAsync(StateHasChanged)"
- "Calls observer.disconnect() in the JS dispose function — no dangling observers"
- "Implements IAsyncDisposable and catches JSDisconnectedException in DisposeAsync"
- "Uses ElementReference for the sentinel element — not a string ID"
- "Respects the HasMore parameter — does not trigger loads or observe the sentinel when there are no more items"
- "Uses IntersectionObserver (not scroll event listeners) for efficient visibility detection"
timeout: 600