.claude/skills/gen-blazor-demo-site/SKILL.md
Scaffold a complete Blazor Server documentation and demo site for a .NET library or tool. Produces a content-driven app with typed data models, in-memory content service, lazy-indexed search, dark-theme layout, and multiple page types (home, list+detail, search, knowledge map). Use for library showcases, learning tools, and interactive documentation. Invoke when the user says: create a demo site, scaffold a Blazor docs site, build a library showcase, interactive documentation site, generate a Blazor demo app. Domain: UI, Documentation. Level: Intermediate.
npx skillsauth add klod68/littlerae gen-blazor-demo-siteInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are a senior Blazor architect. Generate a complete, runnable Blazor Server documentation
site for a .NET library. Every output must compile and run with dotnet run — no placeholders.
${1:e.g. Truenorth.AiContext}${2:e.g. 23 agents, 71 skills, 11 features, 6 principles}${3|net9.0,net8.0|}${4|dark (VS Code-inspired),light,auto (system preference)|}${5:e.g. K:\repos\MyLib\MyLib.slnx}src/{LibraryName}.Web/
├── {LibraryName}.Web.csproj # Microsoft.NET.Sdk.Web
├── Program.cs # DI: content service, search service
├── appsettings.json
├── Properties/launchSettings.json
├── Components/
│ ├── App.razor # HTML entry point, CDN refs, <Routes />
│ ├── Routes.razor # Router with DefaultLayout
│ ├── _Imports.razor # Global usings incl. @using static RenderMode
│ ├── Layout/
│ │ ├── MainLayout.razor # Sidebar + topbar (markup only)
│ │ └── NavMenu.razor # Extracted nav links
│ ├── Pages/
│ │ ├── Home.razor + Home.razor.cs # Hero, stats, featured items, quick install
│ │ ├── {Entity}s.razor + .razor.cs # List + detail (route parameter switching)
│ │ ├── Search.razor + Search.razor.cs # Real-time search with debounce
│ │ └── Map.razor + Map.razor.cs # Collapsible tree/hierarchy visualization
│ └── Shared/
│ ├── {Entity}Card.razor # Card per entity type (markup only)
│ ├── StatCard.razor # Big number + label
│ ├── TagBadge.razor # Pill badge
│ └── CodeBlock.razor # Styled code with copy button
├── Data/
│ ├── Models/ # Typed records per entity category
│ │ ├── KbItem.cs # Abstract base: Id, Name, Description, Tags
│ │ └── {Entity}.cs # Sealed records inheriting KbItem
│ └── Services/
│ ├── IContentService.cs # Interface: Get{Entity}s(), Get{Entity}(id)
│ ├── ContentService.cs # Internal sealed: all data seeded in static builders
│ ├── ISearchService.cs # Interface: Search(query, categoryFilter?)
│ ├── SearchService.cs # Internal sealed: lazy index, scored matching
│ ├── IVersionService.cs # Interface: GetLatestVersionAsync(ct)
│ └── VersionService.cs # Internal sealed: two-tier version resolution
└── wwwroot/
├── css/app.css # Full theme with CSS custom properties
└── js/app.js # copyToClipboard helper
ContentService via static builder
methods. No database, no JSON files. Data IS the product. Singleton lifetime.SearchService builds its term index on first Search() call
using double-checked locking. Tokenizes Name + Description + Tags at index time..razor + .razor.cs split — Every page with logic uses code-behind. .razor files
contain only markup and @ binding expressions. All [Inject], [Parameter], event
handlers, computed properties, and lifecycle methods live in .razor.cs.[Parameter]
inputs and render HTML. No injected services, no logic.@page "/items" and
@page "/items/{ItemId}". Switch between list and detail view based on parameter presence.internal sealed class ContentService : IContentService.Singleton — data is immutable after construction.BuildAgents(), BuildSkills(), etc.IReadOnlyList<T>.GetAllItems() returns a flattened list of all entity types as IReadOnlyList<KbItem>.Dictionary<string, T> internally for O(1) lookups by ID.internal sealed class SearchService : ISearchService.Singleton — shares the content service's data.App.razor: HTML shell with <HeadOutlet />, <Routes />, CDN links for Bootstrap + Bootstrap Icons.Routes.razor: <Router> with DefaultLayout="typeof(Layout.MainLayout)"._Imports.razor must include:
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@rendermode InteractiveServer at the top.Program.cs: AddRazorComponents().AddInteractiveServerComponents() + MapRazorComponents<App>().AddInteractiveServerRenderMode().// In Search.razor.cs
private CancellationTokenSource? _debounce;
private async void OnQueryChanged(ChangeEventArgs e)
{
_query = e.Value?.ToString() ?? "";
_debounce?.Cancel();
_debounce = new CancellationTokenSource();
try
{
await Task.Delay(300, _debounce.Token);
RunSearch();
await InvokeAsync(StateHasChanged);
}
catch (TaskCanceledException) { }
}
transform: translateY(-2px) on hover.A demo/showcase site for a separate tool or library cannot simply read its own assembly version — that reflects the web project's build, not the showcased tool's published version. Querying a NuGet feed at runtime also fails for private feeds (Azure DevOps Artifacts, GitHub Packages) because anonymous HTTP requests return 401/404.
Use a two-tier resolution strategy:
ToolVersion) — set by CI/CD at deploy time to the exact
published version. ASP.NET Core config precedence means an environment variable
ToolVersion=1.3.0 overrides the blank default in appsettings.json.Add MinVer to the web project .csproj with the same tag prefix as the tool project:
<PackageReference Include="MinVer" Version="7.0.0" PrivateAssets="All" />
<MinVerTagPrefix>v</MinVerTagPrefix>
<MinVerDefaultPreReleaseIdentifiers>preview</MinVerDefaultPreReleaseIdentifiers>
Add a blank ToolVersion key to appsettings.json:
{ "ToolVersion": "" }
Version service:
internal sealed class VersionService : IVersionService
{
private readonly string _version;
public VersionService(IConfiguration configuration)
{
var configured = configuration["ToolVersion"];
_version = !string.IsNullOrWhiteSpace(configured)
? configured.TrimStart('v')
: ResolveAssemblyVersion();
}
public Task<string> GetLatestVersionAsync(CancellationToken ct = default)
=> Task.FromResult(_version);
private static string ResolveAssemblyVersion()
{
var attr = typeof(VersionService).Assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
var raw = attr?.InformationalVersion ?? "0.0.0";
var plus = raw.IndexOf('+', StringComparison.Ordinal);
return plus >= 0 ? raw[..plus] : raw;
}
}
| Approach | Problem |
|---|---|
| Query nuget.org | Package may be on a private feed |
| Query private Azure DevOps feed | Anonymous requests return 401/404; adding PAT to the web app is a secrets-management burden |
| Read the tool project's assembly at runtime | Web project has no project reference to the tool project; they may not even run on the same machine |
| MinVer alone on the web project | Derives version from the web project's git tags, which may diverge from the tool's published version |
The config-driven approach is the only reliable pattern: CI/CD knows the published version because it just built/published it, and injects it as an environment variable.
In the deployment pipeline (Azure Pipelines, GitHub Actions), set the environment variable before deploying the web app:
# Azure Pipelines example
- script: echo "##vso[task.setvariable variable=ToolVersion]$(Build.BuildNumber)"
displayName: Set tool version for web app
After creating the project, always add it to the solution file:
dotnet sln {solution-path} add src/{LibraryName}.Web/{LibraryName}.Web.csproj
Or for .slnx, add <Project Path="..." /> manually.
dotnet build passes with 0 errors@code {} blocks in .razor files (all in .razor.cs)_Imports.razor includes @using static RenderMode| Anti-Pattern | Fix |
|---|---|
| Placeholder/lorem ipsum content | Seed ALL real content verbatim |
| Single 500+ line data file | Split builders into one method per entity type |
| @code {} in .razor files | Always use .razor.cs code-behind |
| Search index built at startup | Lazy-build on first search (avoids startup latency) |
| FirstOrDefault() for ID lookups | Use internal Dictionary<string, T> |
| Forgot to add project to solution | Always wire .sln/.slnx as final step |
| Service calls inside @foreach in markup | Pre-fetch in code-behind, pass via parameter |
| Querying NuGet feed at runtime for version | Use config-driven version with MinVer fallback |
| Reading web project's own assembly version as the tool version | Two-tier: CI/CD config key → MinVer fallback |
| Hardcoding version string in layout | Always resolve dynamically via IVersionService |
tools
Use when cross-cutting concerns (logging, metrics, validation, authorization) are tangled into command handlers or service methods, when building database command pipelines with reorderable concerns, or when HTTP client pipelines or message handlers need composable, independently-replaceable processing stages. Covers ICommandInterceptor interface, InterceptorPipeline with reverse-chain construction, zero-cost Empty sentinel to skip overhead when no interceptors are registered, and ConfigureAwait(false) discipline for library code. Domain: Architecture, Cross-Cutting Concerns. Level: Intermediate. Tags: interceptor, pipeline, middleware, decorator, cross-cutting-concerns.
development
Use when writing integration tests for Razor Pages, MVC, or Minimal API applications to validate routing, middleware, page rendering, and HTTP behavior without a browser or live server, or when adding fast smoke tests to a CI pipeline. Covers WebApplicationFactory<Program> setup with public partial class Program, in-memory test server, AngleSharp HTML parsing, CSS selector assertions, redirect and status code testing, and a shared static fixture pattern for minimal per-test startup overhead. Domain: Testing, ASP.NET Core. Level: Intermediate. Tags: integration-testing, webapplicationfactory, razor-pages, anglesharp, http-testing.
development
Use when designing indexes for new tables, diagnosing slow queries that are not using indexes efficiently, reviewing index fragmentation and maintenance, or when the current indexing strategy results in key lookups, table scans, or missing index warnings. Covers clustered index key selection (narrow, unique, ever-increasing), non-clustered index design for query patterns, covering indexes with INCLUDE columns, filtered indexes for subset queries, composite index column ordering, DMV-based monitoring for missing and unused indexes, and rebuild vs reorganize maintenance thresholds. Domain: Database, Performance. Level: Intermediate. Tags: index, sql-server, covering-index, filtered-index, performance, dmv, maintenance.
development
Use when building a searchable in-memory catalog or registry for documentation sites, admin panels, or type/API browsers where you need keyword matching, fuzzy search, and ranked results without an external search engine or database. Covers RegistryService with weighted scoring across name, description, keywords, and method names; Levenshtein fuzzy matching; synonym expansion; category and subcategory filtering; and singleton DI registration for datasets of hundreds to low thousands of items. Domain: Search, Data Access Patterns. Level: Intermediate. Tags: search, registry, fuzzy-matching, in-memory, catalog, filtering.