Enhancement: High-resolution image masters + WebP rendition delivery - #488
Enhancement: High-resolution image masters + WebP rendition delivery#488baltinerdist wants to merge 3 commits into
Conversation
Design for storing high-quality image masters and deriving size-optimized WebP renditions for player photos and player/park/ kingdom heraldry, replacing the current upload-time downscale that destroys quality. Two-phase rollout; static serving preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ShyA7SCTysgREoQPBoxs1Q
Replace the upload-time downscale that destroyed image quality with a store-high-res-master / derive-optimized-renditions pipeline for player photos and player/park/kingdom (and shared unit/event) heraldry. Storage: uploads are kept at up to 3000px (downscale-only, never upscale), PNG when transparent else JPEG q92, with a 6MB post-encode reject ceiling that fires before touching disk (a too-large re-upload can't destroy the existing image). Client-side clampImageIfHuge replaces the ~340KB squeeze, passing original bytes through untouched unless the longest edge >3000px. Delivery: each upload also generates thumb (256px) + display (1024px) WebP renditions served as static files; a size-aware resolve_media_ext serves the right one per surface (nav/list/map = thumb, hero = display, lightbox = master) and falls back to the master when a rendition is missing, so un-backfilled images never break. heraldry-rendition-backfill.php derives renditions for existing images idempotently. Infra: nginx client_max_body_size 8M + webp cache header; PHP upload_max_filesize/post_max_size raised to 8M in all Dockerfiles. Also: remove paths now sweep all master+rendition variants (shared Common::unlink_image_set); over-ceiling rejections propagate to the user instead of reporting false success; lightbox enlarges the master not the rendition; native alert() in heraldry flows replaced with the in-page feedback pattern; stale upload-size UI hints corrected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ShyA7SCTysgREoQPBoxs1Q
# Conflicts: # orkui/controller/controller.Admin.php # orkui/controller/controller.EventAjax.php # orkui/controller/controller.Kingdom.php # orkui/controller/controller.ParkAjax.php # orkui/model/model.Kingdom.php # system/lib/ork3/class.Event.php # system/lib/ork3/class.Kingdom.php # system/lib/ork3/class.Park.php
Branch brought current with
|
| File | Resolution |
|---|---|
orkui/model/model.Kingdom.php |
Upstream's 3 new method blocks + the branch's 'Size' => 'thumb'/'display' args on both GetHeraldryUrl calls. |
orkui/controller/controller.EventAjax.php |
Upstream (heraldry remove now delegates to EventPlanning->remove_heraldry). Branch's rendition cleanup ported into Heraldry::RemoveEventHeraldry() as Common::unlink_image_set($base). Without this the merge would have silently dropped rendition-file cleanup on event heraldry removal. |
orkui/controller/controller.ParkAjax.php |
Upstream's removal of inline ghettocache busts (moved into Report::bustKingdomParkAverageCaches()); kept the branch's $r['Warning'] → warning JSON propagation. |
orkui/controller/controller.Kingdom.php |
Upstream (both blocks now call Model_KingdomProfile). Branch's rendition logic ported into class.KingdomProfile.php — event heraldry and player avatar/heraldry now request Common::resolve_media_ext(..., 'thumb') with the ?v= mtime cache-buster instead of resolve_image_ext(). |
system/lib/ork3/class.Event.php |
Upstream wholesale; re-applied 'Size' => 'display' on both GetHeraldryUrl calls in GetEventDetails. |
system/lib/ork3/class.Kingdom.php |
Upstream (QualTest configs, 'Parks' => array() guard, memcache flush); re-applied the branch's $heraldry_result capture + both "Heraldry was not saved" Warning blocks (create + edit). |
system/lib/ork3/class.Park.php |
Upstream's Report::bustKingdomParkAverageCaches() refactor (that bust code predates the branch); re-applied the branch's $heraldry_result capture + Warning block in CreatePark. |
orkui/controller/controller.Admin.php |
35 hunks, all upstream's Model_AdminDashboard extraction vs the branch's old inline copies. Upstream wholesale, then the branch's 7 real edits re-applied: 2× 465000 → IMAGE_UPLOAD_MAX_BYTES, 2× $r = capture from SetHeraldry/SetImage, $r['Value'] on create-player, $r['Warning'] on edit-kingdom and create-park. |
Worth noting this moved the image-pipeline logic out of controllers and into the domain layer, in line with the ongoing migration.
Verification
- No conflict markers;
class.Authorization.phpnot staged, identical to upstream, no bypass. php -lclean on all 302 changed.php/.tplacross both merge parents.- Line-level cross-check (whitespace/brace-normalized): every branch-added line from the 48 branch-touched files is present in the merged tree, except the 5 EventAjax rendition-unlink lines deliberately relocated to
class.Heraldry.php. Reverse direction: every upstream-added line is present except the 5resolve_image_extlines inclass.KingdomProfile.phpintentionally replaced with rendition-aware versions. - Independently re-confirmed post-push:
Common::unlink_image_setpresent at 5 sites inclass.Heraldry.php;class.KingdomProfile.phphas 3resolve_media_extcalls and zero leftoverresolve_image_ext. Master/rendition distinction intact. - Merge recomputed from its parents with
rereredisabled and diffed against what was pushed: exactly the 8 conflicted files plus the 2 deliberate ports, nothing unexplained. (Two other branches in this batch were hit by a strayrerereline from an unrelated recorded resolution — this one is clean.)
For a human
Heraldry::RemoveEventHeraldry— upstream's two@unlink($base.'.jpg'/'.png')calls were swapped forCommon::unlink_image_set($base). That's a superset (also removes_thumb.webp/.jpg,_display.webp/.jpg) but drops the@error suppression the upstream author used. The sibling methods this branch wrote useunlink_image_setunsuppressed, so it's internally consistent — but it is a behavior change to an upstream-authored method.- 🚨 Post-deploy memcache flush recommended. The kingdom roster and events-tab payloads are cached in GhettoCache; existing entries will keep serving the old master-image URLs until they expire.
- ParkAjax cache busting — upstream's
Park::CreateParknow does its own. The branch's warning propagation was kept and its duplicate bust lines dropped; if the PR description claims the bust as this branch's work, note it was pre-existing merge-base code that upstream refactored.
PR now shows MERGEABLE / CLEAN.
🤖 Generated with Claude Code
Summary
Replaces the upload-time downscale that destroyed image quality with a store-high-res-master / derive-optimized-renditions pipeline for player photos and player/park/kingdom (and shared unit/event) heraldry. Storage is cheap and one-time; delivery bandwidth is the recurring cost — so we stop conflating them.
Design spec is included in the branch:
docs/superpowers/specs/2026-07-07-image-pipeline-masters-renditions-design.md.What changed
Storage — high-res masters. Uploads are kept at up to 3000px (downscale-only, never upscale), PNG when transparent else JPEG q92, with a 6 MB post-encode reject ceiling that fires before touching disk — a too-large re-upload can't destroy the existing image. Client-side
clampImageIfHugereplaces the ~340 KB squeeze, passing original bytes through untouched unless the longest edge exceeds 3000px.Delivery — rendition set. Each upload also generates
thumb(256px) +display(1024px) WebP renditions served as static files. A size-awareresolve_media_extserves the right one per surface (nav/list/map → thumb, hero → display, lightbox → master) and falls back to the master when a rendition is missing, so un-backfilled images never break.heraldry-rendition-backfill.phpderives renditions for existing images idempotently.Transport / infra. nginx
client_max_body_size 8M+webpadded to the static cache regex; PHPupload_max_filesize/post_max_sizeraised to 8M in all three Dockerfiles.Correctness / polish.
Common::unlink_image_set) — previously "removed" images kept being served from leftover renditions.alert()in the heraldry flows replaced with the in-page feedback pattern; stale "340 KB / Max 1 MB" upload hints corrected (banner hints intentionally left — banners are a separate pipeline).Verification
resolve_media_extresolves renditions and falls back to master).php -lclean on all changed PHP/templates;node --checkclean on all changed JS. WebP confirmed available in the PHP 8.1 containers.Known follow-ups
Size => 'master'for print-quality scrolls. Being handled on that branch.heraldry-rendition-backfill.phpat deploy time to generate renditions for existing images.🤖 Generated with Claude Code