Skip to content

Normalize static paths to root-absolute URLs for nested doc pages #19

Description

@cursor

Summary

sphinx-pyrepl-web emits site-relative static paths (_static/...) in <py-repl> attributes (packages, src, replay-src). This works on flat doc layouts (e.g. index.html at the site root) but breaks on nested Sphinx pages (e.g. api/module.html).

Normalize emitted paths to root-absolute form (/_static/...) at build time so REPLs work regardless of page depth.

Problem

The extension currently writes paths like:

<py-repl packages="_static/wheels/foo.whl"
         src="_static/pyrepl/index-1-bootstrap.py"
         replay-src="_static/pyrepl/index-1.py"></py-repl>

At runtime, pyrepl-web resolves these against document.baseURI:

Page document.baseURI Resolved _static/wheels/foo.whl Actual asset location
index.html https://docs.example/en/latest/index.html https://docs.example/en/latest/_static/wheels/foo.whl
api/module.html https://docs.example/en/latest/api/module.html https://docs.example/en/latest/api/_static/wheels/foo.whl ✗ (404)

The same failure affects:

  • packages=micropip.install() cannot fetch the wheel
  • src= → silent bootstrap import script fails to load
  • replay-src= → doctest replay script fails to load

pyrepl-web PR #5 adds runtime URL resolution for packages= via new URL(pkg, document.baseURI), which fixes flat layouts but not nested pages — root-absolute paths (/_static/...) resolve correctly from any page depth.

This was called out as follow-up work in #15 and deferred for initial shipping because current consumers (naming, costa) use flat layouts.

Motivation

  • Enable sphinx-pyrepl-web on standard multi-page Sphinx projects (docs/api/..., docs/guide/...)
  • Avoid requiring every consumer to hand-author /_static/... paths in RST/config
  • One consistent fix for all emitted static references (wheels, bootstrap scripts, replay scripts)

Proposed implementation

Add a small helper in sphinx_pyrepl_web/__init__.py, e.g. normalize_static_ref(path: str) -> str:

def normalize_static_ref(path: str) -> str:
    if path.startswith(("/", "http://", "https://", "emfs:")):
        return path
    if path.startswith("_static/"):
        return "/" + path
    return path

Apply when emitting attributes in:

  • make_pyrepl_raw()packages, src, replay-src
  • register_autodoc_repl() / register_autodoc_bootstrap() return values
  • PyRepl directive (:src:, :packages:) if not already root-absolute

Expected output after fix:

<py-repl packages="/_static/wheels/foo.whl"
         src="/_static/pyrepl/index-1-bootstrap.py"
         replay-src="/_static/pyrepl/index-1.py"></py-repl>

Alternative (more complex): compute ../ depth from docname and emit relative paths. Root-absolute is simpler and matches pyrepl-web's existing resolution behavior for leading /.

Test plan

  1. Unit testnormalize_static_ref():

    • _static/wheels/foo.whl/_static/wheels/foo.whl
    • /_static/wheels/foo.whl → unchanged
    • numpy / https://cdn.example/w.whl → unchanged
  2. Build test — nested page fixture:

    • docs/api/index.rst with .. py-repl:: :packages: _static/wheels/foo.whl
    • Assert api/index.html contains packages="/_static/wheels/foo.whl" (not api/_static/...)
  3. Regression — flat index.html layout still works

Out of scope

  • Changing pyrepl-web runtime resolution (already handles /_static/... correctly)
  • Normalizing user-provided paths outside _static/ (e.g. demo.py for :src:)

Acceptance criteria

  • normalize_static_ref() applied to all _static/... paths emitted by the extension
  • Tests cover nested doc page HTML output
  • README notes that _static/ paths are normalized automatically (optional)
  • Existing flat-layout consumers unaffected

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions