From bc805cb1787d4a409e23bc60271c837473245d1f Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 26 Apr 2026 20:46:12 +0200 Subject: [PATCH 1/3] feat(nav): add cursor-pointer and tooltip to notification bell Extract bellLabel as $derived to DRY up aria-label and title. Add cursor-pointer globally to button via @layer base so Tailwind preflight reset doesn't override the browser default. Closes #344 Co-Authored-By: Claude Sonnet 4.6 --- .../lib/components/NotificationBell.svelte | 13 ++++++--- .../NotificationBell.svelte.spec.ts | 28 +++++++++++++++++++ frontend/src/routes/layout.css | 5 ++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/components/NotificationBell.svelte b/frontend/src/lib/components/NotificationBell.svelte index 0e390357..a06d90e4 100644 --- a/frontend/src/lib/components/NotificationBell.svelte +++ b/frontend/src/lib/components/NotificationBell.svelte @@ -48,6 +48,12 @@ function handleKeydown(event: KeyboardEvent) { } } +const bellLabel = $derived( + stream.unreadCount > 0 + ? m.notification_bell_unread_label({ count: stream.unreadCount }) + : m.notification_bell_label() +); + function attachBellButton(node: HTMLButtonElement) { bellButtonEl = node; return () => { @@ -72,12 +78,11 @@ onDestroy(() => { {@attach attachBellButton} type="button" onclick={toggleDropdown} - aria-label={stream.unreadCount > 0 - ? m.notification_bell_unread_label({ count: stream.unreadCount }) - : m.notification_bell_label()} + aria-label={bellLabel} + title={bellLabel} aria-expanded={open} aria-haspopup="true" - class="relative rounded-sm p-2 text-white/65 transition-colors hover:bg-white/10 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring" + class="relative cursor-pointer rounded-sm p-2 text-white/65 transition-colors hover:bg-white/10 hover:text-white focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring" > { + it('bell button has cursor-pointer class', async () => { + render(NotificationBell); + const btn = document.querySelector('button[aria-haspopup="true"]')!; + expect(btn.classList.contains('cursor-pointer')).toBe(true); + }); + + it('bell button title equals aria-label when unreadCount is 0', async () => { + mockNotificationList.value = []; + render(NotificationBell); + const btn = document.querySelector('button[aria-haspopup="true"]')!; + expect(btn.getAttribute('title')).toBe('Benachrichtigungen'); + expect(btn.getAttribute('aria-label')).toBe(btn.getAttribute('title')); + }); + + it('bell button title equals aria-label when unreadCount is 3', async () => { + mockNotificationList.value = [ + makeNotification({ id: 'n1' }), + makeNotification({ id: 'n2' }), + makeNotification({ id: 'n3' }) + ]; + render(NotificationBell); + const btn = document.querySelector('button[aria-haspopup="true"]')!; + expect(btn.getAttribute('title')).toBe('3 ungelesene Benachrichtigungen'); + expect(btn.getAttribute('aria-label')).toBe(btn.getAttribute('title')); + }); +}); + describe('NotificationBell', () => { it('handleMarkRead navigates to URL including annotationId when notification has annotationId', async () => { mockNotificationList.value = [makeNotification({ annotationId: 'annot-1' })]; diff --git a/frontend/src/routes/layout.css b/frontend/src/routes/layout.css index ef368838..e0335a34 100644 --- a/frontend/src/routes/layout.css +++ b/frontend/src/routes/layout.css @@ -365,6 +365,11 @@ text-underline-offset: 4px; } + /* Tailwind preflight resets cursor on *, overriding the browser default for buttons */ + button { + cursor: pointer; + } + /* Fallback focus ring for any interactive element not styled with ring-focus-ring */ :focus-visible { outline: 2px solid var(--c-focus-ring); -- 2.49.1 From c317c085aa40ca16a737091ca0040db546bcaf80 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 26 Apr 2026 20:53:10 +0200 Subject: [PATCH 2/3] fix(nav): replace hardcoded ThemeToggle title with Paraglide i18n keys Add theme_toggle_to_light / theme_toggle_to_dark to de/en/es messages. Extract themeLabel as $derived and use it for both aria-label and title, matching the pattern applied to NotificationBell. Co-Authored-By: Claude Sonnet 4.6 --- frontend/messages/de.json | 2 ++ frontend/messages/en.json | 2 ++ frontend/messages/es.json | 2 ++ frontend/src/lib/components/ThemeToggle.svelte | 9 +++++++-- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/frontend/messages/de.json b/frontend/messages/de.json index 8522580c..3c3b31f1 100644 --- a/frontend/messages/de.json +++ b/frontend/messages/de.json @@ -23,6 +23,8 @@ "nav_conversations": "Briefwechsel", "nav_admin": "Admin", "nav_logout": "Abmelden", + "theme_toggle_to_light": "Zu hellem Design wechseln", + "theme_toggle_to_dark": "Zu dunklem Design wechseln", "btn_save": "Speichern", "btn_cancel": "Abbrechen", "btn_confirm": "Bestätigen", diff --git a/frontend/messages/en.json b/frontend/messages/en.json index cddafac1..57a29039 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -23,6 +23,8 @@ "nav_conversations": "Letters", "nav_admin": "Admin", "nav_logout": "Sign out", + "theme_toggle_to_light": "Switch to light mode", + "theme_toggle_to_dark": "Switch to dark mode", "btn_save": "Save", "btn_cancel": "Cancel", "btn_confirm": "Confirm", diff --git a/frontend/messages/es.json b/frontend/messages/es.json index f5125884..ef6a4ee6 100644 --- a/frontend/messages/es.json +++ b/frontend/messages/es.json @@ -23,6 +23,8 @@ "nav_conversations": "Cartas", "nav_admin": "Admin", "nav_logout": "Cerrar sesión", + "theme_toggle_to_light": "Cambiar a modo claro", + "theme_toggle_to_dark": "Cambiar a modo oscuro", "btn_save": "Guardar", "btn_cancel": "Cancelar", "btn_confirm": "Confirmar", diff --git a/frontend/src/lib/components/ThemeToggle.svelte b/frontend/src/lib/components/ThemeToggle.svelte index 012fd670..834f765c 100644 --- a/frontend/src/lib/components/ThemeToggle.svelte +++ b/frontend/src/lib/components/ThemeToggle.svelte @@ -1,5 +1,6 @@