# Badge count accurately reflects number of unread or active items

- **Pattern:** `ab-001368` (`extension-ux-performance.badge-notifications.badge-count-accurate`)
- **Severity:** medium
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001368
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001368)

## Why it matters

A badge count that drifts from reality undermines the entire notification contract. When bulk-delete actions, crash-interrupted decrements, or missed code paths leave the counter stale, users open the popup expecting unread items and find none — or worse, miss real items because the badge showed zero. ISO 25010:2011 functional-suitability.functional-correctness applies directly: the badge's job is accurate representation of state, and independent increment/decrement counters are structurally incapable of guaranteeing that.

## Severity rationale

Medium because stale badge counts cause user confusion and erode trust in the extension's reliability, but do not expose security risk or block core functionality.

## Remediation

Derive the badge count from a single source of truth on every update rather than maintaining a separate counter. Tie badge updates to `chrome.storage.onChanged` so any write to the underlying data automatically recalculates the displayed count.

```js
async function updateBadge() {
  const { items } = await chrome.storage.local.get(['items']);
  const unread = (items ?? []).filter(item => !item.read).length;
  chrome.action.setBadgeText({
    text: unread > 0 ? String(unread) : ''
  });
}

chrome.storage.onChanged.addListener(() => updateBadge());
```

Never increment or decrement a counter variable directly — every code path that modifies items must go through storage, and `onChanged` handles the badge automatically.

## Detection

- **ID:** `badge-count-accurate`
- **Severity:** `medium`
- **What to look for:** Examine the logic that sets the badge text via `chrome.action.setBadgeText`. Check whether the badge count is updated when items are marked read, dismissed, or acted upon. Look for whether the count is recalculated from a source of truth (e.g., counting unread items in storage) or tracked independently (incrementing or decrementing a counter variable, which can drift out of sync). Before evaluating, extract and quote the badge update calls (e.g., `chrome.action.setBadgeText`) to verify the count logic. Count all instances found and enumerate each.
- **Pass criteria:** The badge count is derived from a consistent source of truth — e.g., recomputed by counting qualifying items in `chrome.storage.local` each time it updates. Decrement or increment based counters are only used if they are reset from source of truth periodically as a check. At least 1 implementation must be confirmed.
- **Fail criteria:** Badge count is maintained as an independent counter that is incremented on new items and decremented on dismissal, with no reconciliation against actual data. If increment or decrement operations miss a path (e.g., bulk delete not reflected, crash during update), the counter drifts and misrepresents the true count.
- **Skip (N/A) when:** The extension does not use the badge API or the badge does not display a count.
- **Cross-reference:** The `badge-cleared-on-view` check verifies the badge count is reset when the user opens the popup.
- **Detail on fail:** Describe the accuracy issue. Example: `"Badge count incremented on new notification but not decremented when items dismissed via bulk action — count drifts over time"` or `"Badge not updated when user marks items read from the popup — count remains stale until browser restart."`
- **Remediation:** Derive badge count from source of truth rather than maintaining a separate counter:

  ```js
  async function updateBadge() {
    const { items } = await chrome.storage.local.get(['items']);
    const unread = (items ?? []).filter(item => !item.read).length;
    chrome.action.setBadgeText({
      text: unread > 0 ? String(unread) : ''
    });
  }

  // Call updateBadge() wherever items change:
  chrome.storage.onChanged.addListener(() => updateBadge());
  ```

## External references

- iso-25010 functional-suitability.functional-correctness

Taxons: data-integrity, user-experience

HTML version: https://auditbuffet.com/patterns/ab-001368
