Skip to content

Resolve relative image paths against the app's public directory - #56

Draft
simonhamp wants to merge 2 commits into
mainfrom
image-relative-src-public
Draft

Resolve relative image paths against the app's public directory#56
simonhamp wants to merge 2 commits into
mainfrom
image-relative-src-public

Conversation

@simonhamp

@simonhamp simonhamp commented Aug 20, 2026

Copy link
Copy Markdown
Member

Why

src implies a URL to anyone coming from the web, but the underlying renderers want a real path on disk. So <native:image src="img/logo.png"> — the most natural thing to write — silently rendered nothing.

Short-circuit it: a relative path means "an asset in public/", applied consistently to <native:image> and to leadingAvatar / leadingImage on <native:list-item>.

What changed

Resolution lives in the renderers, in one shared helper per platform (NativeUIImageSource.kt / NativeUIImageSource.swift):

src Treated as
https://…, data:…, file://…, content://… remote / as-is
/var/mobile/…/photo.jpg device file path
img/logo.png, ./img/logo.png public/img/logo.png on device

A leading slash deliberately stays a device filesystem path, not the public root as it would be on the web. That's how camera captures and gallery picks arrive, and reclaiming it would break them. Documented in the Boost guidelines and in both helpers, since it's the thing a web developer will get wrong.

Two things fixed on the way:

  • iOS list rows previously used AsyncImage(url:), which can't open any local file — so leadingAvatar="/var/mobile/…/photo.jpg" was silently broken there too, absolute paths included.
  • Local files on iOS now load through an NSCache keyed on path + mtime + size, so a list of local thumbnails doesn't re-read and re-decode per row, and overwriting a file in place (re-taking an avatar) doesn't serve a stale image. Android needs no counterpart — Coil already memory-caches by model.

CI: off dev-element, onto Packagist

Second commit, independent of the above. Now that v4 has shipped, the ^4.0 constraint in composer.json resolves straight from Packagist, so the workflow drops the MOBILE_AIR_TOKEN auth step, the VCS repository and the minimum-stability: dev tweaks — the committed composer.json was always clean, all of that was CI-only mutation. Forks now build the same way we do.

Verified by dry-run: resolves nativephp/mobile 4.2.0 with no token and no VCS repo. The 8.4 pin stays (8.3 still fails to resolve), but its comment was stale — core declares php ^8.4 itself now rather than inheriting it from endroid/qr-code.

Blocked on a core release

src/Elements/ListItem.php calls Native\Mobile\Edge\ImageSource::forDevice(), which doesn't exist yet. It's the Jump half of this: under native:jump PHP runs on the dev machine, so public/ is only reachable over HTTP, and core rewrites relative paths with asset(). The Jump router already forwards a phone-reachable Host header, so this needs no new plumbing and — importantly — no changes to the Jump app: it just receives an ordinary URL, which every existing build already renders.

Because CI now resolves released versions rather than tracking a branch, this PR stays red until that core change merges to main and is tagged, after which the constraint here needs bumping from ^4.0 to that release.

Testing

  • 204 Pest tests pass (3 new, covering the ListItem wiring in both modes).
  • All 45 Swift files pass xcrun swiftc -parse, the check CI runs.
  • Kotlin has no parse-only mode, so that side is verified by review against the existing PHPBridge usage in WebviewRenderer.kt.
  • Not yet exercised on a real device or in a Jump session.

🤖 Generated with Claude Code

simonhamp and others added 2 commits August 20, 2026 12:26
`src` implies a URL to anyone coming from the web, but the renderers
wanted a real path on disk. A relative path now means "an asset in
`public/`" — for `<native:image>` and for the list-item avatar and
image slots alike.

A leading slash deliberately stays a device filesystem path, since
that's how camera captures and gallery picks arrive.

Local files on iOS now go through an NSCache keyed on path, mtime and
size, so a list of local thumbnails doesn't re-read and re-decode per
row. Android needs no counterpart — Coil already memory-caches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drops the dev-element scaffolding: the MOBILE_AIR_TOKEN auth step, the
VCS repository, and the stability tweaks. The committed composer.json
was always clean — all of that was CI-only mutation — so the `^4.0`
constraint now resolves 4.2.0 straight from Packagist, and forks build
the same way we do.

`update` rather than `install`: composer.lock is gitignored, so there
is never a lock file to install from.

The 8.4 pin stays (8.3 still fails to resolve) but its comment was
stale — core declares `php ^8.4` itself now, rather than inheriting it
from endroid/qr-code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant