All notable changes to ShellDocs land here. Format follows Keep a Changelog. Versioning is SemVer with prerelease suffixes (-alpha, -beta, -rc) — the alpha window explicitly reserves the right to break APIs on minor bumps.
Fixes the interactivity gap the 0.1.5-alpha static-prerender pipeline opened up. Four chrome interactions (sidebar section expand/collapse, package selector dropdown, TableOfContents scroll-spy, PreviewFrame source view) were written as ordinary interactive Razor components with @onclick handlers that mutate [Parameter] bool state and re-render via StateHasChanged(). Prerendered HTML captured only the initial state; on a static host with no Blazor runtime, every one of those interactions was dead on the deployed site.
The 0.1.5-alpha CHANGELOG glossed this as "SignalR-backed component state doesn't survive the static build — but a docs site doesn't need it." That was wrong. A docs site's primary navigation surface is the sidebar; if you can't expand a section, you can't reach the pages under it.
- Sidebar section expand/collapse works on static-hosted builds.
DocsSidebarNode.razorno longer routes clicks through Blazor's@onclick="Toggle"+_isOpenstate. Instead,shelldocs.jsattaches a delegatedclicklistener on.sidebar-section-toggleand flips[data-open]on the ancestor.sidebar-sectionand its.sidebar-section-shellchild. The initial[data-open]value (fromOnParametersSet's active-path check) still comes from server rendering — so the ancestor of the current page pre-expands correctly on first paint. CSS unchanged; it already selected on[data-open]for both the chevron rotate and the grid-rows animation. - Package selector dropdown opens/closes on static-hosted builds.
PackageSelector.razoralways renders the.pkg-menunow (previously conditional on_open); CSS hides it underdisplay: noneunless.pkg[data-open="true"]. Delegated JS handler on.pkg-triggerflips[data-open]; outside-click closes any open menu. Options are plain<a href={RootUrl}>— native navigation, no Blazor round-trip. - TableOfContents scroll-spy attaches on static-hosted builds. Removed the
OnAfterRenderAsync→shelldocsToc.attachinvocation (which only fires with a live Blazor runtime). TOC list now emits[data-toc-list]+[data-toc-ids="id1,id2,..."];shelldocs.jsscans for these onDOMContentLoadedand after Blazorenhancedloadand callsshelldocsToc.attachitself. Anchor click uses nativehref="#id"navigation. PreviewFrameandComponentPreviewsource-view expand/collapse work on static-hosted builds. Removed@onclick="Expand"/Collapse/Show/Hideand the_expanded/_showSourcestate fields. Buttons carrydata-preview-toggle="expand|collapse";shelldocs.jstoggles the same.expanded/.collapsedclasses the Blazor state used to toggle. The copy button follows the same pattern ([data-preview-copy]).
Every one of these interactions still works under a live Blazor runtime — the DOM emits the same initial state Blazor's rendering produced; JS mutations happen on top. Delegated document-level click listeners survive Blazor's enhanced-nav DOM swap without re-attaching; only the TOC scroll-spy needs re-init on enhancedload because heading IDs change per page.
DocsSidebarNode.Toggle()method — click handling is JS-side now.PackageSelector._openstate,Toggle(),Choose(),OnBlur()— same reason; navigation is plain anchor.TableOfContents._handle/_sig/Scroll()and theIJSObjectReference/IAsyncDisposablemachinery — scroll-spy attaches from JS, scroll-to uses native anchor.PreviewFrame._expanded/_copied/_highlighted/_codeEl/Expand()/Collapse()/Copy()— all JS-side.ComponentPreview._showSource/_copied/_highlighted/_sourceEl/Show()/Hide()/Copy()— same.
Three authoring / SEO features that stack together to make writing per-component docs and shipping a public site substantially less manual.
<AutoTypeTable Component="Name" />primitive. Renders a full props table for any registered component by reflecting on its[Parameter]properties. Reads type (compact C#-ish rendering including nullability and generic args), default (from[DefaultValue]or from instantiating the type and reading the property), required flag (from[EditorRequired]), and description (from XML doc<summary>on the property, loaded from the sidecar<Assembly>.xmlnext to the DLL). Replaces the "hand-copy every prop into<TypeRow>" pattern that every consumer was doing today. Existing<TypeTable>/<TypeRow>stay for cases where hand curation is preferable.- Named
RenderFragmentslots inrazor:previewfences and inline component tags. Direct-child tags whose name matches a target component's[Parameter] RenderFragmentprop now route into that named slot instead of being flattened intoChildContent. Authors can finally write compositional previews:and have<Alert Title="Heads up"> <Icon><svg>…</svg></Icon> Body text. <Footer>Small print.</Footer> </Alert>
<Icon>render intoAlert.Icon,<Footer>intoAlert.Footer, and the leftover text intoAlert.ChildContent. Unblocks previewing every ShellUI-shaped component that uses named slots (Alert, Card, Sheet, Drawer, Tabs, Accordion, Dialog). shelldocs build --site-url <https://example.com>emits sitemap.xml + robots.txt +og:*meta tags on every prerendered page. Sitemap enumerates every URL discovered by the prerender walk (visible + hidden). Robots allows all and points at the sitemap. Per-page<meta property="og:title">/og:description/og:url/og:typeinjected before</head>from the nav-graph node's title + description. All three artifacts are silently skipped when--site-urlis unset — no accidental broken sitemaps in local dev builds.ShellDocsOptions.SiteUrl— declared symmetrically with the CLI flag for consumers who prefer configuration-side declaration (currently informational;shelldocs build --site-urlremains the mechanism the CLI consumes).
SlotRenderer.BuildParametersnow walks the target's[Parameter]props once to extractRenderFragmentnames before falling back to the pre-fixChildContent-only behavior. Backwards compatible — no named-slot tags in child content → identical output to before.
XmlDocIndex caches per-assembly load once; concurrent consumer requests share the parsed dictionary. XML docs are optional — a component whose assembly ships without an .xml file just renders em-dash descriptions.
The shelldocs build output is now a real static site. Previously it copied publish/wwwroot/ 1:1 — fine on paper, useless in practice for a Blazor Server scaffold, which produces no index.html, no client-side runtime, nothing a static host can serve at the root URL. Deploying the output to GH Pages / Cloudflare Pages / Netlify silently returned a blank shell. Now every URL the site knows about is prerendered at build time and the framework's static assets are merged on top.
shelldocs buildproduces a fully static site deployable to GH Pages / Cloudflare Pages / Netlify without a .NET host. Previously the CLI randotnet publishand copiedwwwroot/verbatim — which, for a Blazor Server scaffold (whatshelldocs initproduces by default), contains only static assets and no HTML entrypoint. Visiting the deployed root returned an empty page; deep links 404'd. New flow: publish → launch the published app on a loopback ephemeral port → walkNavigationGraph.AllUrlsto enumerate every route (visible + hidden) plus/→HttpClient.GetAsynceach URL → save the rendered HTML to<output>/<url>/index.html. Then mergepublish/wwwroot/(_content/,_framework/blazor.web.js,app.css,favicon.png, tokens CSS) on top. Result is a directory any dumb file host can serve; every URL is a real HTML file with content in the response body. Server scaffold unchanged — the prerender happens entirely insideshelldocs build.--base-hrefnow rewrites<base href>in every prerendered HTML file, not just the rootindex.html. The pre-fix pass only touched one file, so subpath deploys (GH user-repo pages at/repo/) worked for the home page but broke every deep-linked doc route (assets 404'd because the deep-linked pages still had<base href="/">). New pass walks<output>/**/*.htmland rewrites every occurrence.
NavigationGraph.AllUrls— public enumeration of every indexed URL (visible + hidden). Needed by the new prerender walk so hidden pages still land in the static output (they route at runtime and would 404 on the static site otherwise). Trivial addition, no API break.PrerenderRunnerinternal helper inShellDocs.CLI— encapsulates the subprocess launch + readiness poll + URL walk + file save. Kept out ofBuildCommandso the orchestration reads linearly. Guarantees subprocess cleanup on every exit path (finally +ProcessExit+CancelKeyPress).
- URL discovery is content-folder-based, not runtime-based.
PrerenderRunnerwalks URLs viaNavigationGraphBuilder.Build(contentRoot)— same logic the running app uses — so file-based routes are complete. Custom routes registered outsidecontent/(bespoke@pagedirectives) won't be prerendered and will need to be added manually. Not a regression from0.1.4-alphabehavior (which prerendered nothing). - Interactive server components at request time. The prerendered HTML is a snapshot of the initial render; hydration on the client picks up from there via
<script src="_framework/blazor.web.js">. Deep-linking to a page hits the static HTML; the theme toggle, search, tabs, code copy work via the shippedshelldocs.json top. Full Blazor Server interactivity (SignalR-backed component state) doesn't survive the static build — but a docs site doesn't need it.
0.1.3-alpha pinned the sidebar footer to a fixed slot but only under the desktop Sidebar variant — footer still floated mid-sidebar on TopNav, mobile drawer, and short-tree cases. This release covers that plus three primitive-DX improvements.
- Sidebar footer now pins to the bottom in every layout context. The
0.1.3-alphafix only worked in the desktop Sidebar variant — theflex: 1; min-height: 0sizing that lets the nav fill its slot was scoped to.docs-shell-sidebar .docs-sidebar-slot > navinside a@media (min-width: 1024px)block. On the TopNav variant, the mobile drawer, or any Sidebar site whose tree is shorter than the slot, the nav still collapsed to intrinsic content size and the footer (GitHub link + theme toggle) sat wherever the tree ended, mid-sidebar. Movedflex: 1; min-height: 0onto.docs-sidebaritself and made.docs-sidebar-slotdisplay: flex; flex-direction: columnin every context. Footer pins hard to the bottom regardless of variant / viewport / item count. razor:previewfences with an unknown outer tag now render a visible error in the frame instead of falling back to a plain code block silently. PreviouslySlotExtractor.TryBuildPreviewSlotreturnednullwhen the fence's first tag didn't resolve to a registered component; the fence rendered as regular fenced code with only a build-log warning. Authors chasing "why isn't my icon rendering" would hunt for a nonexistent component bug. Now the same case emits aPreviewSlotwithComponentType = nulland anErrormessage;PreviewFramerenders a red-tinted error panel in the render region naming the unknown tag and pointing ato.RegisterComponent<T>()/o.RegisterComponentsFromAssembly<TMarker>(). Build-log warning still emitted.PreviewSlot.ComponentTypeis now nullable — technically a source-breaking change for callers pattern-matching on it, though external consumers of that type are ~none in the alpha window.
RegisterComponentsFromAssembly<TMarker>(string namespacePrefix)overload. Registering only components under a specific namespace from a big assembly no longer needs aFunc<Type, bool>— the common "register everything under my Components namespace" case reads as:instead of the lambda form. Theo.RegisterComponentsFromAssembly<Marker>("ShellIcons.Icons");
Funcoverload stays for anything more complex.shelldocs initscaffoldedProgram.csnow surfaces theLayoutVariantknob. Commented-out// o.LayoutVariant = DocsLayoutVariant.Sidebar;line right in theAddShellDocs(...)block, plus theRegisterComponentsFromAssemblyhint. First-time consumers no longer have to grepShellDocs.Components/Layouts/DocsLayout.razorto discover the sidebar-variant option exists.
Two items deferred to future releases; both need spec-level design rather than a patch:
shelldocs buildproduces noindex.html, andinit/buildrender-mode mismatch. The scaffolded project is Server-interactive butbuildprints "publish kind: static (Blazor WASM)" and copies the resultingwwwroot/— which for a Server-interactive project has noindex.html, no_framework/dotnet.js, no runtime blob. Output is unusable as a static site (blocks GH Pages / Cloudflare / Netlify deploys). Two viable paths (server-side prerender walk of the nav graph, or scaffold WASM Standalone by default) — either is a substantial change toBuildCommandand/orInitCommand. Consumers workaround:dotnet runlocally, skipshelldocs build.- Markdig mangles inline HTML wrappers between component slots in
razor:preview. Plain<span style="color:…">around a registered component tag inside a preview loses its parent-child relationship after Markdig's inline pass, because SlotExtractor lifts component tags before Markdig sees them. Workaround: use registered wrapper components with their own attribute props instead of raw inline HTML. Framework fix would need SlotExtractor to lift-and-preserve trivial wrappers (<span>,<a>,<button>) around component tags.
One sidebar-chrome bug + a broader icon vocabulary for real-world consumer sites.
.docs-sidebarno longer pushes its footer off-screen when the tree scrolls. InDocsLayoutVariant.Sidebar, once the sidebar tree grew tall enough to need internal scrolling, the footer (GitHub link + theme toggle) disappeared below the visible area of the floating sidebar card. Root cause:DocsSidebar.razor.csssetheight: 100%on the nav, which in a flex-column parent resolves against the parent's full content box (header + nav) instead of the remaining space, overriding theflex: 1; min-height: 0sizing fromDocsLayout.razor.css. Removed theheight: 100%and addedmin-height: 0in its place — footer now stays pinned to the bottom of the slot regardless of tree depth.
- Broader
SidebarIconscoverage. Hand-curated icon map grew from ~20 entries to ~50. New titles covered:Authoring,CLI(+Clialias for auto-title-cased folder names),Packages,Configuration,Project Structure,Quick Start,Frontmatter,Fenced Code,Razor Preview,Inline Component Tags,Navigation, plus PascalCase and space-separated variants of every content primitive (CardGrid/Card Grid,LinkCard/Link Card,Steps,FileTree/File Tree,TypeTable/Type Table,CodeGroup,PreviewFrame/Preview Frame,ComponentPreview/Component Preview) and the four CLI command names (shelldocs init/add/dev/build+ bareInit/Add/Dev/Build). Closes the visual gap where categories a mature consumer's site actually uses rendered without an icon while the framework's own vocabulary had one. Longer-term a first-class icon package will replace this hand map.
0.1.2-alpha — 2026-07-28
Dogfood-driven addition. Surfaced while building shelldocs.dev: the framework had no way to route to a page without also showing it in the sidebar. Fine for typical docs, blocker for landing pages reached via the sidebar package selector (they'd render redundantly in the sidebar tree AND be the dropdown target).
meta.jsonhiddenarray. New optional field alongsidetitle/pages. Slugs listed there route (URLs resolve, direct links + package-selector navigation work) but never appear in the sidebar tree. Takes precedence overpages— a slug listed in both stays hidden.{ "title": "Documentation", "pages": ["introduction", "getting-started"], "hidden": ["components", "cli", "markdown"] }NavigationGraphconstructor gains an optionalhiddenPagesparameter. Hidden pages get indexed into the URL lookup but are excluded from_flatPages(soGetPrevNextskips them) and never appear asRoot.Children(so sidebar tree andFlatten()skip them). Not intended for direct consumer use —NavigationGraphBuilder.Build()produces the collection during folder walking.
Four new NavigationGraphBuilderTests: hidden slug excluded from sidebar but URL resolves, hidden folder excluded from sidebar but child URLs resolve, hidden takes precedence over pages, hidden slug excluded from auto-append.
0.1.1-alpha — 2026-07-25
First point-release after the dogfood smoke of 0.1.0-alpha. Three consumer-blocking fixes plus release-workflow hardening.
NavigationGraphBuildernow auto-includes.mdfiles not referenced inmeta.json. Previously, whenmeta.jsonexisted, ONLY the entries in itspagesarray made it into the nav — every other file on disk was silently dropped.shelldocs add component Buttoncreatedcontent/docs/components/button.mdon disk but the URL 404'd and the page never appeared in the sidebar until the consumer hand-editedmeta.json. Fix:meta.jsonnow controls ORDERING of explicitly-listed items; presence is driven by the file tree. Unreferenced files/folders get appended alphabetically after the explicit ordering. Backward-compatible — consumers who list everything explicitly get their exact ordering preserved verbatim before the auto-appended tail.shelldocs initscaffold no longer emits a broken<Callout Text=...>example. The intro-page template referenced aTextprop that doesn't exist on<Callout>; the current API isVariant+Title+ChildContent. Every new consumer runningdotnet runon their fresh scaffold saw an empty callout as the first thing on their site. Template updated to<Callout Variant="info" Title="Live component">body content</Callout>.shelldocs initnow inserts a Content Update itemgroup sodotnet publishcopies the markdown corpus. Previously worked ondotnet run(resolves ContentRoot to source) but silently broke first deploy — the published output had zero markdown, so every/docs/*route 404'd. NewAddContentCopyIfMissinghelper adds<Content Update="content/**/*.md;content/**/meta.json" CopyToOutputDirectory="PreserveNewest" />to the consumer's csproj. Idempotent, runs in both CREATE and ATTACH modes.
- Release workflow pre-push existence check. New step queries
nuget.org/v3-flatcontainerfor each of the 6 package IDs at the tag's version before invokingdotnet nuget push. If any version already exists on nuget.org, the workflow fails loud with a "bumpDirectory.Build.propsand re-tag" message.--skip-duplicatestays in the push step (still useful for resuming a workflow re-run that partially completed), but the pre-check catches the "you forgot to bump the version number" case explicitly instead of silently no-op'ing.
0.1.0-alpha — 2026-07-25
First public release. The whole Phase 1 target is shipped, plus most of Phase 2's primitives + consumer DX polish. See ROADMAP.md.
Published to NuGet:
ShellDocs.CLI— global tool:dotnet tool install -g ShellDocs.CLI --prerelease. Commands:init,add,dev,build,previewShellDocs.Components— RCL with<DocsLayout>,<DocsHeader>,<DocsSidebar>,<TableOfContents>,<PrevNextNav>,<DocsBreadcrumb>,<SearchDialog>, content primitives, API-reference primitivesShellDocs.Core— navigation graph, search index model, routing helpers, markdown plain-text extractorShellDocs.Markdown— Markdig pipeline with frontmatter,razor:previewfenced blocks, inline Razor component tagsShellDocs.Templates— starter markdown + Program.cs snippets forshelldocs initscaffoldingShellDocs.Tokens— RCL withtokens.css— shadcn-compatible palette + spacing scale, single source of truth for--background,--foreground,--primary,--radius, dark mode
Markdown pipeline (ShellDocs.Markdown)
- YAML frontmatter parsing via YamlDotNet
```razor:previewfenced blocks — live-rendered previews with source-view toggle- Inline Razor component tags mid-markdown (
<Callout />,<Card ... />) - Component type registry (
RegisterComponent<T>()) with per-type tag aliases (RegisterComponent<Button>("Btn")) - Bulk
RegisterComponentsFromAssembly<TMarker>()scan +[ShellDocsIgnore]opt-out attribute - Automatic string→typed coercion for
bool,int,enumattribute values
Content primitives (ShellDocs.Components)
<Callout Variant="info|warning|danger|tip">— coloured info box with icon + title + body<Card>/<CardGrid Columns="1|2|3">/<LinkCard>— responsive card family<Steps>/<Step>— CSS-counter numbered list with badge-on-rail spine<FileTree>/<FileTreeItem>— recursive project-layout diagram<CodeGroup SyncKey>/<CodeTab>— tabbed code samples with cross-page sync
API-reference primitives (ShellDocs.Components)
<TypeTable>/<TypeRow Name Type Default Description Required>— props/API reference table<ComponentPreview Component="..." ...props>— declarative-prop single-component demos
Chrome (ShellDocs.Components)
<DocsLayout>with two variants (TopNav,Sidebarfloating card)<DocsHeader>with primary nav mega-menu, GitHub link, theme toggle<DocsSidebar>with grouped nav, collapsible sections (animated grid-rows), auto-open on active path<TableOfContents>— right-rail, h2/h3 auto-extraction, scroll-spy indicator with smooth slide<PrevNextNav>— auto-derived from nav-graph adjacency<DocsBreadcrumb>— auto-generated from nav path; sections render as text, current page asaria-current, only leaf pages become links<PackageSelector>— consumer-configurable multi-package selector; hides when 0 or 1 packages declared<BrandLogo>— consumer-configurable logo with three modes:LogoSvg(inline SVG, tints viacurrentColor),LogoLight/LogoDark(theme-paired image URLs), or dot placeholder fallback<SearchDialog>— Cmd+K modal, client-side substring scoring against title / description / section / body, snippet extraction for body-only matches<DocsFooter>/<DocsMobileBar>/<ThemeToggle>
Auto-chrome via DocsPageState
- Consumer's docs page collapses to just
<MarkdownContent Document="_document" />— TOC, PrevNext, Breadcrumb all auto-render from a shared scoped service - Recomputes on
NavigationManager.LocationChanged
Search (ShellDocs.Core)
SearchIndex.FromGraph()— page + heading entries with URL, title, description, section- Page entries carry extracted plain-text
Body(frontmatter / fences / HTML / Razor tags / images / links / inline code / emphasis / heading#all stripped) MarkdownPlainText.Extract()— reusable helper for body extraction, 8KB default cap
Code highlighting (ShellDocs.Components)
- Shiki via WASM (bundle configurable)
- Dual-theme via
--shiki-light/--shiki-darkCSS custom properties
Design tokens (ShellDocs.Tokens)
- Standalone RCL with
tokens.css(base + full variants) - Shadcn-compatible variable names for interop with ShellUI and other consumers
CLI (ShellDocs.CLI)
shelldocs init— two modes: create (default, scaffolds a fresh Blazor Web App) and attach (--attach, augments existing project viaSHELLDOCS_SETUP.md)shelldocs add <component|guide|page> <name>— scaffolds starter.mdfrom template intocontent/shelldocs dev— dotnet watch with .md hot-reloadshelldocs build— publishes static site, handles base-href rewrite + SPA 404 fallback
Animation polish (Phase 2)
- Native view-transitions API for cross-fade on route change (Chromium — silent no-op elsewhere)
- Sidebar section collapse animates via
grid-template-rows: 0fr → 1fr - Copy-icon success bounce
- Global
@media (prefers-reduced-motion: reduce)guard — all animations collapse to instant
Consumer configuration (ShellDocsOptions)
RegisterComponentsFromAssembly<TMarker>(filter?)— bulk-register a whole component library in one lineAddPackage(id, title, description, rootUrl, iconPath?)— declares consumer's package family for the sidebar selectorSetLogo(url)/SetLogo(light, dark, alt?)/LogoSvg— brand logoAddNavLink/AddNavMenu— top-nav wiringLayoutVariant— TopNav or Sidebar
- Body-text search uses substring scoring, not an inverted index — fine for docs-sized corpora (~100 pages), will need rebuilding at 1000+
- Search snippets don't yet highlight the matched substring
<TypeTable>is hand-authored today; XML-doc auto-generation ships inShellDocs.Xml(Phase 4)- No
<DocsBreadcrumb>opt-out — currently hides when the trail has ≤ 1 node, otherwise always renders