---
name: deploy
description: Deploy Preflight to Cloudflare Pages and verify it actually landed. Use when the user asks to ship, deploy, publish, or check whether a change is live, or when they report the live site looking stale.
---

# Deploy and verify

Preflight is static — no build, no framework, no dependencies. Everything served
lives in `public/`; everything outside it (README, `.claude/`) is repo-only and
must never be uploaded.

**Production:** https://preflight.getonnet.cloud
**Pages project:** `preflight` (Cloudflare account: Getonnet AS)
**Fallback URL:** https://preflight-eki.pages.dev

## Deploying

The project is **direct-upload**, not Git-connected. Pushing to `main` does not
deploy — you must upload.

First regenerate the downloadable skill copies the site serves. They are
redacted duplicates of these files, so editing a `SKILL.md` without re-running
this leaves the public copies stale:

```bash
./bin/sync-skills.sh
```

Then upload:

```bash
CLOUDFLARE_API_TOKEN=$CLOUDFLARE_API_TOKEN \
CLOUDFLARE_ACCOUNT_ID=<your-cloudflare-account-id> \
npx wrangler pages deploy public --project-name=preflight --branch=main
```

Push to git as well, so the repo matches what is live:

```bash
git push origin main
```

If the project is ever recreated as Git-connected, the build settings are:

```
Build command:           (none)
Build output directory:  public
Root directory:          /
```

## Verifying — do not trust HTTP 200

A 200 proves something answered, not that *your* change is live. Cloudflare
serves the previous deployment while a new one builds.

Check a real marker from the change you just made:

```bash
curl -s https://preflight.getonnet.cloud/assets/templates.js | grep -c "your-new-template-id"
```

Then confirm the asset parses. A syntax error in `templates.js` breaks the app
silently — `window.PREFLIGHT_TEMPLATES` becomes undefined and the app falls back
to an empty list rather than erroring visibly:

```bash
curl -s https://preflight.getonnet.cloud/assets/templates.js -o /tmp/t.js && node --check /tmp/t.js && echo OK
```

Confirm repo-only files are **not** public. This has regressed once already —
`.assetsignore` is a Workers Assets feature and does nothing for Pages, which is
why the site moved into `public/`:

```bash
for i in 1 2 3; do
  for p in /README.md /.claude/skills/deploy/SKILL.md /.gitignore; do
    printf '%s=%s ' "$p" "$(curl -s -o /dev/null -w '%{http_code}' "https://preflight.getonnet.cloud$p")"
  done; echo
done
# every one must be 404, on every run
```

Run it **three times**, and compare against the origin:

```bash
curl -s -o /dev/null -w '%{http_code}\n' https://preflight-eki.pages.dev/README.md
```

A single pass is not enough. When those files were public they were cached at
the edge with `s-maxage=604800`, and removing them from the deployment does not
evict the copies — different edge nodes kept answering 200 for the same URL that
another node 404'd seconds earlier. Origin returning 404 while the custom domain
intermittently returns 200 means **stale cache, not a bad deploy**. Confirm it
with a cache-busting query string, which forces a fresh origin fetch:

```bash
curl -s -o /dev/null -w '%{http_code}\n' "https://preflight.getonnet.cloud/README.md?cb=$RANDOM"
```

If that 404s but the bare URL 200s, purge. `CLOUDFLARE_API_TOKEN` does **not** carry
Cache Purge or cache-rules permission, so this is a dashboard action: Cloudflare
→ `getonnet.cloud` → Caching → Configuration → Purge Custom URLs.

## Checks worth running after any change

1. App loads and renders a list — not a blank page.
2. Add an item, reload, confirm it survived. That is the whole product working.
3. `⋯ → Load a template…` lists every template with correct item counts.
4. At 375px wide: no horizontal scroll, and the `⋯` button is fully on-screen.
   It has fallen off the right edge once — a flex item's default
   `min-width: auto` let the `<select>` refuse to shrink.
5. Console is clean.

## Gotchas

- **`localStorage` key is `preflight.v1`.** Changing the state shape without
  bumping the key strands existing users on data the new code can't read. Either
  migrate on load or bump to `.v2`.
- **`templates.js` loads before `app.js`** and the order is load-bearing. Keep
  the two `<script>` tags in that order in `public/index.html`.
- **No cache busting.** Assets are referenced by plain path, so a CSS or JS
  change can be served stale from the browser cache. Verify with `curl`, not by
  looking at your own browser.
