Install
$ agentstack add skill-srnichols-plan-forge-ui-scaffold ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
UI Scaffold Skill (Blazor + Fluent UI)
Trigger
"Scaffold a Clients page" / "Add a UI for invoices" / "Create a Blazor form for time entries" / /ui-scaffold Clients --crud
Why This Skill Exists
A naïve scaffold produces a .razor file that injects the DbContext, queries directly, and ships with no tests, no error UI, no accessibility. That's vibe-coded UI — exactly what Plan-Forge exists to prevent. This skill enforces the layered architecture from architecture-principles.instructions.md and the component discipline from blazor-fluent-ui.instructions.md on every new page.
Preconditions (verify before scaffolding)
- The project is a Blazor Server app (or Blazor United host) on .NET 8+
Microsoft.FluentUI.AspNetCore.Componentsis referenced in the Web project- The entity model exists in a Core/Domain project (e.g.,
TimeTracker.Core/Models/Client.cs) .github/instructions/blazor-fluent-ui.instructions.mdis present (loaded by setup)
If any precondition fails, stop and surface the gap — do not scaffold against an incompatible project.
Steps
1. Confirm Scope
Read the entity model and any existing service for the same entity.
read_file path: src/.Core/Models/.cs
file_search query: src/**/Services/IService.cs
If a service already exists, scaffold the UI against it. If not, scaffold the service interface first (Step 2). Do not let the page reach into the DbContext even temporarily — every shortcut becomes permanent.
2. Service Layer (if missing)
Generate or update:
src//Services/IService.cs— interface withGetAllAsync(CancellationToken),GetByIdAsync(Guid, CancellationToken), plusCreateAsync/UpdateAsync/DeleteAsyncfor--crudmode.src//Services/Service.cs— implementation that takesDbContext(or repository) via constructor injection.
Register the service in Program.cs:
builder.Services.AddScoped();
3. Form Model / DTO (for --crud and --form-only)
Create a DTO under src//Models/FormModel.cs. Never bind EditForm to the EF entity directly. Decorate with DataAnnotations for client-side validation:
public class ClientFormModel
{
[Required, StringLength(200)]
public string Name { get; set; } = "";
[Range(0.01, 10_000)]
public decimal HourlyRate { get; set; }
public bool IsActive { get; set; } = true;
}
Add ToCommand() / FromEntity() mapping methods on the DTO.
4. Page Component (Markup + Code-Behind Split)
For non-trivial pages, always use the .razor + .razor.cs partial-class pattern. Single-file @code blocks are reserved for trivial display components.
src//Pages/.razor (markup):
@page "/"@rendermode InteractiveServerat the page boundary- `` set
- Layout via
FluentStack/FluentGrid— no inlinestylefor spacing - Three render branches:
_loading→ `,_loadError→, success →` - All interactive elements have visible text or
aria-label
src//Pages/.razor.cs (code-behind):
partial classimplementingIDisposable[Inject]properties (never@injectdirectives in code-behind pages)- Single
CancellationTokenSource _cts = new();cancelled inDispose OnInitializedAsyncwrapped in try/catch with three branches:OperationCanceledException→ silent (navigation aborted)Exception→ log structured + set_loadErrorfinally→_loading = false;ILoggerinjected for structured logging
For --crud add: Edit/Create.razor + code-behind with EditForm, DataAnnotationsValidator, submit-disabled-while-in-flight, success/error FluentToast via IToastService.
5. bUnit Test (skip with --no-test only when explicitly justified)
tests/.Tests/Pages/Tests.cs:
public class ClientsTests : TestContext
{
[Fact]
public void Renders_progress_ring_while_loading()
{
var service = Substitute.For();
service.GetAllAsync(Arg.Any())
.Returns(new TaskCompletionSource>().Task);
Services.AddSingleton(service);
var cut = RenderComponent();
cut.Markup.ShouldContain("FluentProgressRing");
}
[Fact]
public async Task Renders_grid_with_data_after_load() { /* … */ }
[Fact]
public async Task Renders_error_message_when_service_throws() { /* … */ }
}
At minimum cover: loading state, success state with non-empty data, error state when the service throws.
6. Accessibility Pass (mandatory)
Before declaring the scaffold done, verify against blazor-fluent-ui.instructions.md:
- [ ] `` set
- [ ] Every
FluentButtonhas visible text ORaria-label - [ ] Every form field has
Label="…" - [ ] Loading/error regions have
aria-live(or use Fluent components that handle it) - [ ] No CSS overrides removing the focus ring
7. Hand-off Report
UI Scaffold Complete:
Created:
✓ src/.Web/Services/IService.cs (if missing)
✓ src/.Web/Services/Service.cs (if missing)
✓ src/.Web/Models/FormModel.cs (--crud / --form-only)
✓ src/.Web/Pages/.razor + .razor.cs
✓ src/.Web/Pages/Edit.razor + .razor.cs (--crud)
✓ tests/.Web.Tests/Pages/Tests.cs
Modified:
✓ src/.Web/Program.cs (service registration)
Verified:
✓ No DbContext injected into components
✓ Code-behind split (markup ≤ 100 lines, code-behind ≤ 250 lines)
✓ CancellationToken propagated through all async lifecycle methods
✓ Loading + success + error states all render
✓ Accessibility checklist passes
✓ bUnit tests cover loading / success / error
Run:
dotnet build
dotnet test --filter "FullyQualifiedName~Tests"
dotnet run --project src/.Web
Constraints
- Never scaffold a page that injects
DbContextdirectly. If the service interface is missing, scaffold it first (Step 2). No exceptions. - Never bind
EditFormto an EF entity. Always go through a DTO. - Never skip the test (
--no-test) without an explicit, recorded justification. The test is part of the scaffold; it is what makes the scaffold "enterprise-grade" instead of vibe-coded. - Never reach across UI frameworks. If the project uses Fluent UI, the scaffold uses Fluent UI — do not introduce Bootstrap, MudBlazor, or hand-rolled CSS just because a snippet is faster to write.
- Match the project's existing folder structure — read
presets/dotnet/.github/instructions/blazor-fluent-ui.instructions.mdfor the canonical patterns.
Modes
| Flag | Generates | |---|---| | --read-only (default) | List page only — .razor with FluentDataGrid | | --crud | List + Create + Edit + Delete confirm + DTO + form | | --form-only | Just the form (Create/Edit) — for sub-pages of an existing parent | | --no-test | Skip bUnit test (must be justified — surface a warning) |
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: srnichols
- Source: srnichols/plan-forge
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.