api

PURPOSE — Vercel serverless API endpoints that back the workbench tooling: in production they replace the local vite dev-server middleware with HTTP routes that read and write to Supabase Storage. Each route is a single-handler Vercel function with a narrow JSON contract; all shared concerns (storage client, bucket path layout, JSON parsing, path sanitization, response shaping) live in the server-only _lib shared library.

OWNS

  • The set of HTTP routes exposed to the browser workbench client (state read/write, state backup listing and restore, sprite folder listing, sprite PNG fetch, ships-v4 manifest listing, ships-v4 asset export with optional archive-on-overwrite).
  • The Supabase Storage client singleton (lazy-initialized, service-role-keyed, server-only) and the canonical bucket path layout (state current + rotating backup slots, sprites prefix and per-image paths, ships-v4 base + sidecar paths, ships-v4 archive directory naming).
  • Storage primitives: bytes get/put, JSON get/put, prefix listing with auto-pagination, object delete, object rename (move), object copy, existence check, and a not-found-error heuristic that papers over supabase-js’s empty-originalError quirk.
  • HTTP primitives: JSON response writer with no-store cache header, error response writer, request body parser that handles both pre-parsed objects and raw JSON strings, and a sprite sub-path safety validator (rejects traversal, leading slash, null bytes).
  • The state backup rotation policy (fixed-size ring buffer of state snapshots, oldest dropped, others shifted, current demoted to slot 1 on each write).
  • The ships-v4 archive-on-overwrite policy (when the base PNG of a previously baked ship is rewritten with the archive flag set, the existing base + clean + points sidecar triple is moved to a timestamped archive directory before the new write lands).

READS FROM

  • Environment variables (Supabase URL and service-role key) for storage client configuration.
  • The Supabase Storage workbench bucket for all object reads.
  • Incoming request bodies, query strings, and methods.

PUSHES TO

  • The Supabase Storage workbench bucket for all object writes, moves, copies, and deletes.
  • HTTP responses with JSON payloads and appropriate cache headers, or raw PNG bytes with image content-type and a short cache window for the sprite-image route.

DOES NOT

  • Run on the client. The _lib modules are explicitly server-only because they use the Supabase service-role key.
  • Mirror or substitute for the vite dev-server middleware in development. The routes are the production replacement; in dev the workbench client talks to vite’s /__dev/* routes instead.
  • Authenticate, authorize, rate-limit, or audit requests. Surface assumes trusted callers and a privately-deployed Vercel project.
  • Validate state-blob schemas or workbench payloads beyond “is it parseable JSON”. Schema enforcement lives in the workbench client.
  • Decode, transcode, or inspect image content. PNG bodies arrive as base64 data URLs, are decoded to bytes, and uploaded as-is with a fixed content-type.
  • Manage the dev-only vite middleware paths or the local filesystem — only the cloud Storage bucket.
  • Implement caching beyond response cache headers. There is no in-process or edge cache layer.

Signals fired / Signals watched — none. The surface is plain HTTP request/response; no engine signals or pub/sub mechanism.

Entry points

  • GET /api/workbench/state — fetch the current workbench state blob (returns null if unset).
  • GET /api/workbench/state?action=backups — list available state backup slots with name, path, last-modified timestamp, and size.
  • GET /api/workbench/state?action=restore&path=... — fetch the contents of a specific state backup slot.
  • POST /api/workbench/state — write a new workbench state blob and rotate the backup ring (oldest dropped, others shifted, current demoted to first backup slot).
  • GET /api/workbench/sprites — list ingested sprite sheets grouped by folder, with sidecar variants filtered out so only browseable base PNGs are returned.
  • GET /api/workbench/sprites/image?path=... — fetch a single ingested sprite PNG by its relative folder/file path, served as image/png with a short cache window.
  • GET /api/workbench/ships-v4 — list baked ships-v4 entries with per-entry flags for whether the points sidecar and the clean-PNG sidecar are present.
  • POST /api/workbench/ships-v4/export — write one baked ships-v4 asset (base PNG, clean PNG, or points JSON) as a base64 data URL; when called on the base PNG with the archive flag set and a prior base already exists, sweeps the existing triple into a timestamped archive directory before writing.

Pattern notes

  • Every handler is a single default-exported async function with the standard Vercel signature, wrapped in a try/catch that funnels uncaught errors into a 500 JSON response. Method gating is explicit at the top of each handler.
  • Shared HTTP and storage helpers are imported as .js paths in source to satisfy Vercel’s ESM-on-TS compilation; the routes do not maintain their own ad-hoc fetch clients or bucket-path strings.
  • Bucket path layout is centralized in a single constants object plus a few path-builder functions, so route handlers never construct paths by string concatenation. State backup slot indices are zero-free (slots are 1-based, count is a single constant).
  • The storage client uses a lazy singleton with loud-throw initialization, so misconfigured env vars surface at first request rather than at module load. A separate “is cloud configured” predicate is exported for callers that need to gracefully skip cloud ops.
  • The not-found heuristic centralizes a supabase-js quirk (missing objects return a wrapped error with an empty originalError rather than a 404 status) so callers can treat absence uniformly as a null/false return.
  • The sprite-image route is the only handler that returns non-JSON. All others either return JSON or a JSON error envelope.
  • The state POST flow is sequential and non-atomic: it drops the oldest backup, then walks the ring downward to avoid overwriting still-needed data, then demotes current, then writes the new current. A crash mid-rotation leaves the ring in a partially-shifted state.
  • The ships-v4 export route distinguishes the base PNG from sidecars by suffix, only fires the archive sweep on the base, and treats the archive call site as the single trigger for the whole triple’s move — sidecar writes do not re-archive and land into the freshly-cleared name.