Authoring Documentation
Documentation lives as markdown files under src/MeshWeaver.Documentation/Data/ and is served as mesh nodes under the Doc partition. The file system maps 1:1 to node paths β and links resolve against node paths at render time, not against the file system. Getting that one idea right is most of this page.
Files β node paths
| File | Node path |
|---|---|
Data/Architecture.md |
Doc/Architecture |
Data/Architecture/AsynchronousCalls.md |
Doc/Architecture/AsynchronousCalls |
Data/GUI/ContainerControl/Stack.md |
Doc/GUI/ContainerControl/Stack |
Data/GUI/ContainerControl/index.md |
Doc/GUI/ContainerControl (an index.md is its folder's node) |
Data/ReleaseNotes/3_0_0.md |
Doc/ReleaseNotes/3_0_0 β a flat file on purpose, see below |
π¨ A folder name must be a C# identifier; a file name need not. The embedded-resource name MSBuild
gives a file makes every folder segment an identifier β a folder starting with a digit is prefixed
(3_0_0/ β _3_0_0), a hyphen becomes an underscore β while the file name is kept verbatim. So
Data/ReleaseNotes/3_0_0/index.md is served at /Doc/ReleaseNotes/_3_0_0, and every link to the
documented path is broken with nothing in the build to say so (DocumentationLinkIntegrityTest is
what caught it). Version pages are therefore flat files, Data/ReleaseNotes/3_0_0.md, and no doc
folder starts with a digit or carries a hyphen.
Note the pairing: Architecture.md is the index node and the folder Architecture/ holds its children. Agent definitions follow the same scheme under the Agent partition (content/ai/Agent/Researcher.md β Agent/Researcher), and skills under the Skill partition (content/ai/Skill/code.md β Skill/code).
π¨ Link rules β pinned by DocumentationLinkIntegrityTest
At render time, LinkUrlCleanupExtension resolves every markdown link with PathUtils.ResolveRelativePath against the page's full node path. The page is treated as a container, so:
| You want to link⦠| Write | Resolves to |
|---|---|---|
| Anywhere (the robust default) | [CQRS](/Doc/Architecture/CqrsAndContentAccess) |
exactly that path |
| A sibling page | [CQRS](../CqrsAndContentAccess) |
up one from Doc/Architecture/ThisPage, then down |
| Your own child page (from an index) | [Stack](Stack) |
Doc/GUI/ContainerControl/Stack |
| Another area | [Data Binding](/Doc/GUI/DataBinding) |
absolute β immune to moves of this page |
| An agent definition | [Researcher](/Agent/Researcher) |
the Agent partition |
| A built-in skill | [/code](/Skill/code) |
the Skill partition |
And the three forms that never resolve:
[X](SiblingPage) β from a leaf page this resolves to ThisPage/SiblingPage
[X](xref:Architecture/X) β there is no xref: handler in the pipeline
[X](../SiblingPage.md) β node paths have no .md suffix
DocumentationLinkIntegrityTest (in test/MeshWeaver.Documentation.Test) resolves every link in every doc, agent, and skill page with the real PathUtils and fails the build naming the page, the literal URL, and the bad target β so a broken link never reaches the portal.
Two more link rules:
@/pathis local-only.[text](@/Doc/X)is fine β the renderer strips the@. But never put@/inside a raw HTMLhref=""; HTML passes through the renderer verbatim.- Links inside code spans/fences aren't links. Write
`[text](path)`when you mean to show link syntax.
Frontmatter
---
Name: Human-readable page title # shown in catalogs and the TOC
Category: Architecture # grouping hint
Description: One-line summary β lands on the node's Description column and search.
Icon: <svg β¦/> # inline SVG, content:file.svg, or an absolute URL
---
Thumbnail, Authors, and Tags are also supported. Abstract is the canonical name for the summary and Description is accepted as its legacy alias β both land on the node's Description. Title is likewise a legacy alias of Name. See MarkdownFileParser.MarkdownFrontMatter (src/MeshWeaver.Hosting/Persistence/Parsers/MarkdownFileParser.cs). Icon falls back to Thumbnail when omitted.
Images and diagrams
- Inline SVG directly in the markdown is the house style for diagrams β it themes with
currentColor, needs no asset pipeline, and renders identically in docs and thumbnails. Never put blank lines or HTML comments inside an<svg>block (markdown would split it). An accent colour that is notcurrentColormust hold 4.5:1 against the page in BOTH themes when it colours text (small labels included), so it is declared once per theme, never as one mid-tone value: put the palette in the SVG's own<style>as custom properties on the diagram's root class, override them under[data-theme="dark"] .clsand, for a viewer on the system theme, under@media (prefers-color-scheme: dark) { html:not([data-theme="light"]) .markdown-body:not([data-theme="light"]) .cls { β¦ } }β the same selector pairMarkdownView.razor.cssuses β and measure each value before committing it. Secure Development Framework is the worked example. - Static images go through a content collection:
resolves viaImgPathMarkdownExtensionto the page's static content. - Mermaid fenced blocks render for sequence/flow diagrams where hand-drawn SVG is overkill.
Code samples are executable
When a doc page brings a code example, the example is executable. A real, runnable sample is written as an executable fenced block, never a static fence β the reader sees the code, a Run button on the cell's toolbar, and the result directly below the code (the notebook-cell shape). Blocks auto-execute on page load and Run re-executes them on demand.
The fence syntax:
```csharp --render MyDemo --show-code
MeshWeaver.Layout.Controls.Stack
.WithView(MeshWeaver.Layout.Controls.Markdown("**live!**"))
```
--render <AreaName>executes the block in the kernel and streams its last expression into the named result area below the code β the reader sees the real control, not a screenshot.--show-codedisplays the source above the result (this is what turns the block into a full notebook cell with the Run toolbar). Omit it for a live demo whose code isn't the point.--execute <id>runs silently (setup code shared by later blocks on the same page β blocks on one page share a single kernel REPL session, in document order).- The fence language flows onto the submission:
```python --render Xroutes to the connected Python worker instead of the in-process Roslyn kernel.
A plain fence is the explicit marker for a non-runnable fragment β pseudo-code, wire shapes, framework source excerpts (handlers, layout-area methods, service registrations), bash. If a fence shows real, self-contained runnable code, make it executable.
Every executable C# block is type-checked on every core pull request: DocExecutableCellsCompileTest (in test/MeshWeaver.Documentation.Test) extracts every --render/--execute block from every embedded doc page the way the portal does (MarkdownContent.Parse), and COMPILES each page's blocks as one script chain, in document order, with the kernel's own options (MeshScriptEnvironment.CreateScriptOptions over MeshScriptEnvironment.ReferencesAsync β the same reference set, default imports and globals type KernelExecutor uses). A block that stops compiling fails that page's case, naming the page, the cell's position and id, and the Roslyn diagnostics. It compiles and never executes β execution needs a mesh.
Two kinds of block are skipped there, and each skip is printed with its reason: a non-C# fence (routed to a foreign-language worker, not the Roslyn kernel), and a block that declares #r "nuget:β¦" β together with every later block on the same page, whose session holds the restored package β because the guard compiles offline.
Running the blocks is the other half and lives in MeshWeaver.Plugins, which holds the mesh test bases: DocExecutableBlocksTest (src/MeshWeaver.Persistence.Test there) executes every block through a real kernel session per page and carries the coverage ratchet. It runs with that repository's host suites, so it catches a block that compiles and then throws β but on that repository's schedule, not on the core pull request that changed the page.
Use executable blocks for every UI claim a page makes β if the doc says a control renders, the doc should render it. Pages that define sample models back them with real code in src/MeshWeaver.Documentation/ and tests in test/MeshWeaver.Documentation.Test (see Business Rules for the canonical example).
Style
- Present tense, what exists. Document the current surface; don't narrate deleted APIs or migration history.
- Code-first. Lead with the canonical snippet; prose explains why, tables summarise options.
- One page, one scope. If a page needs a "which page do I read" preamble, add the router table (see Deployment) and keep each page's scope crisp.
- The catalog lists every child node automatically; the curated area index (
Architecture.md,DataMesh.md,GUI.md,AI.md) is what readers actually navigate β add load-bearing pages to its topic map.