# Extension popup renders meaningful content within 200ms of activation

- **Pattern:** `ab-001359` (`extension-ux-performance.popup-responsiveness.popup-renders-fast`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001359
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001359)

## Why it matters

A popup that shows nothing until an async operation resolves trains users to distrust the extension — or abandon it. Chrome's ISO 25010:2011 performance-efficiency.time-behaviour benchmark treats 200ms as the threshold for perceived responsiveness. Blank popups during storage reads or API calls regularly cause 1-star reviews citing "laggy" or "broken" extensions; those reviews cannot be retracted once published to the Chrome Web Store.

## Severity rationale

Critical because a blank popup on every activation is functionally indistinguishable from a broken extension, causing user abandonment before any feature can deliver value.

## Remediation

Render a skeleton or cached state synchronously from `popup.html` or initial `useState`, then populate with real data once async operations resolve. Never gate the first paint on `chrome.storage.get`, `fetch`, or any `useEffect` that must complete before UI is shown.

```tsx
function Popup() {
  const [data, setData] = useState<CachedData | null>(null);

  useEffect(() => {
    chrome.storage.local.get(['cachedData'], (result) => {
      setData(result.cachedData ?? DEFAULT_DATA);
    });
  }, []);

  if (!data) return <SkeletonUI />; // Renders immediately
  return <PopupContent data={data} />;
}
```

For vanilla JS, pre-populate `popup.html` with placeholder content and update via `textContent` after data arrives — never leave `<body>` empty.

## Detection

- **ID:** `popup-renders-fast`
- **Severity:** `critical`
- **What to look for:** Examine the popup's initialization path in `popup.js` or the equivalent entry point. Check whether the popup displays meaningful content (not a blank screen or indefinite spinner) before making async API calls or loading chrome storage. Look for patterns like deferring initial render behind a `chrome.storage.get` callback, a `fetch()` call, or a `useEffect` that must resolve before any UI is shown. Check whether any skeleton or placeholder UI renders synchronously before data arrives. Count every resource loaded during popup initialization (scripts, stylesheets, images, API calls) and enumerate each with its size. Report total resource count.
- **Pass criteria:** The popup renders at least a skeleton state, placeholder content, or cached data from `chrome.storage.local` immediately on open — without blocking on async operations. Network calls or slow storage reads happen after the initial meaningful paint. Report even on pass: "Popup initialization loads X resources totaling Y KB. Estimated render time: Z ms." At least 1 implementation must be confirmed.
- **Fail criteria:** The popup shows nothing (blank or white area) or only a spinner until an async operation completes, with no synchronous fallback render. Async data fetching in the main thread blocks initial paint. Do NOT pass if the popup makes synchronous network requests during initialization — these block rendering and cause perceived freezes.
- **Skip (N/A) when:** The extension has no popup (action-less extension, background-only, or devtools-only extension).
- **Cross-reference:** The `popup-no-cls` check verifies the popup does not shift layout during the fast load this check measures.
- **Detail on fail:** Describe the blocking pattern. Example: `"popup.js awaits chrome.storage.get before rendering any HTML — blank screen visible until storage resolves"` or `"React popup renders null in initial state and shows no loading skeleton while fetching from API."`
- **Remediation:** Show something meaningful immediately, then fill in data as it arrives. For a React popup:

  ```tsx
  function Popup() {
    const [data, setData] = useState<CachedData | null>(null);

    useEffect(() => {
      // Render happens immediately above; load data in background
      chrome.storage.local.get(['cachedData'], (result) => {
        setData(result.cachedData ?? DEFAULT_DATA);
      });
    }, []);

    if (!data) {
      return <SkeletonUI />;  // Shows instantly
    }

    return <PopupContent data={data} />;
  }
  ```

  For vanilla JS popups, render placeholder HTML in `popup.html` and update it via `textContent` / `appendChild` after data loads — never start with an empty body.

## External references

- iso-25010 performance-efficiency.time-behaviour

Taxons: performance, user-experience

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