ADR-079 — Asset-origin spine + source/derived separation + WebP delivery (closes #373 root cause; delivers mobile streaming)
Status · Accepted Date · 2026-07-07 Gates · #373 (GH Pages deploy flakiness) · mobile epic #149 S3 (#191) · v0.7.3 / v0.8
Gating sentence: heavy-media asset URLs resolve through a single assetUrl(path, kind) spine whose origin is configurable (local by default; chipi.github.io under the Capacitor __MOBILE__ build; an optional prod CDN later), build-time source (the 10,360-file i18n overlay tree, image masters) lives outside the served static/ tree so only derived outputs ship, and delivery images are WebP re-encoded locally from masters and committed — CI validates and deploys but never encodes.
Context
Two problems share one root cause.
#373 — GH Pages deploys are a coin-flip. The built site is 2.1 GB across 30,812 files, over GitHub Pages' 1 GB and file-count soft limits; the publish step times out ~40% of the time. Measured 2026-07-07 on
main: 10,360 of 17,161static/files (60%) are the raw per-entity i18n overlay tree (static/data/i18n/<locale>/…) — which is never fetched at runtime (the app loads the 14 collapsed{locale}.jsonbundles vialoadI18nBundle; the raw tree is build-time source thatbuild-i18n-bundles.mjsconsumes). It only inflates the deploy because it is misfiled insidestatic/, whichadapter-staticcopies wholesale. Separately,static/imagesis 1.6 GB of JPEG (5,308 files, 0 WebP) — the byte bulk.Mobile stream-heavy (ADR-078) needs the same heavy buckets (images, audio, non-default-locale data) served from
chipi.github.iounder the Capacitor build. A resolver audit found these URLs are built at ~9 scattered chokepoints with no central helper, and with a real inconsistency in audio (audio-registrystores${base}${path_mp3}whilecolophonstores the raw path and prepends${base}at render — a latent double-prepend bug).
Both trace to: too much media + too many files in one served build, scattered/inconsistent asset-URL construction, and build-time source misfiled as servable assets. Marko also flagged a future need for an optional CDN when Orrery moves to VPS prod — so the origin must be a configurable seam, not a mobile-only hack.
Decision
D1 — assetUrl() origin spine
Introduce one helper (src/lib/asset-url.ts) — assetUrl(path, kind) — through which every heavy-bucket URL (images, audio, per-locale i18n JSON) resolves. Its origin is configurable:
- default: the SvelteKit
base(browser dev / GH Pages / VPS prod) — behaviour unchanged. __MOBILE__build:https://chipi.github.io/orreryfor pruned buckets (this is exactly the mobile S3 / #191 streaming switch — this ADR delivers #191).- future: an optional prod CDN origin via env, the same seam.
The ~9 chokepoints (pickHero; the 4 data.ts gallery builders; spacecraft-diagrams; pickVariant — which the 239 MB hotspots inherit; audio-registry; loadI18nBundle) route through it. The audio-base inconsistency is canonicalized here (one place applies base/origin; colophon aligns).
D2 — Source / derived separation
Build-time source must not live in the served tree.
- i18n: move the raw overlay tree
static/data/i18n/<locale>/→ a non-servedi18n-src/;build-i18n-bundles.mjsreads from there and writes the 14 collapsed{locale}.jsonbundles intostatic/data/i18n/. Cuts ~60% ofstatic/file count from every build — the direct #373 file-count fix. - images: introduce a non-served
masters/store for originals; the build/pipeline re-encodes to derivatives that ship instatic/images/.
Principle: same as .ts → .js — keep source, ship the compiled derivative.
D3 — WebP-only delivery
Re-encode image masters → WebP (universal support ~97%+, ~30% smaller than JPEG, works with existing bare <img src> — no <picture> markup churn). No JPEG fallback and no AVIF for v1: the ~3% ancient-device gap is accepted (decided 2026-07-07). Because masters are retained (D2), JPEG fallback or AVIF can be re-encoded at any time with zero lock-in — deferred, not foreclosed.
D4 — Local encode, committed derivatives; CI never encodes
Image re-encode runs locally and deliberately (extending the existing curated image pipeline — fetch-assets.ts et al.), and the WebP derivatives are committed (GH Pages serves from git). CI remains a validator + deployer, never an encoder. Masters stay out of main git (git-LFS preferred; external bucket an option — settled at Slice C). Rationale: matches the existing local-curated + per-image-review model, keeps deploys fast/deterministic, avoids re-encoding 5,300 images on every push, and avoids putting masters on the CI bandwidth path.
Slices (tracked under #373)
- A — i18n source relocation (D2 i18n). Cheapest, highest #373 impact. Move tree, repoint bundler, fix raw-path tests.
- B —
assetUrl()spine (D1). The refactor; canonicalizes audio base. Closes mobile #191. - C — WebP masters/derivatives (D2 images + D3 + D4). Largest: masters store + git-LFS decision + local encode pipeline over ~5,300 images.
Order A → B → C. A and B are tractable immediately; C is a larger, separable effort.
Consequences
Positive
- #373 root cause fixed on two axes: file count (A) and bytes (C); deploys stop being a coin-flip.
- Mobile streaming (#191) falls out of B for free — one spine serves mobile-now and optional-CDN-prod-later.
- Audio-
basedouble-prepend bug eliminated. - Git image weight drops (WebP replaces JPEG; masters leave main history).
Negative / risks
- Broad refactor surface (B touches ~9 resolvers): a missed chokepoint = silent 404 in the mobile/CDN build. Mitigation: a mobile-build 404 sweep over every route before shipping.
- ~3% ancient devices lose images under WebP-only (D3, accepted).
- Masters storage adds a git-LFS/external dependency and process discipline (re-encode before commit).
- Test churn: tests that read
static/data/i18n/<locale>/…raw paths must repoint toi18n-src/(A).
Rollback: A is a directory move + a bundler path change (revert = move back). B is env-gated and additive (the helper defaults to today's base; browser build unchanged). C is regenerable from masters and reversible to JPEG. No irreversible step.
References
- #373 (deploy flakiness) · #191 (mobile gallery/audio streaming) · #376 (future KTX2 textures)
- ADR-078 (mobile stream-heavy bundle — origin consumer of D1)
- ADR-057 (locale cookie) ·
scripts/build-i18n-bundles.mjs·src/lib/image-hero.ts·src/lib/image-vision.ts·src/lib/data.ts
Orrery · ADR-079 · Asset-origin spine + source/derived separation + WebP · July 2026