Skip to content

♻️ Card header/footer sub-directives: a pure parse replaces raw-text splitting#294

Open
chrisjsewell wants to merge 2 commits into
mainfrom
brief/16-card-pure-parse
Open

♻️ Card header/footer sub-directives: a pure parse replaces raw-text splitting#294
chrisjsewell wants to merge 2 commits into
mainfrom
brief/16-card-pure-parse

Conversation

@chrisjsewell

@chrisjsewell chrisjsewell commented Jul 16, 2026

Copy link
Copy Markdown
Member

Replaces the card directive's bespoke ^^^/+++ raw-line surgery with structural sub-directives, following the tab-set/tab-item pattern from the roadmap (#265 §4):

.. card:: Card title

   .. card-header::

      Header content

   Body content

   .. card-footer::

      Footer content

plus :header:/:footer: options for the one-liner case. The card body is parsed with a single ordinary nested_parse and slots are hoisted by doctree manipulation — no raw-text processing, so the structure is visible to formatters, linters, and any non-Python MyST implementation, and source-line attribution is finally correct (errors inside a footer report the footer's line; the old TODO set proper lines is gone).

Compatibility: zero behaviour change by default

  • The legacy ^^^/+++ syntax remains default-on behind the new sd_card_legacy_separators confval. Review verified byte-identical doctrees vs main across a legacy stress corpus (rst + myst: header-only, footer-only, 4+ carets, separators in code blocks, grid-item-card, carousel).
  • The strongest proof: the pre-existing snippet regression fixtures — generated from the legacy syntax — pass unchanged against the migrated snippet sources, with zero --force-regen drift, on sphinx 7.2.6 / 7.4.7 / 8.2.3.
  • Using legacy separators emits a once-per-document, suppressible design.card_legacy deprecation (with inline remedies in the message), parallel-read-safe. Timeline: deprecated now → default off at 1.0 → removed at 2.0, documented with a migration table.
  • Honest caveat, documented and surfaced in the warnings: while the flag is on, the raw-line pre-scan still runs, so a bare column-0 +++ inside a code block keeps mis-splitting (exactly as before) — the single-parse benefits are fully realized once you migrate and set sd_card_legacy_separators = False.
  • Mixed syntax reconciles predictably: directives win with a warning, including slot directives nested inside legacy chunks (review-caught; now merged without doubled wrappers).

Review coverage

Two independent adversarial reviews (structural correctness; syntax compatibility/migration): no structural findings — the transient parse marker was proven absent from pickled doctrees at the raw-byte level across all error paths; the #281/#282 button-ref transforms work on slot content; gettext now extracts header/footer content (an i18n improvement); latex/text/man degrade cleanly. The private _create_component was renamed — a one-release alias is kept and the rename is changelog-noted for downstream.

Verification

  • Full suite green on all three version rails (7.2.6 / 7.4.7 / 8.2.3, versions printed) + no-myst env
  • 20 dedicated new tests (identity between syntaxes for card and grid-item-card, deprecation mechanics, mixed syntax, orphan warnings, line attribution, empty-slot errors, class dedupe)
  • Docs fully migrated to the new syntax with a marked legacy section; -nW docs build green
  • No CSS changes (slots reuse existing sd-card-header/sd-card-footer classes); artifact untouched

Add parser-portable `card-header` / `card-footer` sub-directives (plus
inline `:header:` / `:footer:` options for one-liners) that are hoisted
into the card's header/footer slots, replacing the `^^^` / `+++`
separators that regex-scanned the card's raw source lines.

The card body is now parsed in a single pass, so source lines are
attributed correctly (kills the old `TODO set proper lines`): an error
inside a card-footer now reports the footer's line, not the card's.

`grid-item-card` inherits the same behaviour via `create_card`.

The legacy separators remain on by default behind the new
`sd_card_legacy_separators` config field (flat `sd_card_legacy_separators`,
bool, default True, rebuild=env). They emit a single, suppressible
`design.card_legacy` deprecation warning per document. Note the raw-line
scan still runs on every card while enabled, so a stray `^^^` / `+++`
(e.g. inside a code block) keeps mis-splitting until the separators are
disabled with `sd_card_legacy_separators=False`; the single-parse
robustness is only fully realised then (default flip planned at 1.0).

Both syntaxes produce identical doctrees (asserted by dedicated tests and
by the unchanged snippet fixtures). Mixing separators and directives warns
(`design.card`, wording points at the flag for incidental separator lines),
directives win and merge with no doubled slot wrappers even when nested in
a separator chunk; multiple headers/footers merge with a warning; a
card-header/card-footer outside a card warns (mirrors tab-item). An empty
card-header/card-footer directive now errors ("Content block expected")
where an empty legacy chunk produced an empty slot.

The private `CardDirective._create_component` helper is renamed to
`_create_slot`, with a back-compatible alias kept for one release.

Docs switch every card/grid example to the new syntax and gain a
"Legacy separator syntax" migration section (rst + myst) with a
migrate-then-flip note.
@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.85057% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.21%. Comparing base (4f66f32) to head (754acef).

Files with missing lines Patch % Lines
sphinx_design/cards.py 98.84% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #294      +/-   ##
==========================================
+ Coverage   92.63%   93.21%   +0.58%     
==========================================
  Files          13       13              
  Lines        1371     1519     +148     
==========================================
+ Hits         1270     1416     +146     
- Misses        101      103       +2     
Flag Coverage Δ
pytests 93.21% <98.85%> (+0.58%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant