---
name: keep-fast
description: >-
  Keep a web page/route within its performance budget. Use after editing a
  page, route, layout, or component that affects what renders, when the user
  asks to "make it fast", "check performance", "keep it under budget", or
  mentions Core Web Vitals / LCP / performance score. Drives an audit → fix →
  re-audit loop against SpeedVault via the speedvault MCP server.
paths:
  - "**/pages/**/*.{tsx,jsx,ts,js,vue,svelte}"
  - "**/app/**/*.{tsx,jsx,ts,js}"
  - "**/routes/**/*.{tsx,jsx,ts,js,vue,svelte}"
  - "**/src/**/*.{tsx,jsx,vue,svelte}"
  - "**/*.{html,astro}"
allowed-tools:
  - mcp__speedvault__run_audit
  - mcp__speedvault__get_audit
  - mcp__speedvault__list_audits
  - mcp__speedvault__get_fix_suggestions
  - mcp__speedvault__crawl_site
  - mcp__speedvault__get_crawl
  - mcp__speedvault__get_rum
  - mcp__speedvault__get_rum_embed
  - mcp__speedvault__list_monitors
  - mcp__speedvault__create_monitor
  - mcp__speedvault__account_status
  - mcp__speedvault__shopify_write_seo
  - mcp__speedvault__shopify_write_redirect
  - mcp__speedvault__shopify_write_schema
  - mcp__speedvault__shopify_run_crew
  - mcp__speedvault__shopify_rollback
  - mcp__speedvault__shopify_guardian
  - mcp__speedvault__shopify_get_posture
  - mcp__speedvault__shopify_get_health
  - mcp__speedvault__shopify_get_ci_runs
  - mcp__speedvault__shopify_get_aeo_history
  - Read
  - Edit
  - Grep
  - Glob
---

# keep-fast

A performance-budget loop for web routes. Given a URL (and optionally a target
score/LCP), audit it, apply the highest-impact fixes, and re-audit — up to 3
iterations — then report the before/after numbers.

This is the `optimize-this-page` workflow the SpeedVault MCP ships as a prompt,
expressed as a Claude Code skill so it triggers automatically when you edit a
page/route/component.

## When to invoke

- The user just changed a page/route/component and wants to confirm it's still fast.
- The user says "keep it under budget", "make this page fast", "check LCP", etc.
- A CI/agent loop needs a pass/fail gate before merging.

## Inputs you need

1. **A publicly reachable URL** for the running route. SpeedVault's audit/crawl path
   **blocks `localhost` and private/internal IPs** (SSRF guard on the API). Use a
   deployed or preview URL — e.g. a Vercel/preview deploy or a production deploy
   from `~/lab/scripts/deploy.sh`, or a tunnel (ngrok). If you only have a local dev
   server, deploy/tunnel it first and audit the public URL.
2. **A target** (optional). A good default budget is LCP ≤ 2500ms and performance score ≥ 80.
3. **Device** (optional): `desktop` (default), `mobile`, or `tablet`.

## The loop (max 3 iterations)

```
1. BASELINE   → run_audit({ url, device? })   (waits for completion by default)
               Record the baseline: performance score, LCP, page size, top opportunities.

2. PASS?      → If the score/LCP already meet the target → go to REPORT. Done.

3. FIX        → get_fix_suggestions({ auditId })  using the auditId from step 1.
               Read the prioritized fixes (each has how_to_fix + a code snippet).
               Apply the SINGLE highest-impact fix with Edit (smallest change that
               addresses the top opportunity). Map ambiguous recommendations to a
               concrete change using reference.md.

4. RE-AUDIT   → run_audit({ url, device? }) again.
               If it meets the target → REPORT. If still short and iterations < 3 →
               back to step 3 with the NEXT highest-impact fix.

5. REPORT     → after pass, or after 3 iterations, show the before/after table.
```

### Rules for the loop

- **One fix per iteration.** Apply the single highest-impact change, then re-audit.
  Never batch speculative fixes — you won't know which one moved the needle.
- **Hard cap at 3 iterations.** If still short after 3, stop and report the remaining
  gap plus the fixes you did not get to. Do not loop forever.
- **Only edit files matched by this skill's `paths`** (page/route/component/markup files)
  plus their direct imports. Do not touch the SpeedVault app itself or unrelated config.
- **Verify each edit is safe**: read the file first, make a minimal targeted Edit, and
  prefer changes that cannot break behavior (lazy-loading, `loading="lazy"`, `defer`,
  preconnect, image format/size) over risky refactors.
- **If a fix has no safe code-level change** (e.g. "Reduce server response time" needs
  infra work), note it in the report and move to the next fix.
- **Each `run_audit` costs 1 credit.** Reuse `get_audit({ auditId })` to re-read an
  existing audit; only `run_audit` again after you've actually applied a change.
- **`get_fix_suggestions` costs 5 credits** when it generates AI fixes (uncached). Cached
  results are free. Crawl fixes are also free.
- **Spend caps:** If the user has set daily or monthly spend caps (in Settings > Usage &
  Budgets), any metered call may return a **402 "Spend cap reached"** error instead of
  "Insufficient API credits". This means the cap — not the balance — is the blocker.
  Tell the user to raise their cap or wait for the next period; do not retry in a loop.

## Picking the fix

`get_fix_suggestions` already ranks fixes and gives code snippets — apply those first.
When you need to translate a raw opportunity title into a concrete change, read
`reference.md` (next to this file). In short, prefer in this order when impacts tie:

1. Render-blocking resources (defer/async/inline-critical) — usually biggest LCP win.
2. LCP image: preload + `fetchpriority="high"` + remove lazy.
3. Image weight (next-gen formats, responsive `srcset`, `width`/`height`, `loading="lazy"`).
4. Unused JavaScript / large bundles (code-split, dynamic import, tree-shake).
5. Text compression / caching headers (often a build/server config one-liner).
6. Layout shift (CLS): reserve space with explicit dimensions / aspect-ratio.

## Reporting (always do this at the end)

Produce a compact before/after summary:

```
keep-fast: <url> (<device>)

           BEFORE    AFTER    TARGET    Δ
LCP        3200ms    2100ms   ≤2500ms   -1100ms ✅
Perf       64        86       ≥80       +22     ✅
Page size  2244KB    1180KB   —         -1064KB

Verdict: PASS after 2 iterations.
Fixes applied:
  1. Reduce unused JavaScript → dynamic import() for ChartWidget (src/pages/Dashboard.tsx)
  2. Properly size images → added srcset + width/height (src/components/Hero.tsx)
Not addressed (no safe code fix): "Reduce server response time (TTFB)" — needs server/infra.
```

If it never passed: state the final numbers, the remaining gap, and the next fix to
try, then stop.

## Whole-site SEO side-check (optional)

If the change touched many pages, `<head>` metadata, canonical tags, or routing, run a
site crawl instead of a single audit:

1. `crawl_site({ url })` → returns a `crawlId`.
2. `get_crawl({ crawlId, wait: true })` → grouped, per-template SEO/structure issues.
3. `get_fix_suggestions({ crawlId, issue_type })` → ready-to-apply snippets (e.g. WebP
   `<picture>`, `<meta name="description">`, `alt=""`, unique `<title>`) for one issue group.

## RUM (optional)

- `get_rum({ siteKey })` reads real-user field Core Web Vitals (p75 LCP/INP/CLS/FCP/TTFB).
- `get_rum_embed({ siteKey })` returns the beacon `<script>` to paste into `<head>` when
  you're building an app that should collect real-user vitals.

## Shopify write-back (optional — needs a write-scoped key)

For connected Shopify stores, SpeedVault can WRITE fixes (not just audit). These are
the only mutating tools and they cost more credits than reads. Every write goes
through the SpeedVault integrity rail server-side: it is journalled, autonomy-gated,
honours the store's kill-switch, and is reversible.

- `shopify_write_seo({ shop, productId, title, desc, dryRun? })` — set a product's SEO
  meta. Costs 5 credits.
- `shopify_write_redirect({ shop, fromPath, toPath, dryRun? })` — create a 404→live-page
  redirect (open-redirect/chain/loop rejected server-side). Costs 3 credits.
- `shopify_write_schema({ shop, productId, dryRun? })` — write clean JSON-LD. Costs 5 credits.
- `shopify_run_crew({ shop, crew, dryRun? })` — batch fix crew (`alt-text` | `schema` |
  `gtin`). GTIN is never fabricated — only written from a trustworthy source. Costs 10 credits.
- `shopify_guardian({ shop })` — Store-CI guardian pass. Costs 8 credits.
- `shopify_rollback({ shop, changeId })` — undo a prior write by its changeId. Free.
- `shopify_get_posture` / `shopify_get_health` / `shopify_get_ci_runs` /
  `shopify_get_aeo_history` — free shop-scoped reads.

Rules for writes:

- **Writes cost more than reads** and require a **write-scoped API key** — a read-only key
  returns 403. Tell the user to create a write-scoped key in Settings → API Keys.
- **Always preview first with `dryRun:true`.** It journals a suggestion and returns the
  proposed before/after WITHOUT writing. Re-run with `dryRun:false` to apply.
- **Every write returns a `changeId`.** Keep it — `shopify_rollback({ shop, changeId })`
  undoes the change.
- **`outcome:"blocked"`** means the store's autonomy kill-switch is on and nothing was
  written. **`outcome:"suggested"`** (on a non-dryRun apply) means the shop's per-crew
  autonomy is suggest-only — it recorded a proposal, not a write.
- Only the shop's owning account may write to it; an unowned `shop` returns 403.

## Guardrails

- Never spend more than 3 `run_audit` cycles (audits are slow and cost credits).
- Never claim a budget passed without a fresh `run_audit` that returned the target scores.
- Never edit files outside the changed route and its imports.
- Prefer `get_audit` over a new `run_audit` when you only need to re-read recent numbers.
- If a tool reports a missing API key, tell the user to set `SPEEDVAULT_API_KEY`.
- On a **402 "Spend cap reached"** response, stop and tell the user — do not retry
  repeatedly. The cap is an intentional safety limit set by the user.
