What happens
An <Image> whose source has no scheme is treated as the name of a bundled
asset, never as a URL. From updateImage in CraftNativeScreen.swift:
if !uri.contains(":") {
guard let image = UIImage(named: uri) else {
imageFailure(view, uri: uri, message: "Bundled image was not found")
return
}
...
}So /api/avatars/9/0a1b2c3d-….jpg is looked up as an asset called
/api/avatars/9/0a1b2c3d-….jpg, fails, and nothing is ever fetched.
Why this bites
It is the one place where a native screen and a web page disagree about a value that is correct in both worlds. An API that answers relative paths — ours does, and so does every Rails/Laravel/Stacks app I have seen — works untouched in the browser and silently shows nothing natively.
Found on a device yesterday: every face in Wildloop's athlete directory and leaderboard was a blank circle. The failure compounds, which is why it took a device to see:
- the image never loads, because it was looked up as an asset;
- the markup is
<Image :show="photo(row)" :source="photo(row)" />with a<Text :show="!photo(row)">initial behind it, so:showsees a non-empty string, renders the image that cannot load, and hides the fallback.
A blank 40×40 gap, no error, nothing in any log. Six of our screens had already grown the same inline workaround independently:
url.startsWith('/') ? nativeApiOrigin(craft) + url : urlTwo had not. That ratio is the argument for fixing it in the host rather than in every app.
What would help
Resolve a schemeless source against the app's own origin before deciding it is
an asset. The host already has it — craft.config.json carries the server the
build targets, and app.url is readable from the script side. Something like:
- if the uri has a scheme, use it as today;
- else if it starts with
/, resolve against the configured origin and fetch; - else treat it as a bundled asset name.
That ordering keeps every existing bundled-asset use working, since those are bare names rather than paths.
The same reasoning applies to fetch with a relative path, which a native
screen also cannot do today, and for the same reason: shared code that works in
a page has to be rewritten to know its own origin.
Versions
craft-native 0.0.237, @stacksjs/stx 0.2.563, iOS 26.5 simulator, Xcode 26.6.