theme(fix[spa-nav]): fragment scroll + copy-button recreation#20
Merged
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #20 +/- ##
==========================================
- Coverage 90.34% 90.31% -0.04%
==========================================
Files 147 147
Lines 12597 12605 +8
==========================================
+ Hits 11381 11384 +3
- Misses 1216 1221 +5 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…tate fires Two bugs in the cross-page fragment scroll path: 1. document.querySelector(hash) parses the hash as a CSS selector, so anchors like #libtmux_mcp.models.SearchPanesResult are read as "id libtmux_mcp, class models, class SearchPanesResult" and never match. Python autodoc ids routinely contain dots, so every :py:class:/:meth: cross-ref was failing. Switch to getElementById with the literal hash, decoded for percent-encoding. 2. The entire scroll block was gated behind !isPop, so popstate-driven navigation (browser back/forward) to a fragment URL never scrolled. Move the hash branch outside the guard and keep only the scrollTo(0, 0) inside it — browsers should still own scroll restoration on fragment-less popstate, but fragment targets should resolve on every navigation path. Verified against libtmux-mcp docs: forward SPA click, browser forward/back, and fragment-less nav all land correctly.
9841caa to
8fbb3f1
Compare
…ge copybutton_selector sphinx-copybutton only runs on DOMContentLoaded. After SPA swap the new DOM lands without buttons, so gp-sphinx's spa-nav.js re-creates them inside ``reinit()``. Two defects in that path: 1. ``addCopyButtons`` captured a live ``.copybtn`` from the initial page to use as a clone template. A landing page with no code blocks (common: index pages, prose overviews) leaves ``copyBtnTemplate`` null forever, so every subsequent SPA-swapped page that DOES have code blocks renders buttonless. 2. ``addCopyButtons`` only iterated ``div.highlight pre``, ignoring any additional selectors the project configured via ``copybutton_selector`` (e.g. custom prompt admonitions). After SPA swap such targets were left without the copy affordance that sphinx-copybutton had injected on initial load. Fixes: - Drop the clone approach. Emit an inline HTML template that matches sphinx-copybutton's button verbatim (same classes, same SVG). The initial page no longer needs any ``.copybtn`` for SPA swaps to work. - Add ``gp_sphinx._inject_copybutton_bridge`` on the ``html-page-context`` event. It emits a tiny inline ``<script>window.GP_SPHINX_COPYBUTTON_SELECTOR=…;</script>`` from the project's ``copybutton_selector`` config (gated on sphinx-copybutton being loaded). ``spa-nav.js`` reads that global and iterates the same selector list sphinx-copybutton used at build time, with ``div.highlight pre`` as the fallback. Verified end-to-end against the libtmux-mcp docs: landing page (no code blocks) → SPA-nav to an 11-code-block page → every ``<pre>`` gets a button; SPA-nav to a prompt-only page → every prompt admonition gets a button.
sphinx-copybutton's ``copybutton.js_t`` emits ``<title>{{ locale copy_to_clipboard }}</title>``
inside its SVG (line 72). The inline template in ``spa-nav.js`` was
missing this element, so SPA-recreated copy buttons had no accessible
name — screen readers announced them as "button"; DevTools / Playwright
accessibility trees rendered them as unlabeled. Clicks still worked
functionally, but the UI was discoverably broken.
Add the ``<title>`` so SPA-recreated buttons are byte-equivalent to
sphinx-copybutton's originals at the accessibility-tree level.
Keeps "Copy to clipboard" hard-coded in English; sphinx-copybutton
localizes this via ``messages[locale]``, which the theme can't see
without another bridge. Non-English docs will get English titles on
SPA-recreated buttons until we wire that through; acceptable for now.
8fbb3f1 to
6395284
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #19 (fragment scroll). Also folds in two related SPA-nav defects surfaced while testing the scroll fix end-to-end.
Bugs fixed
1. Cross-page fragment links don't scroll to anchor
document.querySelector(hash)mis-parses dotted IDs. Python autodoc anchors like#libtmux_mcp.models.SearchPanesResultread as idlibtmux_mcpAND classmodelsAND classSearchPanesResult— never match. Everypy:class/py:meth/py:attrcross-ref silently failed to scroll.Scroll gated behind
!isPop. Thepopstatelistener callsnavigate(..., true), so browser back/forward to a fragment URL never scrolled. gp-sphinx's own fetch+swap bypasses browser-native scroll restoration.2. Copy buttons disappear after SPA swap from a page with no
.copybtnaddCopyButtonscloned an existing.copybtnas its template. Landing pages and prose-only indexes have no code blocks, socopyBtnTemplatestayed null forever. SPA-navigating to any code-block page from there rendered without copy buttons.3. Copy buttons only re-applied to
div.highlight preaddCopyButtonshard-coded that selector, ignoring whatever the project configured viacopybutton_selector(e.g. prompt admonitions). Projects using custom selectors lost those copy affordances on every SPA swap.Fix
packages/sphinx-gp-theme/src/sphinx_gp_theme/theme/static/js/spa-nav.js:querySelector(hash)→getElementById(decodeURIComponent(hash.slice(1))); hash branch moved out of!isPopguard so popstate with fragment scrolls..copybtnfor SPA to work.window.GP_SPHINX_COPYBUTTON_SELECTOR || "div.highlight pre"so project-configured selectors are respected.packages/gp-sphinx/src/gp_sphinx/config.py:_inject_copybutton_bridgeonhtml-page-context. Emits a small inline<script>window.GP_SPHINX_COPYBUTTON_SELECTOR=…;</script>from the project'scopybutton_selectorconfig. Gated onsphinx_copybuttonbeing loaded.Verification
Tested end-to-end against libtmux-mcp docs at
localhost:8024via Playwright MCP. libtmux-mcp configurescopybutton_selector = "div.highlight pre, div.admonition.prompt > p:last-child"— it's a good test bed since it exercises both the fragment-scroll path and the extended-selector path.#libtmux_mcp.models.SearchPanesResultscrollY=9268, at anchorscrollY=9268, at anchorscrollY=0, scrolled to top/(no code blocks) → SPA-nav to/installation/<pre>get copy buttons/→ SPA-nav to/recipes/(prompts only)icon-tabler-copy(matches sphinx-copybutton)Obsoletes libtmux-mcp's
docs/_static/js/spa-copybutton-reinit.jsshim, which was a project-local workaround for Bugs 2 and 3. That shim is being removed in a follow-up commit in libtmux-mcp.Test plan
uv run ruff format --check .uv run ruff check .uv run mypyuv run pytest— all tests passOut of scope
copybutton_image_svgconfig override — we hard-code sphinx-copybutton's default SVG. Projects that override the icon get the default SVG on SPA-swapped buttons until the initial sphinx-copybutton run repaints (which it won't, since it only runs on DOMContentLoaded). Acceptable for now."Copy"instead of sphinx-copybutton's{{ messages[locale]['copy'] }}. Non-English docs get English tooltips on SPA-swapped buttons. Acceptable for now; can be bridged later.