Skip to content

Fix earlier prompts vanishing when a later prompt re-edits the same line - #141

Merged
EsmeYi merged 2 commits into
mainfrom
fix/uncommitted-chain-ghost-tracking
Aug 24, 2026
Merged

Fix earlier prompts vanishing when a later prompt re-edits the same line#141
EsmeYi merged 2 commits into
mainfrom
fix/uncommitted-chain-ghost-tracking

Conversation

@EsmeYi

@EsmeYi EsmeYi commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

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 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.

Fix 1 — the in-progress (uncommitted) chain

For buildUncommittedChanges's direct call to extractSnapshot, 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. Added a dropOverriddenLines parameter, false for this call site, letting deduplicateAILines() do the correct ghost conversion instead.

Fix 2 — the same bug once the chain gets committed (self-review catch)

extractChangesFromSnapshotChain (used by buildCommittedHistory once the user runs git commit) calls extractSnapshot too, left at the default dropOverriddenLines=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 true default is still genuinely needed for this path: 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.

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 own Change object and reaches deduplicateAILines() 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) clean
  • Real git-based repro matching Ranim's report exactly (uncommitted): three consecutive prompts editing the same line — all three now appear, first two as ghost/history, third as live
  • Same repro replayed through to a real git commit (previously broke again at commit time) — same correct result
  • Genuine untracked manual edit made right before commit: single-line case (whole Change correctly disappears) and multi-line case (only the manually-edited line excluded, untouched lines stay attributed)
  • Re-verified the AI-to-AI iteration (uncommitted) and cross-commit contested-line (committed propagation) scenarios are unaffected

EsmeYi added 2 commits August 18, 2026 10:22
…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.
@EsmeYi
EsmeYi merged commit 4192626 into main Aug 24, 2026
1 check passed
@EsmeYi
EsmeYi deleted the fix/uncommitted-chain-ghost-tracking branch August 24, 2026 08:41
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