name: docs-build description: Build, preview, and validate the Uno documentation website (DocFX) locally — surface real content errors vs expected noise, drive rendered pages with Playwright, and validate external-doc commit bumps in import_external_docs.ps1 before a PR. Use when working under doc/, editing articles/** markdown, bumping an external docs ref (uno.themes, studio-docs, hd-docs, uno.chefs, workshops, …), or rendering/validating the docs site.
Docs build & validation (DocFX)
Validate that the documentation is correct after a change: surface DocFX errors/warnings, drive
the rendered site in a real browser, and tell real content problems apart from expected noise.
Everything is under doc/ and is independent of the Uno.UI build — no
crosstargeting_override.props needed.
TL;DR validation loop
cd doc
docfx --version # MUST match build/Uno.UI.Build.csproj <DocfxVersion>; see Gotchas
pwsh -NoProfile -Command "./import_external_docs.ps1" # only if you changed external pins
docfx docfx.json 2>&1 | sed -E 's/\x1b\[[0-9;]*m//g' > /tmp/docfx.log # capture clean (no color) log
grep -iE "error|warning" /tmp/docfx.log | sort | uniq -c | sort -rn # triage
Then filter to the part you changed (§2), drive the rendered pages in a browser (§3), and confirm
they exist in _site.
1. The error/warning taxonomy — what each one means
DocFX prints error and warning lines. The ones that indicate a real content problem you
must fix (and that block/break a page):
| Signal | Meaning | How to fix |
|---|---|---|
InvalidFileLink: Invalid file link:(~/…) |
A relative link to a .md/image/path that doesn't exist |
Fix the link target or add the missing file. Watch for links to .html that should point to .md, and links missing an extension. |
UidNotFound: … invalid cross reference(s) "X" |
An xref:X / @X / <xref> points to a UID no page defines |
Fix the UID, or ensure the page defining it is included in the build. Common after renaming a uid: or removing a page. |
InvalidTocInclude: Referenced TOC file … does not exist |
A toc.yml href: includes another toc.yml that isn't there |
Fix the path, or import the external repo that provides it. (articles/implemented/toc.yml missing is expected locally — see §4.) |
DuplicateUids: Uid(X) has already been defined in … |
Two pages declare the same uid: |
Make UIDs unique; frequent when copying templates across workshops module variants. |
invalid-tab-group: Tab group with different tab id set |
Inconsistent # [Tab](#tab/…) ids within one group |
Align the tab ids across the tab group. |
Build does not reach Build succeeded / non-zero exit |
A fatal error aborted the build | Read the last error in the log; fix before anything else. |
The change you make should not introduce any new line of these types referencing your files.
2. Validate a specific change (diff against the noise)
The site has a baseline of pre-existing warnings unrelated to your change. The reliable method is to scope the output to what you touched, not to read the whole log.
- External-doc commit bump (e.g.
studio-docs): afterimport_external_docs.ps1, expect zero issues mentioning that repo:grep -i "external/<repo>" /tmp/docfx.log # expect: no error/warning lines find _site/articles/external/<repo>/ -name "*.html" # expect: the pages rendered - In-repo markdown edit: grep the log for your file path(s), and click through the rendered
page(s) under
_site/…. - When unsure if a warning is new: capture a baseline log on
master(or before your change), thendiffthe two triaged lists. Only lines new to your branch matter.
3. Runtime validation in a browser (Playwright — recommended)
A clean DocFX log proves the pages built; Playwright proves they render. After serving the
site (dotnet-serve --directory _site --port 8088, or npm start on :3000), drive the pages you
changed and assert on real browser behavior. Use the Playwright MCP browser tools if available, or
npx playwright. For each changed/affected page, check:
- Loads with HTTP 200 and the expected
<h1>/title is present (not a DocFX 404 or empty body). - No broken images — every
<img>hasnaturalWidth > 0. - No dead in-page links to other doc pages (sample the nav/TOC links → 200, not 404).
- No console errors in the page (
page.on('console')/ the MCP console-messages tool). - Visual spot-check — take a screenshot and confirm layout, styles loaded (not unstyled HTML), code blocks/tabs/mermaid render.
Minimal example (adapt the URL to your changed page):
const { chromium } = require('playwright'); // or use the Playwright MCP browser tools
const page = await (await chromium.launch()).newPage();
const errors = [];
page.on('console', m => m.type() === 'error' && errors.push(m.text()));
const resp = await page.goto('http://localhost:8088/articles/external/studio-docs/App/uno-platform-studio-get-started.html');
console.assert(resp.status() === 200, 'page did not load');
console.assert(await page.locator('h1').count() > 0, 'no heading rendered');
const broken = await page.$$eval('img', imgs => imgs.filter(i => !i.naturalWidth).map(i => i.src));
console.assert(broken.length === 0, 'broken images: ' + broken);
console.assert(errors.length === 0, 'console errors: ' + errors);
await page.screenshot({ path: '/tmp/doc-page.png', fullPage: true });
Report findings with explicit labels: Build (DocFX log clean) vs Runtime (Playwright loaded the page and asserted). Don't present a clean build as runtime proof.
4. Expected noise — do NOT chase these locally
These appear on a clean checkout and are not caused by your change:
InvalidTocInclude … articles/implemented/toc.yml does not existand an empty/placeholder implemented TOC — the API "implemented views" are generated by CI only.InvalidFileLink/UidNotFound/DuplicateUidsalready present in unrelated areas (notablyworkshops,silverlight-migration, feature includes). Confirm they don't reference your files.ApplyTemplateRendererError … maximum recursion limit of 256when it hits everytoc.yml→ this is a DocFX version bug (newer versions), not content. Pin DocFX to the CI version (Gotcha #1) and it disappears.
5. Gotchas
- Pin DocFX to the CI version. Read
<DocfxVersion>frombuild/Uno.UI.Build.csproj. The defaultdotnet tool update --global docfxinstalls the latest, which can emit the falserecursion limit of 256on every TOC (see §4).docfx --version dotnet tool uninstall --global docfx dotnet tool install --global docfx --version <DocfxVersion> - Do NOT validate a pinned commit with
import_external_docs_test.ps1. That wrapper forces each repo to its branch (studio-docs = "main", …), so it ignores theref=you committed. Runimport_external_docs.ps1directly (no args) to honor the committed pins.
6. Build mechanics (reference)
CI source of truth: build/ci/docs/.azure-devops-docs.yml → MSBuild target GenerateDoc in
build/Uno.UI.Build.csproj runs (≈ lines 111-134): sync-generator (skip locally) →
import_external_docs.ps1 → generate-llms-full.ps1 → install pinned DocFX → docfx docfx.json.
Styles/scripts come from npm install && npm run build (= gulp build, which also runs DocFX),
output to doc/_site.
To preview with styles: npm install then npm run build, then
dotnet-serve --directory _site --port 8088 (or npm start to build + live-serve on :3000).
Generated/gitignored — never commit: doc/_site, doc/styles, doc/node_modules,
doc/articles/external/**. Only the ref= pins in import_external_docs.ps1 are committed.
For markdown style there are also CI lint checks you can run locally:
cspell --config ./cSpell.json "doc/**/*.md" --no-progress and markdownlint "doc/**/*.md"
(see doc/README.md).