Skip to content

TOC HTML breaks on frontend when headings exist between min/max (excluded levels): close_list() runs without matching open_list() #84

@accounts-lattice

Description

@accounts-lattice

Environment
SimpleTOC 7.0.5
WordPress 6.8.3
Block: simpletoc/toc with min_level: 2, max_level: 3 (so h4 and deeper are excluded from the TOC)

Summary
On the frontend, the generated list markup can become invalid: </ul> for .simpletoc-list appears too early, and additional <li> elements end up outside that <ul> (siblings instead of children). In the block editor the TOC preview often still looks correct; the bug shows up in the PHP render_callback output.

Root cause (as far as we can tell)
In generate_toc() (plugin.php), close_list() is called for every heading in the flattened list, but open_list() and the link are only output when should_exclude_headline() is false.

Headings outside the configured range (e.g. h4 sections when max_level is 3) are excluded from the TOC, but close_list() still runs with this_depth / next_depth from those excluded headings. That can emit extra </li> / </ul> (e.g. the next_depth === this_depth branch when two consecutive excluded h4s appear) without any matching opens from the current line—so the nested list state drifts until the DOM is wrong.

Minimal reproduction
Create a post with a TOC block: min_level 2, max_level 3.
Add headings in document order, for example:
h2 — included
h3 — included
Several h4 headings in a row (excluded by max_level) — e.g. repeated “Pros” / “Cons” pairs like in a columns layout
h2 or h3 — included again
Save and view the front of the post (not only the editor).
Inspect the TOC markup: look for <ul class="simpletoc-list"> closed, then <li> nodes that are not inside that <ul>.

Expected
All TOC entries are inside a single valid nested <ul class="simpletoc-list"> … </ul> (or ol).

Actual
Stray <li> after the closing </ul>; list styling/indent breaks for later sections.

Suggested fix (directional)
Only call close_list() when the current heading is not excluded (same condition as open_list() / link output), or build the TOC from a list of headings pre-filtered to included levels only so open/close logic never sees excluded depths.

Note
Real long posts with many h4 “Pros / Cons” blocks under each h3 review make this show up quickly with max_level: 3.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions