fix(bulk-edit): drop dead initial-* props and clear store on edit-mode discard
Felix B1 — `WhoWhenSection.svelte:37` and `DescriptionSection.svelte:42` mutated $bindable props at top-level script scope, seeding them from `initial*` companion props that no caller ever passes. The pattern stomps parent-owned state in any future component re-evaluation. Removed the dead initialDateIso / initialLocation / initialDocumentLocation props and let the bindables carry their own initial value. dateDisplay and currentTitle now seed from the bindable directly inside untrack — no re-assignment required. Elicit B2 — In edit mode the file map IS the user's bulk selection, so discarding must clear bulkSelectionStore and bounce back to /documents, otherwise the user is left on /documents/bulk-edit with an empty form and a stale count in the bottom bar. Refs #225, PR #331 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -134,6 +134,16 @@ async function handleDiscard() {
|
||||
});
|
||||
if (!ok) return;
|
||||
}
|
||||
if (mode === 'edit') {
|
||||
// In edit mode the file map IS the user's bulk selection — discarding
|
||||
// must clear the upstream store and bounce back to the list, otherwise
|
||||
// the user is left on /documents/bulk-edit with an empty form and a
|
||||
// stale count in the bottom bar (issue #225 Bulk-Edit Panel table).
|
||||
bulkSelectionStore.clear();
|
||||
discardAll();
|
||||
await goto('/documents');
|
||||
return;
|
||||
}
|
||||
discardAll();
|
||||
}
|
||||
|
||||
|
||||
@@ -315,6 +315,30 @@ describe('BulkDocumentEditLayout', () => {
|
||||
|
||||
// ─── mode="edit" ─────────────────────────────────────────────────────────────
|
||||
|
||||
describe('BulkDocumentEditLayout — mode="edit" discard', () => {
|
||||
it('discard in edit mode clears the selection store and navigates back to /documents', async () => {
|
||||
const { bulkSelectionStore } = await import('$lib/stores/bulkSelection.svelte');
|
||||
bulkSelectionStore.setAll(['doc-1']);
|
||||
|
||||
const { container } = render(BulkDocumentEditLayout, {
|
||||
mode: 'edit',
|
||||
initialEditEntries: [
|
||||
{ id: 'doc-1', title: 'Brief 1', pdfUrl: '/api/documents/doc-1/file' },
|
||||
{ id: 'doc-2', title: 'Brief 2', pdfUrl: '/api/documents/doc-2/file' }
|
||||
]
|
||||
});
|
||||
|
||||
const discardBtn = container.querySelector(
|
||||
'button[data-testid="discard-all-btn"]'
|
||||
) as HTMLButtonElement;
|
||||
expect(discardBtn).not.toBeNull();
|
||||
discardBtn.click();
|
||||
|
||||
await vi.waitFor(() => expect(goto).toHaveBeenCalledWith('/documents'), { timeout: 1000 });
|
||||
expect(bulkSelectionStore.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('BulkDocumentEditLayout — mode="edit"', () => {
|
||||
const editEntry = (i: number) => ({
|
||||
id: `doc-${i}`,
|
||||
|
||||
@@ -11,7 +11,6 @@ let {
|
||||
archiveBox = $bindable(''),
|
||||
archiveFolder = $bindable(''),
|
||||
initialTitle = '',
|
||||
initialDocumentLocation = '',
|
||||
initialSummary = '',
|
||||
titleRequired = false,
|
||||
suggestedTitle = '',
|
||||
@@ -24,7 +23,6 @@ let {
|
||||
archiveBox?: string;
|
||||
archiveFolder?: string;
|
||||
initialTitle?: string;
|
||||
initialDocumentLocation?: string;
|
||||
initialSummary?: string;
|
||||
titleRequired?: boolean;
|
||||
suggestedTitle?: string;
|
||||
@@ -32,14 +30,11 @@ let {
|
||||
editMode?: boolean;
|
||||
} = $props();
|
||||
|
||||
// currentTitle seeds from initialTitle once at mount; subsequent edits flow
|
||||
// through the oninput handler that flips titleDirty.
|
||||
let titleDirty = $state(false);
|
||||
currentTitle = untrack(() => initialTitle);
|
||||
const titleValue = $derived(titleDirty ? currentTitle : suggestedTitle || currentTitle);
|
||||
|
||||
// Initialize controlled location field once from the legacy initial-* props so
|
||||
// callers that haven't switched to the bindable form keep their existing
|
||||
// pre-fill behaviour.
|
||||
documentLocation = untrack(() => documentLocation || initialDocumentLocation);
|
||||
</script>
|
||||
|
||||
<div class="rounded-sm border border-line bg-surface p-6 shadow-sm">
|
||||
|
||||
@@ -13,8 +13,6 @@ let {
|
||||
senderId = $bindable(''),
|
||||
selectedReceivers = $bindable<Person[]>([]),
|
||||
dateIso = $bindable(''),
|
||||
initialDateIso = '',
|
||||
initialLocation = '',
|
||||
initialSenderName = '',
|
||||
suggestedDateIso = '',
|
||||
suggestedSenderName = '',
|
||||
@@ -24,8 +22,6 @@ let {
|
||||
senderId?: string;
|
||||
selectedReceivers?: Person[];
|
||||
dateIso?: string;
|
||||
initialDateIso?: string;
|
||||
initialLocation?: string;
|
||||
initialSenderName?: string;
|
||||
suggestedDateIso?: string;
|
||||
suggestedSenderName?: string;
|
||||
@@ -33,8 +29,9 @@ let {
|
||||
editMode?: boolean;
|
||||
} = $props();
|
||||
|
||||
let dateDisplay = $state(untrack(() => isoToGerman(initialDateIso)));
|
||||
dateIso = untrack(() => initialDateIso);
|
||||
// Seed dateDisplay from the bindable's current value once at mount; subsequent
|
||||
// edits flow through handleDateInput which writes back to dateIso.
|
||||
let dateDisplay = $state(untrack(() => isoToGerman(dateIso)));
|
||||
let dateDirty = $state(false);
|
||||
|
||||
const dateInvalid = $derived(dateDirty && dateDisplay.length > 0 && dateIso === '');
|
||||
@@ -122,7 +119,6 @@ $effect(() => {
|
||||
id="location"
|
||||
type="text"
|
||||
name="location"
|
||||
value={initialLocation}
|
||||
placeholder={m.form_placeholder_location()}
|
||||
class="block w-full rounded border border-line px-2 py-3 text-sm shadow-sm focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user