Repository: sphinx-doc/sphinx

Issue:
sphinx.ext.apidoc does not include header in modules.rst
### Describe the bug

The new sphinx.ext.apidoc extension does not have an option equivalent to `--project` of the CLI which becomes "header" in the code. The main issue with this is that this option is used to give the title of the `modules.rst` file that is generated by default. Without it being set, the default `""`, leads to no page title in `modules.rst`. This means that the `modules.rst` page not only has no title, but the title can't be overwritten in an external toctree (ex. `My API <api/modules>`).

Part of the confusion comes from "header" or "--project" being documented as for the "--full" case only, but it isn't.

### How to Reproduce

index.rst:

```restructuredtext
My Docs
=======

.. toctree::
    :maxdepth: 1

    My API <api/modules>

```

conf.py:

```python
# sphinx.ext.apidoc was added to sphinx in 8.2.0
needs_sphinx = "8.2.0"

extensions = ["sphinx.ext.autodoc", "sphinx.ext.apidoc"]

# API docs
apidoc_modules = [
    {
        "path": "../../satpy",
        "destination": "api/",
    },
]
apidoc_separate_modules = True
apidoc_include_private = True
```

### Environment Information

```text
Platform:              linux; (Linux-6.9.3-76060903-generic-x86_64-with-glibc2.35)
Python version:        3.12.8 | packaged by conda-forge | (main, Dec  5 2024, 14:24:40) [GCC 13.3.0])
Python implementation: CPython
Sphinx version:        8.2.1
Docutils version:      0.21.2
Jinja2 version:        3.1.5
Pygments version:      2.18.0
```

### Sphinx extensions

```python
["sphinx.ext.autodoc", "sphinx.ext.apidoc"]
```

### Additional context

The header is passed here:

https://github.com/sphinx-doc/sphinx/blob/44346bb79c889383e8e0dac860b70f9483f6c293/sphinx/ext/apidoc/_generate.py#L193-L197

And used in the template here:

https://github.com/sphinx-doc/sphinx/blob/44346bb79c889383e8e0dac860b70f9483f6c293/sphinx/templates/apidoc/toc.rst.jinja#L1

But is defined in code as `--full` only along with the actual documentation for the apidoc cli tool:

https://github.com/sphinx-doc/sphinx/blob/44346bb79c889383e8e0dac860b70f9483f6c293/sphinx/ext/apidoc/_shared.py#L61-L69

Discussion:
@AA-Turner: Happy to accept a PR.

A@djhoese: For the comment record, the PR is https://github.com/sphinx-doc/sphinx/pull/13391@AA-Turner: [Sphinx 8.2.2](https://pypi.org/project/Sphinx/8.2.2/) has been released.

A

Solution patch:
diff --git a/AUTHORS.rst b/AUTHORS.rst
index 9ecbd9d5a10..ff92ab7eab7 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -45,7 +45,7 @@ Contributors
 * Daniel Eades -- improved static typing
 * Daniel Hahler -- testing and CI improvements
 * Daniel Pizetta -- inheritance diagram improvements
-* Dave Hoese -- ``sphinx.ext.viewcode`` bug fix
+* Dave Hoese -- ``sphinx.ext.viewcode`` and ``sphinx.ext.apidoc`` bug fixes
 * Dave Kuhlman -- original LaTeX writer
 * Dimitri Papadopoulos Orfanos -- linting and spelling
 * Dmitry Shachnev -- modernisation and reproducibility
diff --git a/CHANGES.rst b/CHANGES.rst
index 50919fe907d..887f33d68a9 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -18,6 +18,8 @@ Bugs fixed
 
 * #13392: Fix argument type for ``jieba.load_userdict()``.
 * #13402: Ensure inline ``<script>`` tags are written exactly once.
+* #13391: apidoc: Fix TOC file not having a title.
+  Patch by Dave Hoese.
 
 Testing
 -------
diff --git a/sphinx/ext/apidoc/_extension.py b/sphinx/ext/apidoc/_extension.py
index cb40cc4fd78..c5a67528ec0 100644
--- a/sphinx/ext/apidoc/_extension.py
+++ b/sphinx/ext/apidoc/_extension.py
@@ -217,7 +217,6 @@ def _parse_module_options(
         dest_dir=dest_path,
         module_path=module_path,
         exclude_pattern=exclude_patterns,
-        automodule_options=automodule_options,
         max_depth=max_depth,
         quiet=True,
         follow_links=bool_options['follow_links'],
@@ -226,6 +225,8 @@ def _parse_module_options(
         no_headings=bool_options['no_headings'],
         module_first=bool_options['module_first'],
         implicit_namespaces=bool_options['implicit_namespaces'],
+        automodule_options=automodule_options,
+        header=module_path.name,
     )
 
 


Test patch:
diff --git a/tests/test_extensions/test_ext_apidoc.py b/tests/test_extensions/test_ext_apidoc.py
index 7a9d6219415..a9742337013 100644
--- a/tests/test_extensions/test_ext_apidoc.py
+++ b/tests/test_extensions/test_ext_apidoc.py
@@ -782,13 +782,14 @@ def test_sphinx_extension(app: SphinxTestApp) -> None:
     app.build()
     assert app.warning.getvalue() == ''
 
-    assert set((app.srcdir / 'generated').iterdir()) == {
-        app.srcdir / 'generated' / 'modules.rst',
-        app.srcdir / 'generated' / 'my_package.rst',
-    }
-    assert 'show-inheritance' not in (
-        app.srcdir / 'generated' / 'my_package.rst'
-    ).read_text(encoding='utf8')
+    toc_file = app.srcdir / 'generated' / 'modules.rst'
+    pkg_file = app.srcdir / 'generated' / 'my_package.rst'
+    assert set((app.srcdir / 'generated').iterdir()) == {toc_file, pkg_file}
+    modules_content = toc_file.read_text(encoding='utf8')
+    assert modules_content == (
+        'src\n===\n\n.. toctree::\n   :maxdepth: 3\n\n   my_package\n'
+    )
+    assert 'show-inheritance' not in pkg_file.read_text(encoding='utf8')
     assert (app.outdir / 'generated' / 'my_package.html').is_file()
 
     # test a re-build