Skip to content

fix(format): stop deleting task bodies and everything else inside ## Tasks - #50

Merged
fohara merged 1 commit into
mainfrom
fix/format-deletes-task-bodies
Aug 10, 2026
Merged

fohara merged 1 commit into
mainfrom
fix/format-deletes-task-bodies

Conversation

@fohara

@fohara fohara commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #49, same root cause. Found while fixing #48 and worse than it: lash add misattributed a task's body, lash format destroyed it — and the README tells people to run format.

The bug

#44 stopped format from deleting whole sections, by having it regenerate only the spans the model owns and copy the rest through. But the ## Tasks span was still rebuilt wholesale from the task tree, and the tree holds checkbox lines, their annotations, and the first line of each contextual note. Nothing else that lives in the section had anything to be rebuilt from, so it was deleted — exit code 0, "Formatted 1 file(s) successfully".

The reported repro was a task body. The actual blast radius was larger:

  • free-text bodies — prose, numbered steps, acceptance criteria
  • --- separators and comments
  • ### Subsection headings, which section_span explicitly supports and files routinely use to group tasks
  • the wrapped continuation lines of every contextual note, since ContextualNote records only the note's first line

Formatting this repo's own lash.index.md used to produce a 238-line diff, most of it deletion. It is 52 lines now, all of it label sorting.

The fix

#44's rule applied one level down: walk the section's source, regenerate the lines the model can account for, copy every other line through. format is now the same rule at both levels — rebuild what the model owns, carry everything else.

No new model state. Extending TaskFile to carry bodies would have been a fourth thing to keep in sync with the source, which is the shape of bug this codebase keeps paying for (see the placement-count history in #43/#33/#48).

The note handling is the part worth reviewing. Emitting a task's notes alongside the task looks right and is wrong: only a note's first line is in the model, so continuation lines get copied through where they sit while the first line is hoisted up to the task — stranding each note's text behind an unrelated bullet. Notes are anchored individually at their own source line instead. This was invisible in the small repro and obvious the moment the formatter was pointed at a real file.

Two guards on the walk:

  • A task the walk never finds is written at the end of the section rather than dropped, covering a caller that hands format_file a source the model did not come from.
  • Blank lines bounding the section are not copied, because the caller writes those separators — copying them too added a blank line per run and formatting stopped being idempotent.

Behavior change

Blank lines between tasks now survive. They belong to the author, and lash add writes one when it appends below a task with a body (#49).

Verification

The formatter still normalizes everything it owns — auto-waive, status consistency, label sorting, indentation, trailing whitespace — around preserved bodies; there's a test asserting exactly that.

Tests added:

  • bodies, separators, comments and ### Subsection headings survive
  • a body stays with the task it belongs to
  • a wrapped contextual note is not split from its own text
  • blank lines between tasks are left alone
  • normalization still applies around a body
  • idempotence over all of the above
  • no task is dropped when the source does not match the model
  • two end-to-end lash format tests asserting the on-disk file, including --check reporting a file with bodies as already formatted

Full workspace suite, clippy and rustfmt clean.

Not doing

The lint rule suggested on #48. A misattributed body is syntactically indistinguishable from a correct one, so there is nothing for the linter to check against.

…Tasks

The section was rebuilt from the model, which holds checkbox lines,
annotations and a note's first line only. It is now walked: the formatter
regenerates what it can account for and copies every other line through.
@fohara
fohara merged commit 9508786 into main Aug 10, 2026
21 checks passed
@fohara
fohara deleted the fix/format-deletes-task-bodies branch August 10, 2026 19:05
@fohara fohara mentioned this pull request Aug 11, 2026
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