2727 * not stringified HTML" for the rationale.
2828 */
2929
30- import type { AssetRef } from "@run402/functions" ;
30+ import type { CSSProperties } from "react" ;
31+
32+ // =============================================================================
33+ // Run402ImageAsset — structural-subset asset shape the component actually reads
34+ // =============================================================================
35+
36+ /**
37+ * Minimal asset shape consumed by `<Run402Image>`. v1.0.3 — see GH #401.
38+ *
39+ * `Run402ImageProps.asset` used to be typed as `AssetRef` from `@run402/functions`,
40+ * which is the broad SDK-side shape (`visibility`, `immutable`, `content_digest`,
41+ * camelCase mirrors, …). Two real callers — values returned by `r.assets.put` /
42+ * `r.assets.fromRef` AND values returned by `resolveVariants(manifest, key)`
43+ * from `@run402/astro/manifest` — both have to flow into the same `<Run402Image
44+ * asset={…}>` site. Pinning to the broader SDK shape made the manifest-pipeline
45+ * shape (a structural subset) fail type-check even though `<Run402Image>` only
46+ * reads `cdn_url + variants + width_px + height_px + blurhash_data_url +
47+ * asset_schema` at runtime.
48+ *
49+ * The component declares what it consumes, not what its data source happens to
50+ * produce. `Run402ImageAsset` is the structural supertype of every supported
51+ * shape — both the SDK's `AssetRef` (broader) and the manifest's `AssetRef`
52+ * (narrower) satisfy it. Runtime validation still enforces a non-empty
53+ * `cdn_url` string, so the `string | null` value type here just lets us accept
54+ * the SDK shape verbatim; nulls fail validation immediately with
55+ * `R402_ASTRO_IMAGE_ASSET_WRONG_SHAPE`.
56+ */
57+ export interface Run402ImageAsset {
58+ /** Required at runtime as a non-empty string. Typed `string | null` so the
59+ * SDK's `AssetRef` (which permits `null` for private assets) is assignable. */
60+ cdn_url : string | null ;
61+ /** Optional — used only in error messages + degradation-manifest entries. */
62+ key ?: string ;
63+ /** Optional — used only in error messages + degradation-manifest entries. */
64+ sha256 ?: string ;
65+ /** Optional. Validated to be `image/*` when present; non-image throws
66+ * `R402_ASTRO_IMAGE_NON_IMAGE_ASSET`. HEIC sources without a `display_jpeg`
67+ * variant throw `R402_ASTRO_IMAGE_HEIC_NO_TRANSCODE`. */
68+ content_type ?: string ;
69+ /** Preferred over `cdn_url` for the rendered `<img src>` when non-empty.
70+ * Targets the HEIC `display_jpeg` variant for HEIC sources. */
71+ display_url ?: string | null ;
72+ /** Drives `<img width=…>` (caller's `width` prop overrides). */
73+ width_px ?: number ;
74+ /** Drives `<img height=…>` (caller's `height` prop overrides). */
75+ height_px ?: number ;
76+ /** Pre-decoded LQIP data URL; rendered as the `<img>`'s `background-image`. */
77+ blurhash_data_url ?: string | null ;
78+ /** Shape-contract stamp consumed by schema-filtered strict mode. */
79+ asset_schema ?: "v1.49" | "v1.50" | "v1.54" | null ;
80+ /** Per-variant `<source srcset>` entries plus the HEIC `display_jpeg`
81+ * fallback. */
82+ variants ?: {
83+ thumb ?: Run402ImageAssetVariant ;
84+ medium ?: Run402ImageAssetVariant ;
85+ large ?: Run402ImageAssetVariant ;
86+ display_jpeg ?: Run402ImageAssetVariant ;
87+ } ;
88+ }
89+
90+ /**
91+ * Minimal variant entry shape consumed by `<Run402Image>`.
92+ *
93+ * The component reads `cdn_url ?? url` for the variant URL (with a runtime
94+ * null/empty check) and `width_px` for the srcset width descriptor. `format`
95+ * drives the `<source type=…>` attribute and defaults to `"webp"` when
96+ * missing. Other variant fields (`sha256`, `height_px`, `immutable_url`,
97+ * `cdn_immutable_url`, `kind`) are ignored by the component.
98+ */
99+ export interface Run402ImageAssetVariant {
100+ url ?: string | null ;
101+ cdn_url ?: string | null ;
102+ width_px : number ;
103+ format ?: "webp" | "jpeg" ;
104+ }
31105
32106// =============================================================================
33107// 1.1 — Run402ImageProps interface (binding source of truth)
@@ -46,11 +120,15 @@ import type { AssetRef } from "@run402/functions";
46120 * adding a new optional field is minor; removing or retyping is major.
47121 */
48122export interface Run402ImageProps extends DataAttributes {
49- /** The image source — typed AssetRef from `r.assets.put` / `r.assets.fromRef`.
50- * String URLs are rejected (`R402_ASTRO_IMAGE_ASSET_STRING_URL`); null
51- * / undefined rejected (`R402_ASTRO_IMAGE_ASSET_MISSING`); objects without
52- * a `cdn_url` field rejected (`R402_ASTRO_IMAGE_ASSET_WRONG_SHAPE`). */
53- asset : AssetRef ;
123+ /** The image source — any object structurally matching `Run402ImageAsset`.
124+ * In practice this covers both the SDK's `AssetRef` (returned by
125+ * `r.assets.put` / `r.assets.fromRef`) and the manifest's `AssetRef`
126+ * (returned by `resolveVariants(manifest, key)` from
127+ * `@run402/astro/manifest`). String URLs are rejected
128+ * (`R402_ASTRO_IMAGE_ASSET_STRING_URL`); null / undefined rejected
129+ * (`R402_ASTRO_IMAGE_ASSET_MISSING`); objects without a non-empty
130+ * `cdn_url` rejected (`R402_ASTRO_IMAGE_ASSET_WRONG_SHAPE`). */
131+ asset : Run402ImageAsset ;
54132 /** Required at the type level. Empty string (`alt=""`) signals decorative
55133 * per HTML5 §4.7.4.4 and is allowed. */
56134 alt : string ;
@@ -102,8 +180,14 @@ export interface Run402ImageProps extends DataAttributes {
102180 * semantics" requirement — caller wins on property overlap.
103181 * Note: callers passing `background: <shorthand>` will reset the
104182 * placeholder `background-image`; use longhand (`background-color`,
105- * `background-size`, etc.) to preserve it. */
106- style ?: string | Record < string , string | number > ;
183+ * `background-size`, etc.) to preserve it.
184+ *
185+ * v1.0.3 — accepts `React.CSSProperties` directly so consumers of the
186+ * React entry can pass strongly-typed style objects (`{ objectFit:
187+ * 'cover' }`) without casting through `Record<string, string | number>`.
188+ * Astro consumers may still pass the looser string / object-of-strings
189+ * shape — both serialize identically. See GH #401. */
190+ style ?: string | CSSProperties | Record < string , string | number > ;
107191 /** Forwarded to the outermost element. */
108192 id ?: string ;
109193 /** Forwarded verbatim. Component does NOT emit by default. */
@@ -187,6 +271,10 @@ export type AstroComponent<P> = ((props: P) => unknown) & {
187271// produced by a module without proper imports" issue when consumers
188272// don't have @types/react installed. The ReactElement | null shape is
189273// what JSX expects + survives the brand intersection.
274+ //
275+ // `CSSProperties` is imported at the top of this file for the
276+ // `Run402ImageProps.style` widening (v1.0.3, GH #401).
277+
190278import type { ReactElement } from "react" ;
191279
192280export type ReactComponent < P > = ( ( props : P ) => ReactElement | null ) & {
0 commit comments