@joycostudio/iris 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../packages/react/index.tsx","../packages/browser/index.ts","../packages/react/plan-image.tsx"],"sourcesContent":["\"use client\"\n\nimport {\n createContext,\n useContext,\n useEffect,\n useState,\n type ReactNode,\n} from \"react\"\nimport {\n createIrisDebugger,\n type IrisBrowserManifest,\n type IrisDebugger,\n} from \"../browser\"\n\nconst IrisDebuggerContext = createContext<IrisDebugger | null>(null)\n\nexport function IrisProvider({\n children,\n enabled = true,\n manifest,\n scope = \"marked\",\n console: consoleMode = \"warnings\",\n}: {\n children: ReactNode\n enabled?: boolean\n manifest?: IrisBrowserManifest\n scope?: \"marked\" | \"document\"\n console?: \"silent\" | \"warnings\" | \"all\"\n}) {\n const [iris] = useState<IrisDebugger | null>(() =>\n typeof window === \"undefined\"\n ? null\n : createIrisDebugger({ manifest, scope, console: consoleMode })\n )\n\n useEffect(() => {\n if (!enabled || !iris) return\n iris.observe(document)\n window.__IRIS__ = iris\n return () => {\n iris.disconnect()\n if (window.__IRIS__ === iris) delete window.__IRIS__\n }\n }, [enabled, iris])\n\n return (\n <IrisDebuggerContext.Provider value={enabled ? iris : null}>\n {children}\n </IrisDebuggerContext.Provider>\n )\n}\n\nexport function useIrisDebugger() {\n return useContext(IrisDebuggerContext)\n}\n\ndeclare global {\n interface Window {\n __IRIS__?: IrisDebugger\n }\n}\nexport { IrisPlanImage } from \"./plan-image\"\nexport type { IrisPlanImageProps } from \"./plan-image\"\n","import type { IrisAsset, IrisDiagnostic } from \"../core\"\n\nexport type IrisBrowserManifest = Record<string, IrisAsset>\n\nexport type IrisBrowserReport = {\n id: string\n source: IrisAsset | null\n renderedWidth: number\n renderedHeight: number\n visualWidth: number\n visualHeight: number\n naturalWidth: number\n dpr: number\n idealWidth: number\n selectedWidth: number | null\n selectedUrl: string\n availableWidths: number[]\n declaredSizes: string\n declaredSlotWidth: number | null\n loading: string\n objectFit: string\n transferBytes: number | null\n duration: number | null\n timing: IrisImageTiming | null\n isLcp: boolean\n diagnostics: IrisDiagnostic[]\n}\n\nexport type IrisImageTiming = {\n blurMs: number | null\n previewMs: number | null\n finalMs: number | null\n startedAt: number | null\n previewAt: number | null\n finalAt: number | null\n}\n\nexport type IrisDebugger = ReturnType<typeof createIrisDebugger>\n\nexport function inspectImageElement(\n image: HTMLImageElement,\n manifest: IrisBrowserManifest = {},\n lcpElements: ReadonlySet<Element> = new Set(),\n timing: IrisImageTiming | null = null\n): IrisBrowserReport {\n const rect = image.getBoundingClientRect()\n // `sizes` describes the image's CSS layout slot. DOMRect includes transforms\n // from the image and every ancestor, which makes reveal animations look like\n // a smaller slot and produces false positives while they are running.\n const renderedWidth = image.clientWidth || rect.width\n const renderedHeight = image.clientHeight || rect.height\n const id = image.dataset.iris || image.currentSrc || image.src\n const source =\n manifest[image.dataset.irisSrc || id] ??\n manifest[id] ??\n manifest[image.currentSrc] ??\n manifest[image.src] ??\n null\n const candidates = parseSrcSet(image.srcset, renderedWidth)\n const selectedUrl = image.currentSrc || image.src\n const selectedWidth = selectedCandidateWidth(selectedUrl, candidates)\n const dpr = window.devicePixelRatio || 1\n const idealWidth = Math.ceil(renderedWidth * dpr)\n const declaredSlotWidth = resolveSizes(image.sizes)\n const resource = resourceTiming(selectedUrl)\n const diagnostics = diagnoseRuntime({\n source,\n renderedWidth,\n renderedHeight,\n visualWidth: rect.width,\n visualHeight: rect.height,\n idealWidth,\n selectedWidth,\n availableWidths: candidates.map((candidate) => candidate.width),\n declaredSlotWidth,\n objectFit: getComputedStyle(image).objectFit,\n })\n\n return {\n id,\n source,\n renderedWidth,\n renderedHeight,\n visualWidth: rect.width,\n visualHeight: rect.height,\n naturalWidth: image.naturalWidth,\n dpr,\n idealWidth,\n selectedWidth,\n selectedUrl,\n availableWidths: candidates.map((candidate) => candidate.width),\n declaredSizes: image.sizes,\n declaredSlotWidth,\n loading: image.loading,\n objectFit: getComputedStyle(image).objectFit,\n transferBytes: resource?.transferSize ?? null,\n duration: resource?.duration ?? null,\n timing,\n isLcp: lcpElements.has(image),\n diagnostics,\n }\n}\n\nexport function createIrisDebugger(\n options: {\n manifest?: IrisBrowserManifest\n scope?: \"marked\" | \"document\"\n console?: \"silent\" | \"warnings\" | \"all\"\n } = {}\n) {\n const reports = new Map<HTMLImageElement, IrisBrowserReport>()\n const lcpElements = new Set<Element>()\n let mutationObserver: MutationObserver | null = null\n let resizeObserver: ResizeObserver | null = null\n let performanceObserver: PerformanceObserver | null = null\n let frame = 0\n let tableTimer: ReturnType<typeof setTimeout> | null = null\n let lastTableSignature = \"\"\n const pending = new Set<HTMLImageElement>()\n const timelines = new WeakMap<HTMLImageElement, IrisImageTiming>()\n\n const selector = options.scope === \"document\" ? \"img\" : \"img[data-iris]\"\n const measure = (image: HTMLImageElement) => {\n const report = inspectImageElement(\n image,\n options.manifest,\n lcpElements,\n timelines.get(image) ?? null\n )\n reports.set(image, report)\n scheduleTable()\n }\n const table = () => logReportTable(report(), options.console ?? \"warnings\")\n const scheduleTable = () => {\n if (options.console === \"silent\") return\n if (tableTimer) clearTimeout(tableTimer)\n tableTimer = setTimeout(() => {\n tableTimer = null\n const signature = reportSignature(report())\n if (signature === lastTableSignature) return\n lastTableSignature = signature\n table()\n }, 250)\n }\n const schedule = (image: HTMLImageElement) => {\n pending.add(image)\n if (frame) return\n frame = requestAnimationFrame(() => {\n frame = 0\n for (const pendingImage of pending) measure(pendingImage)\n pending.clear()\n })\n }\n const observe = (root: ParentNode = document) => {\n resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n if (entry.target instanceof HTMLImageElement) schedule(entry.target)\n }\n })\n const register = (image: HTMLImageElement) => {\n resizeObserver?.observe(image)\n image.addEventListener(\"load\", () => schedule(image), { once: true })\n if (image.complete) schedule(image)\n }\n root.querySelectorAll<HTMLImageElement>(selector).forEach(register)\n mutationObserver = new MutationObserver((records) => {\n for (const record of records) {\n for (const node of record.addedNodes) {\n if (!(node instanceof Element)) continue\n if (node.matches(selector)) register(node as HTMLImageElement)\n node.querySelectorAll<HTMLImageElement>(selector).forEach(register)\n }\n }\n })\n mutationObserver.observe(root, { childList: true, subtree: true })\n if (typeof PerformanceObserver !== \"undefined\") {\n performanceObserver = new PerformanceObserver((list) => {\n for (const entry of list.getEntries()) {\n const element = (entry as PerformanceEntry & { element?: Element })\n .element\n if (element) {\n lcpElements.add(element)\n if (element instanceof HTMLImageElement) schedule(element)\n }\n }\n })\n try {\n performanceObserver.observe({\n type: \"largest-contentful-paint\",\n buffered: true,\n })\n } catch {\n performanceObserver = null\n }\n }\n return api\n }\n const disconnect = () => {\n mutationObserver?.disconnect()\n resizeObserver?.disconnect()\n performanceObserver?.disconnect()\n cancelAnimationFrame(frame)\n if (tableTimer) clearTimeout(tableTimer)\n tableTimer = null\n frame = 0\n pending.clear()\n }\n const report = () => [...reports.values()]\n const startTimeline = (image: HTMLImageElement) => {\n timelines.set(image, {\n blurMs: null,\n previewMs: null,\n finalMs: null,\n startedAt: null,\n previewAt: null,\n finalAt: null,\n })\n }\n const markPreview = (\n image: HTMLImageElement,\n preview: HTMLImageElement,\n at = performance.now()\n ) => {\n const timing = timelines.get(image)\n if (!timing) return\n timing.startedAt =\n resourceTiming(preview.currentSrc || preview.src)?.startTime ?? at\n timing.previewAt = at\n timing.blurMs = roundDuration(at - timing.startedAt)\n schedule(image)\n }\n const markFinal = (image: HTMLImageElement, at = performance.now()) => {\n const timing = timelines.get(image)\n if (!timing) return\n const finalStartedAt =\n resourceTiming(image.currentSrc || image.src)?.startTime ?? at\n timing.finalAt = at\n timing.finalMs = roundDuration(at - finalStartedAt)\n if (timing.previewAt === null) {\n timing.startedAt = finalStartedAt\n timing.blurMs = timing.finalMs\n } else timing.previewMs = roundDuration(at - timing.previewAt)\n schedule(image)\n }\n const summary = () => ({\n inspected: reports.size,\n healthy: report().filter((item) => item.diagnostics.length === 0).length,\n warnings: report()\n .flatMap((item) => item.diagnostics)\n .filter((item) => item.severity === \"warning\").length,\n errors: report()\n .flatMap((item) => item.diagnostics)\n .filter((item) => item.severity === \"error\").length,\n })\n const api = {\n observe,\n disconnect,\n report,\n table,\n summary,\n startTimeline,\n markPreview,\n markFinal,\n }\n return api\n}\n\nfunction roundDuration(value: number) {\n return Math.round(value * 10) / 10\n}\n\nfunction resourceTiming(url: string) {\n const normalized = normalizeUrl(url)\n const entries = performance.getEntriesByType(\n \"resource\"\n ) as PerformanceResourceTiming[]\n for (let index = entries.length - 1; index >= 0; index--) {\n if (normalizeUrl(entries[index].name) === normalized) return entries[index]\n }\n return undefined\n}\n\nfunction parseSrcSet(srcset: string, renderedWidth: number) {\n return srcset\n .split(\",\")\n .map((value) => value.trim().split(/\\s+/))\n .map(([url, descriptor]) => ({\n url,\n width: descriptor?.endsWith(\"w\")\n ? Number.parseInt(descriptor, 10)\n : descriptor?.endsWith(\"x\")\n ? Math.round(Number.parseFloat(descriptor) * renderedWidth)\n : 0,\n }))\n .filter((candidate) => candidate.url && candidate.width > 0)\n}\n\nfunction selectedCandidateWidth(\n selectedUrl: string,\n candidates: { url: string; width: number }[]\n) {\n const selected = normalizeUrl(selectedUrl)\n const candidate = candidates.find(\n (item) => normalizeUrl(item.url) === selected\n )\n if (candidate) return candidate.width\n const queryWidth = new URL(\n selectedUrl,\n window.location.href\n ).searchParams.get(\"w\")\n return queryWidth ? Number(queryWidth) : null\n}\n\nfunction normalizeUrl(url: string) {\n try {\n return new URL(url, window.location.href).href\n } catch {\n return url\n }\n}\n\nexport function diagnoseRuntime(input: {\n source: IrisAsset | null\n renderedWidth: number\n renderedHeight: number\n visualWidth?: number\n visualHeight?: number\n idealWidth: number\n selectedWidth: number | null\n availableWidths: number[]\n declaredSlotWidth: number | null\n objectFit: string\n}) {\n const diagnostics: IrisDiagnostic[] = []\n if (\n input.visualWidth !== undefined &&\n input.renderedWidth > 0 &&\n Math.abs(input.visualWidth - input.renderedWidth) / input.renderedWidth >\n 0.02\n ) {\n diagnostics.push(\n runtimeDiagnostic(\n \"css-transform-affects-visual-size\",\n \"info\",\n \"high\",\n \"A CSS transform changes the visual width; responsive selection still uses the untransformed layout slot.\",\n {\n renderedWidth: input.renderedWidth,\n visualWidth: input.visualWidth,\n }\n )\n )\n }\n if (input.declaredSlotWidth && input.renderedWidth > 0) {\n const pixelDifference = input.declaredSlotWidth - input.renderedWidth\n const difference = pixelDifference / input.renderedWidth\n // Relative differences on icons look dramatic while changing no candidate:\n // 49px vs 38px is 28%, but both resolve to the same smallest srcset width.\n // Require a meaningful absolute mismatch as well as a relative one.\n const isMaterialMismatch = Math.abs(pixelDifference) >= 32\n if (difference < -0.1 && isMaterialMismatch) {\n diagnostics.push(\n runtimeDiagnostic(\n \"sizes-understates-layout\",\n \"warning\",\n \"high\",\n \"The active sizes slot is smaller than the rendered CSS width.\",\n {\n declaredSlotWidth: input.declaredSlotWidth,\n renderedWidth: input.renderedWidth,\n pixelDifference,\n difference,\n }\n )\n )\n } else if (difference > 0.25 && isMaterialMismatch) {\n diagnostics.push(\n runtimeDiagnostic(\n \"sizes-overstates-layout\",\n \"warning\",\n \"high\",\n \"The active sizes slot is substantially larger than the rendered CSS width.\",\n {\n declaredSlotWidth: input.declaredSlotWidth,\n renderedWidth: input.renderedWidth,\n pixelDifference,\n difference,\n }\n )\n )\n }\n }\n if (input.source && input.source.width < input.idealWidth) {\n diagnostics.push(\n runtimeDiagnostic(\n \"source-too-small\",\n \"error\",\n \"high\",\n \"The source is smaller than the rendered width × DPR target.\",\n {\n sourceWidth: input.source.width,\n idealWidth: input.idealWidth,\n }\n )\n )\n }\n if (input.selectedWidth && input.selectedWidth < input.idealWidth) {\n const hasLarger = input.availableWidths.some(\n (width) => width >= input.idealWidth\n )\n diagnostics.push(\n runtimeDiagnostic(\n hasLarger ? \"browser-downselected\" : \"missing-useful-candidate\",\n hasLarger ? \"info\" : \"warning\",\n \"high\",\n hasLarger\n ? \"The browser selected below the theoretical target although a larger candidate exists.\"\n : \"No available candidate reaches the theoretical target.\",\n { selectedWidth: input.selectedWidth, idealWidth: input.idealWidth }\n )\n )\n }\n if (input.source && input.objectFit === \"cover\") {\n const sourceRatio = input.source.width / input.source.height\n const renderedRatio = input.renderedWidth / input.renderedHeight\n if (Math.abs(sourceRatio - renderedRatio) / sourceRatio > 0.02) {\n diagnostics.push(\n runtimeDiagnostic(\n \"cropped-image\",\n \"info\",\n \"high\",\n \"object-fit: cover crops part of the downloaded source.\",\n {\n sourceAspectRatio: sourceRatio,\n renderedAspectRatio: renderedRatio,\n }\n )\n )\n }\n }\n return diagnostics\n}\n\nfunction resolveSizes(sizes: string): number | null {\n if (!sizes || sizes.trim() === \"auto\") return null\n for (const rule of splitTopLevel(sizes)) {\n const value = rule.trim()\n let length = value\n if (value.startsWith(\"(\")) {\n const end = matchingParenthesis(value)\n if (end < 0) continue\n if (!matchMedia(value.slice(0, end + 1)).matches) continue\n length = value.slice(end + 1).trim()\n }\n if (!length || length === \"auto\") continue\n const probe = document.createElement(\"div\")\n probe.style.cssText = `position:fixed;visibility:hidden;pointer-events:none;width:${length};height:0`\n document.body.append(probe)\n const width = probe.getBoundingClientRect().width\n probe.remove()\n return Number.isFinite(width) && width > 0 ? width : null\n }\n return null\n}\n\nfunction splitTopLevel(value: string) {\n const output: string[] = []\n let depth = 0\n let start = 0\n for (let index = 0; index < value.length; index++) {\n if (value[index] === \"(\") depth++\n if (value[index] === \")\") depth--\n if (value[index] === \",\" && depth === 0) {\n output.push(value.slice(start, index))\n start = index + 1\n }\n }\n output.push(value.slice(start))\n return output\n}\n\nfunction matchingParenthesis(value: string) {\n let depth = 0\n for (let index = 0; index < value.length; index++) {\n if (value[index] === \"(\") depth++\n if (value[index] === \")\" && --depth === 0) return index\n }\n return -1\n}\n\nfunction runtimeDiagnostic(\n code: string,\n severity: IrisDiagnostic[\"severity\"],\n confidence: IrisDiagnostic[\"confidence\"],\n message: string,\n evidence: IrisDiagnostic[\"evidence\"]\n): IrisDiagnostic {\n return { code, severity, confidence, message, evidence }\n}\n\nfunction logReportTable(\n reports: IrisBrowserReport[],\n mode: \"silent\" | \"warnings\" | \"all\"\n) {\n if (mode === \"silent\") return\n const visible = reports.filter(\n (report) =>\n mode === \"all\" ||\n report.diagnostics.some((diagnostic) => diagnostic.severity !== \"info\")\n )\n if (visible.length === 0) return\n console.table(\n visible.map((report) => ({\n asset: report.id,\n stage: imageStage(report),\n rendered: `${Math.round(report.renderedWidth)}×${Math.round(report.renderedHeight)}`,\n selected: report.selectedWidth,\n ideal: report.idealWidth,\n bytes: report.transferBytes,\n blurMs: report.timing?.blurMs ?? null,\n previewMs: report.timing?.previewMs ?? null,\n finalMs: report.timing?.finalMs ?? null,\n lazy: report.loading === \"lazy\",\n lcp: report.isLcp,\n status: diagnosticStatus(report),\n diagnostics: report.diagnostics\n .map((diagnostic) => diagnostic.code)\n .join(\", \"),\n }))\n )\n}\n\nfunction imageStage(report: IrisBrowserReport) {\n if (report.timing?.finalAt !== null && report.timing?.finalAt !== undefined)\n return \"final\"\n if (report.timing?.previewAt) return \"preview\"\n if (report.timing) return \"blur\"\n return report.naturalWidth > 0 ? \"final\" : \"loading\"\n}\n\nfunction diagnosticStatus(report: IrisBrowserReport) {\n if (report.diagnostics.some((item) => item.severity === \"error\"))\n return \"error\"\n if (report.diagnostics.some((item) => item.severity === \"warning\"))\n return \"warning\"\n if (report.diagnostics.some((item) => item.severity === \"info\")) return \"info\"\n return \"healthy\"\n}\n\nfunction reportSignature(reports: IrisBrowserReport[]) {\n return reports\n .map((report) =>\n [\n report.id,\n imageStage(report),\n report.selectedWidth,\n report.transferBytes,\n report.timing?.blurMs,\n report.timing?.previewMs,\n report.timing?.finalMs,\n diagnosticStatus(report),\n ].join(\":\")\n )\n .join(\"|\")\n}\n","import {\n useCallback,\n useRef,\n useState,\n type CSSProperties,\n type ImgHTMLAttributes,\n} from \"react\"\nimport type { IrisResolvedPlan } from \"../core\"\n\nexport type IrisPlanImageProps = Omit<\n ImgHTMLAttributes<HTMLImageElement>,\n \"src\" | \"srcSet\" | \"width\" | \"height\" | \"children\"\n> & {\n plan: IrisResolvedPlan\n alt: string\n}\n\n/** Native-image adapter. Resolution happens outside render; changed plans reset delivery. */\nexport function IrisPlanImage(props: IrisPlanImageProps) {\n const { plan } = props\n const key = JSON.stringify([\n plan.src,\n plan.final.url,\n plan.preview?.url,\n plan.placeholder?.url,\n plan.placeholder?.blur,\n ])\n return <PlanImage key={key} {...props} />\n}\n\nfunction PlanImage({\n plan,\n alt,\n className,\n style,\n onLoad,\n onError,\n loading = \"lazy\",\n ...props\n}: IrisPlanImageProps) {\n const [finalReady, setFinalReady] = useState(false)\n const [previewReady, setPreviewReady] = useState(false)\n const previewDecoding = useRef(false)\n const previewShown = useRef(false)\n const finalRef = useCallback((image: HTMLImageElement | null) => {\n if (image?.complete && image.naturalWidth) setFinalReady(true)\n }, [])\n const revealPreview = useCallback(async (image: HTMLImageElement) => {\n if (previewDecoding.current || previewShown.current) return\n previewDecoding.current = true\n try {\n await image.decode?.()\n } catch {\n // A successful load can still reject decode in some browsers.\n }\n previewDecoding.current = false\n if (image.isConnected && image.naturalWidth && !previewShown.current) {\n previewShown.current = true\n setPreviewReady(true)\n }\n }, [])\n const previewRef = useCallback(\n (image: HTMLImageElement | null) => {\n if (image?.complete && image.naturalWidth) void revealPreview(image)\n },\n [revealPreview]\n )\n const progressive = Boolean(plan.final.progressive)\n const surface: CSSProperties = {\n position: \"relative\",\n display: \"block\",\n ...style,\n }\n return (\n <span className={className} data-slot=\"iris-image\" style={surface}>\n <img\n {...props}\n ref={finalRef}\n src={plan.final.url}\n alt={alt}\n width={plan.final.width}\n height={plan.final.height}\n data-slot=\"iris-final\"\n data-iris-src={plan.src}\n loading={loading}\n style={{\n position: \"relative\",\n display: \"block\",\n width: \"100%\",\n height: \"auto\",\n }}\n onLoad={(event) => {\n setFinalReady(true)\n onLoad?.(event)\n }}\n onError={onError}\n />\n {!progressive && !finalReady && !previewReady && plan.placeholder && (\n <span\n aria-hidden=\"true\"\n data-slot=\"iris-placeholder\"\n data-stage=\"blur\"\n style={{\n position: \"absolute\",\n inset: 0,\n backgroundImage: `url(${JSON.stringify(plan.placeholder.url)})`,\n backgroundSize: \"100% 100%\",\n filter: `blur(${plan.placeholder.blur}px)`,\n clipPath: \"inset(0)\",\n pointerEvents: \"none\",\n zIndex: 2,\n }}\n />\n )}\n {!progressive && plan.preview && !finalReady && (\n <img\n ref={previewRef}\n src={plan.preview.url}\n alt=\"\"\n aria-hidden=\"true\"\n data-slot=\"iris-preview\"\n width={plan.final.width}\n height={plan.final.height}\n loading={loading}\n decoding={props.decoding}\n crossOrigin={props.crossOrigin}\n referrerPolicy={props.referrerPolicy}\n style={{\n position: \"absolute\",\n inset: 0,\n width: \"100%\",\n height: \"100%\",\n objectFit: \"contain\",\n pointerEvents: \"none\",\n zIndex: 1,\n opacity: previewReady ? 1 : 0,\n }}\n onLoad={(event) => void revealPreview(event.currentTarget)}\n />\n )}\n </span>\n )\n}\n"],"mappings":"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","names":["useState","report","jsx","useState"]}
@@ -0,0 +1,17 @@
1
+ type IrisAsset = {
2
+ source?: string;
3
+ width: number;
4
+ height: number;
5
+ bytes: number;
6
+ format?: string;
7
+ animated?: boolean;
8
+ };
9
+ type IrisDiagnostic = {
10
+ code: string;
11
+ severity: "info" | "warning" | "error";
12
+ confidence: "high" | "medium" | "low";
13
+ message: string;
14
+ evidence: Record<string, string | number | boolean | null>;
15
+ };
16
+
17
+ export type { IrisAsset as I, IrisDiagnostic as a };
@@ -0,0 +1,17 @@
1
+ type IrisAsset = {
2
+ source?: string;
3
+ width: number;
4
+ height: number;
5
+ bytes: number;
6
+ format?: string;
7
+ animated?: boolean;
8
+ };
9
+ type IrisDiagnostic = {
10
+ code: string;
11
+ severity: "info" | "warning" | "error";
12
+ confidence: "high" | "medium" | "low";
13
+ message: string;
14
+ evidence: Record<string, string | number | boolean | null>;
15
+ };
16
+
17
+ export type { IrisAsset as I, IrisDiagnostic as a };
package/package.json CHANGED
@@ -1,6 +1,104 @@
1
1
  {
2
2
  "name": "@joycostudio/iris",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.1",
4
+ "description": "Analyze images and deliver the right version for each screen",
5
+ "license": "ISC",
6
+ "author": "joyco.studio",
7
+ "type": "module",
8
+ "bin": {
9
+ "iris": "dist/cli.js"
10
+ },
11
+ "engines": {
12
+ "node": ">=22.18"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "skills"
17
+ ],
18
+ "publishConfig": {
19
+ "registry": "https://registry.npmjs.org",
20
+ "access": "public"
21
+ },
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/core.d.ts",
25
+ "import": "./dist/core.js",
26
+ "require": "./dist/core.cjs"
27
+ },
28
+ "./node": {
29
+ "types": "./dist/node.d.ts",
30
+ "import": "./dist/node.js",
31
+ "require": "./dist/node.cjs"
32
+ },
33
+ "./browser": {
34
+ "types": "./dist/browser.d.ts",
35
+ "import": "./dist/browser.js",
36
+ "require": "./dist/browser.cjs"
37
+ },
38
+ "./react": {
39
+ "types": "./dist/react.d.ts",
40
+ "import": "./dist/react.js",
41
+ "require": "./dist/react.cjs"
42
+ },
43
+ "./next": {
44
+ "types": "./dist/next.d.ts",
45
+ "import": "./dist/next.js",
46
+ "require": "./dist/next.cjs"
47
+ }
48
+ },
49
+ "scripts": {
50
+ "build": "tsup",
51
+ "dev": "tsup --watch",
52
+ "typecheck": "tsc --noEmit",
53
+ "test": "vitest run",
54
+ "test:watch": "vitest",
55
+ "version:package": "pnpm changeset version",
56
+ "release": "pnpm build && pnpm changeset publish",
57
+ "prepack": "pnpm build"
58
+ },
59
+ "dependencies": {
60
+ "@babel/parser": "8.0.5",
61
+ "fast-glob": "3.3.3",
62
+ "jiti": "2.6.1",
63
+ "minimatch": "10.2.6"
64
+ },
65
+ "packageManager": "pnpm@10.29.2",
66
+ "devDependencies": {
67
+ "@changesets/cli": "^2.29.8",
68
+ "@types/node": "^22.19.15",
69
+ "@types/react": "^19.2.14",
70
+ "@types/react-dom": "^19.2.3",
71
+ "prettier": "^3.9.8",
72
+ "react": "^19.2.4",
73
+ "react-dom": "^19.2.4",
74
+ "sharp": "^0.35.4",
75
+ "tsup": "^8.5.0",
76
+ "typescript": "^5.9.3",
77
+ "vitest": "^4.1.11",
78
+ "next": "16.2.4"
79
+ },
80
+ "peerDependencies": {
81
+ "react": ">=18",
82
+ "sharp": ">=0.33",
83
+ "next": ">=16"
84
+ },
85
+ "peerDependenciesMeta": {
86
+ "react": {
87
+ "optional": true
88
+ },
89
+ "sharp": {
90
+ "optional": true
91
+ },
92
+ "next": {
93
+ "optional": true
94
+ }
95
+ },
96
+ "repository": {
97
+ "type": "git",
98
+ "url": "git+https://github.com/joyco-studio/iris.git"
99
+ },
100
+ "homepage": "https://github.com/joyco-studio/iris#readme",
101
+ "bugs": {
102
+ "url": "https://github.com/joyco-studio/iris/issues"
103
+ }
104
+ }
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: iris-audit
3
+ description: >
4
+ Audit responsive image delivery using the @joycostudio/iris runtime report.
5
+ Use when asked to check image sharpness, srcset or sizes correctness, raw
6
+ source dimensions, preview-preview usefulness, image transfer cost, or
7
+ LCP behavior in an Iris-enabled web app. Do not use for visual art direction
8
+ or image editing.
9
+ license: MIT
10
+ metadata:
11
+ author: joyco-studio
12
+ version: "0.0.1"
13
+ user-invocable: true
14
+ argument-hint: <route-or-url>
15
+ ---
16
+
17
+ # Iris Audit
18
+
19
+ Use Iris's structured runtime output instead of judging sharpness by eye.
20
+
21
+ ## Prerequisites
22
+
23
+ - The app mounts `IrisProvider` in development.
24
+ - Audited images have `data-iris` identifiers.
25
+ - The page exposes `window.__IRIS__` after hydration.
26
+ - Use browser automation for viewport and network scenarios; do not edit assets
27
+ during the audit.
28
+
29
+ ## Workflow
30
+
31
+ 1. Read the consumer's recipes and rules. Record explicit usage/DPR overrides.
32
+ For Next, read widths, qualities and formats from next.config. Read encoding
33
+ settings from the generation options, separately from delivery behavior.
34
+ 2. Start the development server if one is not already running.
35
+ 3. Open the requested route at each representative viewport. At minimum cover:
36
+ `390px @ 3x`, `1440px @ 1x`, `1440px @ 2x`, and `1920px @ 2x` where the
37
+ browser tooling supports DPR emulation.
38
+ 4. Wait for the page and responsive images to settle, then evaluate:
39
+
40
+ ```js
41
+ window.__IRIS__?.report()
42
+ ```
43
+
44
+ 5. Repeat the relevant viewports with network throttling. Treat browser
45
+ downselection as information, not automatically as a defect.
46
+ 6. Correlate runtime findings with the generated catalog and optional report. Distinguish:
47
+ source too small, missing candidate, likely `sizes` mismatch, browser
48
+ downselection, cropping, and an ineffective preview.
49
+ 7. Report evidence for every finding: rendered width, DPR, ideal width, selected
50
+ width, available widths, transfer bytes, duration, source width, and LCP.
51
+
52
+ ## Decision rules
53
+
54
+ - `sourceWidth < renderedWidth × DPR` is a high-confidence raw-source problem.
55
+ - No candidate at or above the theoretical target is a candidate-ladder gap.
56
+ - A smaller selection when a larger candidate exists is browser downselection;
57
+ do not call it broken without corroborating evidence.
58
+ - An preview that appears only shortly before the final image or consumes
59
+ similar bytes is ineffective and should be removed or reduced.
60
+ - `object-fit: cover` with different source/rendered aspect ratios means some
61
+ transferred pixels are intentionally cropped.
62
+ - DOM data cannot prove low compression quality. Report configured quality and
63
+ request perceptual validation when compression artifacts are the concern.
64
+
65
+ ## Output
66
+
67
+ Summarize counts first, then list errors, warnings, and informational browser
68
+ choices separately. Recommend config or source changes only when the report
69
+ contains the evidence needed to support them.
70
+
71
+ ## Checklist
72
+
73
+ - [ ] Used Iris reports rather than visual inspection alone.
74
+ - [ ] Tested both layout/DPR and network variation.
75
+ - [ ] Did not classify browser downselection as an automatic failure.
76
+ - [ ] Compared preview timing and bytes against the final image.
77
+ - [ ] Included concrete evidence and the affected `data-iris` identifier.