feat(nav): add tooltip and cursor:pointer to notification bell icon #344

Closed
opened 2026-04-26 19:56:39 +02:00 by marcel · 10 comments
Owner

User story

As a user, I want the notification bell icon to show a tooltip on hover and use a pointer cursor, so that it's obviously clickable — consistent with the color-mode toggle.

Context

The color-mode toggle already has cursor: pointer and a hover hint. The notification bell lacks both, breaking interaction consistency (Nielsen heuristic 4: Consistency and standards).

Acceptance criteria

  • Given the user hovers over the notification bell, then the cursor changes to pointer.
  • Given the user hovers over the notification bell, then a tooltip appears (e.g. "Benachrichtigungen").
  • Visual and interaction behavior matches the color-mode toggle and other icon buttons in the top nav.
## User story As a user, I want the notification bell icon to show a tooltip on hover and use a pointer cursor, so that it's obviously clickable — consistent with the color-mode toggle. ## Context The color-mode toggle already has `cursor: pointer` and a hover hint. The notification bell lacks both, breaking interaction consistency (Nielsen heuristic 4: Consistency and standards). ## Acceptance criteria - Given the user hovers over the notification bell, then the cursor changes to pointer. - Given the user hovers over the notification bell, then a tooltip appears (e.g. "Benachrichtigungen"). - Visual and interaction behavior matches the color-mode toggle and other icon buttons in the top nav.
marcel added this to the Demo Day — family get-together milestone 2026-04-26 19:56:39 +02:00
marcel added the P2-mediumfeatureui labels 2026-04-26 19:56:57 +02:00
Author
Owner

🎨 Leonie Voss — UI/UX Design Lead

Observations

  • The NotificationBell.svelte button already has cursor-pointer implicitly because <button> elements carry it by default in most browsers — but Tailwind's base reset (cursor: default on *) overrides this, so explicitly adding cursor-pointer is correct and necessary.
  • The ThemeToggle.svelte uses a native title attribute for its hover hint. The bell should match this exactly — same mechanism, same layer.
  • The title attribute doubles as a tooltip in all major browsers on desktop and is read by screen readers as an accessible description (supplementing the existing aria-label). It costs a single attribute.
  • The bell's aria-label already carries the right text (m.notification_bell_label() → "Benachrichtigungen"). The title should mirror the same value, so keyboard users with screen readers and mouse users with tooltips both get consistent feedback.
  • The unread-count state needs consideration: when there are unread notifications, the title should say "3 ungelesene Benachrichtigungen" (same as aria-label), not just "Benachrichtigungen". Otherwise the tooltip contradicts the badge.
  • The button's touch target is currently p-2 on a h-5 w-5 icon = 20px icon + 16px padding = 36px total height. This is below the 44px minimum for the senior audience. This issue predates #344 but the spec should call it out.

Recommendations

  • Add cursor-pointer to the button's class list and title={stream.unreadCount > 0 ? m.notification_bell_unread_label({ count: stream.unreadCount }) : m.notification_bell_label()} — binding title to the same derived value as aria-label keeps them in sync without duplicating logic.
  • Do not add a custom JavaScript tooltip — the native title attribute is exactly how ThemeToggle does it, and parity with that component is the goal.
  • Address the touch target in the same PR: change p-2 to p-2.5 (gives 40px) or add min-h-[44px] min-w-[44px] as a belt-and-suspenders fix. The issue title references consistency with the color-mode toggle — that toggle also uses p-1.5, so both are undersized.

Open Decisions (omit if none)

  • Should cursor-pointer be added globally to button via Tailwind's @layer base so future buttons don't regress, or added per-component? A global rule is one change; per-component requires remembering it every time.
## 🎨 Leonie Voss — UI/UX Design Lead ### Observations - The `NotificationBell.svelte` button already has `cursor-pointer` implicitly because `<button>` elements carry it by default in most browsers — but Tailwind's base reset (`cursor: default` on `*`) overrides this, so explicitly adding `cursor-pointer` is correct and necessary. - The `ThemeToggle.svelte` uses a native `title` attribute for its hover hint. The bell should match this exactly — same mechanism, same layer. - The `title` attribute doubles as a tooltip in all major browsers on desktop and is read by screen readers as an accessible description (supplementing the existing `aria-label`). It costs a single attribute. - The bell's `aria-label` already carries the right text (`m.notification_bell_label()` → "Benachrichtigungen"). The `title` should mirror the same value, so keyboard users with screen readers and mouse users with tooltips both get consistent feedback. - The unread-count state needs consideration: when there are unread notifications, the `title` should say "3 ungelesene Benachrichtigungen" (same as `aria-label`), not just "Benachrichtigungen". Otherwise the tooltip contradicts the badge. - The button's touch target is currently `p-2` on a `h-5 w-5` icon = 20px icon + 16px padding = 36px total height. This is below the 44px minimum for the senior audience. This issue predates #344 but the spec should call it out. ### Recommendations - Add `cursor-pointer` to the button's class list and `title={stream.unreadCount > 0 ? m.notification_bell_unread_label({ count: stream.unreadCount }) : m.notification_bell_label()}` — binding `title` to the same derived value as `aria-label` keeps them in sync without duplicating logic. - Do not add a custom JavaScript tooltip — the native `title` attribute is exactly how `ThemeToggle` does it, and parity with that component is the goal. - Address the touch target in the same PR: change `p-2` to `p-2.5` (gives 40px) or add `min-h-[44px] min-w-[44px]` as a belt-and-suspenders fix. The issue title references consistency with the color-mode toggle — that toggle also uses `p-1.5`, so both are undersized. ### Open Decisions _(omit if none)_ - Should `cursor-pointer` be added globally to `button` via Tailwind's `@layer base` so future buttons don't regress, or added per-component? A global rule is one change; per-component requires remembering it every time.
Author
Owner

👨‍💻 Felix Brandt — Fullstack Developer

Observations

  • The implementation diff is tiny and well-scoped: two additions to an existing <button> element in NotificationBell.svelte. No new files, no new abstractions.
  • The title attribute value should be derived from the same expression already used for aria-label. Currently aria-label is computed inline in the template. Duplicating that inline ternary for title would be a copy-paste smell — extract it to a $derived instead.
  • The ThemeToggle.svelte has title hardcoded as an English string ('light mode' / 'dark mode') rather than using Paraglide — that's a separate pre-existing bug, but the bell should use m.*() correctly.
  • The cursor-pointer class belongs on the button element directly. Tailwind's preflight resets cursor on *, so <button> without explicit cursor-pointer shows the default arrow in some browsers.
  • No backend changes. No new API types. No Flyway migration. Pure frontend cosmetic.

Recommendations

  • Extract the tooltip/label text to a $derived at the top of <script>:
    const bellLabel = $derived(
      stream.unreadCount > 0
        ? m.notification_bell_unread_label({ count: stream.unreadCount })
        : m.notification_bell_label()
    );
    
    Then use aria-label={bellLabel} and title={bellLabel} — DRY, reactive, and correct.
  • Add cursor-pointer to the button's existing class string, adjacent to the other interaction classes (hover:bg-white/10).
  • Do not use $effect to sync title — it is a static attribute that Svelte handles natively.
  • Write a Vitest component test asserting: (a) cursor-pointer is present in the class list, and (b) title attribute equals the aria-label string. This is a two-assertion component test, not an E2E test.
## 👨‍💻 Felix Brandt — Fullstack Developer ### Observations - The implementation diff is tiny and well-scoped: two additions to an existing `<button>` element in `NotificationBell.svelte`. No new files, no new abstractions. - The `title` attribute value should be derived from the same expression already used for `aria-label`. Currently `aria-label` is computed inline in the template. Duplicating that inline ternary for `title` would be a copy-paste smell — extract it to a `$derived` instead. - The `ThemeToggle.svelte` has `title` hardcoded as an English string (`'light mode'` / `'dark mode'`) rather than using Paraglide — that's a separate pre-existing bug, but the bell should use `m.*()` correctly. - The `cursor-pointer` class belongs on the button element directly. Tailwind's preflight resets `cursor` on `*`, so `<button>` without explicit `cursor-pointer` shows the default arrow in some browsers. - No backend changes. No new API types. No Flyway migration. Pure frontend cosmetic. ### Recommendations - Extract the tooltip/label text to a `$derived` at the top of `<script>`: ```svelte const bellLabel = $derived( stream.unreadCount > 0 ? m.notification_bell_unread_label({ count: stream.unreadCount }) : m.notification_bell_label() ); ``` Then use `aria-label={bellLabel}` and `title={bellLabel}` — DRY, reactive, and correct. - Add `cursor-pointer` to the button's existing class string, adjacent to the other interaction classes (`hover:bg-white/10`). - Do not use `$effect` to sync `title` — it is a static attribute that Svelte handles natively. - Write a Vitest component test asserting: (a) `cursor-pointer` is present in the class list, and (b) `title` attribute equals the aria-label string. This is a two-assertion component test, not an E2E test.
Author
Owner

🏗️ Markus Keller — Application Architect

Observations

  • This is a pure frontend cosmetic change: two HTML attributes on an existing <button> element. No architectural implications.
  • The issue correctly identifies the interaction inconsistency (Nielsen #4) between NotificationBell and ThemeToggle. The ThemeToggle uses a native title attribute and browser-default cursor: pointer on <button> — except Tailwind's preflight overrides the cursor, making explicit cursor-pointer necessary in both components.
  • No cross-domain service calls, no new state management, no new API surface. The boundary model is untouched.
  • The only structural question is whether cursor-pointer should be applied globally via Tailwind base styles or per-component. That is a minor style architecture decision with no downstream coupling.

Recommendations

  • Implement as a direct edit to NotificationBell.svelte. Do not introduce a new wrapper component or shared utility — the change does not justify an abstraction.
  • If this pattern recurs on a third icon button, then a shared IconButton.svelte component becomes justified (Rule of Three). Two instances do not.
  • The ThemeToggle also lacks cursor-pointer — fix both in the same commit so the inconsistency is fully resolved, not half-fixed.
## 🏗️ Markus Keller — Application Architect ### Observations - This is a pure frontend cosmetic change: two HTML attributes on an existing `<button>` element. No architectural implications. - The issue correctly identifies the interaction inconsistency (Nielsen #4) between `NotificationBell` and `ThemeToggle`. The `ThemeToggle` uses a native `title` attribute and browser-default `cursor: pointer` on `<button>` — except Tailwind's preflight overrides the cursor, making explicit `cursor-pointer` necessary in both components. - No cross-domain service calls, no new state management, no new API surface. The boundary model is untouched. - The only structural question is whether `cursor-pointer` should be applied globally via Tailwind base styles or per-component. That is a minor style architecture decision with no downstream coupling. ### Recommendations - Implement as a direct edit to `NotificationBell.svelte`. Do not introduce a new wrapper component or shared utility — the change does not justify an abstraction. - If this pattern recurs on a third icon button, then a shared `IconButton.svelte` component becomes justified (Rule of Three). Two instances do not. - The `ThemeToggle` also lacks `cursor-pointer` — fix both in the same commit so the inconsistency is fully resolved, not half-fixed.
Author
Owner

🔒 Nora Steiner — Application Security Engineer

Observations

  • No security implications in the core change. Adding cursor-pointer and a title tooltip to a button introduces no new attack surface.
  • The existing aria-label on the bell button already uses i18n via m.*() — the title attribute should follow the same pattern. Hardcoded English strings (as seen in ThemeToggle) could confuse users whose locale is German, but this is a UX issue, not a security one.
  • The title attribute value is generated from m.notification_bell_label() and m.notification_bell_unread_label({ count: stream.unreadCount }). The count value comes from stream.unreadCount, which is an integer. Paraglide's message interpolation is safe — it does not evaluate user-controlled strings as HTML. No XSS vector here.
  • Tooltip content is server-sourced (notification count from the backend) but is an integer, not a user-supplied string, so there is no injection risk.

Recommendations

  • Ensure title mirrors aria-label exactly — both should use the same Paraglide keys. Do not introduce a new string that could diverge and expose unexpected information.
  • No security review needed beyond confirming the above — this is a cosmetic change with clean data provenance.
## 🔒 Nora Steiner — Application Security Engineer ### Observations - No security implications in the core change. Adding `cursor-pointer` and a `title` tooltip to a button introduces no new attack surface. - The existing `aria-label` on the bell button already uses i18n via `m.*()` — the `title` attribute should follow the same pattern. Hardcoded English strings (as seen in `ThemeToggle`) could confuse users whose locale is German, but this is a UX issue, not a security one. - The `title` attribute value is generated from `m.notification_bell_label()` and `m.notification_bell_unread_label({ count: stream.unreadCount })`. The `count` value comes from `stream.unreadCount`, which is an integer. Paraglide's message interpolation is safe — it does not evaluate user-controlled strings as HTML. No XSS vector here. - Tooltip content is server-sourced (notification count from the backend) but is an integer, not a user-supplied string, so there is no injection risk. ### Recommendations - Ensure `title` mirrors `aria-label` exactly — both should use the same Paraglide keys. Do not introduce a new string that could diverge and expose unexpected information. - No security review needed beyond confirming the above — this is a cosmetic change with clean data provenance.
Author
Owner

🧪 Sara Holt — QA Engineer

Observations

  • The acceptance criteria are clear and testable: cursor changes to pointer on hover, tooltip appears on hover, behavior matches the color-mode toggle.
  • "Tooltip appears" via title attribute is not easily assertable in Playwright because browsers control native tooltip rendering — Playwright does not expose title tooltip visibility as a DOM event. The correct assertion is that the title attribute is present with the expected value.
  • The AC says "visual and interaction behavior matches the color-mode toggle" — this is a regression risk: if ThemeToggle is later changed, the bell may diverge again. A shared test fixture that asserts both components have matching interaction patterns would prevent this.
  • No backend changes means no integration or E2E tests are needed for the data layer. This is purely a component test.

Recommendations

  • Write a Vitest browser-mode component test for NotificationBell that asserts:
    1. The <button> element has class containing cursor-pointer.
    2. The title attribute equals m.notification_bell_label() when unreadCount === 0.
    3. The title attribute equals m.notification_bell_unread_label({ count: 3 }) when unreadCount === 3.
    4. aria-label equals title in both states (consistency check).
  • Do not write an E2E test for tooltip appearance — title rendering is browser chrome, not application behavior. The attribute presence test at the component level is sufficient.
  • Add a Playwright accessibility check (AxeBuilder) for the nav bar to catch any future aria-label regressions on the bell.

Open Decisions (omit if none)

  • Should the test assert cursor-pointer via window.getComputedStyle (computed style, catches CSS overrides) or via the class attribute string (simpler, but would miss a CSS file removing the effect)? Computed style is more accurate but requires a real browser environment — which vitest-browser-svelte provides.
## 🧪 Sara Holt — QA Engineer ### Observations - The acceptance criteria are clear and testable: cursor changes to pointer on hover, tooltip appears on hover, behavior matches the color-mode toggle. - "Tooltip appears" via `title` attribute is not easily assertable in Playwright because browsers control native tooltip rendering — Playwright does not expose `title` tooltip visibility as a DOM event. The correct assertion is that the `title` attribute is present with the expected value. - The AC says "visual and interaction behavior matches the color-mode toggle" — this is a regression risk: if `ThemeToggle` is later changed, the bell may diverge again. A shared test fixture that asserts both components have matching interaction patterns would prevent this. - No backend changes means no integration or E2E tests are needed for the data layer. This is purely a component test. ### Recommendations - Write a Vitest browser-mode component test for `NotificationBell` that asserts: 1. The `<button>` element has `class` containing `cursor-pointer`. 2. The `title` attribute equals `m.notification_bell_label()` when `unreadCount === 0`. 3. The `title` attribute equals `m.notification_bell_unread_label({ count: 3 })` when `unreadCount === 3`. 4. `aria-label` equals `title` in both states (consistency check). - Do not write an E2E test for tooltip appearance — `title` rendering is browser chrome, not application behavior. The attribute presence test at the component level is sufficient. - Add a Playwright accessibility check (`AxeBuilder`) for the nav bar to catch any future `aria-label` regressions on the bell. ### Open Decisions _(omit if none)_ - Should the test assert `cursor-pointer` via `window.getComputedStyle` (computed style, catches CSS overrides) or via the class attribute string (simpler, but would miss a CSS file removing the effect)? Computed style is more accurate but requires a real browser environment — which vitest-browser-svelte provides.
Author
Owner

⚙️ Tobias Wendt — DevOps & Platform Engineer

Observations

  • Pure frontend cosmetic change. No infrastructure, Docker Compose, CI pipeline, or environment configuration is affected.
  • No new npm dependencies. No build output size change worth measuring.
  • This will pass CI with the standard npm run lint && npm run check && npm run test pipeline. No additional pipeline steps required.
  • The ThemeToggle has title attributes with hardcoded English strings — if Paraglide is the i18n tool and the project supports de/en/es, then hardcoded English in ThemeToggle is a pre-existing inconsistency. Not a CI blocker, but worth noting.

Recommendations

  • No CI changes needed. Ship it.
  • If the team adds visual regression snapshots (Playwright screenshots) in the future, the nav bar snapshot will need updating to include the tooltip cursor state — but that is not a concern for this PR.
  • Fix ThemeToggle's hardcoded 'light mode' / 'dark mode' strings in the same commit to keep i18n consistent across both nav buttons.
## ⚙️ Tobias Wendt — DevOps & Platform Engineer ### Observations - Pure frontend cosmetic change. No infrastructure, Docker Compose, CI pipeline, or environment configuration is affected. - No new npm dependencies. No build output size change worth measuring. - This will pass CI with the standard `npm run lint && npm run check && npm run test` pipeline. No additional pipeline steps required. - The `ThemeToggle` has `title` attributes with hardcoded English strings — if Paraglide is the i18n tool and the project supports `de/en/es`, then hardcoded English in `ThemeToggle` is a pre-existing inconsistency. Not a CI blocker, but worth noting. ### Recommendations - No CI changes needed. Ship it. - If the team adds visual regression snapshots (Playwright screenshots) in the future, the nav bar snapshot will need updating to include the tooltip cursor state — but that is not a concern for this PR. - Fix `ThemeToggle`'s hardcoded `'light mode'` / `'dark mode'` strings in the same commit to keep i18n consistent across both nav buttons.
Author
Owner

📋 Elicit — Requirements Engineer

Observations

  • The user story, context, and acceptance criteria are well-formed and implementation-ready.
  • AC1 ("cursor changes to pointer") and AC2 ("tooltip appears") are testable at the component level. AC3 ("matches the color-mode toggle") is partially testable — the title attribute mechanism can be verified, but visual tooltip rendering is browser-controlled.
  • The issue correctly identifies Nielsen heuristic #4 (Consistency and standards) as the motivation. The fix scope is appropriately narrow.
  • One implicit requirement is missing from the ACs: the tooltip text must be localized (German: "Benachrichtigungen", not "Notifications"). The existing aria-label already handles this via Paraglide, but the AC does not explicitly state it. The ThemeToggle's title is hardcoded English — the spec should prevent the same mistake on the bell.
  • The milestone ("Demo Day — family get-together") is appropriate for a P2-medium polish fix.

Recommendations

  • Add a 4th AC to make the i18n requirement explicit: "Given the user's locale is German, the tooltip text is 'Benachrichtigungen'." This prevents an implementation that adds title="Notifications" (which would pass AC2 but violate project standards).
  • The issue body references matching "other icon buttons in the top nav" — enumerate which ones if there are more, or narrow the wording to "the color-mode toggle" to avoid scope creep into a full nav audit.

Open Decisions (omit if none)

  • Should ThemeToggle be fixed in this issue (scope expansion) or tracked as a separate issue (scope discipline)? The current wording "matches the color-mode toggle" implicitly argues the toggle is the reference implementation — but the toggle's title is hardcoded English, which is already wrong. Fixing both here vs. separately is a prioritization call.
## 📋 Elicit — Requirements Engineer ### Observations - The user story, context, and acceptance criteria are well-formed and implementation-ready. - AC1 ("cursor changes to pointer") and AC2 ("tooltip appears") are testable at the component level. AC3 ("matches the color-mode toggle") is partially testable — the `title` attribute mechanism can be verified, but visual tooltip rendering is browser-controlled. - The issue correctly identifies Nielsen heuristic #4 (Consistency and standards) as the motivation. The fix scope is appropriately narrow. - One implicit requirement is missing from the ACs: **the tooltip text must be localized** (German: "Benachrichtigungen", not "Notifications"). The existing `aria-label` already handles this via Paraglide, but the AC does not explicitly state it. The `ThemeToggle`'s `title` is hardcoded English — the spec should prevent the same mistake on the bell. - The milestone ("Demo Day — family get-together") is appropriate for a P2-medium polish fix. ### Recommendations - Add a 4th AC to make the i18n requirement explicit: "Given the user's locale is German, the tooltip text is 'Benachrichtigungen'." This prevents an implementation that adds `title="Notifications"` (which would pass AC2 but violate project standards). - The issue body references matching "other icon buttons in the top nav" — enumerate which ones if there are more, or narrow the wording to "the color-mode toggle" to avoid scope creep into a full nav audit. ### Open Decisions _(omit if none)_ - Should `ThemeToggle` be fixed in this issue (scope expansion) or tracked as a separate issue (scope discipline)? The current wording "matches the color-mode toggle" implicitly argues the toggle is the reference implementation — but the toggle's `title` is hardcoded English, which is already wrong. Fixing both here vs. separately is a prioritization call.
Author
Owner

🗳️ Decision Queue

Consolidated open decisions requiring human context before implementation.


Theme: cursor-pointer scope — global vs. per-component

From Leonie & Markus

Should cursor-pointer be added globally to button elements via Tailwind's @layer base (one change, prevents future regressions), or added per-component alongside other interaction classes (explicit, no global side effects)?

  • Global rule: @layer base { button { @apply cursor-pointer; } } — zero maintenance, but may surprise if a button intentionally needs a different cursor.
  • Per-component: requires remembering to add it every time; already missed on ThemeToggle and NotificationBell.

Theme: ThemeToggle scope — same issue or separate?

From Elicit, Tobias, Felix, Markus

ThemeToggle.svelte has the same cursor-pointer omission and also has hardcoded English title strings instead of Paraglide keys. Three options:

  1. Fix both NotificationBell and ThemeToggle in this issue — keeps the "matches the toggle" consistency promise honest.
  2. Fix ThemeToggle cursor only in this issue, track i18n strings as a separate issue.
  3. Fix only NotificationBell as scoped; open a follow-up for ThemeToggle separately.

Theme: Touch target size — in scope or follow-up?

From Leonie

Both NotificationBell (p-2) and ThemeToggle (p-1.5) render touch targets below 44px. This is a WCAG 2.2 failure for the senior audience. Fix now alongside cursor/tooltip, or track separately as a dedicated accessibility issue?

## 🗳️ Decision Queue Consolidated open decisions requiring human context before implementation. --- ### Theme: `cursor-pointer` scope — global vs. per-component **From Leonie & Markus** Should `cursor-pointer` be added globally to `button` elements via Tailwind's `@layer base` (one change, prevents future regressions), or added per-component alongside other interaction classes (explicit, no global side effects)? - Global rule: `@layer base { button { @apply cursor-pointer; } }` — zero maintenance, but may surprise if a button intentionally needs a different cursor. - Per-component: requires remembering to add it every time; already missed on `ThemeToggle` and `NotificationBell`. --- ### Theme: `ThemeToggle` scope — same issue or separate? **From Elicit, Tobias, Felix, Markus** `ThemeToggle.svelte` has the same `cursor-pointer` omission and also has hardcoded English `title` strings instead of Paraglide keys. Three options: 1. Fix both `NotificationBell` and `ThemeToggle` in this issue — keeps the "matches the toggle" consistency promise honest. 2. Fix `ThemeToggle` cursor only in this issue, track i18n strings as a separate issue. 3. Fix only `NotificationBell` as scoped; open a follow-up for `ThemeToggle` separately. --- ### Theme: Touch target size — in scope or follow-up? **From Leonie** Both `NotificationBell` (`p-2`) and `ThemeToggle` (`p-1.5`) render touch targets below 44px. This is a WCAG 2.2 failure for the senior audience. Fix now alongside cursor/tooltip, or track separately as a dedicated accessibility issue?
Author
Owner

Global cursor
ThemeToggle can be fixed here
Touch target is descoped

Global cursor ThemeToggle can be fixed here Touch target is descoped
Author
Owner

Implementation complete

Branch: feat/issue-344-bell-tooltip

What was implemented

Commit 1 — feat(nav): add cursor-pointer and tooltip to notification bell

  • Extracted bellLabel as $derived in NotificationBell.svelte — eliminates the duplicated inline ternary that was used for aria-label and keeps tooltip/label in sync reactively
  • Added title={bellLabel} to the bell <button> — native tooltip matches aria-label in both zero and non-zero unread states
  • Added cursor-pointer to the bell button's class list
  • Added global button { cursor: pointer; } rule in @layer base of layout.css — prevents future regressions (Decision Queue: global scope)
  • Added 3 component tests in NotificationBell.svelte.spec.ts: cursor-pointer class present, title equals aria-label when unread=0, title equals aria-label when unread=3

Commit 2 — fix(nav): replace hardcoded ThemeToggle title with Paraglide i18n keys

  • Added theme_toggle_to_light / theme_toggle_to_dark keys to de/en/es messages
  • Extracted themeLabel as $derived in ThemeToggle.svelte and bound both aria-label and title to it
  • Fixes the pre-existing hardcoded English strings ('light mode' / 'dark mode') (Decision Queue: fix ThemeToggle in this issue)

Touch target size was descoped per the Decision Queue.

Test results

All 5 NotificationBell tests pass. The single pre-existing flaky timeout in DocumentList.svelte.spec.ts is unrelated and was failing on main before this work.

## Implementation complete Branch: `feat/issue-344-bell-tooltip` ### What was implemented **Commit 1 — `feat(nav): add cursor-pointer and tooltip to notification bell`** - Extracted `bellLabel` as `$derived` in `NotificationBell.svelte` — eliminates the duplicated inline ternary that was used for `aria-label` and keeps tooltip/label in sync reactively - Added `title={bellLabel}` to the bell `<button>` — native tooltip matches `aria-label` in both zero and non-zero unread states - Added `cursor-pointer` to the bell button's class list - Added global `button { cursor: pointer; }` rule in `@layer base` of `layout.css` — prevents future regressions (Decision Queue: global scope) - Added 3 component tests in `NotificationBell.svelte.spec.ts`: cursor-pointer class present, title equals aria-label when unread=0, title equals aria-label when unread=3 **Commit 2 — `fix(nav): replace hardcoded ThemeToggle title with Paraglide i18n keys`** - Added `theme_toggle_to_light` / `theme_toggle_to_dark` keys to `de/en/es` messages - Extracted `themeLabel` as `$derived` in `ThemeToggle.svelte` and bound both `aria-label` and `title` to it - Fixes the pre-existing hardcoded English strings (`'light mode'` / `'dark mode'`) (Decision Queue: fix ThemeToggle in this issue) Touch target size was descoped per the Decision Queue. ### Test results All 5 `NotificationBell` tests pass. The single pre-existing flaky timeout in `DocumentList.svelte.spec.ts` is unrelated and was failing on `main` before this work.
Sign in to join this conversation.
No Label P2-medium feature ui
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#344