fix(drafts): keep a released cell readable for every save still in flight

Two saves of the same cell can be in flight at once, and consuming the record on
the first settle left the second reading the released cell as "nothing newer" —
so it deleted the edit the first had just preserved. The window that recorded it
already owns its lifetime and drops it when the last save closes, so the read
does not have to.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcyBRxE8j3ujCYkum9KrC4
This commit is contained in:
Diego Imbert
2026-09-07 10:30:28 +02:00
co-authored by Claude Opus 5
parent 6516b836fd
commit 25480bc7f2
+8 -7
View File
@@ -175,16 +175,17 @@ export function beginDraftSettleWindow(
releasedValues.delete(mk)
}
}
/** Read once: what it describes is settled by the caller that reads it. */
function takeReleasedValue<V>(
/**
* Only reads: the window that recorded it owns its lifetime. Two saves of the same
* cell can be in flight at once, and the second settle has to see the same edit the
* first did, or it reads the released cell as "nothing newer" and deletes it.
*/
function releasedValue<V>(
itemKind: UserDraftItemKind,
path: string,
opts?: UserDraftOptions
): V | undefined {
const mk = mapKey(resolveWorkspace(opts), itemKind, path)
const value = releasedValues.get(mk) as V | undefined
releasedValues.delete(mk)
return value
return releasedValues.get(mapKey(resolveWorkspace(opts), itemKind, path)) as V | undefined
}
const liveEditorDrafts = new Map<string, LiveEditorDraft>()
@@ -338,7 +339,7 @@ export async function settleDraftAfterWrite<V>(
// nothing is watching any more — except when the release itself is what took
// the handle away mid-write, in which case what it held still speaks for the
// user. Absent both, there is nothing newer to protect.
const held = live !== undefined ? live : takeReleasedValue<V>(itemKind, fromPath, opts)
const held = live !== undefined ? live : releasedValue<V>(itemKind, fromPath, opts)
const diverged = held !== undefined && !draftValuesEqual(held, written)
if (savedPath === fromPath && diverged) return
// The form stays editable across the delete's own request, so a keystroke made