Fix earlier prompts vanishing when a later prompt re-edits the same line - #141
Merged
Conversation
…he same line Ranim reported: editing one line with three consecutive prompts (add, then edit, then a small tweak) made the earlier prompts disappear from the line's history entirely, not just lose the live highlight. Root cause: extractSnapshot() computed a snapshot's attributed lines, then dropped any line that got touched again before the target tree, and discarded the WHOLE Change object if nothing survived — before deduplicateAILines() ever got a chance to run. deduplicateAILines() already handles exactly this situation correctly (demoting an overridden line to a ghost line instead of erasing it), but only for Change objects that actually reach it. For the in-progress, uncommitted chain specifically (buildUncommittedChanges), this pre-filtering is redundant with deduplicateAILines() by construction: its targetTree is always exactly the chain's own last snapshot, so anything that "overrides" a line before targetTree is necessarily a later entry in the same chain, which gets its own Change object anyway. Filtering it out here first just means deduplicateAILines() never gets the chance to ghost it. Added a dropOverriddenLines parameter to extractSnapshot(), defaulting to the existing behavior (needed for the committed-history path, where targetTree is a real finalized commit that can include last-minute edits with no other Change object to represent them — there, dropping early is the only way to catch that case). buildUncommittedChanges's direct call passes false, letting deduplicateAILines() do the (correct) ghost conversion instead. Verified with a real git-based repro matching Ranim's report exactly: three consecutive prompts editing the same line. Before the fix, only the most recent prompt appeared at all. After, all three appear — the first two as ghost/history entries, the third as the live line. Re- verified the AI-to-AI iteration scenario (uncommitted, different lines) and the cross-commit contested-line scenario (committed path, using extractChangesFromSnapshotChain's still-default dropOverriddenLines=true) both produce identical results to before.
Self-review catch: the previous commit's fix only covered the in-progress (uncommitted) chain, via buildUncommittedChanges's own direct call to extractSnapshot(dropOverriddenLines=false). But extractChangesFromSnapshotChain (used by buildCommittedHistory once the user runs `git commit`, finalizing the whole chain into one real commit) calls extractSnapshot too, left at the default dropOverriddenLines=true — so the exact same bug (a line touched again by a later entry in the same chain gets dropped entirely instead of ghosted, and the whole Change disappears if nothing else survives) still applied there. In practice: the history looked right immediately after several same-line prompts, right up until the user committed, at which point it broke again. The dropOverriddenLines=true default is still genuinely needed for this path though: targetTree there is a real finalized commit, which can include a last-minute manual edit made right before `git commit` with no AI turn (and so no Change object) representing it at all — that has to be caught somewhere, or it gets silently credited to AI. Fixed by narrowing WHAT counts as an "override" worth dropping for: only the gap strictly AFTER the tracy-local chain's own last snapshot (chain[chain.length-1].treeHash -> targetTree), not the whole span from this snapshot to targetTree. A line touched by a later entry WITHIN the same chain is already represented by that entry's own Change object (same chain.map() call) and reaches deduplicateAILines() to be demoted to a ghost line correctly; only what's left over past the chain's own tip is genuinely untracked and needs to be dropped here. While rescoping this, also fixed the filter comparing mismatched tree coordinates: `lines` is already mapped into targetTree's coordinate space, but the check compared it against the residual hunks' OLD side (source-tree-relative) instead of their NEW side (also targetTree-relative). Didn't affect the bug above (no line ever shifts position in a single-line repro), but was live in the code and directly adjacent to what this touches. Verified with three real git-based scenarios: the three-prompt same-line repro from the previous commit, replayed through to a real `git commit` (previously broke again at commit time — now all three prompts still show, two as ghost history, one live); and a genuine untracked manual edit made right before commit, both for a single-line case (the whole Change correctly disappears — nothing else survives) and a multi-line case (only the manually-edited line is excluded, the untouched lines stay correctly attributed). Re-verified the AI-to-AI iteration and cross-commit contested-line scenarios are unaffected.
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.
Summary
Ranim reported: editing one line with three consecutive prompts (add, then edit, then a small tweak) made the earlier prompts disappear from the line's history entirely — not just lose the live highlight, gone from the "previously touched by" dropdown too.
Root cause:
extractSnapshot()computed a snapshot's attributed lines, then dropped any line that got touched again before the target tree, and discarded the WHOLEChangeobject if nothing survived — beforededuplicateAILines()ever got a chance to run.deduplicateAILines()already handles exactly this situation correctly (demoting an overridden line to a ghost line instead of erasing it), but only forChangeobjects that actually reach it.Fix 1 — the in-progress (uncommitted) chain
For
buildUncommittedChanges's direct call toextractSnapshot, this pre-filtering is redundant withdeduplicateAILines()by construction: itstargetTreeis always exactly the chain's own last snapshot, so anything that "overrides" a line beforetargetTreeis necessarily a later entry in the same chain — which gets its ownChangeobject anyway. Added adropOverriddenLinesparameter,falsefor this call site, lettingdeduplicateAILines()do the correct ghost conversion instead.Fix 2 — the same bug once the chain gets committed (self-review catch)
extractChangesFromSnapshotChain(used bybuildCommittedHistoryonce the user runsgit commit) callsextractSnapshottoo, left at the defaultdropOverriddenLines=true— so the exact same bug still applied there: the history looked right immediately after several same-line prompts, right up until the user committed, at which point it broke again.The
truedefault is still genuinely needed for this path:targetTreethere is a real finalized commit, which can include a last-minute manual edit made right beforegit commitwith no AI turn (and so noChangeobject) representing it at all — that has to be caught somewhere.Fixed by narrowing what counts as an "override" worth dropping for: only the gap strictly after the chain's own last snapshot, not the whole span from this snapshot to
targetTree. A line touched by a later entry within the same chain already gets its ownChangeobject and reachesdeduplicateAILines()correctly; only what's left over past the chain's own tip is genuinely untracked. While rescoping this, also fixed the filter comparing mismatched tree coordinates (it compared an already-target-mapped line against the residual hunks' old/source side instead of their new/target side) — didn't affect the bug above, but was live and directly adjacent.Test plan
npm run compile/npm run lint/npm run test:unit(53 tests) cleangit commit(previously broke again at commit time) — same correct result