Skip to content

fix(storage-walker): preserve nested task lists in storage → markdown - #245

Merged
pchuri merged 3 commits into
mainfrom
fix/nested-task-lists
Sep 30, 2026
Merged

pchuri merged 3 commits into
mainfrom
fix/nested-task-lists

Conversation

@pchuri

@pchuri pchuri commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

Fixes #240

Pull Request Template

Description

Storage → markdown conversion now preserves nested task lists. There are two storage forms:

  • Nested inside <ac:task-body>: this is the form in the issue. It used to be collapsed onto the parent line (- [ ] Parent - [x] Child).
  • Sibling of the parent <ac:task>: this is what the Confluence Cloud editor writes (for example, marked-space's documented editor output). These nested tasks used to be dropped entirely.

Both forms now render as indented children of the parent task:

- [ ] Parent
  - [x] Child

Implementation (lib/storage-walker.js):

  • handleTaskList groups each sibling <ac:task-list> under the preceding <ac:task>. The task body is then rendered with the same renderListItemBody() and LIST_INDENT path that fix(storage-walker): preserve nested list structure in storage → markdown (#238) #239 added for <li>.
  • A sibling list with no preceding task stays at the current level.
  • The per-item formatting in handleList moves into a shared formatListItem() helper. This is not a behavior change.
  • renderListItemBody() gets an optional checkbox argument:
    • By default it is prefixed to the first line of the body. This includes bodies starting with <pre> or an expand macro, which render as paragraph text.
    • Before a block that can interrupt a paragraph (fence, ATX heading, quote, table, HTML block, list, or thematic break), it stays alone on the marker line, followed directly by that block. GitHub only renders a lone [ ] as a checkbox when it is not a paragraph of its own. A leading --- becomes *** so it doesn't turn into a setext heading.
    • markdown → storage turns that bare-checkbox output into a plain <li> with [ ] (as text or <p>) followed by the block. That shape renders the same way on the way back, so the round-trip is stable. A bare [ ] followed by ordinary text in a plain <li> is left alone.

Side effects, found and fixed during review:

  • Two adjacent nested lists in one item now join tightly (they used to be separated by a blank line). The old output became a loose list and changed after a round-trip.
  • A list flattened inside a <p> in a list item no longer leaks sentinel spaces. This was already an issue on main for <ul> inside <p>.

Both side effects also apply to ordinary lists and have their own tests.

Behavior change: task bodies are now rendered as blocks instead of one flattened line. Code macros, multiple paragraphs, and regular lists inside a task keep their structure. Inline-only bodies are unchanged, including whitespace collapsing. Task lists inside table cells are still flattened to one line.

Round-trip: markdown → storage still emits plain <ul><li>[ ] …</li></ul>, since writing tasks back to Confluence is out of scope. A task-list storage → markdown → storage → markdown round-trip is stable. Tests cover nested tasks and every first-block kind (code, pre, heading, table, blockquote, info, hr, list, task list), in both tight and loose items.

Known limitation (with #246's <ol start> support): a bare checkbox followed by an ordered list that starts at a number other than 1 renders as - [ ]\n 3. c. GFM doesn't let such a list interrupt a paragraph, so it merges into the [ ] line. This is rare and not handled here, because every alternative loses either the checkbox or the list.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Performance improvement
  • Code refactoring

Testing

  • Tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

New exact-output (toBe) tests are in the nested task lists block of tests/macro-converter.test.js. They cover:

  • the nested-in-body and sibling forms
  • a leading sibling list
  • an empty parent task
  • mixed body and sibling nesting
  • a body starting with a code macro or <hr>
  • multi-paragraph bodies
  • tasks inside a list item, a callout, a table cell, and a <p>
  • the round-trips above
  • a Confluence Cloud-shaped sample in parity fixture 08-lists-and-tasks: the sibling form with ac:task-id, the placeholder-inline-tasks span, and pretty-printed whitespace

npx jest (1403 tests, on current main including #246–#250) and npx eslint lib tests both pass.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • Any dependent changes have been merged and published in downstream modules

Screenshots (if applicable)

N/A

Additional Context

Follow-up to #238 / #239. No documentation changes are needed. The changelog is generated by semantic-release.

A task list nested inside <ac:task-body> was collapsed onto the parent
task's line, and a nested <ac:task-list> written as a sibling of its
parent <ac:task> (the Confluence Cloud editor form) was dropped
entirely. Both forms now render as indented children of the parent
task, reusing the list-item body renderer and LIST_INDENT sentinel
from #239.

Task bodies are rendered as blocks like <li> bodies, so code macros,
paragraphs and regular lists inside a task keep their structure. A
task whose body starts with a block keeps a bare checkbox on the
marker line. Adjacent nested lists in one item are now joined tightly.

Fixes #240
…lock

Only put a task checkbox on its own line when the following block can
interrupt a paragraph (fence, heading, quote, table, HTML, list or
thematic break). Bodies starting with <pre> or an expand macro keep the
checkbox inline, as before.

A plain <li> holding a bare `[ ]` followed by such a block, which is
what markdown -> storage produces for that output, now renders the same
way, so the round-trip is stable.

Add round-trip tests for each first-block kind, tests for the tight
join of adjacent lists and the sentinel leak inside <p> on ordinary
lists, and a Confluence Cloud-shaped nested task sample to parity
fixture 08.
@pchuri
pchuri merged commit 8698e72 into main Sep 30, 2026
6 checks passed
@pchuri
pchuri deleted the fix/nested-task-lists branch September 30, 2026 00:55
github-actions Bot pushed a commit that referenced this pull request Sep 30, 2026
## [2.25.7](v2.25.6...v2.25.7) (2026-09-30)

### Bug Fixes

* **storage-walker:** preserve nested task lists in storage → markdown ([#245](#245)) ([8698e72](8698e72)), closes [#239](#239) [#240](#240)
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 2.25.7 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nested task lists inside a task body are flattened in storage → markdown

1 participant