Skill example images
New skills require one to five images, uploaded during manual creation or SKILL.md import. Owners can add, remove, and reorder images from the detail page. Images are public; the first image is the catalog cover. Image descriptions and before/after roles are no longer used. Existing skills and uploaded files are preserved, including older skills without images.
The detail viewer displays one uncropped image with previous/next arrows, a counter, keyboard navigation, touch swipes, and enlargement. Only the first image loads initially; selecting another image loads it on demand. Visited images stay mounted for the life of the viewer, and HTTP browser caching reuses responses for one hour. No thumbnails or speculative prefetching download unselected images.
Storage setup
The production template already binds mazeseek-files-prod as SKILL_IMAGES.
Create that Standard R2 bucket before deploying, or change the bucket name to your own.
For another environment, bind its bucket as SKILL_IMAGES in the intended
environment in wrangler.jsonc:
"r2_buckets": [
{ "binding": "SKILL_IMAGES", "bucket_name": "mazeseek-files-prod" }
]Named environments do not inherit top-level bindings. The top-level local
binding uses simulated R2; staging and production need their own binding.
The bucket can remain private. The application serves current images through
GET /api/skill-images/<imageId>, with a one-hour public browser cache.
No S3 credentials or bucket CORS configuration are needed for this flow.
Omitting SKILL_IMAGES disables creation and image editing with an availability message; existing text skills remain readable.
The separate BUCKET binding enables avatar uploads.
Apply the D1 migration before deploying the updated application:
pnpm db:migrate:local
# For production, after reviewing the migration:
pnpm db:migrate:prodImage limits
- Originals: JPEG, PNG or WebP, up to 5,000,000 bytes and 24 megapixels.
- Browser processing: preserve aspect ratio, never upscale, maximum 1,600 px longest edge, reduce WebP quality and then dimensions as needed.
- Stored images: static WebP, maximum 200,000 bytes per image.
- Original files are never uploaded or stored.
- Preview the compressed result before saving. No per-image description is required.
- Save one to five images in order. Reorder or remove individual images, retaining at least one.
The server bounds the multipart request stream, checks authentication and skill ownership, checks revision conflicts, and validates actual WebP container headers, dimensions, and byte size rather than trusting the submitted MIME type. It does not perform a full server-side image decode.
Data and lifecycle
skill_image stores metadata, with one row per (skillId, position); new files use
skills/<skillId>/examples/<imageId>.webp keys. Existing object keys and image IDs are preserved. The skill's
image_revision provides optimistic concurrency control. R2 writes happen before
a conditional, transactional D1 batch updates the ordered gallery. New skills and their image metadata are published in one transactional batch after upload validation. Existing examples remain
active if a replacement fails. Failed or ambiguous writes are collected later.
Replaced and removed objects are deleted after a successful database commit. The existing daily maintenance cron scans one page of up to 500 R2 objects, persists its pagination cursor in KV, and deletes unreferenced objects older than 24 hours. This also handles account/skill deletion: D1 cascades remove metadata, and the collector removes the corresponding objects on a later scan. With more than 500 objects, a full scan takes multiple cron runs. Already cached public images can remain visible for up to an hour after removal.