Repository navigation
🧭 Roadmap: backlog triage, modernization, card syntax redesign & new components #265
Description
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→@use migration (#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-orr9g0 branch under briefs/, including an execution plan with parallelization tracks and a file-conflict matrix.
1. Open PR triage
| PR | Verdict | Notes |
|---|---|---|
| #205 | ✅ merged via #266 (81dbab8) | 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 |
| #262 | ✅ merged (03ccc10) | pre-commit autoupdate (ruff 0.15, mypy 2.1); [dependency-groups] pins synced in #267 |
| #264 | ✅ merged via #270 (834eebe) | Button i18n fix, carried with authorship credit + crash hardening and i18n test coverage. Closed #96, #44, #263 |
| #230 | ✅ merged via #283 | Focus rings (a11y), carried onto the post-@use SCSS with author credit to gabalafou, as part of the a11y pass |
| #158 | ✅ closed | Button docs clarification; prose landed via #287 with authorship preserved |
| #287 | ✅ merged | Button docs clarification (buttons are styled links), carried from #158 with authorship preserved |
| #241 | ✅ closed | Superseded by the merged #276 (static-asset rework), which includes its HTML-only gating; credited in-thread |
| #237 | ✅ closed | .readthedocs.yml on main already has sphinx.configuration |
| #220 | ✅ closed | pyproject already allows myst-parser>=4,<6 |
| #203 | ✅ closed | Superseded by the merged #272 (codecov-action v5 + fixing the dead upload condition) |
| #175 | ✅ closed | Superseded by #264/#270 |
| #221 #225 #226 #227 | ✅ closed | Superseded by the merged #267 (pins refreshed + dependabot groups) |
| #268 | ✅ merged | dependabot: bump the actions group (4 GitHub Actions updates) |
| #269 | ✅ closed | dependabot python-deps group bump — superseded by #280, which folded the pin bumps in while keeping hook revs and dependency-group pins in sync |
| #280 | ✅ merged | 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).
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)- Remaining: Scope of tabs is visually unclear #173 (tab scope CSS), Vertically-oriented tabs #219 (vertical orientation option), Tab-item is modifying .html files #238 (raw HTML inside tab-item), i18n: Table in the tab does not have translation #234 (i18n of table content inside tabs — tracked under i18n above)
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)- Bugs: Adding padding to a grid-card-item adds it to the sd-col div and not to sd-card div #26 (verified:
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) - Enhancements worth accepting: Option to open clickable cards in new tab #170 (
: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 #157
Icons
✅ 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)- Remaining: Material icons are padded slightly below by default #100 (material icon baseline), Option for fontawesome icons instead of octicon for dropdowns #233 (FA icons for dropdown markers — the
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.)- Remaining: Can't use anything for a
button-linkURL other than a string? #222 (URL from substitution) - Known follow-up: a future
: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, #247 closed by #273. Remaining: #52 (indent MyST examples).
Close / answer — ✅ done
#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_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 Trusted Publishing (OIDC) — replace the long-lived
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).- Packaging polish: bump
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. - Frontend hygiene:
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 slated for removal✅ removed in ♻️ Drop Sass: plain CSS + a Python utility generator #290 — plain CSS plus a Python utility generator over declarative TOML data, runtimecolor-mix()hover shades (so user--sd-color-*overrides now propagate), a Baseline Widely Available support policy, and a rendered "Design: the CSS pipeline" page recording the invariants and their enforcing guards. - LaTeX degradation pass (see cluster above) — largest coherent pain cluster after i18n.
- Testing gaps:
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 (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 linesin_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.
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 paragraphs
Proposal: card-header / card-footer sub-directives + doctree hoisting
Adopt the same structural pattern as tab-set/tab-item:
- Register
card-headerandcard-footerdirectives. Content ofcardis parsed with one ordinarynested_parse(pure rST/MyST — no raw-text surgery). - After parsing, the card directive hoists any
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.
.. card:: Card title
.. card-header::
Header content
Body content
.. card-footer::
Footer contentErgonomics 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(defaultTrue) and emit a once-per-document deprecation notice (subtypedesign.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.migratehelper 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 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
- Phase 0 — PR hygiene ✅ complete ([pre-commit.ci] pre-commit autoupdate #262, ♻️ Remove unused constants #266, ⬆️ Refresh dev/docs pins, group dependabot updates #267 merged; 8 PRs closed; issue sweep done)
- Phase 1 — bugs & infra ✅ complete (6 landed; trusted publishing consciously deferred):
- declarative config foundation (♻️ Central declarative SdConfig dataclass #274, 70cf79b)
- button i18n: make button text translatable #264 carried by 🐛 Keep buttons intact under gettext translation #270 (834eebe) — closed internationalization is not working for buttons #96, i18n: translating button-ref label results in loss of CSS classes #44, Buttons cannot be rendered correctly if the content is translated by Gettext #263
- CI codecov fix + action modernization (🧪 Fix dead codecov upload, modernize CI actions #272, 5f41b8e)
- quick win comments inside a grid generate confusing warnings #86 (🐛 Ignore comments/targets in component child validation #271, aa59d89) + anchor follow-up (🐛 Keep target anchors rendered in tab-set and tab-set-code #275, 9f62c7d — a retrospective review caught two rendered-anchor bugs in 🐛 Ignore comments/targets in component child validation #271)
- docs quick wins Documentation for the sizing CSS classes is incorrect #172/better way to set img src file for an avatar? #122/Documentation and examples #184/Document how to include query arguments when linking to tabs #247 (📚 Fix sizing classes, grid rows, tab links, MyST prereqs #273, ce1e0a9)
- static-asset rework (♻️ Serve static assets via html_static_path #276, 1bfd79d) — closed
_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.2 - PyPI trusted publishing (brief 06) — deferred: needs a one-time publisher registration on PyPI; revisit before the next release
- Phase 2 — a11y & frontend ✅ complete:
- public testing module (✨ Add public sphinx_design.testing helpers #277) — closed Provide
normalize_doctree_xmlviasphinx_design.testing#260 - card/dropdown
sd-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 #40 - icon toc/search leak fix (🐛 Stop icon roles leaking SVG into toc labels and search #279) — closed
toctreeskips an entry if its title begins with an inline icon directive #99, icons not shown correctly in rst toctree #117 - button-ref target + rich-content fixes (🐛 Fix button-ref target whitespace and nested content #281) — closed :ref: does not resolve autosectionlabel #110, Nested parse broken for button-ref but not button-link #228
- a11y pass (♿ Restore focus rings and wire tab ARIA relationships #283) — focus rings carried from Restore focus rings #230,
aria-controlswired (Include radio/label ID as part of tab-content div #30) - tabs JS (👌 Sync tabs via change events, open on URL hash, persist choice #284) — Click+drag on tab causes synced tabs to go out of sync #46, Open tab when its
:name:is used in URL #239, Support sticky tab selection #103 - FontAwesome modernization (✨ FontAwesome v6 roles, CDN config, fontawesome5 LaTeX support #285) + follow-up (✨ Add sd_fontawesome_version for version-agnostic icon roles #288) — FontAwesome import interfering with Pro License #174, Latex build broken in Sphinx 8 #242
- badge tooltips (✨ Add tooltip suffix syntax to badge roles #286) — Add tooltip for badges #81
- card
:link:whitespace fix (🐛 Fix card 🔗 whitespace stripping for ref targets #282) — completes :ref: does not resolve autosectionlabel #110 - button docs clarification (Clarify the meaning of button in the docs #158) landed via 📚 Clarify that buttons are styled links #287, authorship preserved
- the sass-pin bump is superseded by Phase 2.5 below
- public testing module (✨ Add public sphinx_design.testing helpers #277) — closed Provide
- Phase 2.5 — CSS toolchain ✅ complete in ♻️ Drop Sass: plain CSS + a Python utility generator #290: Sass/Node dropped for plain CSS + a Python utility generator over declarative TOML; runtime
color-mix()hover shades; 163 dead vendor prefixes purged; Baseline support policy. The migration was proven rule-set and cascade-order equivalent to the old stylesheet, and the build fails loudly on unregistered files, malformed tokens, or a stale/unparsable artifact — rationale and invariants in the new "Design: the CSS pipeline" docs page. Phase 3 styles are authored in this system. - Phase 3 — card redesign & new components: §4 pure-parse cards with deprecation path;
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) - Phase 4 — LaTeX pass & JS testing: make latex translates the dropdown title into a \subsubsection* #107/Tabs items shouldn't be rendered as they are in LaTeX output #218/Sphinx Grids with Image not compiling to Latex properly #179; Playwright harness.
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) - Design docs for the settled architecture (configuration principles, syntax portability, static assets / the 1.0 API surface, testing strategy) — extends ♻️ Drop Sass: plain CSS + a Python utility generator #290's design-page pattern; best written after Phase 3 (brief 27)
- Phase 5 — 1.0: Dev Status stable, PEP 639, backlog re-sweep
Feedback welcome on any of the verdicts above — in particular §4 (card syntax) and which of §5 to green-light.
Activity
chrisjsewell commentedon Jul 12, 2026
Phase 0 progress (checklist in the description updated):
Remaining in Phase 0: the dev-pins refresh PR (+ dependabot
groups, + syncing the[dependency-groups]mypy/ruff pins) and the issue close/answer sweep.Generated by Claude Code
chrisjsewell commentedon Jul 12, 2026
Phase 1 progress (description updated): 5 of 7 items complete.
Merged today across Phases 0–1: #266, #267, #271, #272, #273, #270, #274, #275 — closing #86, #96, #44, #263, #172, #122, #184, #247 (plus the 6 issues swept at Phase 0).
Highlights:
sd_*options now live on a typed, TOML-compatible dataclass — the foundation for the upcoming tabs/FA/card-syntax options.Remaining in Phase 1: the static-asset rework (fixes #200/#235) and PyPI trusted publishing. Suggested infra follow-up from today's friction: run the
no-mysttox env in CI and widentox -e mypytotests/, so all checker environments agree.Generated by Claude Code