Problem
Image tests cover conversion but not the asynchronous view lifecycle. Current risks include:
- URL loading uses a semaphore and
nonisolated(unsafe) result state in Sources/TUIkitImage/ImageLoader.swift:248-279.
URLImageCache.shared is global (ImageLoader.swift:194-201).
_ImageCore constructs its concrete loader and writes source/phase boxes during rendering (Sources/TUIkit/Views/_ImageCore.swift:65-118).
- Phase updates can arrive outside the correct actor, repeat invalidation, or let an old response overwrite a newer source.
- Cancellation, status/MIME validation, byte/pixel limits, timeout, cache behavior, and disappear are not covered end to end.
Proposed solution
- Inject a per-app loader, cache, clock, and transport; keep live networking out of tests.
- Model request state on the owning actor and cancel on source/identity change, disappear, and shutdown.
- Deduplicate identical in-flight requests and prevent stale completion from committing.
- Validate HTTP status, MIME, byte count, dimensions, pixel count, timeout, redirects, and decode/decompression limits.
- Build public AsyncImage phase/API behavior on this pipeline in the content API issue.
Acceptance criteria
- Deterministic tests cover cache hit/miss, deduplication, source change, cancellation, timeout, redirect policy, status/MIME rejection, size/decode errors, disappear, and shutdown.
- A stale response can never replace a newer view phase.
- Two app runtimes share neither cache nor in-flight requests unless explicitly configured.
- Rendering does not mutate image phase merely by reading the same source.
- No semaphore or unsafe cross-actor result variable remains in async loading.
- Swift 6.0 macOS/Linux build, tests, lint, and DocC are warning-free.
Dependencies
Depends on per-app runtime #8, async loop #9, stable lifecycle identity #10, and pure Swift decoder #16.
Parallelization
After those contracts settle, transport/cache work can proceed beside content API work, with _ImageCore lifecycle integration owned here.
Commit structure
- Split this issue into small, thematic, independently revertible commits wherever the work can remain coherent.
- Every commit must build and keep its applicable tests/gates green. Do not commit an intentionally failing regression test; use local/known-issue characterization or land the test with the smallest fix.
- Keep characterization/fixtures, mechanical renames or moves, semantic changes, and documentation/migration updates separate when each step remains green.
- The issue boundary is not a commit boundary; multiple commits are expected for independently reversible changes.
Problem
Image tests cover conversion but not the asynchronous view lifecycle. Current risks include:
nonisolated(unsafe)result state inSources/TUIkitImage/ImageLoader.swift:248-279.URLImageCache.sharedis global (ImageLoader.swift:194-201)._ImageCoreconstructs its concrete loader and writes source/phase boxes during rendering (Sources/TUIkit/Views/_ImageCore.swift:65-118).Proposed solution
Acceptance criteria
Dependencies
Depends on per-app runtime #8, async loop #9, stable lifecycle identity #10, and pure Swift decoder #16.
Parallelization
After those contracts settle, transport/cache work can proceed beside content API work, with
_ImageCorelifecycle integration owned here.Commit structure