Skip to content

🧭 Roadmap: backlog triage, modernization, card syntax redesign & new components #265

Description

@chrisjsewell

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

Cards / grids

Icons

Refs / links

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

  1. 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.
  2. 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.
  3. 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. Drop the _compat.findall shim βœ… done in ♻️ Serve static assets via html_static_pathΒ #276 β€” all call sites now use node.findall(...) directly (note for downstream: the private helper is gone).
  5. Packaging polish: bump Development Status :: 4 - Beta β†’ 5 - Production/Stable; adopt PEP 639 license = "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.
  6. Frontend hygiene: a11y pass βœ… done in β™Ώ Restore focus rings and wire tab ARIA relationshipsΒ #283 (focus rings carried from Restore focus ringsΒ #230, aria-controls wiring 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, runtime color-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.
  7. LaTeX degradation pass (see cluster above) β€” largest coherent pain cluster after i18n.
  8. 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.
  9. Declarative configuration βœ… landed in ♻️ Central declarative SdConfig dataclassΒ #274: all sd_* options on a typed dataclass (modelled on myst-parser's MdParserConfig + 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 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.
  • add_card_child_classes then stamps sd-card-text onto all descendant paragraphs (βœ… fixed in πŸ› Stamp sd-card-text on direct paragraphs only, never replace classesΒ #278 β€” now direct children only).

Proposal: card-header / card-footer sub-directives + doctree hoisting

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)

  1. vNext (0.8): both syntaxes work; docs/examples switch to the new sub-directives; ^^^/+++ remain the default-on legacy path.
  2. 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.
  3. 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.
  4. 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.

6. Suggested sequencing

Feedback welcome on any of the verdicts above β€” in particular Β§4 (card syntax) and which of Β§5 to green-light.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions