diff --git a/frontend/src/lib/itemStore.svelte.ts b/frontend/src/lib/itemStore.svelte.ts index 04f9c78af8..13870377d3 100644 --- a/frontend/src/lib/itemStore.svelte.ts +++ b/frontend/src/lib/itemStore.svelte.ts @@ -522,15 +522,19 @@ class Entry { * item, so the row is the caller's. `failed`: has it, on a baseline it cannot refresh. */ async discarded(): Promise<'done' | 'absent' | 'failed'> { const asked = this.asOf() + // A row this entry has none of is not one it can discard: it does nothing, and the caller + // would read that as the row being gone while another tab's is still there. + let knewRow = this.row !== null await this.discard(asked) if (this.retired || !this.loaded) return 'absent' - if (!this.stale) return 'done' + if (!this.stale) return knewRow ? 'done' : 'absent' // Stale refused it. A reload is what clears that, and with a baseline it can trust the // discard means something again — so the one thing worth trying before giving up. The // retry carries the state as of the request, so an edit typed during that reload stands. if (!this.adapter || !(await this.load(this.adapter)).ok) return 'failed' + knewRow = this.row !== null await this.discard(asked) - return this.stale ? 'failed' : 'done' + return this.stale ? 'failed' : knewRow ? 'done' : 'absent' } /** diff --git a/frontend/src/lib/itemStore.test.ts b/frontend/src/lib/itemStore.test.ts index 0f6a1ee944..3261e6d38b 100644 --- a/frontend/src/lib/itemStore.test.ts +++ b/frontend/src/lib/itemStore.test.ts @@ -1127,6 +1127,27 @@ describe('item store: origins', () => { }) }) + it('says a row it never knew of is not its to discard', async () => { + const rows = fakeRows() + const store = createItemStore(rows.port) + // Loaded clean: no draft existed when this editor read the item. + const { handle: item } = store.acquire( + { workspace: 'w', kind: 'resource', path: 'u/me/r' }, + { workspace: 'w', path: 'u/me/r' }, + adapter({ deployed: deployedRes }) + ) + await settle() + expect(item.dirty).toBe(false) + + // Another tab creates the draft afterwards. This editor knows nothing of it. + rows.sent.set('u/me/r', { ...deployedRes, description: 'the other tab' }) + rows.handExternally('u/me/r') + + // Its discard does nothing, so answering `done` would have the caller skip the delete and + // report a draft gone that is still on the server. + expect(await store.bridge.discard('w', 'resource', 'u/me/r')).toBe('absent') + }) + it('says it did not deal with the row when its own first read never arrived', async () => { const rows = fakeRows() const store = createItemStore(rows.port)