0.34.x¶
0.34.0¶
- Released:
03.09.2026
The story of this release, in five arcs:
link checking became a step you can leave switched on —
ubc build linkcheck requests every external URL the project references,
reports every place a bad one is written, grades seven verdict classes at seven
severities, verifies #fragment anchors, and can rewrite permanently redirected
URLs in the source that wrote them;
diagrams stopped being approximations —
needuml and needarch draw for the first time, sequence and C4 diagrams
keep the arrows, text and life lines they were losing, and ubc build html
(alpha) can hand the whole job to a PlantUML executable you install yourself;
the needs domain moved in step with Sphinx-Needs
(8.5.0, released alongside) —
one scope on needpie and needbar, spelled four portable ways or as a
:cypher:, counts the same needs under either engine, and needextract
renders the needs it names instead of ending a Sphinx build;
a cross-reference no longer has to name what it points at —
the :any: role walks Sphinx’s whole ladder, every un-annotated sphinx-design
button and card link rides it, configuration values join it as a target space with
.. confval:: drawing a real object description where a placeholder box used to
sit, and a name two inventories both answer is reported;
and footnotes and citations grew up —
numbered, linked and back-linked in the built site and the live preview alike,
citations resolving across documents, and Markdown footnotes collecting to the
foot of the page the way MyST does.
Around fifty new diagnostic codes ship, nearly all of them on by default,
so an unchanged project can see new warnings (see Breaking Changes).
✨ Link checking (alpha)¶
ubc build linkcheck— external link checking (alpha)Every external URL the project references is requested once, and every occurrence of a bad one is reported with its own
file:line:column. Seven verdict classes carry seven severities —linkcheck.brokenis an error,linkcheck.redirect_temporaryinformational, the other five warnings — so by default only a verified-dead link fails the command and somebody else’s outage does not. Results are cached, so a second run over an unchanged project makes no network requests at all, and failures are never cached; findings go through the ordinary[lint]filters, per occurrence. The command requires an active license however small the project: there is no unlicensed free tier here. See Link checking.Anchors are verified, and a check that could not run says so. A URL’s
#fragmentis checked against the anchors the page declares.linkcheck.anchor_missingis reported only when the whole page was read and nothing matched, and it is a warning, so it does not fail a build by default. A fragment the checker declined to judge is unverified, one on a page never requested is not checked, and both are counted separately. Turn the check off with[linkcheck] anchors. See Anchors.Fix redirected links in place:
--fix-redirectsrewrites permanently redirected URLs (301/308) in the source that wrote them, and--dry-runprints the plan without writing. The file must still hash to what was indexed — re-checked immediately before it is replaced — and the bytes at the computed position must be the URL being replaced; anything else is reported as unfixable with a reason. See Fixing redirected links.SARIF output.
--output-format sarifwrites a SARIF 2.1.0 document — one result per occurrence, with rule metadata, stable fingerprints and a machine-applicable fix for every permanent redirect — so in GitHub code scanning each finding lands inline on the pull request’s own diff. See SARIF output.Per-host settings and request headers.
[linkcheck.hosts."slow.example.com"]setsmax_concurrentandmin_interval_ms, and[linkcheck.request_headers."https://api.example.com/"]sends extra headers to URLs under a prefix, with${VARIABLE}environment indirection so a token stays out of the file. Those headers reach that host only, never another. See Per-host settings.
✨ Diagrams¶
needumlandneedarchdrawBoth directives rendered a placeholder box. A body’s Jinja is now rendered against the project’s needs into PlantUML by an engine shared with the editor preview — the same callables, context precedence,
:key:recursion guard and per-node template Sphinx-Needs has. ubCode has no PlantUML server, so the diagram is transpiled to Mermaid and drawn client-side, with no network call and no_images/copy. A need also stores its diagrams:archcarries the unrendered Jinja keyed by:key:, in and out ofneeds.json. Three[needs]keys configure it — diagram_template, [needs.render_context] and [needs.flow_configs].Every
needumlfailure is a located warning, not a dead build. Ten inputs that ended a Sphinx build with an uncatchable traceback carrying no document and no line — an unknown need id, an invalid filter, a wrong-typedimport(), aneedarchoutside a need — are each reported under their ownneeds.uml_*code, and the diagram renders as far as it got (Sphinx-Needs 8.5.0 now reports six of them upstream too). A construct Mermaid cannot express reportsneeds.uml_unconvertibleand shows the produced PlantUML in its place. The full list is at Differences from a Sphinx build.A
needflow:cypher:can name its own edges. The query has always selected a node set, with the edges derived; one returning paths or relationships is now read as the graph to draw, so a diagram can leave out a shortcut link the option model cannot exclude without collapsing the node set with it. Such a page renders a different diagram under Sphinx, not merely a plainer one. See Edges in a needflow.Bring your own PlantUML executable
ubc build html(alpha) can draw your PlantUML diagrams by running an executable you install and maintain, instead of converting them to Mermaid for the browser. One switch covers theneedumlandneedarchviews and the authoreduml/plantumldirectives and their MyST fences:[build.html.plantuml] enabled = true command = ["java", "-jar", "{config_dir}/libs/plantuml.jar"]
Two things it buys that no improvement to the conversion could: the diagrams are what PlantUML draws, so a
saltor aganttshows a picture instead of its own source; and aneeduml’s defaultflow()nodes keep the traceability links the conversion can only place in a class diagram.timeoutbounds the run,min_versionraises the accepted floor, and a rendered diagram is cached by its content. See External PlantUML renderer.What ubCode does on your behalf, and what it does not
Naming an executable grants it arbitrary code execution over your own build, so the defaults are stated. ubCode always passes
--ignore-startuml-filename, so a diagram cannot choose where bytes land; always sets PlantUML’sSANDBOXprofile, with a fresh temporary working directory; always passes--no-error-image; bounds the run on a wall clock; and refuses to inline an SVG carrying a script, a style block, an event handler or an out-of-policy link rather than editing it. That is a safe default, not a guarantee — what you put incommandcan relax it, and that is your explicit choice. PlantUML 1.2026.2 or newer is required, a security floor rather than a compatibility one: an older release accepts an option it does not recognise and then silently ignores it. Three things to know first. The live preview always uses the Mermaid conversion, so a diagram can look different in the editor than on the built page. An externally drawn diagram is finished bytes, shown on a light card in both themes, so dark diagrams should be themed at the source. And a local!includeis refused by PlantUML underSANDBOX— inline the text, or use a stdlib include.
✨ Needs¶
needextractrenders the needs it namesAn extract site drew a placeholder row; it now renders one need-view card per selected need — the same card the need gets on its own page, metadata hydrated live and body re-lowered through the ordinary pipeline, in the editor preview and in
ubc build html(alpha) alike. It is a view, not a copy: no need is contributed toneeds.json, to any filter or to any count, and it mints no duplicate anchors where upstream re-mints the need’s own id on every copy. Six inputs that end a Sphinx build with a traceback render or degrade here; Sphinx-Needs 8.5.0 reports the same six upstream instead of aborting.:layout:and:style:on the site now apply to every card it draws, beating the need’s own resolved value, where they were reported and ignored before; an unknown design is reported once and overrides nothing, where upstream ends the whole build on a typo.A portable scope on
needpieandneedbarBoth charts now take
:filter:,:status:,:tags:and:types:as a SCOPE — the set of needs each content line or grid cell is counted in, resolved once per chart — and both read the ubCode-only:cypher:the same way.:cypher:wins when both are written, so carrying both is the portable form: ubCode evaluates the query, a Sphinx build reads the python surfaces, and the two engines show the same numbers — the needs tutorial renders one such paired chart live under both. The same four options are a chart’s scope in Sphinx-Needs 8.5.0, which also accepts and ignores:cypher:; on 8.4.0 and earlier all five are unknown and the directive is dropped from the page, so upgrade both together. See A chart’s filter options SCOPE its slices... src-trace::renders its need cards. A codelinks project’s traced needs had no card anywhere and so no anchor: the site drew the generic directive placeholder. It now renders one card per traced need, in ascending id order, and a traced need becomes a hyperlink in other needs’ link rows and a resolvable:need:target. See The .. src-trace:: directive.All six Sphinx-Needs dynamic functions resolve, in prose as well as in fields.
check_linked_valuesandcalc_sumare implemented, andcopyandlinks_from_contentaccept theirneed_idargument, so the only one ubCode does not evaluate is a function your project registers throughneeds_functions— it cannot run project Python.:ndf:takes a bare call and:need_func:text containing a[[...]]call. See Dynamic functions.needextendrenders nothing in a built page, matching a Sphinx-Needs build exactly, so a reader no longer sees the id being extended or raw option rows such as+tags. The editor preview still draws it as a chip, so an otherwise invisible construct stays visible while you author it.
✨ Cross-references¶
The
:any:role, and every sphinx-design link that defaults to it:any:(and its MyST{any}twin) resolves a target without naming its kind, walking Sphinx’s own ladder in Sphinx’s own order: a document, a project-wide label, a glossary term, then the intersphinx inventories.[parse] default_role = "any"now works end to end. The same ladder is what sphinx-design’s cross-reference directives ride, sinceanyis the DEFAULTref-type: an un-annotatedbutton-refor card:link:at a document, a glossary term or an inventory-only object rendered dead. That is fixed, ananyreference inside imported need content resolves too, and dangling and ambiguous references both reportstd.any. See The :any: role.Configuration values:
.. confval::and the:confval:role. The directive drew a grey placeholder box and the role drew a chip; both now work, on reStructuredText and Markdown pages alike, at byte parity with asphinx-build. A configuration value reaches everything a cross-reference target reaches:objects.inv, the:any:ladder, site search,ubc query search, and go-to-definition and hover in the editor. A dangling reference reportsstd.confval, a value defined twicestd.duplicate_confval. See Configuration values.A cross-inventory ambiguity is reported:
intersphinx.ambiguous. A reference that misses every local rung and is then answered by more than one configured inventory — or by more than one object type — used to resolve silently to whichever won. It is now reported at informational level, naming the project and type taken and the ones also matched, across every reference role.
✨ Footnotes & citations¶
Footnotes and citations are numbered, linked and back-linked
Auto footnotes (
[#]_), symbol footnotes ([*]_), manually numbered ones and citations are numbered, turned into links, and given back-links from the definition to every reference — in the built site and in the editor’s live preview alike. Auto definitions take their numbers in definition order, each the next integer no footnote or citation on the page already claims; the markup is docutils’ own, byte for byte against a Sphinx build. A citation label is a standard-domain label, so[CIT2002]_now resolves across documents and brings goto-definition, find-references and the dangling-reference sweep with it. Five new warnings replace the old silence:ref.too_many_autonumbered,ref.too_many_symbol_refs,ref.unknown_footnote,ref.duplicate_footnoteandstd.footnote_unreferenced. See Footnotes and citations.A citation nobody cites is reported:
std.citation_unreferenced. A bibliography entry no page in the project references is now reported at its own definition. The sweep runs over the whole project on every index pass, so deleting the last reference on page B makes the finding appear on page A — and restoring it clears it — with page A never re-parsed.Markdown footnotes collect to the foot of the page. A Markdown page’s footnote definitions now leave the block they were written in and are written below the last section, ordered by the number each was given and preceded by a horizontal rule — MyST’s
myst_footnote_sortandmyst_footnote_transition, spelledfootnote_sortandfootnote_transitionunder[parse.parsers.md], both on by default. See Footnotes.
✨ The HTML build (alpha)¶
:keywords:feed the site search. A page’s.. meta::keywords have been collected since the page<head>channel landed; the search index never looked at them. Each comma-separated item is now searchable on that page, with Sphinx’s three gates reproduced: onlyname="keywords"is indexed, alangmust be absent oren, and a:nosearch:page contributes nothing. A multi-word item is an exact, full-score hit on both words. See Page <head> meta tags.
👌 Improvements¶
ubc build linkcheck cache dir | list | cleanmanages the stored results — where the cache is, what is in it (URL, verdict and age), and forgetting one URL or all of them.--refreshand--offlinegive the freshness controlubc intersphinxalready has;--show-linksprints every finding even on a pass, and--output-format jsonemits a versioned record.A relationship arrow’s direction is read in any case.
A -UP-> Bwas unreadable, so every C4-PlantUMLRel_Up/Rel_Down/Rel_Left/Rel_Rightwas silently dropped from the drawn diagram, with no edge and no warning. An unreadable line now leaves a%% unknown:comment in all five diagram families and a-[hidden]-layout hint is omitted instead of drawn: on fourteen C4 samples, 59 drawn edges became 97.The bundled Mermaid moves to 11.17.2 (from 11.16.0), in the editor preview and in
ubc build html(alpha) alike. Class diagrams are the one place an existing project can see the difference with no source edit (see Breaking Changes); flowcharts, which is whatneedflowdraws, are unaffected.Jinja templates gain
|wordwrapand a need’s stored diagrams and link fields, on every ubCode template surface — soubc reportandneedreporttemplates gain them too, where|wordwrapwas an unknown-filter error before.One image file gets one
_images/copy, however it was spelled. Two pages writing one file two ways published two byte-identical copies under two names, and burned the name in between, so an unrelated thirdlogo.pngwas pushed tologo2.png.A mistyped label or property in a
needbar‘s:cypher:is reported. The option was read by nobody there; it now scopes the grid, and a typo raisesneeds.cypher_vocabularyinstead of silently drawing zeros. An unparseable scope:filter:reportsneeds.filter_invalid.A dynamic-function call written in a need’s option or link value is checked, by the same static check the roles run, so it reaches
ubc checkandubc build html(alpha) rather than only the editor. A mistyped name previously produced no signal anywhere and the item was dropped at resolution.The editor grades diagnostics as the command line does. Both language-server publishers hardcoded warning, so a code deliberately graded informational was still squiggled as a warning.
A hover
¶permalink on every need card that mints an anchor, so a reader can copy a link to a need instead of assembling the fragment by hand.The page a
needextendis written on is no longer rewritten by an incremental build when one of its options changes; only the pages whose cards the extend reaches are.Severity-labelled diagnostic blocks.
ubc check,ubc build index,ubc build htmlandubc build linkchecknow head each finding with its severity, following therustc/cargoconvention:warning[image.not_found]where the header used to read[image.not_found]. The label is plain text, so it survives a pipe, a CI log andNO_COLOR; on a terminal an error header is red, a warning header yellow, and an informational block is dimmed throughout. A script anchored on the old[code]header needs the label added; the JSON and SARIF documents are unchanged.ubc build linkcheckprints its findings errors first, then warnings, then info, URL order within each grade; the JSON and SARIF documents keep URL order.
‼️ Breaking Changes¶
A
schemas.jsonrule that does not mean what it says is now refusedA misspelled or missing key changed what a rule MEANT and said nothing:
selctforselectleft the rule with no filter, so it fired on every need; a rule with novalidatemember enforced nothing. Each printedNo errors found.and exited 0, wheresphinx-buildrefuses to build the file. ubCode now reportsconfig.schema_unknown_key,config.schema_missing_validateorconfig.schema_invalid_value— wrong-typed values included — applies no rule from the file, and exits 1. A project carrying such a rule starts failing where it silently passed; the fix is in the file.Unchanged pages render differently
Several constructs that drew a placeholder now draw their real output, and one that drew a placeholder now draws nothing:
needextract,.. src-trace::,needuml/needarchand.. confval::all render, and.. needextend::renders nothing at all. Need cards gain a hover¶, and a card carrying an explicit*_borderstyle frames its leading edge instead of keeping the per-type accent stripe. Markdown footnote definitions move to the foot of the page. An unnamed auto footnote definition now consumes the document-wideidNcounter, so a later duplicate section on such a page shifts by one. Class diagrams are re-laid-out by Mermaid 11.17’s unified renderer. And one image file spelled two ways now gets one_images/copy, so deep links to built images can move. A screenshot-diffing or byte-comparing pipeline will see all of it, andobjects.invgains a row per.. confval::.Dynamic functions that used to be left as written now compute.
check_linked_valuesandcalc_sumcalls, andcopy/links_from_contentcalls carrying aneed_id, were previously reported and left as the authored text. They now resolve, so an unchanged project’s resolved field values change —needs.json, filters, tables and schema validation all see the computed value, and the firstubc diffacross the upgrade reports those fields as changed. Exclude them withubc diff --deny-extra <field>.New warnings may appear on an unchanged project
This release adds around fifty new diagnostic codes — forty-four of them warnings — and on the default gate (
deny = "warning") a warning failsubc checkandubc build html. The broadest isstd.any:anyis sphinx-design’s DEFAULTref-type, so every un-annotatedbutton-refand every card:link:rides that arm, and a dangling or ambiguous one is now reported where it previously rendered dead without a word. Thenstd.footnote_unreferencedandstd.citation_unreferenced(a footnote or bibliography entry nothing references — previously silent on both), theneeds.uml_*family onneeduml/needarchbodies that were accepted without being read, andconfig.schema_*on aschemas.jsonone of the two engines refuses.intersphinx.ambiguousis graded info and never fails a build, but fires on any project loading two overlapping inventories. Separately, a warmubc build htmlused to drop a reused page’sneeds.*warnings, so--deny warningcould exit 0 warm and 1 cold over the same tree; they are all reported now. Silence any code individually or by family under[lint]:ignore = ["needs.uml_*"]. The sevenlinkcheck.*codes are not in this group: they are reported only byubc build linkcheck, which nothing runs for you.ubc build linkcheck --fix-redirectsis the first ubCode command that edits your source. It is opt-in per run and never implied, and--dry-runprints the whole plan without writing a byte.One-time full re-index and re-render on upgrade. The cache format moved (6.145 → 6.167) and the build manifest with it (84 → 99). The first run after upgrading rebuilds the index and re-renders every page once — about the cost of a cold build — then incremental behaviour resumes. Self-healing.
🐛 Fixes¶
A warm build no longer drops a reused page’s
needs.*warnings. They were filtered through a lookup that could only recognise the HTML builder’s own codes, so five view warnings vanished on every warm build — andneeds.report_template_failedhas no other surface at all, so a warm build removed the finding from existence. Self-repairing, with no re-render.Sequence diagrams draw what you wrote.
A -> Bwas emitted as a Mermaid token that renders as a line with no arrowhead at all, and every left-pointing arrow pointed the wrong way. A;anywhere in a label, note, guard or title made the whole diagram a parse error and a#silently truncated the text; every text position is now escaped, so a colouredboxkeeps its title and its colour. And a destroyed participant’s life line actually ends.A need’s stored diagrams survive a
needs.jsonround trip.archis a hidden core field, which the external-source reader folds into its known-field set — so aneeds.jsonthat statedarchhad it neither stored nor reported: it evaporated. Both ingest paths read it now.An explicit
*_bordercard style owns all four edges. A card given:style: green_borderframed only three of them — the leading edge kept the per-type accent stripe — where Sphinx-Needs draws a four-sided frame.A MyST directive’s literal body keeps its indentation. The same diagram written once in reStructuredText and once in Markdown produced two different strings, the Markdown one flush against the margin. That string is a need’s stored
archdiagram, so a Markdown page drew it de-indented.Named hyperlink references resolve in the editor preview. External targets, indirect chains and anonymous references were rendered as dead text: the preview already ran the whole-document resolution on every keystroke and simply never handed the result to the writer.
A citation reference written inside imported need content counts. It was collected as no reference at all, so a bibliography cited only from
needimported cards would have been reported as uncited. The same fix makes an unresolvable one reportstd.refnaming the imported need.Two
ubc build linkcheckcollectors were blind. Atoctreeentry naming an external URL was collected by nobody, so such a URL was reported as neither working nor broken; and a link carried by a substitution is now reported once, at its definition, rather than once per use with no position.A chart
:cypher:written in an.. included fragment stored an absolute path, which leaked the build machine’s project root into the debugstd_domain.jsondump. Both chart families now relativise it.