You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is a tracking issue consolidating a full triage of the open backlog (56 issues, 14 PRs as of 2026-07), a code-level review of the extension, and a forward plan β to agree direction before implementation starts.
Implementation briefs: each work item below has a detailed, self-contained brief (one PR each, with file:line references, steps, tests, and acceptance criteria) on the claude/sphinx-design-modernize-orr9g0 branch under briefs/, including an execution plan with parallelization tracks and a file-conflict matrix.
Verified: OCTICON_CSS in icons.py was dead code. Original fork branch couldn't re-trigger required checks (2024 runs too old to re-run), so re-created with authorship preserved
pre-commit autoupdate + pin sync (hook revs and dependency-group pins kept in sync)
2. Open issue triage (by cluster)
i18n β β buttons fixed
#44, #96, #263 closed by #270. Remaining: #234 (table content inside tabs missing from .pot) β separate extraction bug to investigate. Known gap documented in #270: button-ref text is absent from .pot extraction (std-domain xref resolution flattens explicit-title content before the gettext builder extracts); translation itself works.
Static files / builder lifecycle β β fixed
#200, #235 closed by #276: static assets are now served via Sphinx's standard html_static_path mechanism (HTML-format builders only), nothing is written into outdir outside Sphinx's own copying, and the Sphinx floor moved to β₯7.2.
LaTeX output
#107 (dropdown β \subsubsection*), #218 (same for tabs), #179 (grids with images). #242 (fontawesome5 package clash) β fixed in #285: sd_fontawesome_latex now also accepts "fontawesome5" (adding fontawesome5.sty and emitting \faIcon{...}), resolving the clash with themes that load it. The extension has essentially no LaTeX visitors; everything degrades via sphinxuseclass. Plan: a dedicated LaTeX pass β dropdown/tab-item β boxed environments (e.g. sphinxShadowBox-style), grids β sequential blocks (conditional fontawesome5 loading shipped in #285). CI now also compiles the docs PDF, which exercises the fontawesome5 icon path (see Phase 4).
Known follow-up: a future :tooltip: option on button-ref would lose the reference title when the cross-reference resolves (pre-existing std-domain behaviour, documented during the tooltips work)
#6, #77, #80, #177, #165, #182 all closed with explanations; #260 β done in #277 (public sphinx_design.testing module).
3. Modernization plan
Rework static-asset handling β done in β»οΈ Serve static assets via html_static_pathΒ #276 β update_css_js/update_css_links replaced by a single builder-inited hook registering sphinx_design/static/ on html_static_path + add_css_file/add_js_file (HTML-format builders only, served filenames unchanged); Sphinx floor bumped to β₯7.2 (native ?v= checksums), md5 branch and test-suite compat shims deleted.
CI coverage is silently dead β fixed in π§ͺ Fix dead codecov upload, modernize CI actionsΒ #272 (condition, codecov-action v5, action bumps, concurrency group). Follow-up idea from Phase 1 friction: run the no-myst tox env in CI and widen tox -e mypy to cover tests/, so all checker environments agree (several Phase 1 failures were only visible in envs CI doesn't run). Phase 2 addendum: the ~=7.0 CI rail resolves to 7.4.x, so the declared 7.2 floor is never CI-tested β three floor-only bugs were caught in local pinned-version runs; consider adding a sphinx==7.2.* matrix entry.
PyPI Trusted Publishing (OIDC) β replace the long-lived PYPI_KEY secret + flit publish with pypa/gh-action-pypi-publish under a pypi environment; attestations for free. Deferred for now β needs a one-time publisher registration on PyPI; revisit before the next release.
4. Card directive: replace bespoke header/footer syntax with a pure parse
Today card (and grid-item-card via CardDirective.create_card) pre-processes its raw source lines: split_content regex-scans for the first ^{3,} line (header separator) and the last \+{3,} line (footer separator), slices the StringList into three chunks, and nested-parses each separately.
Problems with this:
False positives in nested content β a +++ or ^^^ line inside a nested code-block/literal block (e.g. Jupytext cell markers, rST section underlines in examples) is treated as a separator. The "last occurrence" heuristic for footers mitigates but doesn't solve.
Approximate source mapping β there's a literal TODO set proper lines in _create_component; error locations inside cards are off.
Invisible to tooling β the separators aren't part of any parse; formatters, MyST linters/LSPs, and other renderers (including any future non-Python MyST implementation) can't understand card structure.
Adopt the same structural pattern as tab-set/tab-item:
Register card-header and card-footer directives. Content of card is parsed with one ordinary nested_parse (pure rST/MyST β no raw-text surgery).
After parsing, the card directive hoists any card-header/card-footer component children into their slots and wraps the remaining siblings in card-body. Pure doctree manipulation, correct line attribution for free.
card-header/card-footer used outside a card warn, mirroring tab-item.
.. card:: Card title
.. card-header::
Header content
Body content
.. card-footer::
Footer content
Ergonomics notes:
Slightly more verbose than ^^^/+++, but explicit, unambiguous, and identical in rST and MyST (MyST users already nest fences for tab-set/tab-item).
Optionally also add :header:/:footer:options accepting inline markup for the very common one-liner case β to decide.
Alternative considered (rejected): transition-based splitting
Parse the content and split on nodes.transition (the old sphinx-panels approach). Rejected because the two parsers disagree: ^^^^ (4+) parses as a transition in rST but +++ does not, while MyST tokenizes +++ as a block-break β so no single separator has consistent semantics across parsers, and transitions inside genuine card body content would be misinterpreted.
Deprecation pathway (not back-breaking)
vNext (0.8): both syntaxes work; docs/examples switch to the new sub-directives; ^^^/+++ remain the default-on legacy path.
Add config sd_card_legacy_separators (default True) and emit a once-per-document deprecation notice (subtype design.card_legacy, suppressible) when separators are used.
Publish a short migration guide (precedent: the sphinx-panels migration docs); the rewrite is mechanical enough for a python -m sphinx_design.migrate helper if wanted.
1.0/2.0: flip the default off; remove one major later.
5. New components & directive improvements
New directives that fit the repo's CSS-first / minimal-JS philosophy:
accordion β a group of dropdowns where opening one closes the others. Dropdowns already render as <details>/<summary>, and native HTML <details name="..."> gives exclusive-open behaviour with zero JS (Baseline 2024). Implementation is essentially a container directive assigning a shared name + CSS. Bootstrap-parity component that gets asked for regularly.
steps / step β numbered procedure steps (install guides, tutorials), CSS counters only. One of the most-copied custom CSS snippets across Sphinx sites.
aside (Aside boxesΒ #97) β floated call-out box with text wrap-around; CSS-only, long-standing request.
tooltip β either a role or a :tooltip: option on badges/buttons (Add tooltip for badgesΒ #81) β done in β¨ Add tooltip suffix syntax to badge rolesΒ #286: badge roles accept a trailing ; tooltip suffix, rendered as a native HTML title tooltip on all bdg-* families (for the link/ref badges the suffix applies only after the explicit text <target> form, since semicolons are valid in URLs and reference targets; a literal ; in badge text is escaped as \;). Documented limitation: a literal \; in a bare (non-explicit) MyST link/ref badge target is not normalized. Config-defined custom badge roles are a planned follow-up (see Phase 3).
Improvements to existing components (no new directives): vertical tab orientation (#219), carousel controls via CSS scroll-snap (#181, #133), card img-left/right (#211), dropdown FA icon option (#233).
Considered and decided out of scope: menu-style dropdowns / nav widgets (#165, closed) β interactive navigation rather than content components; better served by themes.
the sass-pin bump is superseded by Phase 2.5 below
Phase 2.5 β CSS toolchain (before any Phase 3 style work): drop Sass for plain CSS + a Python utility generator over declarative TOML data; runtime color-mix() for the hover-highlight shades (so user overrides of --sd-color-* finally propagate); vendor-prefix purge; docs gain a Baseline Widely Available support policy. Must land before Phase 3's style-heavy work so new components are authored once in the final system (brief 25).
Context
This is a tracking issue consolidating a full triage of the open backlog (56 issues, 14 PRs as of 2026-07), a code-level review of the extension, and a forward plan β to agree direction before implementation starts.
Groundwork already landed: v0.7.0 (Python 3.11β3.14, Sphinx 7β9) via #250/#255/#256,
AGENTS.md(#251, #254), Sass@importβ@usemigration (#259), tab-set crash fix (#258).Implementation briefs: each work item below has a detailed, self-contained brief (one PR each, with file:line references, steps, tests, and acceptance criteria) on the
claude/sphinx-design-modernize-orr9g0branch underbriefs/, including an execution plan with parallelization tracks and a file-conflict matrix.1. Open PR triage
OCTICON_CSSinicons.pywas dead code. Original fork branch couldn't re-trigger required checks (2024 runs too old to re-run), so re-created with authorship preserved[dependency-groups]pins synced in #267@useSCSS with author credit to gabalafou, as part of the a11y pass.readthedocs.ymlon main already hassphinx.configurationmyst-parser>=4,<6groups)2. Open issue triage (by cluster)
i18n β β buttons fixed
#44, #96, #263closed by #270. Remaining: #234 (table content inside tabs missing from.pot) β separate extraction bug to investigate. Known gap documented in #270:button-reftext is absent from.potextraction (std-domain xref resolution flattens explicit-title content before the gettext builder extracts); translation itself works.Static files / builder lifecycle β β fixed
#200, #235closed by #276: static assets are now served via Sphinx's standardhtml_static_pathmechanism (HTML-format builders only), nothing is written intooutdiroutside Sphinx's own copying, and the Sphinx floor moved to β₯7.2.LaTeX output
#107 (dropdown β
\subsubsection*), #218 (same for tabs), #179 (grids with images).#242(fontawesome5package clash) β fixed in #285:sd_fontawesome_latexnow also accepts"fontawesome5"(addingfontawesome5.styand emitting\faIcon{...}), resolving the clash with themes that load it. The extension has essentially no LaTeX visitors; everything degrades viasphinxuseclass. Plan: a dedicated LaTeX pass β dropdown/tab-item β boxed environments (e.g.sphinxShadowBox-style), grids β sequential blocks (conditionalfontawesome5loading shipped in #285). CI now also compiles the docs PDF, which exercises thefontawesome5icon path (see Phase 4).Tabs
Click+drag on tab causes synced tabs to go out of syncΒ #46β fixed in π Sync tabs via change events, open on URL hash, persist choiceΒ #284: synced tabs stay in sync when activated by click-and-drag or keyboard, by syncing on the radio input'schangeevent rather than a label click (the root cause of the desync)Open tab when itsβ fixed in π Sync tabs via change events, open on URL hash, persist choiceΒ #284: a URL fragment targeting a tab (e.g.:name:is used in URLΒ #239page.html#tab-name), or an element inside a tab panel, now opens that tab β both on load and onhashchangeSupport sticky tab selectionΒ #103β fixed in π Sync tabs via change events, open on URL hash, persist choiceΒ #284: tab selections now persist across browsing sessions vialocalStorage(a behaviour change fromsessionStorage), and the storage-key prefix is configurable via the newsd_tabs_storage_prefixoption (set it to an empty string to disable persistence)Include radio/label ID as part of tab-content divΒ #30β fixed in βΏ Restore focus rings and wire tab ARIA relationshipsΒ #283: each tab label now carries anaria-controlsattribute referencing its panel id, establishing the programmatic relationship in the accessibility tree (note: this is not a full WAI-ARIA tabs pattern β assistive-technology support foraria-controlsvaries); landed as part of the a11y pass, carrying gabalafou's focus rings (Restore focus ringsΒ #230)Cards / grids
comments inside a grid generate confusing warningsΒ #86β closed by π Ignore comments/targets in component child validationΒ #271 (+ anchor follow-up π Keep target anchors rendered in tab-set and tab-set-codeΒ #275)Multi-line content in dropdown has extra spacing on first lineΒ #40β fixed in π Stamp sd-card-text on direct paragraphs only, never replace classesΒ #278: the dropdown transform no longer wipes user paragraph classes (inverted conditional), andsd-card-textis stamped on direct child paragraphs only (a CSS-child-selector alternative is noted as a follow-up for the card redesign)grid-item-cardapplies:padding:to the outersd-coldiv, and the plaincarddirective has nopaddingoption at all), docutils hyperlink targets don't work for clickable cardsΒ #164 (docutils hyperlink targets as card links).Also: the cardβ fixed in π Fix card π whitespace stripping for ref targetsΒ #282 (:link:option shared :ref: does not resolve autosectionlabelΒ #110's whitespace-stripping bug (directives.uriin the option spec)card/grid-item-card:link:no longer strips whitespace from reference targets, and lowercases them forlink-type: ref, matching the:ref:role):link-newtab:), [Feature request] Add class-link option for cardsΒ #261 (:class-link:), CSS Class for Thumbnails/Images with fixed HeightΒ #152 (fixed-height image class), Add img-left and img-right to grid-item-cardΒ #211 (img-left/right), After screen resize or on mobile card-carousel looks badΒ #133/Card Carousel with Controls/IndicatorsΒ #181 (carousel options/controls), ENH: Adddownloadas clickable ref type for CardsΒ #56, Changing colors on cardsΒ #157Icons
β fixed/documented in π Stop icon roles leaking SVG into toc labels and searchΒ #279: icon roles no longer leak SVG into toctree labels or the search index (newtoctreeskips an entry if its title begins with an inline icon directiveΒ #99/icons not shown correctly in rst toctreeΒ #117sd_iconnode with emptyastext()); icons not shown correctly in rst toctreeΒ #117 documented as a core-Sphinx limitation (toctree entry titles are plain text). Bonus:article-infoicons regained their silently-droppedsd-pr-2spacing classFontAwesome import interfering with Pro LicenseΒ #174β fixed in β¨ FontAwesome v6 roles, CDN config, fontawesome5 LaTeX supportΒ #285 (declarative FontAwesome loading via the newsd_fontawesome_source/sd_fontawesome_cdn_urloptions, the supported path for FA Pro kits) and β¨ Add sd_fontawesome_version for version-agnostic icon rolesΒ #288 (sd_fontawesome_versionmakes icon role names version-agnostic, so upgrading FontAwesome is a one-lineconf.pychange)sd_icon/role machinery from the FontAwesome work makes an:icon:option usingstyle;namesyntax a small follow-up)Refs / links
:ref: does not resolve autosectionlabelΒ #110, Nested parse broken for button-ref but not button-linkΒ #228β fixed in π Fix button-ref target whitespace and nested contentΒ #281:button-refno longer strips whitespace from targets (multi-word autosectionlabel labels resolve;ref-type: reflowercases for:ref:parity), and rich/nested button content survives resolution instead of being flattened. (The card:link:follow-up for the same root cause β landed in π Fix card π whitespace stripping for ref targetsΒ #282 β see cards cluster.)button-linkURL other than a string?Β #222 (URL from substitution):tooltip:option onbutton-refwould lose the reference title when the cross-reference resolves (pre-existing std-domain behaviour, documented during the tooltips work)Docs-only quick wins β β done
#172, #122, #184, #247closed by #273. Remaining: #52 (indent MyST examples).Close / answer β β done
#6, #77, #80, #177, #165, #182 all closed with explanations;
#260β done in #277 (publicsphinx_design.testingmodule).3. Modernization plan
Rework static-asset handlingβ done in β»οΈ Serve static assets via html_static_pathΒ #276 βupdate_css_js/update_css_linksreplaced by a singlebuilder-initedhook registeringsphinx_design/static/onhtml_static_path+add_css_file/add_js_file(HTML-format builders only, served filenames unchanged); Sphinx floor bumped to β₯7.2 (native?v=checksums), md5 branch and test-suite compat shims deleted.CI coverage is silently deadβ fixed in π§ͺ Fix dead codecov upload, modernize CI actionsΒ #272 (condition, codecov-action v5, action bumps, concurrency group). Follow-up idea from Phase 1 friction: run theno-mysttox env in CI and widentox -e mypyto covertests/, so all checker environments agree (several Phase 1 failures were only visible in envs CI doesn't run). Phase 2 addendum: the~=7.0CI rail resolves to 7.4.x, so the declared 7.2 floor is never CI-tested β three floor-only bugs were caught in local pinned-version runs; consider adding asphinx==7.2.*matrix entry.PYPI_KEYsecret +flit publishwithpypa/gh-action-pypi-publishunder apypienvironment; attestations for free. Deferred for now β needs a one-time publisher registration on PyPI; revisit before the next release.Drop theβ done in β»οΈ Serve static assets via html_static_pathΒ #276 β all call sites now use_compat.findallshimnode.findall(...)directly (note for downstream: the private helper is gone).Development Status :: 4 - Betaβ5 - Production/Stable; adopt PEP 639license = "MIT";refresh theme extras pins(done in β¬οΈ Refresh dev/docs pins, group dependabot updatesΒ #267); plan a 1.0 once i18n + static-asset fixes land.a11y passβ done in βΏ Restore focus rings and wire tab ARIA relationshipsΒ #283 (focus rings carried from Restore focus ringsΒ #230,aria-controlswiring for Include radio/label ID as part of tab-content divΒ #30);FA import opt-outβ done in β¨ FontAwesome v6 roles, CDN config, fontawesome5 LaTeX supportΒ #285 (sd_fontawesome_source/sd_fontawesome_cdn_url). The Sass toolchain is now slated for removal entirely β plain CSS plus a small Python utility generator over declarative TOML data, with a Baseline Widely Available browser-support policy replacing the stale "Supported browsers" list (Phase 2.5 below).no i18n/gettext tests(β added in π Keep buttons intact under gettext translationΒ #270); JS behaviour (tab sync) still entirely untested β doctree regressions can't catch Click+drag on tab causes synced tabs to go out of syncΒ #46/Tab-item is modifying .html filesΒ #238-class bugs, and the π Ignore comments/targets in component child validationΒ #271 anchor bugs (fixed in π Keep target anchors rendered in tab-set and tab-set-codeΒ #275) were likewise invisible to doctree assertions. Add a minimal Playwright smoke test.Declarative configurationβ landed in β»οΈ Central declarative SdConfig dataclassΒ #274: allsd_*options on a typed dataclass (modelled on myst-parser'sMdParserConfig+ validators), every field TOML-representable. New options β the tabs storage prefix (π Sync tabs via change events, open on URL hash, persist choiceΒ #284) and the FontAwesome source / CDN / version / LaTeX settings (β¨ FontAwesome v6 roles, CDN config, fontawesome5 LaTeX supportΒ #285, β¨ Add sd_fontawesome_version for version-agnostic icon rolesΒ #288) β have since been added as fields.4. Card directive: replace bespoke header/footer syntax with a pure parse
Today
card(andgrid-item-cardviaCardDirective.create_card) pre-processes its raw source lines:split_contentregex-scans for the first^{3,}line (header separator) and the last\+{3,}line (footer separator), slices theStringListinto three chunks, and nested-parses each separately.Problems with this:
+++or^^^line inside a nested code-block/literal block (e.g. Jupytext cell markers, rST section underlines in examples) is treated as a separator. The "last occurrence" heuristic for footers mitigates but doesn't solve.TODO set proper linesin_create_component; error locations inside cards are off.add_card_child_classesthen stamps(β fixed in π Stamp sd-card-text on direct paragraphs only, never replace classesΒ #278 β now direct children only).sd-card-textonto all descendant paragraphsProposal:
card-header/card-footersub-directives + doctree hoistingAdopt the same structural pattern as
tab-set/tab-item:card-headerandcard-footerdirectives. Content ofcardis parsed with one ordinarynested_parse(pure rST/MyST β no raw-text surgery).card-header/card-footercomponent children into their slots and wraps the remaining siblings incard-body. Pure doctree manipulation, correct line attribution for free.card-header/card-footerused outside a card warn, mirroringtab-item.Ergonomics notes:
^^^/+++, but explicit, unambiguous, and identical in rST and MyST (MyST users already nest fences for tab-set/tab-item).:header:/:footer:options accepting inline markup for the very common one-liner case β to decide.Alternative considered (rejected): transition-based splitting
Parse the content and split on
nodes.transition(the old sphinx-panels approach). Rejected because the two parsers disagree:^^^^(4+) parses as a transition in rST but+++does not, while MyST tokenizes+++as a block-break β so no single separator has consistent semantics across parsers, and transitions inside genuine card body content would be misinterpreted.Deprecation pathway (not back-breaking)
^^^/+++remain the default-on legacy path.sd_card_legacy_separators(defaultTrue) and emit a once-per-document deprecation notice (subtypedesign.card_legacy, suppressible) when separators are used.python -m sphinx_design.migratehelper if wanted.5. New components & directive improvements
New directives that fit the repo's CSS-first / minimal-JS philosophy:
accordionβ a group of dropdowns where opening one closes the others. Dropdowns already render as<details>/<summary>, and native HTML<details name="...">gives exclusive-open behaviour with zero JS (Baseline 2024). Implementation is essentially a container directive assigning a sharedname+ CSS. Bootstrap-parity component that gets asked for regularly.steps/stepβ numbered procedure steps (install guides, tutorials), CSS counters only. One of the most-copied custom CSS snippets across Sphinx sites.aside(Aside boxesΒ #97) β floated call-out box with text wrap-around; CSS-only, long-standing request.tooltipβeither a role or aβ done in β¨ Add tooltip suffix syntax to badge rolesΒ #286: badge roles accept a trailing:tooltip:option on badges/buttons (Add tooltip for badgesΒ #81); tooltipsuffix, rendered as a native HTMLtitletooltip on allbdg-*families (for the link/ref badges the suffix applies only after the explicittext <target>form, since semicolons are valid in URLs and reference targets; a literal;in badge text is escaped as\;). Documented limitation: a literal\;in a bare (non-explicit) MyST link/ref badge target is not normalized. Config-defined custom badge roles are a planned follow-up (see Phase 3).Improvements to existing components (no new directives): vertical tab orientation (#219), carousel controls via CSS scroll-snap (#181, #133), card img-left/right (#211), dropdown FA icon option (#233).
Considered and decided out of scope: menu-style dropdowns / nav widgets (#165, closed) β interactive navigation rather than content components; better served by themes.
6. Suggested sequencing
_sphinx_design_staticdirectory in_build/latexΒ #200/Missing build directory leads tobuilder-initederrorΒ #235, superseded Limit copying _sphinx_design_static to HTML based buildersΒ #241; Sphinx floor now β₯7.2normalize_doctree_xmlviasphinx_design.testingΒ #260sd-card-textfixes (π Stamp sd-card-text on direct paragraphs only, never replace classesΒ #278) β closed Multi-line content in dropdown has extra spacing on first lineΒ #40toctreeskips an entry if its title begins with an inline icon directiveΒ #99, icons not shown correctly in rst toctreeΒ #117aria-controlswired (Include radio/label ID as part of tab-content divΒ #30):name:is used in URLΒ #239, Support sticky tab selectionΒ #103:link:whitespace fix (π Fix card π whitespace stripping for ref targetsΒ #282) β completes :ref: does not resolve autosectionlabelΒ #110color-mix()for the hover-highlight shades (so user overrides of--sd-color-*finally propagate); vendor-prefix purge; docs gain a Baseline Widely Available support policy. Must land before Phase 3's style-heavy work so new components are authored once in the final system (brief 25).accordion;steps;aside; card options (Option to open clickable cards in new tabΒ #170/[Feature request] Add class-link option for cardsΒ #261/CSS Class for Thumbnails/Images with fixed HeightΒ #152/Adding padding to a grid-card-item adds it to the sd-col div and not to sd-card divΒ #26); custom badge roles via config (sd_custom_rolesβ generalizessd_custom_directivesto roles, with baked tooltips)CI compiles the docs PDFβ done in π§ Compile the docs PDF in CIΒ #289 (thefontawesome5LaTeX icon path is exercised on every PR; TeX Live pinned to 2025 until the Malformedcolorrowsbehavior with TexLive 2026Β sphinx-doc/sphinx#14465 fix ships in Sphinx 9.1.1)Feedback welcome on any of the verdicts above β in particular Β§4 (card syntax) and which of Β§5 to green-light.