Stop lash add prepending above the H1 when the Tasks section is empty - #36
Merged
Merged
Conversation
`lash add` against an existing file whose `## Tasks` section held no tasks wrote the checkbox at line 1, above the H1. The parser never saw it as a task, so `lash index` reported 0 tasks and `lash lint` passed clean: the task was on disk and nowhere else, with exit code 0 and a normal success message pointing at `path:0`. `resolve_append` returned `line_number: 0` whenever the file had no tasks, commented "Signal for new file", and the emitter mapped 0 to insert index 0. The sentinel conflated "brand-new file" with "existing file whose Tasks section is empty", and only the first is safe to write at offset 0. `PlacementInfo` now carries an `InsertAnchor` instead of a bare line number: either a concrete `Line(n)` or `EndOfTasksSection`. The ambiguity cannot recur, because there is no longer a number that means something other than a line. Resolving `EndOfTasksSection` needs the source text. A parsed `TaskFile` records task line numbers and nothing about section boundaries, so an empty section has nothing to anchor to, which is why the old code fell back to a hardcoded guess of line 15. The emitter resolves it instead, against the content it already reads, via a new `parser::header::tasks_section_body`. That returns the section's line span using pulldown-cmark, so a `##` inside a code fence does not close the section, and H3 headings stay inside it (files group tasks under `### Subsection` headings). With no `## Tasks` heading at all it appends at end of file rather than guessing. The line reported on success now comes from where the task was actually written, for new files as well, so `path:0` is gone. Two things fixed in passing, both in code this commit rewrites: `find_end_of_tasks_section` and its line-15 estimate are deleted rather than ported, and `insert_into_existing` restores the trailing newline that `join` dropped, which had every `lash add` leaving the file ending mid-line. Tests: five integration tests in add_command_test.rs (empty section lands below the header and reports the right line, is indexed afterwards, a following `## Notes` section survives intact, the new-file path still writes a well-formed file, and files end in a newline) plus six unit tests on anchor resolution covering empty sections, trailing blank lines, a heading inside a code fence, a file with no Tasks section, and clamping. Four of the five integration tests fail without the fix.
This was referenced Aug 9, 2026
This was referenced Aug 9, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
lash addagainst an existing file whose## Taskssection held no taskswrote the checkbox at line 1, above the H1. The parser never saw it as a task,
so
lash indexreported 0 tasks andlash lintpassed clean. The task was ondisk and nowhere else, with exit code 0 and a success message pointing at
path:0.Repro: a well-formed file with a header and an empty
## Taskssection(
# T/@id: t/## Tasks). The identical command against the same filewith one seed task appended correctly, which is what pinned the trigger to the
empty section.
resolve_appendreturnedline_number: 0whenever the file had no tasks,commented "Signal for new file", and the emitter mapped 0 to insert index 0.
The sentinel conflated "brand-new file" with "existing file whose Tasks section
is empty", and only the first is safe to write at offset 0.
The fix
PlacementInfonow carries anInsertAnchorinstead of a bare line number:either a concrete
Line(n)orEndOfTasksSection. The ambiguity cannot recur,because there is no longer a number that means something other than a line.
Resolving
EndOfTasksSectionneeds the source text. A parsedTaskFilerecords task line numbers and nothing about section boundaries, so an empty
section has nothing to anchor to, which is why the old code fell back to a
hardcoded guess of line 15. The emitter resolves it instead, against content it
already reads, through a new
parser::header::tasks_section_body. That returnsthe section's line span via pulldown-cmark, so a
##inside a code fence doesnot close the section, and H3 headings stay inside it (files group tasks under
### Subsectionheadings). With no## Tasksheading at all it appends at endof file rather than guessing.
The line reported on success now comes from where the task was actually
written, for new files as well, so
path:0is gone.Fixed in passing
Both in code this PR rewrites:
find_end_of_tasks_sectionand its line-15 estimate are deleted rather thanported.
insert_into_existingrestores the trailing newline thatjoindropped,which had every
lash addleaving the file ending mid-line and showing\ No newline at end of filein later diffs. This was filed as a minor noteunder the separate task-ID ticket.
Tests
Five integration tests in
add_command_test.rs: an empty section lands belowthe header and reports the right line, the task is indexed afterwards, a
following
## Notessection survives intact, the new-file path still writes awell-formed file, and files end in a newline. Plus six unit tests on anchor
resolution covering empty sections, trailing blank lines, a heading inside a
code fence, a file with no Tasks section, and clamping.
Four of the five integration tests were confirmed to fail without the fix.