From 915a2bb9ca42c51d213e004836dc79a941e45d76 Mon Sep 17 00:00:00 2001 From: erogluorhan Date: Fri, 9 Oct 2026 12:26:48 -0600 Subject: [PATCH 1/4] Auto-number the API reference headings The API reference page mixes conventions: Grid/UxDataArray/UxDataset are top-level sections in some places and subsections of a topic (Plotting, Subsetting, Remapping, ...) in others, which makes the page hard to navigate. Add a `.. sectnum::` directive so Docutils numbers every heading on the page down to depth 3. The hierarchy is now explicit both in the headings themselves and in the page contents sidebar, and the numbering stays correct when sections are added, removed, or reordered. Note that Sphinx keeps the page title as a section, so Docutils counts it as the first level: headings read "1.3 Grid" / "1.3.1 Constructors" rather than "3. Grid" / "3.1. Constructors". Co-Authored-By: Claude Opus 5 (1M context) --- docs/api.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 3e8f59b82..ea9bb15ee 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2,6 +2,10 @@ .. _api: +.. Section numbers below are generated by Docutils; do not number headings by hand. +.. sectnum:: + :depth: 3 + API reference ============= From 79d46d158291ed83f07e25e996bd8d697319329a Mon Sep 17 00:00:00 2001 From: erogluorhan Date: Fri, 9 Oct 2026 12:28:44 -0600 Subject: [PATCH 2/4] Stop the API reference sidebar from expanding into autosummary stubs The sidebar entry for the API reference expanded into a list of `uxarray.grid.neighbors.DataArrayNeighborhood.*` stub pages instead of staying collapsed like every other entry. Those stubs are the pages `autosummary` writes for `:toctree: generated/`, and it files each one under the page that summarised it. `sphinx_remove_toctrees` was supposed to prune them, which is why only part of them showed up: it removes toctree nodes while `findall` is still iterating over the document, and docutils' `findall` is a generator, so removing a node makes it skip the siblings that follow. One of the 43 toctrees on the API reference page was skipped every build, and its 14 entries leaked into the sidebar. Replace the extension with a `env-updated` handler that materializes the node list before touching the tree, and drop the dependency. Co-Authored-By: Claude Opus 5 (1M context) --- ci/docs.yml | 1 - docs/conf.py | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/ci/docs.yml b/ci/docs.yml index 8eddd92dd..74cf28564 100644 --- a/ci/docs.yml +++ b/ci/docs.yml @@ -23,7 +23,6 @@ dependencies: - sphinx<8.2 - sphinx-book-theme - sphinx-autosummary-accessors - - sphinx-remove-toctrees - sphinx-copybutton - myst-nb - sphinx-design diff --git a/docs/conf.py b/docs/conf.py index a7ab0b6b5..ff39218aa 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,7 +14,9 @@ import matplotlib.pyplot import sphinx_autosummary_accessors import yaml +from sphinx import addnodes from sphinx.application import Sphinx +from sphinx.environment import BuildEnvironment from sphinx.util import logging PROJECT_ROOT = pathlib.Path(__file__).resolve().parents[1] @@ -63,7 +65,6 @@ def __getattr__(cls, name): "sphinx_design", "IPython.sphinxext.ipython_directive", "IPython.sphinxext.ipython_console_highlighting", - "sphinx_remove_toctrees", "sphinx_copybutton", ] @@ -103,8 +104,6 @@ def __getattr__(cls, name): # Notebook execution (per cell) nb_execution_timeout = 120 -remove_from_toctrees = ["generated/*"] - napoleon_use_admonition_for_examples = True napoleon_include_special_with_doc = True @@ -286,7 +285,37 @@ def update_gallery(app: Sphinx): LOGGER.info("Gallery page updated.") +STUB_DIR = "generated/" + + +def prune_generated_toctrees(app: Sphinx, env: BuildEnvironment): + """Keep the autosummary stub pages out of the navigation sidebar. + + ``:toctree: generated/`` is what makes ``autosummary`` write one stub page + per API entry, but it also files every stub under the page that summarised + it. Left alone, the sidebar entry for the API reference therefore expands + into hundreds of ``generated/...`` pages instead of the page's own + sections. Drop those entries once the environment is built, which is late + enough that the references have been resolved and no "document isn't + included in any toctree" warnings are emitted. + """ + for toc in env.tocs.values(): + # Materialize the iterator before touching the tree: removing a node + # while ``findall`` is still walking makes it skip the next siblings. + for toctree in list(toc.findall(addnodes.toctree)): + entries = [ + entry + for entry in toctree.attributes.get("entries", []) + if not entry[1].startswith(STUB_DIR) + ] + if entries: + toctree.attributes["entries"] = entries + else: + toctree.parent.remove(toctree) + + # Allow for changes to be made to the css in the theme_overrides file def setup(app): app.add_css_file("theme_overrides.css") app.connect("builder-inited", update_gallery) + app.connect("env-updated", prune_generated_toctrees) From 8245836b05325f97423e41bf3e17716a824d59b9 Mon Sep 17 00:00:00 2001 From: erogluorhan Date: Fri, 9 Oct 2026 12:29:38 -0600 Subject: [PATCH 3/4] Fix the empty Helpers subsection under Remapping The heading rendered but its table was always empty: `RemapWeights` is exported from `uxarray.remap`, not from the top-level `uxarray` package, so under `.. currentmodule:: uxarray` autosummary could not import it and silently dropped the entry. Summarise it as `remap.RemapWeights`, matching how the page already refers to `grid.intersections.*` and `utils.computing.*`. Co-Authored-By: Claude Opus 5 (1M context) --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index ea9bb15ee..94e6a1c72 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -483,7 +483,7 @@ Helpers .. autosummary:: :toctree: generated/ - RemapWeights + remap.RemapWeights UxDataArray From 58abf730e0d054ffaa27a8adf2b2bdfc2f886680 Mon Sep 17 00:00:00 2001 From: erogluorhan Date: Tue, 10 Nov 2026 12:20:00 -0700 Subject: [PATCH 4/4] Number API reference sections without the page title Sphinx keeps a page's title as a section of its own rather than promoting it to a document title, so Docutils' `.. sectnum::` counted "API reference" as the first level and numbered every heading on the page "1.something" -- "1.2 Tutorial", "1.3.1 Constructors". Replace the directive with a `NumberSections` transform that numbers the sections below the title instead, giving "2. Tutorial" and "3.1. Constructors". The trailing period plus a small CSS nudge separate the number from the heading, since Docutils' HTML writer collapses its own no-break-space padding to a single regular space. Also move the theme_overrides comment inside setup(), next to the add_css_file line it actually describes. Co-Authored-By: Claude Opus 5 (1M context) --- docs/_static/theme_overrides.css | 5 ++++ docs/api.rst | 5 ++-- docs/conf.py | 51 +++++++++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/docs/_static/theme_overrides.css b/docs/_static/theme_overrides.css index 08cfc12ff..5329f62a2 100644 --- a/docs/_static/theme_overrides.css +++ b/docs/_static/theme_overrides.css @@ -33,3 +33,8 @@ html[data-theme="dark"] { --pst-color-primary: #016691; --pst-color-secondary: #049390; } + +/* Give the auto-generated section numbers room to breathe. */ +.sectnum { + margin-right: 0.25em; +} diff --git a/docs/api.rst b/docs/api.rst index 442a3043a..8e9f73452 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2,9 +2,8 @@ .. _api: -.. Section numbers below are generated by Docutils; do not number headings by hand. -.. sectnum:: - :depth: 3 +.. The section numbers on this page are generated by the ``NumberSections`` + transform in ``docs/conf.py``; do not number headings by hand. API reference diff --git a/docs/conf.py b/docs/conf.py index c4d9e6dff..e345ec6bc 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,9 +14,11 @@ import matplotlib.pyplot import sphinx_autosummary_accessors import yaml +from docutils import nodes from sphinx import addnodes from sphinx.application import Sphinx from sphinx.environment import BuildEnvironment +from sphinx.transforms import SphinxTransform from sphinx.util import logging PROJECT_ROOT = pathlib.Path(__file__).resolve().parents[1] @@ -315,8 +317,55 @@ def prune_generated_toctrees(app: Sphinx, env: BuildEnvironment): toctree.parent.remove(toctree) -# Allow for changes to be made to the css in the theme_overrides file +# Pages whose headings are numbered by ``NumberSections`` below. +NUMBERED_SECTION_PAGES = frozenset({"api"}) + +# How many heading levels below the page title to number. +SECTION_NUMBER_DEPTH = 3 + + +def number_subsections(section: nodes.section, prefix: tuple[str, ...] = ()) -> None: + """Prepend "1.", "2.", "2.1." ... to the headings nested in ``section``.""" + if len(prefix) >= SECTION_NUMBER_DEPTH: + return + subsections = [child for child in section if isinstance(child, nodes.section)] + for count, subsection in enumerate(subsections, start=1): + numbers = (*prefix, str(count)) + # The trailing no-break spaces are how Docutils separates the number + # from the heading text; its HTML writer trims them back to a single + # regular space, and other writers keep them. + label = ".".join(numbers) + "." + "\u00a0" * 3 + subsection[0].insert(0, nodes.generated("", label, classes=["sectnum"])) + number_subsections(subsection, numbers) + + +class NumberSections(SphinxTransform): + """Number the headings of the pages in ``NUMBERED_SECTION_PAGES``. + + Docutils' own ``.. sectnum::`` directive is the obvious way to do this, but + Sphinx keeps a page's title as a section of its own rather than promoting it + to a document title. Docutils therefore counts the title as the first level + and numbers every heading on the page "1.something" -- "1.2 Tutorial", + "1.3.1 Constructors". Numbering the sections *below* the title instead gives + the "2. Tutorial", "3.1. Constructors" reading that was wanted. + """ + + # Docutils numbers sections at priority 710; anything after that and before + # the table of contents is collected works just as well. + default_priority = 720 + + def apply(self) -> None: + if self.env.docname not in NUMBERED_SECTION_PAGES: + return + # The first section of a Sphinx page is the one holding its title. + title_section = next(self.document.findall(nodes.section), None) + if title_section is not None: + number_subsections(title_section) + + def setup(app): + # Allow for changes to be made to the css in the theme_overrides file app.add_css_file("theme_overrides.css") app.connect("builder-inited", update_gallery) app.connect("env-updated", prune_generated_toctrees) + app.add_transform(NumberSections)