diff --git a/frontend/src/routes/favorites/+page.svelte b/frontend/src/routes/favorites/+page.svelte
index dab700d6..225c6b20 100644
--- a/frontend/src/routes/favorites/+page.svelte
+++ b/frontend/src/routes/favorites/+page.svelte
@@ -286,31 +286,10 @@
// prunes its own selection when items reload) — benches/ROUND11.md §S1.
type Selectable = FileItem | FolderItem;
- function batchTargets(sel: Selectable[]) {
- return sel.map((i) => ({ id: i.id, name: i.name, kind: kindOf(i) }));
- }
-
function batchDownload(sel: Selectable[]) {
for (const i of sel) downloadItem(i);
}
- async function batchDelete(sel: Selectable[]) {
- const ok = await confirmDialog({
- title: t('common.delete', 'Delete'),
- message: t('files.confirm_delete_n', { count: sel.length }, 'Delete {{count}} item(s)?'),
- confirmText: t('common.delete', 'Delete'),
- danger: true
- });
- if (!ok) return;
- try {
- await Promise.all(sel.map((i) => (isFile(i) ? deleteFile(i.id) : deleteFolder(i.id))));
- const removed = new Set(sel.map((i) => i.id));
- raw = raw.filter((i) => !removed.has(i.resource.id));
- } catch (e) {
- errorToast(e);
- }
- }
-
onMount(() => load(true));
@@ -352,25 +331,25 @@
}}
>
{#snippet batchActions(sel)}
+
- sel.forEach(unfavorite)}
+ >{t('files.unfavorite', 'Remove favorite')}
{/snippet}
diff --git a/frontend/src/routes/favorites/page.test.ts b/frontend/src/routes/favorites/page.test.ts
index cd0277ff..989a45bc 100644
--- a/frontend/src/routes/favorites/page.test.ts
+++ b/frontend/src/routes/favorites/page.test.ts
@@ -86,13 +86,18 @@ it('unfavorites a row via the star button', async () => {
await waitFor(() => expect(removeFavorite).toHaveBeenCalledWith('file', 'f1'));
});
-it('batch-deletes selected favorites after confirmation', async () => {
+it('batch-removes-from-favorite the selection', async () => {
+ // /favorites' batch bar was intentionally trimmed to Download +
+ // Remove-from-favorite. Bulk-deleting the underlying file from
+ // this view (previous behaviour) confused the "this is a
+ // bookmarks list" semantics — destructive actions belong in the
+ // row's context menu, not in the batch bar. This test pins the
+ // new shape: batch button just un-stars the selection.
withOneFile();
- confirmDialog.mockResolvedValue(true);
- m(deleteFile).mockResolvedValue(undefined);
+ m(removeFavorite).mockResolvedValue(undefined);
render(FavoritesPage);
await screen.findByText('photo.png');
await fireEvent.click(screen.getByTestId('resource-list-select-f1-checkbox'));
- await fireEvent.click(await screen.findByTestId('favorites-batch-delete-btn'));
- await waitFor(() => expect(deleteFile).toHaveBeenCalledWith('f1'));
+ await fireEvent.click(await screen.findByTestId('favorites-batch-remove-btn'));
+ await waitFor(() => expect(removeFavorite).toHaveBeenCalledWith('file', 'f1'));
});
diff --git a/frontend/src/routes/recent/+page.svelte b/frontend/src/routes/recent/+page.svelte
index 84c7e0b5..c1ff3477 100644
--- a/frontend/src/routes/recent/+page.svelte
+++ b/frontend/src/routes/recent/+page.svelte
@@ -319,31 +319,10 @@
// prunes its own selection when items reload) — benches/ROUND11.md §S1.
type Selectable = FileItem | FolderItem;
- function batchTargets(sel: Selectable[]) {
- return sel.map((i) => ({ id: i.id, name: i.name, kind: kindOf(i) }));
- }
-
function batchDownload(sel: Selectable[]) {
for (const i of sel) downloadItem(i);
}
- async function batchDelete(sel: Selectable[]) {
- const ok = await confirmDialog({
- title: t('common.delete', 'Delete'),
- message: t('files.confirm_delete_n', { count: sel.length }, 'Delete {{count}} item(s)?'),
- confirmText: t('common.delete', 'Delete'),
- danger: true
- });
- if (!ok) return;
- try {
- await Promise.all(sel.map((i) => (isFile(i) ? deleteFile(i.id) : deleteFolder(i.id))));
- const removed = new Set(sel.map((i) => i.id));
- raw = raw.filter((i) => !removed.has(i.resource.id));
- } catch (e) {
- errorToast(e);
- }
- }
-
onMount(() => {
void load(true);
});
@@ -401,25 +380,26 @@
{/if}
{/snippet}
{#snippet batchActions(sel)}
+
- sel.forEach(removeItem)}
+ >{t('recent.remove_item', 'Remove from recent')}
{/snippet}
{#snippet itemActions(item)}
diff --git a/frontend/src/routes/recent/page.test.ts b/frontend/src/routes/recent/page.test.ts
index 63076714..1aae3136 100644
--- a/frontend/src/routes/recent/page.test.ts
+++ b/frontend/src/routes/recent/page.test.ts
@@ -92,15 +92,20 @@ it('removes a recent row via the broom button', async () => {
await waitFor(() => expect(removeFromRecent).toHaveBeenCalledWith('file', 'r1'));
});
-it('batch-deletes selected recent items after confirmation', async () => {
+it('batch-removes-from-recent the selection', async () => {
+ // /recent's batch bar was intentionally trimmed to Download +
+ // Remove-from-recent. Bulk-deleting the underlying file from
+ // this history view (previous behaviour) confused the "this is
+ // activity log" semantics — destructive actions belong in the
+ // row's context menu, not in the batch bar. This test pins the
+ // new shape: batch button just forgets the selection from history.
withOneFile();
- confirmDialog.mockResolvedValue(true);
- m(deleteFile).mockResolvedValue(undefined);
+ m(removeFromRecent).mockResolvedValue(undefined);
render(RecentPage);
await screen.findByText('notes.txt');
await fireEvent.click(screen.getByTestId('resource-list-select-r1-checkbox'));
- await fireEvent.click(await screen.findByTestId('recent-batch-delete-btn'));
- await waitFor(() => expect(deleteFile).toHaveBeenCalledWith('r1'));
+ await fireEvent.click(await screen.findByTestId('recent-batch-remove-btn'));
+ await waitFor(() => expect(removeFromRecent).toHaveBeenCalledWith('file', 'r1'));
});
it('renders an empty state when there is no recent activity', async () => {
diff --git a/frontend/src/routes/trash/+page.svelte b/frontend/src/routes/trash/+page.svelte
index 83913fdb..beb4c2af 100644
--- a/frontend/src/routes/trash/+page.svelte
+++ b/frontend/src/routes/trash/+page.svelte
@@ -16,6 +16,7 @@
import { formatDate } from '$lib/utils/display';
import type { Drive, FileItem, FolderItem, TrashResourceItem } from '$lib/api/types';
import Icon from '$lib/icons/Icon.svelte';
+ import Button from '$lib/components/Button.svelte';
import ResourceList, {
isFile,
type GroupByDef,
@@ -290,24 +291,27 @@
{/if}
{/snippet}
{#snippet batchActions(sel)}
-
{/snippet}
{#snippet rowBadge(_item, ctx)}
{@const chip = expiryChip(ctx?.date)}
diff --git a/tests/e2e/spa/favorites.spec.ts b/tests/e2e/spa/favorites.spec.ts
index 9d5a476a..165fd0f1 100644
--- a/tests/e2e/spa/favorites.spec.ts
+++ b/tests/e2e/spa/favorites.spec.ts
@@ -55,8 +55,12 @@ test('favorites batch select-all then move dialog', async ({ page }) => {
await page.getByTestId('resource-list-select-all-checkbox').check();
await expect(page.getByTestId('resource-list-batch-close-btn')).toBeVisible();
- // Batch-move opens the move dialog; cancel it.
- await page.getByTestId('favorites-batch-move-btn').click();
- await expect(page.getByTestId('move-dialog')).toBeVisible({ timeout: 15_000 });
- await page.getByTestId('move-dialog-cancel-btn').click();
+ // Batch-remove-from-favorite un-stars every selected row without
+ // touching the underlying file — the /favorites batch bar was
+ // trimmed to Download + Remove-from-favorite (destructive-to-content
+ // actions moved into the row context menu). Verify the two folders
+ // vanish from the list after the click.
+ await page.getByTestId('favorites-batch-remove-btn').click();
+ await expect(page.getByTestId(f1)).toHaveCount(0, { timeout: 15_000 });
+ await expect(page.getByTestId(f2)).toHaveCount(0);
});
diff --git a/tests/e2e/spa/recent.spec.ts b/tests/e2e/spa/recent.spec.ts
index ddd24cd5..ad9b0c0a 100644
--- a/tests/e2e/spa/recent.spec.ts
+++ b/tests/e2e/spa/recent.spec.ts
@@ -23,12 +23,15 @@ test('recent shows accessed items, batch selection, and clear', async ({ page })
await expect(page.getByTestId('appshell-logo-link')).toBeVisible({ timeout: 15_000 });
// Switch to list view (reveals the select-all header) and batch-select.
+ // /recent's batch bar was trimmed to Download + Remove-from-recent
+ // (destructive-to-content actions moved into the row context menu),
+ // so this exercises the new remove-from-recent batch instead of the
+ // old batch-move-into-dialog flow.
await page.getByTestId('display-mode-view-list-btn').click({ timeout: 3_000 }).catch(() => {});
const selectAll = page.getByTestId('resource-list-select-all-checkbox');
if (await selectAll.isVisible().catch(() => false)) {
await selectAll.check();
- await page.getByTestId('recent-batch-move-btn').click({ timeout: 3_000 }).catch(() => {});
- await page.getByTestId('move-dialog-cancel-btn').click({ timeout: 3_000 }).catch(() => {});
+ await page.getByTestId('recent-batch-remove-btn').click({ timeout: 3_000 }).catch(() => {});
}
// Clear the history if the control is present.