# Manual Verification Checklist

> A standing memo, **not** a milestone. These are the things automated tests can't
> cover — they need a real browser, a real install, or a human eye. Run the relevant
> sections before a release, or after any change to the app shell, PWA config,
> routing, theming, or focus/keyboard behavior. Tests stay green ≠ these pass.

## Offline & installable (PWA)

- [ ] First load online, then go offline (DevTools → Network → Offline) and reload —
      the app boots, fonts render (no system-font fallback), a previously-opened
      snippet still renders.
- [ ] Install as a standalone app (desktop install button / Android "Add to Home
      screen") — it installs, launches in its own window, and shows the astrolabe
      icon (not a generic glyph).
- [ ] Android adaptive icon (maskable) fills the OS shape without clipping the mark.
- [ ] Service-worker update flow: ship a new build, reload — the update-available
      prompt appears and applying it loads the new version (`registerType: 'prompt'`).
- [ ] iPad add-to-home-screen (Safari → Share → Add to Home Screen) shows the astrolabe
      `apple-touch-icon.png`, not a page screenshot or generic glyph. Phones are out of
      scope — iPad is the only touch surface Astrolabe targets.

## Keyboard & accessibility

- [ ] Full keyboard-only run-through: create/select/edit a snippet, open and dismiss
      each modal, drive the pane toggle strip and both resize handles, reach Datasets.
- [ ] Focus is never orphaned or trapped: modals trap focus and return it to the
      opener on close; hiding a pane from the strip keeps focus on the toggle.
- [ ] Visible focus ring on every interactive control, in both themes.
- [ ] Text and UI contrast pass AA in light and dark.

## Routing & view-state

- [ ] Reload restores the view from the URL hash (`#snippet-<id>`, `#datasets/…`).
- [ ] Browser Back/Forward moves through view-state as expected.

## Theming

- [ ] Light⇄dark flip repaints the whole app — chrome, Monaco, and the chart.
- [ ] No placeholder styling or raw hexes leak through on any surface.

## Reduced motion & feedback

- [ ] With `prefers-reduced-motion`, toasts and transitions honor it (no large motion).
- [ ] Toasts stack, auto-dismiss, and fade as specified; the live-preview busy
      indicator appears for slow (>~1s) renders and clears after.

## URL datasets (remote data snapshot)

> Needs a real network, real CORS, and real offline — tests mock the fetch.

- [ ] Add a dataset by URL from a CORS-friendly host (e.g. a GitHub raw `.csv` or a
      vega-datasets URL) — Save shows "Fetching…", then the dataset appears profiled
      (rows, columns, size) with a "Fetched <time>" line and the source address.
- [ ] Go offline (DevTools → Network → Offline) and reload — a snippet that references
      that URL dataset still renders from the local snapshot (no fetch at render time).
- [ ] Add a URL that blocks cross-origin requests (or while offline) — the form shows a
      cause-specific error and a "Paste data inline instead" button that switches to an
      inline paste keeping the name/comment; no broken record is saved.
- [ ] Refresh a URL dataset whose source changed — rows/size and the "Fetched" time
      update; refreshing while offline raises an error toast and leaves the snapshot intact.
- [ ] Build Chart from a fetched URL dataset works (columns are known); from an
      unfetched URL reference the builder has no schema until Refresh.

## Visual sweep of the M6 surfaces

- [ ] Library search / sort / empty states, the storage monitor, About & Donate
      modals, and the busy indicator all look deliberate and behave per spec.

## Driving these checks headlessly

The eye-checks above can be scripted as a headless walk-through (Playwright with the
already-installed Chromium browser cache) for repeatable screenshots, alongside a live
browser.

- **Select by accessible name / role / placeholder, never by CSS class.** CSS-module class
  names are content-hashed and unstable; the component tests select the same way.
- **Reaching the Chart Builder:** open Datasets from the **header command button** (the
  pane-toggle-strip `Datasets` button only exists once the workspace has content) → **New
  Dataset** → paste rows inline → **Build Chart**.
