diff --git a/frontend/e2e/documents.spec.ts b/frontend/e2e/documents.spec.ts
index 242d3515..4870f4e3 100644
--- a/frontend/e2e/documents.spec.ts
+++ b/frontend/e2e/documents.spec.ts
@@ -382,17 +382,32 @@ test.describe('PDF annotations — admin', () => {
});
// Record count now — the draw test may have created more than one annotation
const countBefore = await page.locator('[data-testid^="annotation-"]').count();
+ // Guard against a missing seed: without this, a count of 0 would turn the
+ // post-delete assertion into toHaveCount(-1) and fail with a misleading timeout.
+ expect(countBefore).toBeGreaterThan(0);
- // Enable annotate mode to show delete buttons
+ // Enable annotate mode — deletion is only available while annotating
await page.getByRole('button', { name: /^annotieren$/i }).click();
- const deleteBtn = page.getByRole('button', { name: /annotation löschen/i }).first();
- await expect(deleteBtn).toBeVisible({ timeout: 8000 });
- await deleteBtn.click();
+ // The on-canvas delete button was removed (issue #722). Delete via the
+ // kept keyboard shortcut: focus an annotation, press Delete, confirm.
+ const annotation = page.locator('[data-testid^="annotation-"]').first();
+ // Capture the identity of the specific annotation we delete so we can assert
+ // that exact element is gone afterwards — a count drop alone is identity-blind.
+ const deletedTestId = await annotation.getAttribute('data-testid');
+ expect(deletedTestId).toBeTruthy();
+ await annotation.click();
+ await annotation.press('Delete');
+
+ await page.getByRole('button', { name: /^bestätigen$/i }).click();
await expect(page.locator('[data-testid^="annotation-"]')).toHaveCount(countBefore - 1, {
timeout: 8000
});
+ // Identity check: the specific annotation we deleted must no longer exist.
+ await expect(page.locator(`[data-testid="${deletedTestId}"]`)).toHaveCount(0, {
+ timeout: 8000
+ });
await page.screenshot({ path: 'test-results/e2e/annotation-deleted.png' });
});
diff --git a/frontend/src/lib/document/annotation/AnnotationLayer.svelte.spec.ts b/frontend/src/lib/document/annotation/AnnotationLayer.svelte.spec.ts
index 3c5aea0b..ae92db33 100644
--- a/frontend/src/lib/document/annotation/AnnotationLayer.svelte.spec.ts
+++ b/frontend/src/lib/document/annotation/AnnotationLayer.svelte.spec.ts
@@ -98,28 +98,22 @@ describe('AnnotationLayer', () => {
expect(el2.style.opacity).toBe('1');
});
- it('does not show delete button when annotation is not hovered or active', async () => {
+ // The on-canvas delete button was removed (issue #722). Even in annotate mode
+ // with an active annotation, no delete button must render over the document.
+ it('never renders a delete button, even in annotate mode with an active annotation', async () => {
render(AnnotationLayer, {
annotations: [makeAnnotation('ann-1')],
canDraw: true,
color: '#00C7B1',
- onDraw: () => {}
- });
-
- await expect.element(page.getByTestId('annotation-ann-1')).toBeInTheDocument();
- await expect.element(page.getByTestId('annotation-delete-ann-1')).not.toBeInTheDocument();
- });
-
- it('does not show delete button when canDraw is false even if annotation is active', async () => {
- render(AnnotationLayer, {
- annotations: [makeAnnotation('ann-1')],
- canDraw: false,
- color: '#00C7B1',
activeAnnotationId: 'ann-1',
onDraw: () => {}
});
await expect.element(page.getByTestId('annotation-ann-1')).toBeInTheDocument();
+ // Positive control: the previously-removed testid must stay absent.
await expect.element(page.getByTestId('annotation-delete-ann-1')).not.toBeInTheDocument();
+ // Real invariant: the annotation must contain no clickable delete control at all.
+ const annotationEl = page.getByTestId('annotation-ann-1').element() as HTMLElement;
+ expect(annotationEl.querySelectorAll('button').length).toBe(0);
});
});
diff --git a/frontend/src/lib/document/annotation/AnnotationLayer.svelte.test.ts b/frontend/src/lib/document/annotation/AnnotationLayer.svelte.test.ts
index 3e5598d5..7dcc8cef 100644
--- a/frontend/src/lib/document/annotation/AnnotationLayer.svelte.test.ts
+++ b/frontend/src/lib/document/annotation/AnnotationLayer.svelte.test.ts
@@ -240,7 +240,7 @@ describe('AnnotationLayer', () => {
expect(document.querySelector('[data-testid="annotation-ann-1"]')).not.toBeNull();
});
- it('renders without throwing when canDraw is true (delete button visible)', async () => {
+ it('renders without throwing when canDraw is true (no delete button)', async () => {
expect(() =>
render(AnnotationLayer, {
annotations: [annotation],
diff --git a/frontend/src/lib/document/annotation/AnnotationShape.svelte b/frontend/src/lib/document/annotation/AnnotationShape.svelte
index 8bfc7c7b..c0cb985f 100644
--- a/frontend/src/lib/document/annotation/AnnotationShape.svelte
+++ b/frontend/src/lib/document/annotation/AnnotationShape.svelte
@@ -32,8 +32,6 @@ let {
onpointerleave: () => void;
} = $props();
-const deleteVisible = $derived(showDelete && (isHovered || isActive));
-
function hexToRgba(hex: string, alpha: number): string {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
@@ -119,51 +117,6 @@ let shapeStyle = $derived(
{blockNumber}
{/if}
- {#if deleteVisible}
-
- {/if}
{#if isResizable}
{/if}
diff --git a/frontend/src/lib/document/annotation/AnnotationShape.svelte.spec.ts b/frontend/src/lib/document/annotation/AnnotationShape.svelte.spec.ts
index a896489b..79cb7680 100644
--- a/frontend/src/lib/document/annotation/AnnotationShape.svelte.spec.ts
+++ b/frontend/src/lib/document/annotation/AnnotationShape.svelte.spec.ts
@@ -33,55 +33,14 @@ describe('AnnotationShape', () => {
await expect.element(page.getByTestId('annotation-ann-1')).toBeInTheDocument();
});
- it('does not show delete button when showDelete is false', async () => {
+ // The on-canvas delete button was removed (issue #722) because it overlapped
+ // the document text. Deletion now happens via the transcription panel or the
+ // keyboard Delete shortcut. No visible delete button must ever render — even
+ // when hovered and active in delete mode.
+ it('never renders a delete button, even when hovered and active in delete mode', async () => {
render(AnnotationShape, {
annotation: makeAnnotation(),
isHovered: true,
- isActive: false,
- showDelete: false,
- onDeleteRequest: vi.fn(),
- onclick: () => {},
- onpointerenter: () => {},
- onpointerleave: () => {}
- });
-
- await expect.element(page.getByTestId('annotation-delete-ann-1')).not.toBeInTheDocument();
- });
-
- it('does not show delete button when showDelete is true but neither hovered nor active', async () => {
- render(AnnotationShape, {
- annotation: makeAnnotation(),
- isHovered: false,
- isActive: false,
- showDelete: true,
- onDeleteRequest: vi.fn(),
- onclick: () => {},
- onpointerenter: () => {},
- onpointerleave: () => {}
- });
-
- await expect.element(page.getByTestId('annotation-delete-ann-1')).not.toBeInTheDocument();
- });
-
- it('shows delete button when showDelete is true and isHovered is true', async () => {
- render(AnnotationShape, {
- annotation: makeAnnotation(),
- isHovered: true,
- isActive: false,
- showDelete: true,
- onDeleteRequest: vi.fn(),
- onclick: () => {},
- onpointerenter: () => {},
- onpointerleave: () => {}
- });
-
- await expect.element(page.getByTestId('annotation-delete-ann-1')).toBeInTheDocument();
- });
-
- it('shows delete button when showDelete is true and isActive is true', async () => {
- render(AnnotationShape, {
- annotation: makeAnnotation(),
- isHovered: false,
isActive: true,
showDelete: true,
onDeleteRequest: vi.fn(),
@@ -90,49 +49,12 @@ describe('AnnotationShape', () => {
onpointerleave: () => {}
});
- await expect.element(page.getByTestId('annotation-delete-ann-1')).toBeInTheDocument();
- });
-
- it('calls onDeleteRequest when delete button is clicked', async () => {
- const onDeleteRequest = vi.fn();
-
- render(AnnotationShape, {
- annotation: makeAnnotation(),
- isHovered: true,
- isActive: false,
- showDelete: true,
- onDeleteRequest,
- onclick: () => {},
- onpointerenter: () => {},
- onpointerleave: () => {}
- });
-
- const deleteBtn = page.getByTestId('annotation-delete-ann-1');
- await deleteBtn.click();
-
- expect(onDeleteRequest).toHaveBeenCalledOnce();
- });
-
- it('does not call onclick when delete button is clicked', async () => {
- const onclick = vi.fn();
- const onDeleteRequest = vi.fn();
-
- render(AnnotationShape, {
- annotation: makeAnnotation(),
- isHovered: true,
- isActive: false,
- showDelete: true,
- onDeleteRequest,
- onclick,
- onpointerenter: () => {},
- onpointerleave: () => {}
- });
-
- const deleteBtn = page.getByTestId('annotation-delete-ann-1');
- await deleteBtn.click();
-
- expect(onclick).not.toHaveBeenCalled();
- expect(onDeleteRequest).toHaveBeenCalledOnce();
+ await expect.element(page.getByTestId('annotation-ann-1')).toBeInTheDocument();
+ // Positive control: the previously-removed testid must stay absent.
+ await expect.element(page.getByTestId('annotation-delete-ann-1')).not.toBeInTheDocument();
+ // Real invariant: the annotation must contain no clickable delete control at all.
+ const annotationEl = page.getByTestId('annotation-ann-1').element() as HTMLElement;
+ expect(annotationEl.querySelectorAll('button').length).toBe(0);
});
it('calls onDeleteRequest when Delete key is pressed on the annotation', async () => {
diff --git a/frontend/src/lib/document/transcription/TranscriptionBlock.svelte b/frontend/src/lib/document/transcription/TranscriptionBlock.svelte
index d6e2dd87..8ae156af 100644
--- a/frontend/src/lib/document/transcription/TranscriptionBlock.svelte
+++ b/frontend/src/lib/document/transcription/TranscriptionBlock.svelte
@@ -231,7 +231,7 @@ async function handleDelete() {