Skip to content

API: 1d fresh + 7d stale-while-revalidate (auto-invalidate on art changes) - #166

Merged
pablostanley merged 1 commit into
mainfrom
feat/cache-revalidate
Apr 20, 2026
Merged

API: 1d fresh + 7d stale-while-revalidate (auto-invalidate on art changes)#166
pablostanley merged 1 commit into
mainfrom
feat/cache-revalidate

Conversation

@pablostanley

Copy link
Copy Markdown
Owner

Why

API renders were tagged `Cache-Control: immutable` — fine back when `/api/pixabot/{id}` was truly deterministic forever. PR #156 broke that assumption: sprite art can now change on the same ID. Yesterday's purge was the manifestation.

What

Replace `immutable, max-age=31536000` with `max-age=86400, stale-while-revalidate=604800` for all deterministic image + SVG + JSON responses.

Window Behavior
First 1 day Fresh. CDN / browser serve instantly, no revalidation.
Next 7 days Stale content still served (fast), background refetch re-renders with current art.
Past 8 days Cold render.

Translation: after an art change, within ~1 day of traffic on a given URL, the cache has the new render. Manual dashboard purge still works for urgent rollout.

Scope

  • `app/src/lib/api.ts` — exported as `DETERMINISTIC_CACHE`; `imageResponse()` uses it.
  • `/api/pixabot/[id]` SVG branch — was a hardcoded header, now uses the constant.
  • `/api/pixabot/batch` deterministic branch — same.

Random endpoints (`/api/pixabot/random`) keep `no-store` — they're not deterministic.

Test plan

  • `curl -sI https://pixabots.com/api/pixabot/2156 | grep -i cache-control` shows new value
  • Deploy → sub-animations show in /browse / /bot/[id] within ~1 day without a manual purge
  • Hot path is still cached: repeat curl same URL returns from CDN (HTTP 200, no re-render latency)

🤖 Generated with Claude Code

The API used to mark every render `immutable, max-age=31536000` —
fine for a deterministic render pre-sub-animations, wrong now that
sprite art can change on the same ID. After PR #156 shipped new
sprite sheets, `/browse` and `/bot/[id]` kept serving pre-animation
GIFs from the CDN until a manual dashboard purge.

New policy: `public, max-age=86400, stale-while-revalidate=604800`.
- 1 day fresh — CDN + browser serve instantly, no revalidation.
- 7 days stale — stale content still served, background refetch
  triggers a new render. Art updates propagate within a day of deploy.
- 30 days past stale window — cold render.

Manual CDN purge is still available for urgent changes. This just
removes the requirement to do it every time.

- `app/src/lib/api.ts`: exported as `DETERMINISTIC_CACHE`. Applied
  to `imageResponse()` (PNG / GIF / WebP).
- `/api/pixabot/{id}` SVG branch: same header (was hardcoded).
- `/api/pixabot/batch` deterministic response: same.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Apr 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Apr 20, 2026 1:33am

Request Review

@pablostanley
pablostanley merged commit 5814b3e into main Apr 20, 2026
3 checks passed
@pablostanley
pablostanley deleted the feat/cache-revalidate branch April 20, 2026 01:34
pablostanley added a commit that referenced this pull request Apr 20, 2026
PR #165 added the frames metadata endpoint with a hand-rolled
`immutable` Cache-Control header — the exact policy PR #166 just
moved every other deterministic endpoint away from. Make it consistent
so animVersion bumps propagate within ~1 day without a manual purge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pablostanley added a commit that referenced this pull request Apr 20, 2026
PR #165 added the frames metadata endpoint with a hand-rolled
`immutable` Cache-Control header — the exact policy PR #166 just
moved every other deterministic endpoint away from. Make it consistent
so animVersion bumps propagate within ~1 day without a manual purge.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pablostanley added a commit that referenced this pull request Apr 20, 2026
PR #166 swapped the deterministic cache header from 1-year immutable
to 1-day fresh + 7-day stale-while-revalidate. The ?v= versioning
story we shipped in PRs #164#165 assumed the old immutable headers,
so the docs overclaimed how long stale animations linger.

Updates:
- api.mdx intro / embedding blurb / caching section reflect the SWR
  window (≤ 1 day fresh, up to 8 days long-tail).
- Animation-versioning section frames ?v=N as the escape hatch for
  instant propagation rather than "the only way out of an immutable
  trap."
- openapi.json descriptions match.
- Root CLAUDE.md points agents at DETERMINISTIC_CACHE in app/src/lib/api.ts.

No code changes — /frames route was already switched to DETERMINISTIC_CACHE
in PR #168.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pablostanley added a commit that referenced this pull request Apr 20, 2026
PR #166 swapped the deterministic cache header from 1-year immutable
to 1-day fresh + 7-day stale-while-revalidate. The ?v= versioning
story we shipped in PRs #164#165 assumed the old immutable headers,
so the docs overclaimed how long stale animations linger.

Updates:
- api.mdx intro / embedding blurb / caching section reflect the SWR
  window (≤ 1 day fresh, up to 8 days long-tail).
- Animation-versioning section frames ?v=N as the escape hatch for
  instant propagation rather than "the only way out of an immutable
  trap."
- openapi.json descriptions match.
- Root CLAUDE.md points agents at DETERMINISTIC_CACHE in app/src/lib/api.ts.

No code changes — /frames route was already switched to DETERMINISTIC_CACHE
in PR #168.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pablostanley added a commit that referenced this pull request Apr 20, 2026
… cache policy

Rolls the important stuff from this long session into the living docs
so next-agent (or next-me) doesn't have to reread the whole thread.

CLAUDE.md
- **Key conventions**: replace the old 8-frame-bounce line with the
  16-tick super-loop model; add sub-animation `kind` (static/blink/
  sequence) + `resolveFrameIndex` as the one source for frame picks;
  document `TOTAL_COMBOS_LABEL` as the single-source for the count;
  add the keydown `hasModifier` rule (earned from the ⌘R bug).
- **Adding new parts**: split static vs animated workflows. Static
  is flat PNG + stitcher copy-through. Animated uses subdir layouts
  (blink: open/closed pair, sequence: numbered) that the stitcher
  detects automatically; updated the parts.ts entry shape
  (`'name'` vs `{ name, frames, kind }`).
- **npm**: drop the stale "CLI token caveat" note — first-publish
  is done, token allow-list extended.
- **API**: refresh the endpoint list (frames metadata, batch, palette
  + bg params); document the 1d fresh / 7d SWR cache policy and the
  same-origin rate-limit bypass.

ROADMAP.md
- Appends Done entries for PR #158 (⌘R guard), #160 (SW bump),
  #162 (TOTAL_COMBOS dedupe), #166/#168/#169 (cache revalidate),
  and #170 (browse 429s). Each entry includes the "why we needed it"
  so future readers see the failure mode, not just the fix.

Memory (not in this commit; lives outside the repo):
- New `feedback_keyboard_modifier_guard.md` — the ⌘R lesson promoted
  to a rule.
- New `project_animation_infra.md` — the sub-animation mental model.
- Updated `project_npm_published.md` with current versions of core /
  react / cli.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pablostanley added a commit that referenced this pull request Apr 20, 2026
… cache policy (#171)

Rolls the important stuff from this long session into the living docs
so next-agent (or next-me) doesn't have to reread the whole thread.

CLAUDE.md
- **Key conventions**: replace the old 8-frame-bounce line with the
  16-tick super-loop model; add sub-animation `kind` (static/blink/
  sequence) + `resolveFrameIndex` as the one source for frame picks;
  document `TOTAL_COMBOS_LABEL` as the single-source for the count;
  add the keydown `hasModifier` rule (earned from the ⌘R bug).
- **Adding new parts**: split static vs animated workflows. Static
  is flat PNG + stitcher copy-through. Animated uses subdir layouts
  (blink: open/closed pair, sequence: numbered) that the stitcher
  detects automatically; updated the parts.ts entry shape
  (`'name'` vs `{ name, frames, kind }`).
- **npm**: drop the stale "CLI token caveat" note — first-publish
  is done, token allow-list extended.
- **API**: refresh the endpoint list (frames metadata, batch, palette
  + bg params); document the 1d fresh / 7d SWR cache policy and the
  same-origin rate-limit bypass.

ROADMAP.md
- Appends Done entries for PR #158 (⌘R guard), #160 (SW bump),
  #162 (TOTAL_COMBOS dedupe), #166/#168/#169 (cache revalidate),
  and #170 (browse 429s). Each entry includes the "why we needed it"
  so future readers see the failure mode, not just the fix.

Memory (not in this commit; lives outside the repo):
- New `feedback_keyboard_modifier_guard.md` — the ⌘R lesson promoted
  to a rule.
- New `project_animation_infra.md` — the sub-animation mental model.
- Updated `project_npm_published.md` with current versions of core /
  react / cli.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant