diff --git a/frontend/src/lib/geschichte/JourneyEditor.svelte b/frontend/src/lib/geschichte/JourneyEditor.svelte index 8060776f..85e07f37 100644 --- a/frontend/src/lib/geschichte/JourneyEditor.svelte +++ b/frontend/src/lib/geschichte/JourneyEditor.svelte @@ -32,6 +32,7 @@ let { geschichte, onSubmit, submitting = false }: Props = $props(); const unsaved = createUnsavedWarning(); let title = $state(geschichte.title ?? ''); + let body = $state(geschichte.body ?? ''); let status: 'DRAFT' | 'PUBLISHED' = $state(geschichte.status ?? 'DRAFT'); let selectedPersons: PersonOption[] = $state( @@ -64,6 +65,17 @@ const alreadyAddedIds = $derived( const canPublish = $derived(items.length > 0 && !titleEmpty); const showPublishedEmptyWarning = $derived(status === 'PUBLISHED' && items.length === 0); +// Skip the initial run so mounting with pre-existing persons doesn't mark dirty. +let _personEffectMounted = false; +$effect(() => { + void selectedPersons.length; + if (!_personEffectMounted) { + _personEffectMounted = true; + return; + } + unsaved.markDirty(); +}); + let listEl: HTMLElement | null = $state(null); let editorColEl: HTMLElement | null = $state(null); diff --git a/frontend/src/lib/geschichte/JourneyEditor.svelte.spec.ts b/frontend/src/lib/geschichte/JourneyEditor.svelte.spec.ts index f65d2b84..c851840d 100644 --- a/frontend/src/lib/geschichte/JourneyEditor.svelte.spec.ts +++ b/frontend/src/lib/geschichte/JourneyEditor.svelte.spec.ts @@ -678,6 +678,44 @@ describe('JourneyEditor — unsaved warning banner', () => { }); }); +describe('JourneyEditor — selectedPersons marks dirty', () => { + function getNavCallback() { + const calls = vi.mocked(beforeNavigate).mock.calls; + const [callback] = calls[calls.length - 1]; + return (cancel = vi.fn()) => { + (callback as (nav: { cancel: () => void; to: { url: URL } | null }) => void)({ + cancel, + to: { url: new URL('http://localhost/geschichten') } + }); + return cancel; + }; + } + + it('removing a person chip marks the editor dirty', async () => { + render( + JourneyEditor, + defaultProps({ + geschichte: makeGeschichte({ + persons: [{ id: 'p1', firstName: 'Anna', lastName: 'Schmidt' }] + }) + }) + ); + + // Confirm navigation is NOT blocked initially (clean state) + const triggerNav = getNavCallback(); + expect(triggerNav()).not.toHaveBeenCalled(); + + // Remove the person chip (aria-label = m.comp_multiselect_remove() = "Entfernen") + await userEvent.click(page.getByRole('button', { name: m.comp_multiselect_remove() })); + + // After person removal, navigation should be blocked + await vi.waitFor(() => { + const cancel = triggerNav(); + expect(cancel).toHaveBeenCalled(); + }); + }); +}); + describe('JourneyEditor — person chips from GeschichteView', () => { it('renders person names in the sidebar chips (PersonView carries no displayName)', async () => { render(