feat(frontend): hide outdated annotations when file version changes
Some checks failed
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
CI / Unit & Component Tests (push) Has started running
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / E2E Tests (pull_request) Has been cancelled

- Regenerate API types with fileHash on Document and DocumentAnnotation
- PdfViewer accepts documentFileHash prop; filters visibleAnnotations to
  those whose hash matches (or is null) and shows an amber notice banner
  when any annotations are hidden due to a hash mismatch
- Document detail page passes doc.fileHash to PdfViewer
- Add i18n key annotation_outdated_notice in de/en/es
- E2E: two new tests covering hide-on-reupload and restore-on-original-reupload
  scenarios; add minimal2.pdf fixture for a different-hash upload

Closes #55

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit was merged in pull request #58.
This commit is contained in:
Marcel
2026-03-24 17:09:26 +01:00
parent 93f57477cd
commit 7fbc33b32d
8 changed files with 624 additions and 2 deletions

View File

@@ -4,6 +4,7 @@ import { SvelteMap } from 'svelte/reactivity';
import type { PDFDocumentProxy, PDFPageProxy, RenderTask } from 'pdfjs-dist';
import AnnotationLayer from './AnnotationLayer.svelte';
import AnnotationCommentPanel from './AnnotationCommentPanel.svelte';
import { m } from '$lib/paraglide/messages.js';
let {
url,
@@ -11,7 +12,8 @@ let {
canAnnotate = false,
canComment,
currentUserId,
canAdmin
canAdmin,
documentFileHash
}: {
url: string;
documentId?: string;
@@ -19,6 +21,7 @@ let {
canComment?: boolean;
currentUserId?: string | null;
canAdmin?: boolean;
documentFileHash?: string | null;
} = $props();
let pdfDoc = $state<PDFDocumentProxy | null>(null);
@@ -51,6 +54,7 @@ type Annotation = {
height: number;
color: string;
createdAt: string;
fileHash?: string | null;
};
let annotations = $state<Annotation[]>([]);
@@ -59,6 +63,11 @@ let annotateColor = $state('#ffff00');
let commentCounts = new SvelteMap<string, number>();
let activeAnnotationId = $state<string | null>(null);
const visibleAnnotations = $derived(
annotations.filter((a) => !a.fileHash || !documentFileHash || a.fileHash === documentFileHash)
);
const outdatedCount = $derived(annotations.length - visibleAnnotations.length);
onMount(async () => {
// Dynamic import keeps pdfjs out of the SSR bundle entirely
const [lib, { default: workerUrl }] = await Promise.all([
@@ -298,6 +307,27 @@ function zoomOut() {
</div>
{:else}
<div class="flex h-full w-full flex-col bg-[#2A2A2A]">
{#if outdatedCount > 0}
<div
class="flex shrink-0 items-center gap-2 border-b border-amber-500/30 bg-amber-500/10 px-4 py-2"
data-testid="annotation-outdated-notice"
>
<svg
class="h-4 w-4 shrink-0 text-amber-400"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
/>
</svg>
<span class="font-sans text-xs text-amber-300">{m.annotation_outdated_notice()}</span>
</div>
{/if}
<!-- Controls -->
<div
class="flex shrink-0 items-center justify-between gap-2 border-b border-white/10 px-4 py-2"
@@ -439,7 +469,7 @@ function zoomOut() {
style="position: absolute; top: 0; left: 0; overflow: hidden; pointer-events: none; line-height: 1;"
></div>
<AnnotationLayer
annotations={annotations.filter((a) => a.pageNumber === currentPage)}
annotations={visibleAnnotations.filter((a) => a.pageNumber === currentPage)}
canAnnotate={annotateMode}
color={annotateColor}
onDraw={handleAnnotationDraw}