imazen / imazen/imageflow-server
QA: Server-Timing delivery diagnostics + sample overlay to flag non-optimal images
- Dominant language
- C#
- Stars
- 316
- Forks
- 37
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
Add a QA capability for spotting images that **aren't delivered in their final optimal form** (browser-upscaled, oversized for their render box, sub-optimal format, passthrough/unoptimized, etc.).
Two parts:
1. **Server side** — emit a small `Server-Timing` diagnostics block on every imageflow-processed response so the client can read what imageflow did.
2. **Client side** — a drop-in QA overlay (sample JS below) that reads those headers, combines them with what the browser actually rendered, and draws a colored outline + tooltip on every non-optimal image.
The client script is useful **today** for the purely client-side checks (resolution efficiency) even before the server change lands; the `Server-Timing` headers make it richer (format/quality/source-size/cache verdict).
## Part 1 — proposed `Server-Timing` schema
`Server-Timing` is the right channel because it's the one response header purpose-built to be surfaced to JS via the Resource Timing API, and it survives CDNs (Akamai/Fastly/Cloudflare) that strip arbitrary headers or need `Access-Control-Expose-Headers` for custom `X-*` ones. Each entry reaches JS as `{ name, duration, description }`.
Proposed entries (all optional, prefix `if-`):
| name | field | meaning |
|---|---|---|
| `if` | `desc` | engine/version, e.g. `"3.0"` |
| `if-cache` | `desc` | `hit` \| `miss` \| `revalidated` |
| `if-src` | `desc` | source dimensions `"WxH"` |
| `if-out` | `desc` | delivered dimensions `"WxH"` |
| `if-fmt` | `desc` | output format, e.g. `avif` / `webp` / `jpeg` |
| `if-q` | `dur` | encode quality (numeric) |
| `if-opt` | `desc` | verdict: `optimal`, or a `+`-joined list of issues |
**Optimal example:**
```
Server-Timing: if;desc="3.0", if-cache;desc="miss", if-src;desc="4032x3024", if-out;desc="1600x1200", if-fmt;desc="avif", if-q;dur=64, if-opt;desc="optimal"
```
**Non-optimal example** (source smaller than requested + a better format was available for this client):
```
Server-Timing: if-src;desc="800x600", if-out;desc="1600x1200", if-fmt;desc="jpeg", if-q;dur=80, if-opt;desc="upscaled+format-suboptimal"
```
Suggested `if-opt` tokens: `optimal`, `upscaled` (output > source), `format-suboptimal` (client `Accept` allowed a better format than was served), `passthrough` (not re-encoded), `recompress-only`. The raw `if-src` / `if-out` / `if-fmt` facts are included so the QA tool can derive its own verdict if `if-opt` isn't sent.
**Constraints / gotchas for the C# emitter:**
- `desc` values must not contain commas (commas separate `Server-Timing` entries) — that's why `if-opt` uses `+` as the joiner.
- **Cross-origin requirement:** when the imageflow host is a different origin than the page (the common CDN case), the page can only read `serverTiming` if the response also carries **`Timing-Allow-Origin`** (e.g. `Timing-Allow-Origin: *` or the page origin). Without it the array comes back empty and the script silently degrades to client-only checks. The CDN must pass both `Server-Timing` and `Timing-Allow-Origin` through.
## Part 2 — sample QA overlay (drop-in / bookmarklet)
Paste into DevTools console (or wrap as a bookmarklet). Re-run to toggle off. Works client-only without the server change; uses the headers above when present.
```js
(() => {
// imageflow-server QA overlay — flags images not delivered in optimal form. Re-run to toggle off.
const FLAG = '__ifQaOverlay';
if (window[FLAG]) {
document.querySelectorAll('[data-if-qa]').forEach(el => {
el.style.outline = ''; el.style.outlineOffset = '';
el.removeAttribute('data-if-qa'); el.removeAttribute('title');
});
document.querySelectorAll('.if-qa-badge').forEach(b => b.remove());
window[FLAG] = false;
console.info('[if-qa] overlay off');
return;
}
window[FLAG] = true;
const OVER = 1.25; // delivered > needed * OVER => oversized (wasted bytes)
const UNDER = 1.10; // needed * UNDER > delivered => upscaled in browser (blurry)
const dpr = window.devicePixelRatio || 1;
const COLORS = { error: '#ff2d55', warn: '#ff9500', info: '#34c759', unknown: '#8e8e93' };
const timingFor = (url) => {
const entry = performance.getEntriesByName(url, 'resource').at(-1);
const st = {};
if (entry && entry.serverTiming) for (const t of entry.serverTiming) st[t.name] = { dur: t.duration, desc: t.description };
return { entry, st };
};
const parseDim = (s) => { const m = /^(\d+)x(\d+)$/.exec(s || ''); return m ? { w: +m[1], h: +m[2] } : null; };
const worstOf = (issues) => issues.some(i => i.sev === 'error') ? 'error'
: issues.some(i => i.sev === 'warn') ? 'warn' : 'info';
function audit(img) {
const url = img.currentSrc || img.src; // currentSrc = the variant actually loaded (srcset-aware)
if (!url || url.startsWith('data:')) return null;
const issues = [];
const r = img.getBoundingClientRect();
const renderW = Math.round(r.width * dpr), renderH = Math.round(r.height * dpr);
const natW = img.naturalWidth, natH = img.naturalHeight;
// --- client-side: resolution efficiency (no server changes needed) ---
if (natW && renderW) {
if (natW > renderW * OVER) issues.push({ sev: 'warn', msg: `oversized: delivered ${natW}px, shown ${renderW}px@${dpr}x` });
else if (renderW > natW * UNDER) issues.push({ sev: 'error', msg: `upscaled by browser: delivered ${natW}px, shown ${renderW}px@${dpr}x` });
}
// --- server-side: imageflow Server-Timing verdict (when present) ---
const { entry, st } = timingFor(url);
if (st['if-opt']?.desc) {
for (const tok of st['if-opt'].desc.split('+').filter(t => t && t !== 'optimal')) {
issues.push({ sev: tok === 'upscaled' ? 'error' : 'warn', msg: `imageflow: ${tok}` });
}
} else { // derive from raw facts if no verdict token
const src = parseDim(st['if-src']?.desc), out = parseDim(st['if-out']?.desc);
if (src && out && (out.w > src.w || out.h > src.h))
issues.push({ sev: 'error', msg: `imageflow upscaled source ${src.w}x${src.h} -> ${out.w}x${out.h}` });
}
const noTiming = !entry || !Object.keys(st).length;
return { img, url, issues, noTiming, natW, natH, renderW, renderH, st };
}
function paint(a) {
const worst = a.issues.length ? worstOf(a.issues) : (a.noTiming ? 'unknown' : 'info');
if (worst === 'info') return; // optimal — leave it alone
a.img.style.outline = `3px solid ${COLORS[worst]}`;
a.img.style.outlineOffset = '-3px';
a.img.setAttribute('data-if-qa', worst);
const lines = a.issues.map(i => `• ${i.msg}`);
if (a.noTiming) lines.push('• no Server-Timing (set Timing-Allow-Origin / enable imageflow diagnostics)');
a.img.title = `[imageflow QA] ${a.url}\n${lines.join('\n')}`;
}
const run = (log) => {
const reports = [...document.images].map(audit).filter(Boolean);
reports.forEach(paint);
if (!log) return;
const flagged = reports.filter(a => a.issues.length || a.noTiming);
console.groupCollapsed(`[if-qa] ${flagged.length}/${reports.length} images flagged`);
console.table(flagged.map(a => ({
url: '…' + a.url.slice(-56),
severity: a.issues.length ? worstOf(a.issues) : 'unknown',
delivered: `${a.natW}x${a.natH}`, displayed: `${a.renderW}x${a.renderH}`,
fmt: a.st['if-fmt']?.desc ?? '?', q: a.st['if-q']?.dur ?? '?',
issues: a.issues.map(i => i.msg).join('; ') || 'no server-timing',
})));
console.groupEnd();
};
run(true);
try { new PerformanceObserver(() => run(false)).observe({ type: 'resource', buffered: true }); } catch {}
console.info('[if-qa] overlay on — re-run to toggle off');
})();
```
**Color legend**
- 🔴 red — **upscaled** (browser stretching the image, or imageflow upscaled the source): visible quality loss.
- 🟠 orange — **oversized / format-suboptimal**: correct-or-better resolution than needed, or a better format was available — wasted bytes.
- ⚪ gray — **couldn't measure**: no `Server-Timing` reached JS (missing `Timing-Allow-Origin`, CDN stripped it, or diagnostics off) — fix the headers to get a verdict.
- (green = optimal; not outlined, only shown in the console report.)
## Notes
- The resolution checks (`oversized` / `upscaled`) are pure client-side and work against any image today.
- `currentSrc` is used so `srcset`/`` resolve to the variant the browser actually loaded.
- A `PerformanceObserver` re-audits lazy-loaded / late images.
- Thresholds (`OVER`/`UNDER`) and the `if-*` schema are starting points — open to bikeshedding.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.