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 onubc 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 approximationsneeduml 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).

✨ Diagrams

  • needuml and needarch draw

    Both 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: arch carries the unrendered Jinja keyed by :key:, in and out of needs.json. Three [needs] keys configure it — diagram_template, [needs.render_context] and [needs.flow_configs].

  • Every needuml failure 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-typed import(), a needarch outside a need — are each reported under their own needs.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 reports needs.uml_unconvertible and 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 the needuml and needarch views and the authored uml / plantuml directives 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 salt or a gantt shows a picture instead of its own source; and a needuml’s default flow() nodes keep the traceability links the conversion can only place in a class diagram. timeout bounds the run, min_version raises 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’s SANDBOX profile, 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 in command can 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 !include is refused by PlantUML under SANDBOX — inline the text, or use a stdlib include.

✨ Needs

  • needextract renders the needs it names

    An 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 to needs.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 needpie and needbar

    Both 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_values and calc_sum are implemented, and copy and links_from_content accept their need_id argument, so the only one ubCode does not evaluate is a function your project registers through needs_functions — it cannot run project Python. :ndf: takes a bare call and :need_func: text containing a [[...]] call. See Dynamic functions.

  • needextend renders 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, since any is the DEFAULT ref-type: an un-annotated button-ref or card :link: at a document, a glossary term or an inventory-only object rendered dead. That is fixed, an any reference inside imported need content resolves too, and dangling and ambiguous references both report std.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 a sphinx-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 reports std.confval, a value defined twice std.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_footnote and std.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_sort and myst_footnote_transition, spelled footnote_sort and footnote_transition under [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: only name="keywords" is indexed, a lang must be absent or en, 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 | clean manages the stored results — where the cache is, what is in it (URL, verdict and age), and forgetting one URL or all of them. --refresh and --offline give the freshness control ubc intersphinx already has; --show-links prints every finding even on a pass, and --output-format json emits a versioned record.

  • A relationship arrow’s direction is read in any case. A -UP-> B was unreadable, so every C4-PlantUML Rel_Up / Rel_Down / Rel_Left / Rel_Right was 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 what needflow draws, are unaffected.

  • Jinja templates gain |wordwrap and a need’s stored diagrams and link fields, on every ubCode template surface — so ubc report and needreport templates gain them too, where |wordwrap was 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 third logo.png was pushed to logo2.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 raises needs.cypher_vocabulary instead of silently drawing zeros. An unparseable scope :filter: reports needs.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 check and ubc 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 needextend is 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 html and ubc build linkcheck now head each finding with its severity, following the rustc / cargo convention: 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 and NO_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 linkcheck prints 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.json rule that does not mean what it says is now refused

    A misspelled or missing key changed what a rule MEANT and said nothing: selct for select left the rule with no filter, so it fired on every need; a rule with no validate member enforced nothing. Each printed No errors found. and exited 0, where sphinx-build refuses to build the file. ubCode now reports config.schema_unknown_key, config.schema_missing_validate or config.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 / needarch and .. confval:: all render, and .. needextend:: renders nothing at all. Need cards gain a hover , and a card carrying an explicit *_border style 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-wide idN counter, 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, and objects.inv gains a row per .. confval::.

  • Dynamic functions that used to be left as written now compute. check_linked_values and calc_sum calls, and copy / links_from_content calls carrying a need_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 first ubc diff across the upgrade reports those fields as changed. Exclude them with ubc 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 fails ubc check and ubc build html. The broadest is std.any: any is sphinx-design’s DEFAULT ref-type, so every un-annotated button-ref and every card :link: rides that arm, and a dangling or ambiguous one is now reported where it previously rendered dead without a word. Then std.footnote_unreferenced and std.citation_unreferenced (a footnote or bibliography entry nothing references — previously silent on both), the needs.uml_* family on needuml / needarch bodies that were accepted without being read, and config.schema_* on a schemas.json one of the two engines refuses. intersphinx.ambiguous is graded info and never fails a build, but fires on any project loading two overlapping inventories. Separately, a warm ubc build html used to drop a reused page’s needs.* warnings, so --deny warning could 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 seven linkcheck.* codes are not in this group: they are reported only by ubc build linkcheck, which nothing runs for you.

  • ubc build linkcheck --fix-redirects is the first ubCode command that edits your source. It is opt-in per run and never implied, and --dry-run prints 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 — and needs.report_template_failed has 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 -> B was 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 coloured box keeps its title and its colour. And a destroyed participant’s life line actually ends.

  • A need’s stored diagrams survive a needs.json round trip. arch is a hidden core field, which the external-source reader folds into its known-field set — so a needs.json that stated arch had it neither stored nor reported: it evaporated. Both ingest paths read it now.

  • An explicit *_border card style owns all four edges. A card given :style: green_border framed 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 arch diagram, 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 report std.ref naming the imported need.

  • Two ubc build linkcheck collectors were blind. A toctree entry 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 debug std_domain.json dump. Both chart families now relativise it.