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). - 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 block is enforced by CI: DocExecutableBlocksTest (in test/MeshWeaver.Persistence.Test) extracts every --render/--execute block from every embedded doc page and executes it through a real kernel session β a block that stops compiling fails the build naming the page and the block. The same test carries a coverage ratchet, so silently converting an executable block back to a prose-only fence is visible.
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.