Replace sphinx-inline-tabs/sphinx-design with first-party UX packages#38
Draft
tony wants to merge 11 commits into
Draft
Replace sphinx-inline-tabs/sphinx-design with first-party UX packages#38tony wants to merge 11 commits into
tony wants to merge 11 commits into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #38 +/- ##
==========================================
+ Coverage 91.81% 92.32% +0.50%
==========================================
Files 219 249 +30
Lines 17758 19714 +1956
==========================================
+ Hits 16304 18200 +1896
- Misses 1454 1514 +60 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…the gp-sphinx namespace
why: Provide a first-party `{octicon}` role that ships only the icons
gp-sphinx actually consumes and keeps every emitted class inside the
`gp-sphinx-octicon` namespace.
what:
- Add `sphinx-ux-octicons` workspace package with `OcticonRole`,
`render_octicon`, and `load_octicons` exposed from the top-level module
- Bundle 18 curated octicons (12 currently in use plus 6 headroom)
as `_data/octicons.json` audited from `_data/octicons_curated.txt`
- Ship `_static/css/sphinx_ux_octicons.css` under `@layer gp-sphinx`
using `currentColor` so icons inherit the surrounding text colour
- Add a `scripts/sync_octicons.py` maintainer one-shot that rewrites
the bundled JSON from an upstream `@primer/octicons` checkout
- Cover the package with render, height-parse, role, and per-icon
snapshot tests (snapshots stored under `tests/ext/octicons`)
- Wire the package into the workspace: `pyproject.toml`, docs
package index, redirect map, cluster classification, CI smoke
runner, and the docs sys.path bootstrap
…y BadgeNode
why: Provide first-party `{bdg-<color>}` and `{bdg-<color>-line}` roles
that route through BadgeNode and the gp-sphinx-* namespace, so the
authoring surface is drop-in while every emitted class stays inside
gp-sphinx-badge.
what:
- Add _roles.py with a programmatic factory and `register_bdg_roles`
registering both fill and outline variants for the eleven semantic
colours (primary, secondary, success, info, warning, danger, light,
muted, dark, white, black)
- Extend sab_palettes.css with semantic colour triples (bg/fg/border)
plus dark-mode overrides, and bind each `gp-sphinx-badge--color-*`
class to the shared `--gp-sphinx-badge-{bg,fg,border}` tokens
- Add `SAB.COLOR_*` constants for the eleven colours
- Wire `register_bdg_roles(app)` into `setup()` and re-export it from
the package's `__all__`
- Cover the new surface with role-registration, factory-invocation,
and integration-build tests in `tests/ext/badges/test_badges.py`
…} directives
why: Provide a first-party drop-in for sphinx-design's grid markup that
keeps every emitted class inside the `gp-sphinx-grid*` namespace and
drives layout from CSS Grid via inlined CSS custom-property overrides
instead of Bootstrap-derived float classes.
what:
- Add `sphinx-ux-grid` workspace package exposing `GridDirective`,
`GridItemDirective`, `GridItemCardDirective`, `SUG`, and
`LinkPassthrough` from the top-level module
- Parse breakpoint column specs ("N" or "xs sm md lg" with each in
[1..12]) into a four-int tuple and inline them as
`--gp-sphinx-grid-cols-{xs,sm,md,lg}` style properties; `:gutter:`
accepts the 0..5 Bootstrap scale (mapped to a fixed CSS length) or a
raw CSS length, surfaced as `--gp-sphinx-grid-gutter`
- Support every `{grid-item-card}` option the workspace consumes —
`:link:` / `:link-type:` (url/any/ref/doc) with `addnodes.pending_xref`
for non-URL targets, `:shadow:` (none/sm/md/lg), `:img-top:`,
`:img-bottom:`, `:img-background:`, `:width:`, `:text-align:`, and the
full set of `:class-*:` overrides
- Split card content on `^^^` (header) and `+++` (footer) markers,
matching the splitter shape consuming docs already use
- Wrap each stretched link in a `LinkPassthrough` text element so the
HTML5 writer accepts a bare `nodes.reference` / `pending_xref` as a
card child without tripping its image-only assertion path
- Ship `_static/css/sphinx_ux_grid.css` under `@layer gp-sphinx` driving
the layout from the inlined custom properties with sensible fallbacks
- Cover the package with option-parsing unit tests, docutils-tree
directive tests (including stretched-link node-type assertions for
`url` and `doc` link types), and an integration build asserting
HTML structure plus internal-doc/external-URL link resolution
- Wire the package into the workspace: `pyproject.toml` (sources,
dev-deps, isort, testpaths), docs package index, redirect map,
cluster classification, docs `sys.path` bootstrap, the CI smoke
runner, and `tests/test_package_reference.py`
…x-inline-tabs + sphinx-design why: Replace sphinx-inline-tabs and sphinx-design's tab-set/tab-item with a first-party package whose JS does not collide with spa-nav and whose CSS stays under the gp-sphinx-* namespace. what: - Add sphinx-ux-tabs: directives .. tab:: (with :new-set:), .. tab-set::, .. tab-item:: - Custom nodes TabContainer, TabSetNode, TabItemNode, TabInputNode, TabLabelNode - Two-pass TabsPostTransform: group consecutive .. tab:: siblings, then expand to radio-input HTML - CSS-only tab switching under @layer gp-sphinx with tokens for active/inactive states - SPA-aware sync JS hooked onto gp-sphinx:navigated, idempotent via data-gp-sphinx-tabs-bound guard - Tests: pure-docutils node tests, transform unit tests, integration build covering both authoring styles
…ons for first-party
why: The first-party sphinx-ux-tabs, sphinx-ux-grid, sphinx-ux-octicons,
and sphinx-ux-badges packages own the directives and roles for {grid},
{grid-item-card}, {bdg-*}, {octicon}, .. tab::, {tab-set}, and {tab-item}
in the workspace docs. Listing them in DEFAULT_EXTENSIONS makes them the
source of truth and removes the {bdg-*} duplicate-role collisions that
arose while both sphinx-design and sphinx-ux-badges were loaded.
what:
- Replace sphinx_inline_tabs and sphinx_design in DEFAULT_EXTENSIONS
with sphinx_ux_tabs, sphinx_ux_grid, sphinx_ux_octicons, and
sphinx_ux_badges (the last explicit so the contract is independent
of which autodoc package happens to also pull it in)
- Update the >>> len(DEFAULT_EXTENSIONS) doctest to 15
- Add _LEGACY_EXTENSION_REPLACEMENTS plus _check_legacy_extension_collisions
in merge_sphinx_config, raising ExtensionError with a migration message
when a downstream consumer's extra_extensions reinstates a legacy name
alongside its first-party replacement; all collisions surface in a
single error
- Cover the validation in tests/test_config.py: rejection for sphinx_design
and sphinx_inline_tabs individually, multi-collision reporting, no-raise
on clean merge, and no-raise when a legacy entry replaces the new stack
via remove_extensions
- Switch the remove_extensions doctest and the existing
test_merge_sphinx_config_remove_extensions case from sphinx_design to
sphinx_ux_grid so they exercise an actual default
…dependencies and workarounds why: First-party sphinx-ux-tabs, sphinx-ux-grid, sphinx-ux-octicons, and sphinx-ux-badges packages now own the directives that sphinx-inline-tabs and sphinx-design used to provide. The legacy dependencies and the remove_tabs_js workaround they required are dead code. what: - Remove remove_tabs_js function and its build-finished hook from config.py - Drop sphinx-inline-tabs and sphinx-design from packages/gp-sphinx/pyproject.toml - Drop the sphinx_inline_tabs._impl warning filter from the workspace pyproject.toml - Remove redundant sphinx_ux_badges listing from docs/conf.py extra_extensions (now part of DEFAULT_EXTENSIONS) - Drop the now-unused sphinx_design entry from the FastMCP docs-page test scenario - Refresh DEFAULT_EXTENSIONS table and remove_extensions example in the docs to reflect the current extension stack - Drop tabs.js removal and sphinx_design from feature lists in README / AGENTS.md - Regenerate uv.lock
why: sphinx-ux-octicons, sphinx-ux-grid, and sphinx-ux-tabs shipped
with only stub landing pages; every other common-library package in
the workspace carries the full tutorial / how-to / reference /
explanation / examples / dependents tree. Also documents the
{bdg-*} MyST role family on the sphinx-ux-badges reference page.
what:
- Add full docs trees under docs/packages/sphinx-ux-octicons,
docs/packages/sphinx-ux-grid, docs/packages/sphinx-ux-tabs
- Document the {bdg-*} MyST role family in
docs/packages/sphinx-ux-badges/reference.md with live colour examples
for all 11 colours in both filled and outline variants
- Register [tool.gp-sphinx.docs] showcase = ["dependents"] in each
new package's pyproject.toml so the dependents subpage is picked
up by the package-landing directive's auto-discovery
…d deep-link via URL params
why: sphinx-design and sphinx-inline-tabs both shipped storage-based
persistence and URL query-param pre-selection; the drop-in
replacement should match.
what:
- Add :class-container: option on {tab-item}; propagate to the panel container
- Introduce TabPanelNode so the panel <div> carries data-sync-id /
data-sync-group HTML attributes
- Persist sync clicks to localStorage under gp-sphinx-tabs.sync.<group>
- Restore selections on script-load and gp-sphinx:navigated
- Accept ?tabs=Label and ?<group>=<id> URL params on first paint;
URL params win over localStorage and write through so they stick
… add --large size variant why: The prior styling defined its own colour tokens rather than using Furo's; the libtmux-mcp installer widget's tab styling (color-mix background tints, :focus-visible outline, brand-primary active state) sets a higher bar that travels well to the generic tab system. what: - Drop bespoke --gp-sphinx-tabs-* colour custom properties; use Furo's --color-* tokens directly so the light/dark flip travels through without per-mode rules - color-mix tint on the tab container, --color-background-hover fallback on hover, :focus-visible outline (no plain :focus ring on pointer interactions), brand-primary active state with background flip to --color-background-primary - Active panel paints --color-background-primary to "punch out" the label-row tint, so the body reads as a raised surface - Add .gp-sphinx-tabs--large modifier scaling label padding and font size; authors opt in via :class: gp-sphinx-tabs--large on a tab-set - Replace directives.class_option on TabSetDirective.:class: with a raw whitespace splitter so BEM modifier syntax (-- in gp-sphinx-tabs--large) survives the option round-trip instead of getting collapsed to a single dash by nodes.make_id - Integration test asserts the modifier class survives the :class: option round-trip
…on synced tab restore why: localStorage and URL-driven tab restoration happen after JS runs; on first paint the user briefly sees the default tab before the JS swaps to the saved selection. An inline <head> script that sets <html data-gp-sphinx-tabs-sync-<group>="<id>"> before any stylesheet loads, combined with a dedicated CSS layer that paints the matching label and panel directly from those attributes, eliminates the flash. what: - Add _prehydrate.py with _build_style() / _script() / inject_tabs_prehydrate hook - Collect (sync_group, sync_id) pairs into env.gp_sphinx_tabs_sync_pairs in the post-transform - Register the html-page-context hook, plus env-purge-doc and env-merge-info for incremental/parallel builds - Tests cover the generator output, the gated empty-set short-circuit, and an integration build confirming the <head> payload appears for sync'd pages and is absent otherwise
…ep-links, prehydrate
why: Four author-facing capabilities landed without reference docs;
make them discoverable.
what:
- Add :class-container: row to {tab-item} option table
- Add Size variants section to reference.md and how-to.md
- Add Persistence and deep-links section to reference.md and how-to.md
- Add Prehydrate subsection under JavaScript in reference.md
- Examples.md gains side-by-side default-vs-large tab-set and deep-link demo
Member
Author
Code reviewFound 1 issue:
Deps block (no gp-sphinx/packages/gp-sphinx/pyproject.toml Lines 27 to 40 in da10255 DEFAULT_EXTENSIONS (all four enabled by default): gp-sphinx/packages/gp-sphinx/src/gp_sphinx/defaults.py Lines 80 to 95 in da10255 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
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.
gp-sphinx no longer depends on
sphinx-inline-tabsorsphinx-design. Four first-party packages now own the directives and roles previously provided by those upstreams, all styled under thegp-sphinx-*CSS namespace and integrated with the workspace's Tailwind v4 +@layer gp-sphinxcascade. The authoring surface in source docs (RST and MyST) is unchanged — every directive and role keeps its name and option spec, so downstream consumers need no source edits. The tabs package was further polished to full feature parity with both upstreams (localStorage persistence, URL deep-links, prehydrate to eliminate flash-of-wrong-selection) and restyled to the visual quality of the libtmux-mcp installer widget.Summary
sphinx-ux-tabs—.. tab::(with:new-set:),.. tab-set::,.. tab-item::. Drop-in for bothsphinx-inline-tabsandsphinx-design's tab directives, with SPA-aware sync JS, localStorage persistence, URL-based deep-links, and agp-sphinx-tabs--largesize modifier.sphinx-ux-grid—.. grid::/.. grid-item::/.. grid-item-card::with CSS Grid layout and breakpoint custom-property overrides.sphinx-ux-octicons—{octicon}role with a curated bundle of icons gp-sphinx actually uses, inlined as SVG.sphinx-ux-badges— adds the{bdg-*}MyST role family (11 colors × filled/outline) routed to the existingBadgeNode, including semantic color triples with dark-mode overrides.sphinx_inline_tabsandsphinx_designinDEFAULT_EXTENSIONSwith the four first-party packages, in a single commit so directive registration is never order-ambiguous.merge_sphinx_configraisesExtensionErrorif a downstreamextra_extensionsreinstates a legacy name alongside its first-party replacement.remove_tabs_jspost-build workaround that deleted_static/tabs.jsafter every build, along with thesphinx_inline_tabs._implwarning filter and the legacy dependencies frompyproject.toml.{package-landing}directive.Packages introduced
sphinx-ux-octicons{octicon}role, curated 18-icon JSON bundle, inline SVG rendersphinx-ux-grid.. grid::,.. grid-item::,.. grid-item-card::with sphinx-design-parity option setsphinx-ux-tabs.. tab::(:new-set:),.. tab-set::,.. tab-item::, two-pass post-transform, localStorage sync, URL deep-links, prehydratesphinx-ux-badges(extended){bdg-primary|secondary|success|info|warning|danger|light|muted|dark|white|black}and-lineoutline variantsAll four follow the existing workspace package shape (pre-built static CSS under
_static/css/,setup(app)registering nodes viaapp.add_node(..., html=(visit, depart)),builder-initedcallback appending the package's_static/tohtml_static_path). None requiressphinx-vite-builder— Vite stays scoped to the theme package.Authoring surface preserved
.. tab:: Label/:new-set::::{tab-set}/:::{tab-item}:::{grid} 1 2 3 4:::{grid-item-card}{bdg-primary}\`Alpha\`{octicon}\`rocket\`Rendered output uses the
gp-sphinx-*CSS namespace exclusively. The radio-button HTML structure for tabs is identical in shape; classes are renamed (sd-tab-*→gp-sphinx-tabs__*,sd-card→gp-sphinx-grid-card,sd-badge→gp-sphinx-badge, etc.).New capabilities beyond the migration
Tabs
:class: gp-sphinx-tabs--largemodifier — opt into the larger size variant (body-size labels, roomier padding) suitable for callout sections, feature-comparison tables, and landing-page hero tabs. Default size matches the prior compact rendering.gp-sphinx-tabs.sync.<group>— a user's tab choice survives page reloads and SPA navigation.?tabs=Label(sphinx-inline-tabs idiom) and?<group>=<id>(sphinx-design idiom) both pre-select tabs on first paint, write through to localStorage. URL takes precedence on first paint; subsequent SPA-nav events read from localStorage only.<head><script data-cfasync="false">(Cloudflare Rocket Loader compatible) plus@layer gp-sphinx-tabs-prehydrateCSS paints the saved tab on the first frame, eliminating flash-of-wrong-selection. Gated to pages with sync'd tabs only.:class-container:option on{tab-item}— completes the sphinx-design option parity (alongside:class-label:and:class-content:).var(--color-*)),color-mixbackground tints,:focus-visibleoutlines, brand-primary active state.Design decisions
Single atomic flip of
DEFAULT_EXTENSIONS. Sphinx directive registration is last-registered-wins. A release with both old and new extensions in the default list would have order-dependent runtime behavior. The flip and the four new package landings are sequenced so each commit leaves the workspace buildable, and the cutover happens in one commit.nodes.containerfor grid/card, custom nodes for tabs. Grid layout is decoration —nodes.containerplus class names degrades cleanly to LaTeX/text/man builders. Tab rendering needs the radio-button HTML structure that no default visitor produces, so it's worth five custom nodes (TabContainer,TabSetNode,TabItemNode,TabInputNode,TabLabelNode) and a two-pass post-transform.Breakpoint values as inline custom properties. Grid breakpoint columns/gutter/margin/padding land as
style="--gp-sphinx-grid-cols-xs: 1; ..."on the container. The static CSS file has a finite set of rules consuming the custom properties, regardless of how many grid directives appear in the docs.Custom
:class:parser on{tab-set}. docutils'directives.class_optioncollapses--to-, so the BEM modifiergp-sphinx-tabs--largecannot survive a round-trip through the standard parser.sphinx-ux-tabsships a scoped_raw_class_optionthat splits on whitespace and keeps tokens verbatim, narrowly applied toTabSetDirective's:class:option.Curated icon bundle, not the full Octicons set. Bundling only the ~18 icons gp-sphinx actually uses keeps the wheel small. A maintainer-only
scripts/sync_octicons.pyregenerates the JSON from upstream@primer/octiconswhen the curated set changes.Borrowed prehydrate pattern, not a generic package. libtmux-mcp's
{mcp-install}widget pioneered the inline-<head>-script-plus-@layer-CSS approach to prevent flash-of-wrong-selection on SPA navigation. The pattern applies cleanly to tabs; the mechanism (env walker +html-page-contexthook + JSON-encoded script payload) is duplicated insidesphinx-ux-tabs/_prehydrate.pyrather than extracted into a shared package — single-consumer abstraction would have required parametrizing storage-key namespaces and selector generators, work that is not justified until a second consumer appears.Migration guardrail rather than silent override. Downstream consumers who explicitly re-add
sphinx_designorsphinx_inline_tabsviamerge_sphinx_config(..., extra_extensions=[...])get anExtensionErrornaming the legacy extension and its replacement. Single error reports all collisions at once.Verification
No
sd-*classes remain in the rendered docs:```console
$ rm -rf docs/_build && just build-docs && rg -n 'sd-(badge|card|tab|grid|row|col)\b' docs/_build/html/
```
No references to the dropped extensions remain in the workspace:
```console
$ rg -n 'sphinx_-' --type-add 'allcode:*.{py,toml,md,yaml,yml,json}' -t allcode | grep -v 'drop-in|replacement|_LEGACY_EXTENSION_REPLACEMENTS|ecosystem context'
```
The
remove_tabs_jsworkaround is gone:```console
$ rg -n 'remove_tabs_js|tabs.js' packages/ tests/
```
Neither legacy package is installed:
```console
$ uv pip list | grep -iE 'sphinx-?(inline|design)'
```
The first-party packages own the directives at runtime:
```console
$ rg -n 'gp-sphinx-(badge|grid-card|octicon|tabs)' docs/_build/html/index.html
```
Prehydrate emits when sync'd tabs are present, and only then:
```console
$ rg -n 'gp-sphinx-tabs-prehydrate' docs/_build/html/packages/sphinx-ux-tabs/examples/index.html
$ rg -n 'gp-sphinx-tabs-prehydrate' docs/_build/html/packages/sphinx-ux-octicons/index.html
```
Test plan