diff --git a/frontend/src/lib/itemStore.svelte.ts b/frontend/src/lib/itemStore.svelte.ts index 997ab3ae6d..402d02cd66 100644 --- a/frontend/src/lib/itemStore.svelte.ts +++ b/frontend/src/lib/itemStore.svelte.ts @@ -418,9 +418,11 @@ class Entry { } /** Take over the edits of a deployed entry this one replaced at its key: each field its value - * changed from its own deployed side, so they stay a draft over what this entry deployed. */ + * changed from its own deployed side, so they stay a draft over what this entry deployed. + * Compared exactly, as a save is: a field the draft comparison ignores (run-as) is an edit. */ carryEditsOf(old: Entry): void { - if (old.origin !== 'deployed' || !old.dirty || this.value === undefined) return + if (old.origin !== 'deployed' || old.value === undefined || old.deployed === undefined) return + if (this.value === undefined) return const edited = old.value as Record const base = old.deployed as Record const next = snapshot(this.value) as Record diff --git a/frontend/src/lib/itemStore.test.ts b/frontend/src/lib/itemStore.test.ts index 75e4660479..40206d535f 100644 --- a/frontend/src/lib/itemStore.test.ts +++ b/frontend/src/lib/itemStore.test.ts @@ -530,6 +530,34 @@ describe('item store: one entry per key', () => { expect(rows.writes.at(-1)).toEqual({ path: 'u/me/b', value: kept }) }) + it('keeps a change the draft comparison ignores in an editor a move replaces', async () => { + type Sched = { path: string; summary: string; permissioned_as?: string } + const rows = fakeRows() + const store = createItemStore(rows.port) + const s: Sched = { path: 's', summary: '', permissioned_as: 'u/a' } + const { handle: open } = store.acquire( + { workspace: 'w', kind: 'trigger_schedule', path: 's' }, + { workspace: 'w', path: 's' }, + { + load: async () => ({ deployed: structuredClone(s) }), + write: async () => {} + } as ItemAdapter + ) + const temporary = newItemPath() + const { handle: moving } = store.acquire( + { workspace: 'w', kind: 'trigger_schedule', path: temporary }, + { workspace: 'w', path: temporary, template: s }, + { write: async () => {} } as ItemAdapter + ) + await settle() + open.value = { ...s, permissioned_as: 'u/b' } + expect(open.dirty).toBe(false) + moving.value = { ...s, summary: 'moved' } + + expect(await moving.save()).toMatchObject({ ok: true, moved: true }) + expect(open.value).toEqual({ ...s, summary: 'moved', permissioned_as: 'u/b' }) + }) + it('writes an item it moves onto after the saves queued there, and supersedes later ones', async () => { const rows = fakeRows() const store = createItemStore(rows.port)