0.32.x¶
0.32.0¶
- Released:
20.08.2026
The first stable release since 0.30.3, consolidating the 0.31.x pre-release series together with everything landed since — the 0.31.x notes carry the full detail of each pre-release.
The story since 0.30.3, in six arcs:
the rendered preview grew up — need cards, cross-file links and far wider markup,
hydrated live from the index;
the HTML build went public — ubc build html builds your whole project as a static site (alpha),
with furo-parity page chrome, offline math, search, an objects.inv and true incremental rebuilds;
the needs view family is complete — all seven view directives now render,
with needpie, needbar and needgantt drawing real deterministic charts,
and need cards became a designable surface;
openCypher became the needs query language, with intersphinx cross-project references beside it;
warnings became gates — ubc check is a whole-project, severity-aware CI linter,
and the HTML build now fails on the same findings;
and Pharaoh — ubCode’s experimental agentic engineer — made its debut,
with an in-editor agent panel, MCP reference tools and documentation grounding.
✨ The HTML build (alpha)¶
ubc build htmlis public, as an alphaThe static-site builder landed across the pre-release train: a user guide, offline KaTeX math, sphinx-design components, furo-parity page chrome (hover
¶permalinks, tinted admonitions, scrolling tables), document topmatter as metadata, and coverage radars that say what could not be rendered. It is marked alpha: it works and is under active development, but markup, theming and options may still change between releases.The furo theming surface fills in
[build.html]gains furo’s own keys, semantics verbatim, so an existinghtml_theme_optionsblock ports across by copy:announcement(a site-wide banner strip),footer_icons,hide_attribution, and the fivesource_repositoryfamily keys that put furo’s view-this-page / edit-this-page buttons in the content-icon row — with the common host URL shapes built in, and misconfigurations reported instead of emitting a dead button.extra_staticpublishes the files a stylesheet references (a directory’s contents are copied preserving structure into_static/, so the sheet’s own relativeurl(…)still resolves). See the [build.html] reference.The theme toggle is drawn, and lives where furo puts it
The light/dark/auto toggle drew its states as Unicode characters, which showed as an empty box on systems whose fonts lack them — reported in the field. All three states are now inline SVG, and the toggle moved into the content-icon row at desktop widths and the sticky mobile header at phone widths. The row floats over the content instead of reserving a strip, so pages are about 42 pixels shorter, and a project that configures nothing still gains the row at desktop widths.
ubc build htmlwritesobjects.invA built site is now an intersphinx target: the output root carries a standard Sphinx inventory of the project’s documents, labels and glossary terms, written on every build exactly as
sphinx-builddoes. Another project — via ubCode’s[intersphinx.projects]or Sphinx’s ownintersphinx_mapping— can point at the published site and resolve:ref:,:doc:and:term:into it. Measured againstsphinx-buildon the sphinx-needs documentation, the two inventories are identical. Set[project] nameandversionso the inventory identifies itself.needs.jsoncan be built with the siteThe
[needs]keysbuild_json,json_exclude_fields,json_remove_defaults,json_include_link_conditionsandreproducible_jsonare now supported:ubc build htmlemitsneeds.jsonalongside the pages, shaped exactly as Sphinx-Needs shapes its own export.
✨ Need views & charts¶
The sphinx-needs view directives arrived across the train —
needlist, needtable and needflow in 0.31.1b1,
needsequence as a Mermaid sequence diagram in 0.31.2b1 —
and the family is completed in this release:
needpieandneedbardraw real chartsEach renders an inline SVG plus the numbers as text, in the preview and the built site alike, counted through the same machinery
:need_count:uses. Inline SVG buys what Sphinx-Needs cannot offer: byte-identical output for the same inputs, correctness on incremental builds, an accessible named-and-described chart, and rendering without JavaScript and in print. Fifteen authoring mistakes that abort a Sphinx-Needs build each become a warning and a deterministic degradation instead. The data table below the plot is shown when you set:legend:. See the differences register for the deliberate divergences.needganttrenders — and solves its scheduleA real gantt chart: inline SVG, an elbow connector per dependency, a progress track on every task that has one. ubCode computes a day offset per task from the constraint links, where Sphinx-Needs hands the text to PlantUML — so a chart with no
:start_date:has a well-defined layout, a constraint cycle is a reported failure rather than a nonsense picture, and a dangling link is an inline notice rather than a build crash. Point the new[needs]keysduration_option/completion_optionat fields you already have, or declare the two fields in[needs.fields].needtable::style_row:and:show_parts:are implemented:style_row:puts its value on each need’s table row as a CSS class — through the dynamic-function evaluator per need, so:style_row: needs_[[copy('status')]]colours each row by its own status — and every row now carries the Sphinx-Needs row classes, so a stylesheet written against a Sphinx build works unchanged.:show_parts:renders each need’s parts as extra rows beneath it, carrying the new[needs] part_prefix(default"→ ").needreportrenders its report.. needreport::renders the project’s need types, link types, extra fields and usage counts — the same sections a Sphinx build produces — and unlike Sphinx-Needs theusagenumbers are real rather than permanently zero. Your own Jinja template runs too::template:on the directive, or project-wide with the new[needs] report_templatekey; a template fault is reported and the default report renders instead.A project-wide cap for the views
The new
needs.views_max_itemskey sets the default:max_items:cap forneedlist,needtable,needflowandneedsequence(unset it stays at 100, as before;0removes the cap), andubc build htmlnow warns (build.need_view_truncated) whenever a cap drops items from a view.
✨ Need cards¶
Need cards debuted in 0.31.0b1; this series makes them a designable surface:
[needs.card_layouts]: design your own need cards:layout:is now honoured, and a card design is a declarative spec: five regions (header,meta,content,footer, asidecolumn) over an element vocabulary (id,title,type,field:<name>,image:<field>, …), withextendsinheriting every key you omit and per-element options (height/widthonimage,labelonfield). ubCode ships ten designs interpreted by the same code, and an unknown:layout:renders the default design with a warning — Sphinx-Needs raises an exception that ends the whole build. See Card designs with [needs.card_layouts].:style:and a need type’s colour reach the cardEach
:style:token becomes a class on the card, with styling shipped for every name Sphinx-Needs documents (colour fills,*_borderframes,*_baredge stripes and their aliases), and a[[needs.types]] coloroutlines the type badge and paints the card’s leading edge. Both resolve off the index, so they work however the value arrived — authored, extended, defaulted, variant-produced or imported.A hidden need collapses instead of disappearing
A need whose
hideresolves true renders its whole card inside a closed disclosure. Nothing is withheld: everything nested still renders, every anchor still exists, and the need’s own anchor sits outside the disclosure, so a:need:reference to a hidden need needs no expansion.[needs.string_links]: matching field values render as linksA rule is a pattern plus two templates; values of the fields it names render as links on the card, in a
needtable, in the built site and in the preview. Matching semantics are pinned to a measured Sphinx-Needs oracle; a bad rule costs one warning naming it, where Sphinx-Needs raises out of the first need it renders. See Field links with [needs.string_links].
✨ Querying & cross-project references¶
openCypher and intersphinx (0.31.1b1)
A real openCypher engine replaces the pseudo-cypher filter dialect — in the CLI, the MCP tools (
query_cypher,get_graph_schema) and the IDE filter boxes, with typo guards and Python-dialect parity. Intersphinx brings cross-project references to the IDE and preview, offline-capable, with inventories inspectable in the editor.New
ubc intersphinx diffCompare two
objects.invinventories — each a local path or a URL — and get the rows added, removed, or changed (with the changed column named).-f jsonemits the same content as one object, and the exit code (0identical,1differences,2no comparison) makes it usable as a CI gate;--exit-zeroreports without failing.
✨ Sphinx parity¶
The index learned Sphinx’s own rules across the train —
dangling :ref: / :doc: / :term: references warn with Sphinx’s message shapes,
sphinx.ext.autosectionlabel and sphinx.ext.todo are ported,
and every image, figure, table and diagram dimension is validated.
New in this release:
Glossaries render, in both flavours
.. glossary::renders the<dl class="glossary">a Sphinx build renders —:sorted:applied, hover¶permalinks, definition-less entries kept — instead of falling through to the generic directive path. A MyST{glossary}now reads and registers too: terms land in the standard domain and inobjects.inv, so:term:and{term}resolve against either flavour, and the falseterm not in glossarywarnings on Markdown glossaries are gone.contentsrenders a real page-local table of contents.. contents::and{contents}render the page’s own TOC in Sphinx’s shape, with:local:,:depth:,:class:,:backlinks:and a title argument all honoured.Built-in extension ports ship on by default
[parse].extensionsis now an override map rather than an opt-in list: every markup port ships on —sphinx_design,sphinxcontrib.mermaid,sphinxcontrib.plantuml,sphinxcontrib.video,sphinx.ext.todoand more — so their markup renders without configuration (sphinx.ext.autosectionlabelstays opt-in). A switched-off port’s markup is known but gated: parsed, completed and hovered, rendered as a labelled placeholder with adirective.extension_disabledwarning naming the fix.
✨ Needs data¶
Imports and external needs grew up (0.31.2b1)
An external need’s
external_urlis computed and stored as sphinx-needs does, so external needs are linked everywhere a need renders; imported need content renders as sandboxed cards (deny-by-default asset access); silentneedimportoutcomes now say so; and remoteneeds.jsonsources are cached, revalidated and served stale instead of vanishing on a failed fetch.A
needs.jsonwith noneeds_schemais read, not rejectedubc build validate-jsonand the needs.json panel accepted only exports carrying aneeds_schemasection (sphinx-needs 3.0.0+), so every older export was rejected. Such a source is now read best-effort — fields inferred from the values, with one warning stating exactly what was guessed.
✨ Pharaoh, MCP & the editor¶
Across the train: Pharaoh — ubCode’s experimental agentic
engineer — debuted, with heartbeat / gap / workflow verbs, onboarding for greenfield
and brownfield projects, and independent quality analysis of need substance;
the Agent panel drives Claude Code, Mistral Vibe or OpenCode in-editor;
and MCP gained agent-grade tools — query_cypher / get_graph_schema,
search_references / resolve_reference over everything the project can
cross-reference, and search_docs / fetch_doc_page grounding answers on the
official documentation — with structured content on every tool
and one-command registration with terminal Claude Code.
New in this release, the ubc agent command itself is public, as an alpha:
it is listed by ubc --help,
and its subcommand set, options and JSON output shapes may still change between releases.
👌 Improvements¶
Warm HTML builds are much faster, and reach a true no-op. Three per-page terms (two quadratic, one linear) are now computed once per build: on a 2,000-page project a warm build goes from 4.2 s to 1.9 s, emitted bytes unchanged. And a page carrying a view directive whose filter fails no longer re-renders forever — an unchanged project now reports
0 rendered, with the warning still reported.Build warnings say where they came from. Render-phase warnings carry a real source location, printed through the same block as the indexing warnings; indexing warnings print before build warnings, and a failing quality gate names each stream’s contribution.
ubc build html -vprints a per-phase timing breakdown.--denygains anonelevel on every command that has it (“report findings but never fail”), andubc build htmlgains--lint-extend-ignore, matchingubc build index.An external source whose
target_urltemplate fails now reports one warning per distinct failure naming how many needs it cost, rather than one warning per need.[[needs.types]]entries keep the order you declare them in wherever ubCode presents the type list.The cache is byte-reproducible (0.31.2b1): two cold builds of an unchanged project leave byte-identical caches.
Codelinks reads preprocessor-aware C/C++ (opt-in libclang engine), plus Go and Bash comment markers (0.31.1b1).
The changelog is now split into one page per minor release series, under a hub at the same URL. Existing deep links such as
development/changelog.html#release-0-30-3continue to work — the hub forwards them to the series page with the fragment intact.
‼️ Breaking Changes¶
Upgrading from 0.30.3 you get every change below;
the 0.31.x pre-releases already carried most of them, and each entry says when it landed.
The single most effective migration step:
if your ubproject.toml sits above the directory that actually holds your sources,
say so with [project] srcdir —
on ubCode’s own documentation taken at 0.30.3,
that one line removed 65 of 92 new warnings
(every toctree.not_included, std.doc and root-document complaint at once).
Warnings now fail your builds by default
ubc checkandubc build indexfail on any warning-or-worse finding (0.30.3 failed only on errors; since 0.31.1b1), andubc build htmlnow applies the same quality gate (new in this release) — the site is still written in full before the gate is judged, so only the exit code changes. Relax the bar with--deny error/--deny none/--max-warnings N, set it project-wide withdenyunder[build.html], or suppress individual codes through the lint configuration — a narrowed code never reaches the gate.New warnings may appear on an unchanged project
The train added whole diagnostic families, all on by default: dangling standard references (
std.ref/std.doc/std.term), toctree and duplicate-label checks, fine-grainedconfig.*codes,needimportoutcomes and dimension validation. This release adds theneedreportexecution warnings on unchanged sources (needs.report_no_sections,needs.report_template_not_found— sphinx-needs warns on the bare form too),build.need_view_truncated,build.need_layout_unknownandbuild.need_style_invalidon the HTML build. Every one is suppressible by code, whole families in one entry with trailing-.*wildcards.Options that were previously ignored now apply
:layout:is a closed set of card designs: a name defined under sphinx-needs’needs_layoutsis not a card design (ubproject.tomlcurrently swallows a[needs.layouts]table without comment) — port it to [needs.card_layouts], drop the option, or suppressbuild.need_layout_unknown. Six names that were aliases (clean_l,clean_r,clean_lp,clean_rp,focus_l,focus_r) are now real side-column designs, so those cards change.:style:is comma-separated and validated: a space-separated sphinx-needs value likeblue borderis one invalid token — writeblue, border.[needs.string_links]rules — previously inert — now render matching values as links. And withsphinx.ext.todoon by default, atodolistlists the project’s todos instead of a placeholder (switch the port off with"sphinx.ext.todo" = falseunder[parse.extensions]to keep the previous output — nottodo_include_todos = false, which hides more).Suppressions to re-check
:export_id:now warns asneeds.option_deprecated(wasneeds.filter_unsupported), on all five views that handle it. The:style_col:message text was rewritten, so amessage-ignorespattern matching the old wording stops matching — as do patterns that quoted a build warning’s position, which now lives in the location line rather than the message. A bare"config"ignore matches only code-less configuration warnings (use"config.*"), andblock.directive_unknownno longer fires for a disabled port’s markup (usedirective.extension_disabled). In the other direction,[lint] per-file-ignoresnow applies to build warnings too.One-time full re-index and re-render on upgrade
The cache format moved (6.4 → 6.116 since 0.30.3; 6.99 → 6.116 since 0.31.2b1) and the build manifest with it. 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, nothing to delete by hand.
openCypher replaces the pseudo-cypher dialect (0.31.1b1)
Missing fields read as
nullwith three-valued logic,=~anchors as full-string regex,upper()/lower()becometoUpper()/toLower(), and the MCP toolsget_data_for_single_need/get_schema_for_need_filter/query_needsare replaced byquery_cypherandget_graph_schema. See the migration notes.View output is capped (0.31.1b1)
needlist/needtable/needflow/needsequencedefault to:max_items: 100with an explicit truncation notice, and default ordering is id-sorted for reproducibility. Raise or remove the cap project-wide with the new[needs] views_max_items.Exports change shape once
needs.json/ Parquet / SQLite exports gainexternal_urlanddoctype, link and backlink lists are deduplicated and naturally sorted (0.31.2b1), and the defaultneeds.jsonnow mirrors Sphinx-Needs in dropping itscollapseandhidefields (json_exclude_fields = []restores them). Aneeds.jsonat the root of a ubCode-owned output directory is builder-managed and is pruned when the build does not produce it. A pipeline diffing exports byte-for-byte across the upgrade sees churn once.VS Code agent defaults (0.31.x)
ubcode.agent.cliPathdefaults to the version-matched bundled binary (was: whateverubcis onPATH), and driving Claude Code is ACP-only via the Pharaoh Agent panel (requiresANTHROPIC_API_KEY).Smaller behaviour changes carried by the train: a
:width:/:height:/:figwidth:value docutils rejects warns and is dropped instead of reaching the page’s CSS;ubc build list-documentsorders by toctree (--order pathrestores discovery order); a failed cache write stops the build instead of continuing silently; and warm builds converge to the same needs state as a cold build.
🐛 Fixes¶
Warm builds no longer serve stale imported or generated content. A reference or view written inside an imported need’s content — or a
needreporttemplate — was invisible to the incremental fingerprint, so a page carrying one could go stale on the first warm build after an edit somewhere else. Both now record what their content resolves to, so a warm rebuild is byte-identical to--fresh.ubc build htmlreports and gates on the re-indexed state after a mid-build source drift, rather than printing warnings from the pre-drift index.A bare
:ref:shows the implicit title, not the label name. A reference without an explicit title takes its text from the section title, rubric, caption or first term it points at, the way Sphinx does — whether the label sits above the block or is the directive’s own:name:, in both reStructuredText and MyST.objects.invdisplay names follow the same data.A
:name:on a diagram or view directive now emits its anchor. Nine directives registered the target, resolved:ref:to it and published it intoobjects.invwithout ever putting the id on the page, so the deep link scrolled to the top.An extra field named after one of the hidden core names (
[needs.fields.arch],sections, …) now shows on cards — a project’s own declaration wins visibility.needbarno longer warns about the always-empty corner cell of aFROM_DATAgrid — the documented upstream authoring shape — and no longer renders an empty label as a literal0; data-cell complaints now name the coordinates the author wrote.PlantUML visibility directives no longer delete edges. In a class, component or state diagram,
hide/show/removedropped every relationship whose endpoint was created implicitly or declared with an unrecognised node kind —hide empty membersalone could empty a diagram.A view directive whose filter fails no longer dumps a stack backtrace into the build warning when
RUST_BACKTRACEis set.Crash fixes across the train (0.31.1b1): a
needextendargument of a lone"aborted the CLI, a malformed table could crash the language server, and pathological filter strings could overflow the stack — filters are now bounded.Patched advisories in the VS Code extension’s bundled production dependencies across the window:
brace-expansion(DoS),dompurify(XSS),uuid(bounds check) in 0.31.1b1, andsmol-toml(GHSA-7w5x-hrqm-74c2) in this release.