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.

Activity

  1. chrisjsewell commented on Jul 12, 2026

    @chrisjsewell
    MemberAuthor

    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

  2. chrisjsewell commented on Jul 12, 2026

    @chrisjsewell
    MemberAuthor

    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:

    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-myst tox env in CI and widen tox -e mypy to tests/, so all checker environments agree.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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