fix(#73): open annotation side panel when deep-linking via ?annotationId=

- NotificationBell now includes annotationId in the deep-link URL when available
- +page.svelte reads ?annotationId= param and sets activeAnnotationId on mount,
  opening the side panel instead of the bottom discussion drawer
- AnnotationSidePanel accepts and forwards targetCommentId to CommentThread
  so the specific comment is highlighted when navigating via a notification

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-03-28 11:44:51 +01:00
parent d13422c65a
commit 7825c7749a
3 changed files with 14 additions and 3 deletions

View File

@@ -9,6 +9,7 @@ type Props = {
canComment: boolean; canComment: boolean;
currentUserId: string | null; currentUserId: string | null;
canAdmin: boolean; canAdmin: boolean;
targetCommentId?: string | null;
onClose: () => void; onClose: () => void;
}; };
@@ -19,6 +20,7 @@ let {
canComment, canComment,
currentUserId, currentUserId,
canAdmin, canAdmin,
targetCommentId = null,
onClose onClose
}: Props = $props(); }: Props = $props();
@@ -57,6 +59,7 @@ const visible = $derived(activeAnnotationId !== null);
canComment={canComment} canComment={canComment}
currentUserId={currentUserId} currentUserId={currentUserId}
canAdmin={canAdmin} canAdmin={canAdmin}
targetCommentId={targetCommentId}
loadOnMount={true} loadOnMount={true}
/> />
{/key} {/key}

View File

@@ -9,6 +9,7 @@ type NotificationItem = {
type: 'REPLY' | 'MENTION'; type: 'REPLY' | 'MENTION';
documentId: string; documentId: string;
referenceId: string; referenceId: string;
annotationId: string | null;
read: boolean; read: boolean;
createdAt: string; createdAt: string;
actorName: string; actorName: string;
@@ -62,7 +63,9 @@ async function markRead(notification: NotificationItem) {
console.error('Failed to mark notification as read', e); console.error('Failed to mark notification as read', e);
} }
} }
const url = `/documents/${notification.documentId}?commentId=${notification.referenceId}`; const url = notification.annotationId
? `/documents/${notification.documentId}?commentId=${notification.referenceId}&annotationId=${notification.annotationId}`
: `/documents/${notification.documentId}?commentId=${notification.referenceId}`;
closeDropdown(); closeDropdown();
goto(url); goto(url);
} }

View File

@@ -10,6 +10,7 @@ import type { DocumentPanelTab } from '$lib/types';
let { data } = $props(); let { data } = $props();
const targetCommentId = $derived(page.url.searchParams.get('commentId')); const targetCommentId = $derived(page.url.searchParams.get('commentId'));
const targetAnnotationId = $derived(page.url.searchParams.get('annotationId'));
const doc = $derived(data.document); const doc = $derived(data.document);
const canComment = $derived((data.canAnnotate || data.canWrite) ?? false); const canComment = $derived((data.canAnnotate || data.canWrite) ?? false);
@@ -95,8 +96,11 @@ onMount(() => {
if (!isNaN(h) && h >= 80) panelHeight = h; if (!isNaN(h) && h >= 80) panelHeight = h;
} }
if (targetCommentId) { if (targetAnnotationId) {
// Deep-link: always open discussion tab regardless of saved state // Deep-link into an annotation comment: open the side panel
activeAnnotationId = targetAnnotationId;
} else if (targetCommentId) {
// Deep-link into a document-level comment: open discussion tab
panelOpen = true; panelOpen = true;
activeTab = 'discussion'; activeTab = 'discussion';
} else if (savedOpen === 'true') { } else if (savedOpen === 'true') {
@@ -169,6 +173,7 @@ $effect(() => {
canComment={canComment} canComment={canComment}
currentUserId={currentUserId} currentUserId={currentUserId}
canAdmin={canAdmin} canAdmin={canAdmin}
targetCommentId={targetAnnotationId ? targetCommentId : null}
onClose={() => { onClose={() => {
activeAnnotationId = null; activeAnnotationId = null;
activeAnnotationPage = null; activeAnnotationPage = null;