Summary
perry/ui exposes ImageFile(path) (local file path), ImageSymbol(name) (system symbols), and ImageGallery(...) (paged gallery — added in #553, accepts [{url, alt}]). There's no inline single-image-by-URL primitive — i.e. an Image(url) you can drop into an HStack for an avatar or into a card body for a thumbnail.
Most production mobile apps need this widget at multiple zoom levels:
- Avatar circle in chat row, profile header, owner strip on item detail
- Cover photo at the top of a profile / item detail
- Thumbnail on each
LazyVStack row (Items list, search results, my-items)
ImageGallery already does the URL-fetching work for the gallery case; the inline-single equivalent is what's missing.
Proposed surface
Image({
url: string, // remote URL; `data:` URIs welcome
alt?: string, // accessibility label
placeholder?: Widget, // shown while the request is in flight + on error
contentMode?: "fit" | "fill" | "cover", // matches UIImageView contentMode
}): Widget
Native iOS: UIImageView + URLSession.dataTask (or SDWebImage if a dependency is acceptable).
Native Android: ImageView + Glide/Coil/Picasso.
Web: <img src=…>.
Why it's a blocker for production apps
Reusing ImageGallery for single-image cases works only if the layout can accommodate a paged carousel — it can't, for an avatar in a chat row. Downloading to a local file and using ImageFile works but pushes a non-trivial amount of caching/expiry/ETag plumbing into every app.
Workaround until this lands
Currently downloading the URL to a local cache file via fetch + fs.writeFile, then rendering ImageFile(localPath). Acceptable for one-off avatars; painful for a list of 20 cards each with a thumbnail.
Out of scope
- Image upload (already covered elsewhere via
imagePickerPick from perry/system).
- Animation / progressive decode — not required for v1.
- Resize-on-fetch / responsive srcset — server-side concern.
Acceptance
Image({ url, alt }) returns a widget that can sit in an HStack next to other content.
- The image loads asynchronously; the widget shows
placeholder (or a transparent box) until the response arrives.
- Failed requests show
placeholder indefinitely (no crash); a follow-up callback for "load completed / failed" is nice-to-have but not v1.
- Available on iOS, Android, and web.
Context
Surfaced while porting an Angular Material mobile app (sharity-app → wishare) to Perry. Three production screens currently fall back to colored-circle placeholders or "Lade …" text where Angular shows actual photos: profile header (avatar + cover photo), item detail (gallery already works via #553), and item list (thumbnail on each card).
Summary
perry/uiexposesImageFile(path)(local file path),ImageSymbol(name)(system symbols), andImageGallery(...)(paged gallery — added in #553, accepts[{url, alt}]). There's no inline single-image-by-URL primitive — i.e. anImage(url)you can drop into anHStackfor an avatar or into a card body for a thumbnail.Most production mobile apps need this widget at multiple zoom levels:
LazyVStackrow (Items list, search results, my-items)ImageGalleryalready does the URL-fetching work for the gallery case; the inline-single equivalent is what's missing.Proposed surface
Native iOS:
UIImageView+URLSession.dataTask(orSDWebImageif a dependency is acceptable).Native Android:
ImageView+ Glide/Coil/Picasso.Web:
<img src=…>.Why it's a blocker for production apps
Reusing
ImageGalleryfor single-image cases works only if the layout can accommodate a paged carousel — it can't, for an avatar in a chat row. Downloading to a local file and usingImageFileworks but pushes a non-trivial amount of caching/expiry/ETag plumbing into every app.Workaround until this lands
Currently downloading the URL to a local cache file via
fetch+fs.writeFile, then renderingImageFile(localPath). Acceptable for one-off avatars; painful for a list of 20 cards each with a thumbnail.Out of scope
imagePickerPickfromperry/system).Acceptance
Image({ url, alt })returns a widget that can sit in anHStacknext to other content.placeholder(or a transparent box) until the response arrives.placeholderindefinitely (no crash); a follow-up callback for "load completed / failed" is nice-to-have but not v1.Context
Surfaced while porting an Angular Material mobile app (sharity-app → wishare) to Perry. Three production screens currently fall back to colored-circle placeholders or "Lade …" text where Angular shows actual photos: profile header (avatar + cover photo), item detail (gallery already works via #553), and item list (thumbnail on each card).