Repository navigation
Fix lash add splicing into a preceding multi-line annotation - #33
Merged
Merged
Conversation
`lash add` spliced a new task into the middle of a preceding task's multi-line `@agent-note`, and the orphaned continuation lines were then destroyed on the next reindex — silent data loss with a success message and exit code 0. `PlacementResolver::count_annotation_lines` counted every annotation as exactly one source line. The parser folds indented continuation lines into the annotation value, joining them with `\n` (see `parser::annotations::parse_annotation_block`), so a three-line note is a single `Option<String>` holding two newlines and occupies three lines on disk. Counting it as one put the insertion point after the note's first line. Count actual source lines via `value.lines().count().max(1)`, applied to every single-value annotation the parser can fold: `@agent-note`, `@owner`, `@estimate`, and custom annotations. The `.max(1)` floor matters — an empty value has `lines().count() == 0` but still occupies its own `@key:` line. This is the same class as the `@id` undercount fixed previously (the issue #24 regression test); the assumption of one line per annotation was simply never revisited for values that can wrap. Tests: four unit tests in placement.rs (multi-line note, single-line note, empty note, multi-line owner/estimate) and two end-to-end tests in add_command_test.rs (note stays contiguous in the file; all lines survive reindex). Verified the multi-line tests fail without the fix. Two related defects found while verifying are filed as follow-ups in lash.index.md rather than fixed here: `@depends-on` in comma form over-counts and can append outside the tasks section, and `lash add --agent-note` with an embedded newline emits an unindented continuation that the parser silently drops.
Two are in `lash add` itself: it prepends above the H1 when the target file has a `## Tasks` section with nothing in it (the task lands on disk, `lash index` never sees it, and `lash lint` passes), and the task ID it prints on success is not the one the indexer stores, so copying it into `lash show` or `@depends-on` fails. The third is a test-isolation bug. `test_user_config_save_and_load` in lash-types writes the developer's real `~/.lash/config.toml` and only restores it if the test runs to completion. An interrupted `cargo test` leaves `color_scheme = "Test Theme"` behind, which no theme resolves, and every later `lash` invocation on that machine fails.
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
When the last task in the target file carried a multi-line
@agent-note,lash addinserted the new task after the first line of that note. Theorphaned continuation lines were then destroyed on the next reindex. Exit code
was 0 with a normal success message, so nothing signalled the loss.
PlacementResolver::count_annotation_linescounted every annotation as exactlyone source line. The parser folds indented continuation lines into the
annotation value and joins them with
\n, so a three-line note is oneOption<String>holding two newlines while occupying three lines on disk.The fix
Count actual source lines with
value.lines().count().max(1), applied to everysingle-value annotation the parser can fold:
@agent-note,@owner,@estimate, and custom annotations. The.max(1)floor matters, since an emptyvalue has
lines().count() == 0but still occupies its own@key:line.Same class as the
@idundercount fixed for issue #24. The one-line-per-annotation assumption was never revisited for values that can wrap.
Tests
Four unit tests in
placement.rs(multi-line note, single-line note, emptynote, multi-line owner/estimate) and two end-to-end tests in
add_command_test.rscovering note contiguity in the file and survival throughreindex. The multi-line tests were confirmed to fail without the fix.
Not fixed here
Two related defects found while verifying are filed as follow-ups in
lash.index.md, along with three more found afterwards. The@depends-oncomma form over-counts and can append outside the tasks section, and
lash add --agent-notewith an embedded newline emits an unindentedcontinuation the parser silently drops.