Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,31 @@ All notable changes to ShellDocs land here. Format follows [Keep a Changelog](ht

## [Unreleased]

## [0.1.17-alpha] — 2026-10-14

llms.txt for AI agents, and five fixes found while writing the ShellDocs docs site.

### Added

- **llms.txt for AI agents.** `o.EnableLlmsTxt(l => …)` serves `/llms.txt` (every page with its description, grouped by sidebar section), `/llms-full.txt` (every page as Markdown) and each page as Markdown at its URL + `.md`, the same under every version root. Options: `Summary`, `RulesFile` (Markdown shown at the top, such as rules for agents), `InstallCommandPattern` (a regex; the first match in a page's code blocks is listed after it) and `AgentFiles`. Files are `text/plain`; links are absolute from `SiteUrl` or the request.
- **Agent instruction files.** `AGENTS.md`, `CLAUDE.md`, `copilot-instructions.md` and a Cursor rule (`{site}.mdc`) under `/agents/` and each version root, served as downloads. Each is a `## {SiteName}` section, so it can be appended to an instructions file a project already has.
- **Page actions menu.** A ⋯ menu by the page title: Copy page (the page's Markdown), View as Markdown, Open in Claude and Open in ChatGPT (prefilled with "Read {page}.md and help me with it."). It works in static exports. Turn it off with `PageActions = false`.
- **Markdown for every ShellDocs tag.** Demos become their source in a fence, callouts blockquotes (code fences inside kept), tabs and code groups a bold label per tab, cards a list, steps numbered, file trees a text block, type tables a table, other registered components their text; links to docs pages become absolute `.md` links. Tags inside code fences are left alone.
- **`shelldocs build` exports them.** `llms.txt`, `llms-full.txt`, every page's `.md` and the agent files are written into the output; links use `--site-url`, then `SiteUrl`, else they're root-relative under `--base-href`.
- **`shelldocs init` turns it on** in the generated `Program.cs`.

### Changed

- **Links to `.txt` and `.md` files load the file.** Blazor's router and enhanced navigation showed the docs' "Page not found" for them; `shelldocs.js` now lets the browser follow them.

### Fixed

- **The missing-tab note lost its spaces.** It read "Not available onRazoryet": the note is a flex container, so the text around the label became separate flex items and the spaces were dropped. The sentence is now one inline box.
- **A `razor:preview` fence holding only HTML rendered as a code block.** It now renders live in the frame, like a fence with components. A fence with no tags at all is still a code block, with a warning.
- **`404.html` named the build's probe URL.** Every unknown URL showed "The page /docs/\_\_shelldocs-not-found\_\_ doesn't exist". `shelldocs build` now blanks and hides that line, and `shelldocs.js` fills in the visitor's path (relative to the site's base).
- **`LogoSvg` logos weren't sized.** The scoped rule meant to fit the SVG to `LogoHeight` never matched, so an SVG without its own size could collapse to nothing. Sites that added their own `.brand-logo-svg > svg` rule can drop it.
- **Search on home pages.** `HomeLayout` had no `<SearchDialog />`, so Cmd/Ctrl+K and `[data-search-open]` did nothing there unless the site added one. It's included now; a site that added its own can remove it.

## [0.1.16-alpha] — 2026-10-14

Fixes `<IframePreview>` showing the previous page's preview after navigating within a site.
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

<!-- Package metadata (applies to any project with IsPackable=true) -->
<PropertyGroup>
<Version>0.1.16-alpha</Version>
<Version>0.1.17-alpha</Version>
<Authors>ShellUI</Authors>
<Company>ShellUI</Company>
<Copyright>Copyright © 2026 ShellUI</Copyright>
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Already have a Blazor project? Run `shelldocs init --attach` inside it: it adds
- **Markdown-first authoring.** YAML frontmatter, Shiki-highlighted code fences, live `razor:preview` examples, inline component tags mid-prose. Set `o.RenderPageTitle = true` to render the frontmatter `title` and `description` as the page header instead of writing `# Title`.
- **File-based navigation.** Drop a `.md` in `content/docs/` and it's a page. Sidebar, breadcrumb, prev/next and TOC come from the folder tree; `meta.json` controls order, dividers, subsections and hidden pages.
- **`Cmd+K` search** across titles, headings and page text, with body snippets. The index is built at startup and searched in the browser, so there's no external service and it works in the static export too.
- **llms.txt for AI agents.** `o.EnableLlmsTxt()` serves `/llms.txt`, `/llms-full.txt` and every page as Markdown at its URL + `.md`, plus `AGENTS.md`, `CLAUDE.md`, Copilot and Cursor instruction files, and adds a **Copy page / View as Markdown / Open in Claude / Open in ChatGPT** menu by the title. See [AI agents](#ai-agents-llmstxt).
- **Blazor-native.** Your components render as real Razor components, not iframes.
- **Composable.** Bring your own component library (ShellUI, MudBlazor, Radzen, hand-rolled) and register it in one line:
```csharp
Expand Down Expand Up @@ -91,6 +92,30 @@ o.AddRedirect("/docs/v0.3.0", "/docs/v0.3"); // also /docs/v0.3.0/x → /docs/

`AddShellDocs` registers middleware that answers these before routing, so a dotted version root works even though `/docs/{*Path:nonfile}` routes can't match it. `shelldocs build` writes the same redirects as static pages. Turn it off with `o.EnableRedirects = false`.

## AI agents (llms.txt)

As on the ShellUI docs ([shellui.dev/llms.txt](https://shellui.dev/llms.txt)):

```csharp
o.EnableLlmsTxt(l =>
{
l.Summary = "ShellUI is a shadcn/ui-style component library for Blazor."; // defaults to SiteTagline
l.RulesFile = "Llms/agent-rules.md"; // shown at the top, e.g. "Rules for AI agents"
l.InstallCommandPattern = @"shellui add [\w-]+"; // listed after each page that has one
// l.AgentFiles = false; l.PageActions = false;
});
```

| URL | Contents |
|---|---|
| `/llms.txt` | Site name, summary and rules, then every page with its description, by sidebar section |
| `/llms-full.txt` | The same header, then every page as Markdown |
| `{page}.md` | One page as Markdown |
| `{version root}/llms.txt`, `/llms-full.txt` | One version (the bare files cover the latest plus unversioned pages) |
| `/agents/AGENTS.md`, `CLAUDE.md`, `copilot-instructions.md`, `{site}.mdc` | Instruction files to download into a project, each a `## {SiteName}` section that can be appended to an existing file (also under each version root) |

The Markdown is the page source with ShellDocs tags turned into Markdown: demos become their source, callouts blockquotes, tabs a label per tab, cards a list, and links to docs pages absolute `.md` links. The files are `text/plain` (some browsers download `text/markdown`); links use `SiteUrl`, or the request's address. `shelldocs build` writes all of them into the export, and links to `.txt` / `.md` files load the file instead of the docs' not-found page. Copy the rules file to the output and publish folders (`<None Update="Llms/agent-rules.md" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />`).

## Component previews

- **Preview | Code toolbar on every example**, with copy and a ⋯ menu: *Open in new tab*, *Report a bug*, *Suggest something*. The issue links go to `https://github.com/{GitHubRepo}/issues/new`, pre-filled with the example and page URL; point them elsewhere with `o.IssueTrackerUrl`.
Expand Down
29 changes: 23 additions & 6 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ Placeholders plus a slot list keep everything at render time: no generated Razor

### `razor:preview` fences

`PreviewParser` turns the fence into ordered nodes: component nodes (registered capitalised tags; their children kept raw and parsed the same way, as Razor, when the component renders), element nodes (HTML, including unregistered capitalised tags) and text. `@code { }` / `@functions { }` blocks, `@* *@` comments and directive lines (`@using`, `@inject`, …) are skipped for rendering but stay in the Code tab. If no registered component is found the slot carries an `Error`, rendered as a red panel instead of a silent code block. `razor:preview stretch` in the info string sets `PreviewSlot.Layout`.
`PreviewParser` turns the fence into ordered nodes: component nodes (registered capitalised tags; their children kept raw and parsed the same way, as Razor, when the component renders), element nodes (HTML, including unregistered capitalised tags) and text. `@code { }` / `@functions { }` blocks, `@* *@` comments and directive lines (`@using`, `@inject`, …) are skipped for rendering but stay in the Code tab. With no registered component, a fence holding an unregistered capitalised tag carries an `Error`, rendered as a red panel instead of a silent code block; one holding only HTML renders it as is (`ComponentType` is null). A fence with no tags at all stays a code block, with a warning. `razor:preview stretch` in the info string sets `PreviewSlot.Layout`.

---

Expand Down Expand Up @@ -191,7 +191,21 @@ All answers are computed at render time and emitted as plain `<a href>`, so prer

Searching happens in the browser, so it's the same on a static host. `DocsSearchIndexMiddleware` (added by an `IStartupFilter`, like the redirects) serves the index at `/_shelldocs/search.json`: `{ versions: [{ id, root, latest }], entries: [{ u, t, d, s, k, b, v }] }`, where `k` is `p` (page) or `h` (heading) and `v` is the id of the version that owns the entry. `shelldocs build` copies that file into the export.

`SearchDialog` renders a hidden template. On Cmd/Ctrl+K or a `[data-search-open]` button, `shelldocs.js` clones it onto `<body>` (outside the DOM Blazor owns, so a circuit starting doesn't close it), fetches the index once, keeps the current version's entries plus those outside every version, and scores them: whole-query and per-token matches against title (strongest), section, description and body; every token must match somewhere. Body-only matches get a ~150-character snippet. Results are links, cloned from the template's `[data-search-row]` rows. `shelldocsSearch.open()` / `.close()` do the same from script.
`SearchDialog` renders a hidden template; `DocsLayout` and `HomeLayout` both include one. On Cmd/Ctrl+K or a `[data-search-open]` button, `shelldocs.js` clones it onto `<body>` (outside the DOM Blazor owns, so a circuit starting doesn't close it), fetches the index once, keeps the current version's entries plus those outside every version, and scores them: whole-query and per-token matches against title (strongest), section, description and body; every token must match somewhere. Body-only matches get a ~150-character snippet. Results are links, cloned from the template's `[data-search-row]` rows. `shelldocsSearch.open()` / `.close()` do the same from script.

---

## llms.txt

`o.EnableLlmsTxt(l => …)` registers `LlmsTxtFiles` and `LlmsTxtMiddleware` (an `IStartupFilter` added before the redirects, so a page's `.md` is never redirected and no path reaches the docs page route). The middleware answers `/llms.txt`, `/llms-full.txt`, each version root's `llms.txt` / `llms-full.txt`, `{page URL}.md` and, with `AgentFiles`, `agents/AGENTS.md`, `CLAUDE.md`, `copilot-instructions.md` and `{site}.mdc` at `/` and under each version root. Everything is `text/plain` (some browsers download `text/markdown`); the agent files are attachments. `/_shelldocs/llms.json` lists every path for `shelldocs build`.

- **Scope.** The bare files cover pages outside every version plus the latest version (every page on an unversioned site); a version root's cover that version. Index groups are the page's sidebar sections below the scope's common folder; when unversioned and latest pages are listed together, the latest version's groups carry its label.
- **Header.** `# {SiteName}`, a `>` line (`Summary`, defaulting to `SiteTagline`, plus which version this is and links to the others' `llms.txt`), then the `RulesFile` as written. The index adds each page's description and the first `InstallCommandPattern` match in its code blocks.
- **Agent files.** A `## {SiteName}` section (leading newline, so `>>` appends cleanly): which version the project uses, where `llms.txt` is, and the rules with their headings one level down. The Cursor rule adds `description` / `alwaysApply` frontmatter.
- **Markdown** (`LlmsMarkdown`). Fences and code spans are masked first and come back unchanged (`razor:preview` becomes `razor`). Registered tags are read with `RazorTagScanner` and matched to their type: `Callout` → a blockquote with a bold title (fences inside are quoted too), `Tabs`/`Tab` and `CodeGroup`/`CodeTab` → a bold label per tab, `CardGrid`/`Card`/`LinkCard` → a list, `Steps`/`Step` → numbered bold titles, `FileTree` → a `text` fence, `TypeTable`/`TypeRow`/`AutoTypeTable` → a table, `DemoPreview` → the demo's source, `ComponentPreview` → its markup without the frame attributes, `IframePreview` → a link, `SyncSwitch` → nothing; previews with `<CodeTab>` children show those instead. Any other registered component becomes its text. Root-relative link targets and `href`/`src` attributes become absolute, with `.md` for docs pages. `LlmsMarkdown.HandledTypes` lists every built-in, and a test fails when a new built-in is missing from it.
- **Base URL.** `SiteUrl`, else the request's origin and path base. `shelldocs build` sends `X-ShellDocs-Site-Url` (its `--site-url`) and `X-ShellDocs-Base-Path` (its `--base-href`); the middleware honours them only from loopback, falling back to `SiteUrl`, then root-relative links under the base path.

`DocsPageActions` (rendered first in `MarkdownContent`, floated right of the title, only on graph pages with `PageActions`) is a ⋯ menu: **Copy page** (`[data-page-copy]`), **View as Markdown** (the page's base-relative `.md`), **Open in Claude** / **Open in ChatGPT** (`[data-page-ai]`, the chat URL prefix).

---

Expand All @@ -206,7 +220,9 @@ Chrome that must work without a Blazor runtime follows one contract:
| Feature | Markup | Behaviour |
|---|---|---|
| Sidebar sections | `.sidebar-section[data-open]` | toggle on click |
| Package / version selectors, preview ⋯ menu | `.pkg` / `.ver` / `.preview-menu` `[data-open]` | open on trigger; close on outside click, Escape or option |
| Package / version selectors, preview and page ⋯ menus | `.pkg` / `.ver` / `.preview-menu` / `.page-actions` `[data-open]` | open on trigger; close on outside click, Escape or option |
| Page actions | `.page-actions[data-page-md]`, `[data-page-copy]`, `[data-page-ai]` | Copy page fetches the `.md` (resolved against `<base href>`) into the clipboard through a `ClipboardItem` holding the pending text (Safari's gesture rule), then closes; the chat links get `Read {absolute .md URL} and help me with it.` at load and again on click |
| File links | any same-origin `<a>` to a `.txt` / `.md` / `.mdc` path | a window capture listener stops the click before Blazor's router and enhanced navigation (which would show "Page not found"), so the browser loads the file |
| Preview tabs | `.preview-frame[data-preview-tab]` | Preview / Code; arrow keys; copy reads the source |
| Tabs, CodeGroup, preview code tabs, page switch | `[data-tabs]`, `[data-tab-target]`, `[data-tab-panel]` | switch on click / arrows; `[data-tabs-sync]` groups switch together and persist in `localStorage`; a `[data-tabs-missing]` group without the chosen value shows its `[data-tab-missing]` note; with nothing saved yet, groups follow a `[data-tabs-switch]` for their key |
| Iframe previews | `[data-iframe-preview][data-src][data-lazy]` | sets the iframe's `src` (plus `?theme=`) when visible (IntersectionObserver), on `[data-iframe-run]` or at once; posts `{ type: "shelldocs-theme", theme }` to loaded frames on theme change; a host Blazor reuses for another page (`data-src` or `data-lazy` changed) is unloaded and set up again |
Expand Down Expand Up @@ -246,10 +262,11 @@ One neutral, shadcn-shaped palette of CSS variables in `ShellDocs.Tokens/tokens.
2. Builds the navigation graph and collects every URL (visible and hidden) plus `/`.
3. `PrerenderRunner` starts the published app on a free port, requests each URL and writes `<output>/<path>/index.html`. A URL that answers 404, or renders `<DocsNotFound>`, fails the build.
4. Reads the app's redirect map and writes a redirect page for each source URL without a prerendered page (meta refresh plus `location.replace`, relative to `<base href>`).
5. Requests `/docs/__shelldocs-not-found__` and, if it renders `<DocsNotFound>` with a 404, writes it as `404.html`.
5. Requests `/docs/__shelldocs-not-found__` and, if it renders `<DocsNotFound>` with a 404, writes it as `404.html`. The probe's path is blanked and its line hidden; `shelldocs.js` fills in the visitor's path.
6. Copies the app's search index to `_shelldocs/search.json`.
7. Merges the published `wwwroot/` into the output without overwriting prerendered HTML.
8. Optionally rewrites `<base href>` in every HTML file, copies `index.html` to `404.html` (replacing the not-found page), and with `--site-url` writes `sitemap.xml`, `robots.txt` and `og:*` meta.
7. With `EnableLlmsTxt`, reads `/_shelldocs/llms.json` and writes each listed file (`llms.txt`, `llms-full.txt`, every page's `.md`, the agent files) at its path. Links use `--site-url`, then the app's `SiteUrl`, else they're root-relative under `--base-href`.
8. Merges the published `wwwroot/` into the output without overwriting prerendered HTML.
9. Optionally rewrites `<base href>` in every HTML file, copies `index.html` to `404.html` (replacing the not-found page), and with `--site-url` writes `sitemap.xml`, `robots.txt` and `og:*` meta.

---

Expand Down
3 changes: 2 additions & 1 deletion docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ Ships to `ShellDocs.Components` + `ShellDocs.Templates` + `ShellDocs.CLI` + `She
**Authoring fix (`ShellDocs.Markdown`)**
- `SlotExtractor.ReplaceComponentTags` no longer `.Trim()`s the raw child content of inline component tags. The Trim was stripping the first line's indent and defeating `SlotRenderer.Dedent` — Markdig then interpreted the remaining 4-space-indented lines as an indented code block. Symptom was the same "literal `<pre>` around placeholder divs" bug that had already been fixed for `razor:preview` fences; the inline-tag code path was still hitting it.

### ✅ Hardening releases (`0.1.3`–`0.1.16-alpha`) — shipped
### ✅ Hardening releases (`0.1.3`–`0.1.17-alpha`) — shipped
Driven by building the ShellUI docs on ShellDocs. Highlights (details in the CHANGELOG):

- `feat/build-static-prerender` (0.1.5) — `shelldocs build` prerenders every URL into a static site.
Expand All @@ -223,6 +223,7 @@ Driven by building the ShellUI docs on ShellDocs. Highlights (details in the CHA
- `fix/not-found-and-sidebar-collapse` (0.1.14) — `<DocsNotFound>` answers 404, `build` fails on not-found pages and writes `404.html`, desktop sidebar collapse in `shelldocs.js`.
- `feat/static-search` (0.1.15) — search runs in `shelldocs.js` against `/_shelldocs/search.json`, which `build` copies into the export, so static sites have search.
- `fix/iframe-preview-navigation` (0.1.16) — an `<IframePreview>` reused for the next page after in-site navigation is unloaded and set up again.
- `fix/docs-site-findings` (0.1.17) — llms.txt for AI agents (`EnableLlmsTxt`: `/llms.txt`, `/llms-full.txt`, per-page `.md`, agent instruction files, page actions menu, static export); HTML-only `razor:preview` fences render live, `404.html` shows the visitor's path, `LogoSvg` sizing, search on `HomeLayout`, spacing in the missing-tab note.
- `fix/preview-fidelity`, `feat/generics-redirects-page-title` (0.1.10) — `not-prose` previews, Razor child content, stretch layout, generic components in markdown, redirects (middleware + static pages), `RenderPageTitle`, theme sync and a static-host `ThemeToggle`.

### `feat/animation-polish`
Expand Down
Loading
Loading