Purpose
-------
Render the workspace-mode docs hub front page. Resolves the template
override at ``<workspace>/docs/_hub/index.html.j2`` and falls back to
the bundled neutral default at ``oct/templates/docs_hub/index.html.j2``.
Substitutes a manifest-driven ``subprojects`` context into the Jinja
template and writes the result to ``<site_root>/index.html``. Also
copies ``<workspace>/assets/`` (if present) into ``<site_root>/_assets/``,
falling back to the bundled defaults bundled under
``oct/templates/docs_hub/_assets/``.
Responsibilities
----------------
- Resolve the active template via :func:`resolve_template_path`,
preferring the workspace override.
- Build the Jinja render context from the loaded
:class:`oct.core.workspace.Workspace`.
- Render the template and write ``index.html`` to the site root.
- Copy assets from the workspace (or bundled defaults) into the
site's ``_assets/`` directory.
Diagnostics
-----------
Domain: OCT-DOCS
Levels:
L2 - render entry / exit
L3 - template resolution decision, asset directory selection
L4 - per-asset copy
Contracts
---------
- Render is pure: same inputs produce the same HTML output (modulo the
``generated_at`` ISO timestamp).
- Asset copy uses :func:`shutil.copytree` with ``dirs_exist_ok=True`` -
no symlinks (Windows safety).
- The renderer never reads or executes user code; the Jinja environment
has autoescape enabled for HTML safety.
Dependencies
------------
- jinja2 (lazy; Environment, FileSystemLoader, select_autoescape - renders the template, already in via Sphinx)
- oct.core.workspace (Workspace dataclass)
- oct.core.diagnostics (_dbg structured logger)
- oct.docs.doxyfile_init (lazy; has_doxyfile - whether a subproject gets a Doxygen card)
- oct.docs.typedoc (lazy; has_typedoc - whether a subproject gets a TypeDoc card)
| dict[str, Any] oct.docs.front_page.build_render_context |
( |
Workspace | workspace | ) |
|
Build the Jinja context dict for the front page.
The shape is intentionally narrow so override templates can rely on
a stable contract:
- ``workspace_name`` -- string, from the manifest
- ``option_c_href`` -- string, relative URL to the Option C tier
- ``subprojects`` -- list of dicts ``{"name", "sphinx_href",
"doxygen_href", "typedoc_href"}``; ``doxygen_href`` is ``None`` for
a subproject that declares no Doxyfile or whose manifest entry sets
``html_docs: {"doxygen": false}``, and ``typedoc_href`` is ``None``
for one with no ``typedoc.json``; the template shows no card for a
``None`` href, and a row left with only its Sphinx card is laid out
at full width
- ``generated_at`` -- ISO-8601 timestamp (seconds precision)
Only subprojects the manifest declares as carded appear in
``subprojects``: an entry with ``html_docs: false`` has no output to
link to, and one with ``html_docs: {"hub": false}`` is built and
reachable by URL but deliberately kept off the index. The template
therefore does not need to know the manifest schema - it renders
every entry it is given.
The Doxygen card follows the same declaration the build fan-out uses,
a Doxyfile at :data:`oct.docs.doxyfile_init.DOXYFILE_REL`. Before
2026-09-27 every card linked to ``<name>/doxygen/html/index.html``
unconditionally, which put five dead links on the front page - one per
subproject that never had a Doxyfile. Since 2026-09-28 the card also
follows the manifest switch ``html_docs: {"doxygen": false}``, because
the fan-out then neither builds nor aggregates that subproject's
Doxygen and a card would point at a withdrawn tree.
Definition at line 111 of file front_page.py.