Appearance
Migration Guide: Flude v1.0 to v2.0
NOTE
Archive: historical migration document. V2.0 shipped; this guide's audience is now narrowly "I still have a v1.0 setup to move," not "how does V2.0 work." Everything described below is also covered, as current fact rather than migration delta, in the main sections listed on the Documentation Map — start there for anything that isn't specifically about moving off v1.0.
This guide is the complete checklist for moving a working v1.0 setup to v2.0. Every command, field name, error message and output sample below was verified against the shipped engine on 2026-08-02 — where v2.0 raises a hard error on a v1.0 input, the exact message is quoted so you can match it against your logs. Two passages below were corrected on 2026-08-14 after a follow-up code audit found they no longer matched the shipped engine (the [groups] schema, and one quoted exception message) — see Sidebar & Groups for the current, authoritative version of that material.
Read the Hard stop sections first: they are the changes that make a previously working configuration fail outright.
Hard stop 1: every document directory now requires sidebar.toml
This is the single most likely reason a v1.0 project stops building on v2.0.
sidebar.toml replaced the per-renderer toc_*.json files as the source of navigation truth. Delete any toc_*.json you carried over — they are no longer read by anything.
The file is mandatory and must declare at least one [[sidebar]] entry. The pipeline refuses a document directory otherwise rather than falling back to defaults, because navigation that renders successfully but wrongly is far harder to notice than a failed build. Three separate failures, all fatal:
text
Required sidebar.toml not found in <dir>. Every document directory must contain a sidebar.toml file.
sidebar.toml in <dir> declares no [[sidebar]] entries. There is no default navigation to fall back on ...
Failed to parse required sidebar.toml '<path>': <parser detail>Place one in each <project>/ directory that has a ude_doc_config.json, including the Hugo variants. A minimal file is three lines:
toml
[[sidebar]]
type = "api_reference"
label = "API Reference (C++)"
url = "index.html"Configuration for most other keys (src_dir, renderer.type, output_subdir, ...) resolves through a three-tier deep merge (global → SDK → document), so shared values live at the outer tiers and each document directory carries only its overrides. [[sidebar]] and [groups] are the exception: neither participates in that JSON cascade at all — see below.
The folder taxonomy that used to live in template JSON now sits in a [groups] table, inside the same mandatory sidebar.toml:
toml
[groups]
namespace_level = ["Classes", "Structures", "Enums"]namespace_level is the only key GroupsConfig permits (extra="forbid", engine/ude/config.py:56-68) — there is no class_level. A misspelled key (or a stray class_level copied from an old example) is a hard error, not a silently-ignored one.
There is no cascade and no engine-tier default for [groups], and none for [[sidebar]] either — the former per-Lang×Output default TOML fixtures were removed outright in [IMP-32.10]. sidebar.toml's own [groups] is the sole source; groups/sidebar keys are actively rejected if they appear in ude_global_config.json, ude_sdk_config.json, or ude_doc_config.json (see Sidebar & Groups).
[[sidebar]] and [groups] behave identically, deliberately — no asymmetry: both are mandatory, and both fail the build if empty or omitted (engine/ude/orchestrator.py:339, :352):
| Table | If you omit it |
|---|---|
[[sidebar]] | Build fails. There is no default navigation at any tier. |
[groups] | Build fails. There is no default taxonomy at any tier — every document must declare at least one namespace_level entry. |
An earlier version of this guide described [groups] as falling back to an engine default when omitted. That engine-tier default was removed before v2.0 shipped; both tables are equally mandatory today. So an empty sidebar.toml is not valid — it fails exactly like a missing one:
text
sidebar.toml in <dir> declares no [[sidebar]] entries. There is no default
navigation to fall back on — every document directory must define its own
[[sidebar]] table.An explicit sidebar = [] is rejected for the same reason.
Hard stop 2: removed and renamed configuration keys
sidebar_structures_dir was removed from ude_global_config.json. It is trapped explicitly rather than ignored, so a stale key fails loudly:
text
sidebar_structures_dir is removed; folder taxonomy is now sourced exclusively from sidebar.toml [groups].Remove the key and move its content into sidebar.toml's [groups] table.
renderer.type must be a renderer family key, not a renderer class name. Configs naming a concrete class (CppHtmlODARenderer, PyHugoDefaultRenderer, …) fail with:
text
Unsupported format '<value>' in target config.The renderer's own factory selects the language-specific subclass from collector.language, so the family key is all you supply:
renderer.type | Output |
|---|---|
html, static_html | Standalone static HTML |
oda_html | ODA-conventions HTML |
hugo_markdown, markdown, hugo | Hugo Markdown |
oda_hugo_markdown, oda_markdown, oda_hugo | ODA-conventions Hugo Markdown |
Unknown keys elsewhere in ude_global_config.json are still ignored silently (extra="ignore"), and every field has a default — a config missing a field will not raise.
Typed entity models
The untyped ClassEntity has been replaced by strict Pydantic models. The change you are most likely to feel is in class fields:
- v1.0:
fields: ["int count", "string name"] - v2.0:
fields: List[VariableModel], each with.name,.typeand.docstring
If you maintain a custom renderer or any code that reads the IR, replace string parsing with attribute access. Seven typed models ship in total, covering variables, constants, enums, type aliases, parameters, overloads and methods.
Existing imports keep working. ClassEntity, NamespaceEntity and MethodEntity remain as module-level aliases of the new model classes and are explicitly maintained for external consumers — you do not have to rename imports as part of this migration.
ProjectCatalog metadata
ProjectCatalog gained project_name and version. Both are optional, defaulting to an empty string — no action is required, and neither belongs in ude_global_config.json. They are IR-level metadata populated from your document config; set project_name in ude_doc_config.json if you want it carried into the IR.
Decoupled CLI
The v1.0 flat interface is unchanged and still supported — ude --doc-config … behaves exactly as before, and ude compile is its explicit equivalent. v2.0 adds subcommands that split parsing from rendering, which is what you want if you plan to cache or archive the IR between CI stages.
Note that the IR is gzip-compressed; use a .json.gz extension.
Generate IR:
bash
ude parse --doc-config path/to/ude_doc_config.json --output-ir catalog.json.gzRender from IR:
bash
ude render --input-ir catalog.json.gz --output ./public --format html--output-ir is required for parse; --input-ir and --output are required for render. Both accept --global-config / -g and --doc-config / -d.
Coverage auditing and ude audit
Two new GlobalConfig fields drive the documentation coverage gate:
coverage_mode—allow-undocumented(default, reports only) orreject-undocumented(fails the build).coverage_threshold— a fraction between0.0and1.0, default1.0.
Unit mismatch, worth pinning to memory. The config field is a fraction (
0.98), while the CLI flag is a human-friendly percentage (--threshold 98, valid range 0–100). Passing--threshold 0.98is accepted but means 0.98 percent, which will pass almost anything. The CLI divides by 100 before handing the value to the gate.
--mode and --threshold override the config values when given explicitly.
Exit codes
| Code | Meaning |
|---|---|
0 | Audit completed; gate passed, or mode is allow-undocumented |
2 | Gate rejected the target: reject-undocumented and coverage below threshold |
Actual output format
ude audit writes a Markdown report to stdout: a summary header followed by one row per entity — not per module.
markdown
# Documentation Coverage Audit
**Total Entities:** 4948
**Documented Entities:** 969
**Overall Coverage:** 19.58%
| Type | Entity Name | Documented |
| :--- | :--- | :--- |
| Class | `ODA::Publish::PdfPublish::OdPdfPublish_Od2dGeometryBlock` | PASS |
| Method | `ODA::Publish::PdfPublish::OdPdfPublish_Od2dGeometryBlock.Format` | FAIL |On a large SDK this is thousands of rows — redirect it to a file rather than into a CI log, and use the exit code for the pass/fail decision.
GitHub Actions integration
yaml
- name: Flude Coverage Audit
shell: bash -euo pipefail {0}
env:
PYTHONPATH: engine
run: |
python -m ude.cli audit \
--doc-config path/to/ude_doc_config.json \
--threshold 98 \
--mode reject-undocumentedWhen embedding the engine as a library instead, apply_coverage_gate() raises UdeException rather than calling sys.exit(), so a host application is never killed by a coverage failure.
Also new in v2.0 (no migration action required)
These arrive automatically and need no configuration change:
- Unified logging — one
uderoot logger driven bylog_level/log_file. Importingude.configwithout callinglogging_setup()produces no output, per the standard library contract, so embedding the engine stays quiet by default. - L2 render cache — unchanged entities skip re-rendering on repeat builds.
- Three-tier Doxyfile merge — key-level merge across global, SDK and document tiers, resolved via
global_templates_dir. - Public library API —
UdeOrchestratorexposesparse,renderandrunfor embedding without going through the CLI.
Step-by-step migration checklist
- [ ] Add a
sidebar.tomldeclaring at least one[[sidebar]]entry to every directory containing aude_doc_config.json, Hugo variants included. The build fails on a missing, empty or malformed file — there is no default navigation. - [ ] Delete leftover
toc_*.jsonfiles; move any folder taxonomy intosidebar.toml's[groups]table. - [ ] Remove
sidebar_structures_dirfromude_global_config.jsonif present. - [ ] Change any
renderer.typenaming a concrete renderer class to the corresponding family key from the table above. - [ ] Update custom renderers and IR consumers to read
VariableModelattributes (.name,.type,.docstring) instead of parsing strings. LegacyClassEntity/NamespaceEntity/MethodEntityimports need no change. - [ ] Optional: adopt
ude parse/ude renderif you want the IR as a separate CI artifact.ude compileand the v1.0 flat flags keep working. - [ ] Optional: set
coverage_modeandcoverage_threshold, and addude auditto CI — remembering the fraction-vs-percentage distinction.
Verifying the migration
bash
# 1. A v1.0 config still compiles through the flat interface
ude --doc-config path/to/ude_doc_config.json
# 2. A previously saved v1.0 IR still loads
python -c "from ude.storage import load_compressed_ir; load_compressed_ir('old_catalog.json.gz')"
# 3. Split pipeline produces the same output as a single-pass compile
ude parse --doc-config path/to/ude_doc_config.json --output-ir /tmp/ir.json.gz
ude render --input-ir /tmp/ir.json.gz --output /tmp/split --format html --doc-config path/to/ude_doc_config.json
ude compile --doc-config path/to/ude_doc_config.json --output /tmp/single --format html
# diff -rq /tmp/split /tmp/single should report no difference except
# .build_cache.json.gz (internal L2 cache metadata, not rendered output).
--doc-configon therenderline is not optional, despite--input-iralone being enough to produce some output. Without it,renderhas nosidebar.toml/[groups]context to resolve navigation from and falls back to a bare, unlabeledapi_referenceentry — different folder grouping, different sidebar labels, same entities. Verified 2026-08-10 end-to-end: a real split-vs-compile run with--doc-configomitted fromrenderproduced a structurally differentnav_data.js(flat class list instead of a grouped "Classes" folder, generic "API Reference" label instead of the project's own); adding the flag back made every output file byte-identical to the single-pass compile.
Related Docs: (Paths below are repository-root-relative.)
user-docs/docs/cli-reference.md— full reference for the subcommands introduced hereuser-docs/docs/changelog.md— condensed v1.0 -> v2.0 release notesuser-docs/docs/global-settings.md—ude_global_config.jsonfield referenceuser-docs/docs/target-settings.md—ude_doc_config.jsonfield reference
