react-native-image-compression-kit
v0.2.55
Published
Native-first image compression and transcoding pipeline for React Native.
Maintainers
Readme
React Native Image Compression Kit is a native image compression and transcoding pipeline that loads any supported image format, compresses it, and returns it in a supported format selected by the developer.
Overview
React Native image compression workflows are often split across format-specific or feature-specific modules. Compression, resizing, HEIC conversion, WebP handling, metadata policy, and platform capability checks can become separate decisions instead of one predictable pipeline.
This project is designed to make compression the center of the API. It will combine detect, decode, auto-orient, resize, transcode, and encode steps behind one consistent React Native interface.
Format conversion is treated as part of the compression result. Developers choose the supported output format they want, and the native pipeline handles the work needed to produce it.
Status
Version 0.2.55 is the Action Pin artifact GitHub OIDC attestation and offline signer verification release. It keeps native behavior and the public API unchanged while attesting each successful Action Pin Review artifact-manifest.json, retaining the Sigstore bundle and pinned GitHub trusted root separately, and cross-checking the signed repository/workflow/ref/source commit with the execution identity reproduced from the provenance artifact.
Version 0.2.54 was the previous unpublished Action pin provenance execution identity and artifact manifest binding candidate. It bound every manual Action Pin Review report to its source repository/ref/head SHA, workflow identity, run ID/attempt, exact workflow definition, canonical Action lock, normalized dispatch event, and a complete evidence manifest that replays offline.
Version 0.2.53 was the previous unpublished GitHub Action pin update provenance and manual review gate candidate. It resolved reviewed lightweight or annotated Action tags, compared trusted baseline and candidate locks, and retained an offline-replayable tag-to-commit report.
Version 0.2.52 was the previous unpublished immutable GitHub Actions pin and workflow supply-chain gate candidate. It pinned every remote workflow Action to a full commit SHA and added the network-free canonical workflow lock gate.
Version 0.2.51 was the previous unpublished expiration-independent release evidence archive and offline replay gate candidate. It moved the validated v0.2.50 provenance and attestation evidence under repository ownership and added the complete network-free replay gate.
Version 0.2.50 was the previous npm latest GitHub artifact attestation and offline identity verification release for react-native-image-compression-kit. It keeps native behavior, the public API, the registry report schema, and the existing four-file provenance bundle unchanged. The release attests the canonical bundle-manifest.json with GitHub Actions OIDC, preserves the attestation bundle and pinned trusted root separately, and verifies subject digest, repository, workflow, ref, source commit, issuer, predicate, hosted runner, and verified timestamps without network access.
Version 0.2.49 was the previous unpublished Registry provenance bundle offline verification candidate. It atomically retained the exact tarball consumed by registry smoke with canonical report/stdout files and a checksum manifest, then verified the bundle without npm, GitHub, or other network access.
Version 0.2.48 was the previous npm latest registry provenance and manual CI gate release for react-native-image-compression-kit. It keeps Android/iOS runtime behavior and the public API unchanged. It extends the post-publish registry smoke with exact version/dist-tag agreement, real tarball README status, integrity/shasum, required/forbidden package contents, and clean consumer install/typecheck evidence in one stable canonical JSON report.
Version 0.2.47 was the previous npm latest iOS PASS replay automation gate release for react-native-image-compression-kit. It keeps Android and iOS runtime behavior unchanged, keeps AVIF output disabled, and turns the committed PASS replay fixture into an explicit local and CI quality gate. validateIOSSmokePassPayload() now enforces the exact capability-selected field order, platform: 'ios', positive safe-integer *ResultBytes values, boolean WebP-output/AVIF-input flags, and duplicate-free capability-consistent unsupported format arrays. validateIOSSmokePassReplayFixture() applies that semantic contract in addition to the existing fixture schema, provenance, source-line, and SHA-256 checks. scripts/refresh-ios-smoke-pass-replay.mjs retains refresh and text-mode check behavior, adds source-log-free --audit, and emits stable machine-readable schemaVersion, mode, status, artifactPath, differences, and error fields through --check --json and --audit --json. Check and audit paths remain read-only and network-free. pnpm verify and the iOS Validation workflow now run the standalone audit, while Vitest pins payload-invalid, current, stale, noncanonical, missing, malformed, schema-invalid, flag-conflict, stdout/stderr, and no-write contracts.
The committed replay artifact keeps workflow iOS Validation, run 28928015548, head SHA c6981c3b6b06e5e6e34f42147a94e4299a0f82b2, and source-line SHA-256 c20c9e72f2b9f3159d7db56c7c811a3ecb81555a9d9e90350d2e155e6f832dc6. When it becomes stale, download or export a newer successful iOS Validation job log separately, then run the offline refresh command below. The CLI performs no GitHub or other network requests.
pnpm fixtures:ios-pass-replay -- \
--log-file /path/to/ios-validation.log \
--workflow-name "iOS Validation" \
--run-id 28928015548 \
--run-url https://github.com/GGULBAE/react-native-image-compression-kit/actions/runs/28928015548 \
--head-sha c6981c3b6b06e5e6e34f42147a94e4299a0f82b2The command extracts exactly one complete PASS source line, derives its job, step, and timestamp, recalculates SHA-256 over the exact UTF-8 line without a trailing newline, and regenerates the structured JSON fields before the documented local verification gates are run.
Use the same local log and provenance to verify the committed artifact without modifying it:
pnpm fixtures:ios-pass-replay:check -- \
--log-file /path/to/ios-validation.log \
--workflow-name "iOS Validation" \
--run-id 28928015548 \
--run-url https://github.com/GGULBAE/react-native-image-compression-kit/actions/runs/28928015548 \
--head-sha c6981c3b6b06e5e6e34f42147a94e4299a0f82b2Append --json to the check command when automation needs the stable report schema instead of human-readable text. The report fields are ordered as schemaVersion, mode, status, artifactPath, differences, and error; status is current, stale, or invalid. A current report exits 0, while stale and invalid reports exit 1 and still write exactly one JSON object to stdout with no human prefix.
Audit the committed artifact without retaining or passing the original Actions log:
pnpm fixtures:ios-pass-replay:audit -- --jsonAudit mode validates canonical JSON, fixture schema, provenance fields, the exact source-line SHA-256, and the capability-driven PASS payload contract owned by scripts/ios-smoke-contract.mjs and scripts/ios-smoke-pass-replay-fixture.mjs. Check additionally compares the artifact with a supplied local log and provenance. Check and audit modes perform no writes; refresh, check, and audit perform no GitHub or other network requests.
Registry verification confirmed both npm version and dist-tags.latest at 0.2.50, published at 2026-07-14T06:05:27.963Z. The real 51-file registry tarball retained the registry-independent Status: v0.2.50 release package README, matched integrity sha512-FCIEFSpcbb3pUbk7jVWuQxke0+h77q3f2jAtK2C3GILlvi6X7MvjSLH0BBMqd94qHdeluFclW6Rft+SjpjK/rw== and shasum da8779a4fb67e52ef081302eec76aa12706691a2, contained no guarded stale-status snippets or development-only files, and passed clean consumer installation and public API typechecking. The npm-only promotion used one successful npm publish --tag latest; no manual dist-tag change, git tag, or GitHub Release was created.
Run the registry smoke with --artifact-dir to atomically preserve the exact validated tarball instead of downloading it again in the workflow:
pnpm smoke:registry -- \
--version 0.2.50 \
--expect-tag latest \
--json \
--artifact-dir registry-validationThe fixed bundle contains registry-provenance.json, byte-identical stdout.json, package.tgz, and canonical bundle-manifest.json. The manifest pins ordered schemaVersion, status, package/version/tag identity, report/stdout SHA-256, tarball SHA-512 integrity and SHA-1 shasum, file count, packed size, unpacked size, and error fields. Bundle creation uses a sibling temporary directory plus atomic rename, so a write or rename failure does not expose an incomplete artifact.
After downloading a Registry Validation artifact, verify it without npm, GitHub, or any other network access:
pnpm verify:registry-provenance -- \
--artifact-dir /path/to/registry-validation \
--expect-package react-native-image-compression-kit \
--expect-version 0.2.50 \
--expect-tag latest \
--jsonThe offline verifier requires the exact four-file bundle, canonical manifest/report bytes and field ordering, report/stdout byte equality, caller-supplied package/version/tag expectations, all declared digests, actual gzip/tar sizes, tarball package.json, required/forbidden package files, and real README release status. It parses the archive in memory, rejects traversal, links, duplicate files, unsupported entries, and paths outside package/, and never extracts tarball contents. --report-file <path> atomically writes the exact canonical verifier JSON emitted to stdout. The manual Registry Validation workflow builds this bundle from the same tarball used by registry smoke, runs the offline verifier before upload, and records bundle-manifest, report, stdout, and tarball checksums in the GitHub Step Summary. Successful Registry Validation run 29182554246 on commit d233529ddb3804b9fff05832bc4b327348f0fc51 uploaded the fixed four-file v0.2.48 bundle with GitHub artifact digest sha256:9039f1c127ce2f743d17a80e4469972a65343cabf91f3b5074808294ac670fa3; the downloaded bundle passed all offline checks without publishing a package.
The same manual workflow uses actions/attest@v4 to attest the canonical manifest and uploads a separate attestation artifact. After downloading both workflow artifacts, verify the GitHub identity and subject digest offline:
pnpm verify:registry-attestation -- \
--manifest /path/to/registry-validation/bundle-manifest.json \
--attestation-bundle /path/to/registry-attestation/attestation.jsonl \
--trusted-root /path/to/registry-attestation/trusted-root.jsonl \
--expect-repository GGULBAE/react-native-image-compression-kit \
--expect-workflow GGULBAE/react-native-image-compression-kit/.github/workflows/registry-validation.yml \
--expect-ref refs/heads/master \
--expect-head-sha <workflow-head-sha> \
--json \
--report-file /path/to/registry-attestation/attestation-verification.jsonThe attestation verifier calls the official GitHub CLI with --bundle and --custom-trusted-root while network proxies are forced to a closed local endpoint. It pins the trusted-root bytes to SHA-256 65ca537f6ed8a47fd0e560c421baa1f6c1efb8b25fc200d8c5c02c0e92eb2b9c, requires GitHub's OIDC issuer and SLSA v1 predicate, rejects self-hosted runners, and then independently validates the canonical verifier JSON. Its fixed report fields are schemaVersion, status, subject, subjectSha256, repository, signerWorkflow, sourceRef, sourceDigest, oidcIssuer, predicateType, verifiedTimestamps, and error; verified timestamps are normalized to UTC ISO strings so report bytes are timezone-independent, and --report-file atomically writes exactly the stdout bytes. The existing registry-provenance-<version> artifact remains exactly four files. The separate registry-provenance-attestation-<version> artifact contains attestation.jsonl, trusted-root.jsonl, and attestation-verification.json so the evidence can be replayed without npm, GitHub, Sigstore, or any other network service.
Successful Registry Validation run 29310375801 on release-ready commit 2b198c5f6125de6ad5bae76fc835ff5b935984f0 attested manifest SHA-256 0a152ce1989267ce5c2fa01096a5e0dc44200245e29843857b24c52d0b746773 as attestation 35201998. The exact four-file registry-provenance-0.2.50 artifact has GitHub digest sha256:031302873239c74234179041e6b3f7ff8d6fe281351dfb93a3f5e4ed9573ec71; the separate three-file registry-provenance-attestation-0.2.50 artifact has GitHub digest sha256:01a74cc10cb2c89e309d58ddf83f285ada1a3ceb3af70e78b8cd70dc6c627cea. Downloaded offline replay reproduced the workflow report byte-for-byte at SHA-256 380574a9b985e7d046953fa1338d47437753097ee531af85990d0257b3addb8e under both UTC and Asia/Seoul, while the Step Summary recorded successful provenance, online attestation, downloaded-bundle offline attestation, and pinned-root checks.
Expiration-independent release evidence
The v0.2.51 candidate commits the downloaded v0.2.50 workflow evidence at evidence/npm/0.2.50/. The repository-owned archive contains one canonical release-evidence-index.json, exactly four files in provenance/, and exactly three files in attestation/. The index pins Registry Validation run 29310375801, both artifact IDs and GitHub archive digests, attestation 35201998, source commit 2b198c5f6125de6ad5bae76fc835ff5b935984f0, artifact expiration 2026-10-12T06:06:31Z, every retained file size/SHA-256, and aggregate evidence SHA-256 1548695379c92cfb3ab679292ac173dd2148e174371d559ec0512b12e796a149.
Replay the entire retained trust chain with one network-free command:
pnpm verify:release-evidence -- --version 0.2.50The verifier requires the exact archive layout and canonical index bytes, matches the index against the committed version policy, recomputes all seven file digests and the aggregate evidence digest, runs the existing tarball provenance verifier, and invokes GitHub CLI attestation verification with the retained bundle and pinned trusted root while all network proxies point to a closed local endpoint. It then requires the regenerated attestation report to match the retained attestation-verification.json byte-for-byte and checks package/version/tag, repository, workflow, ref, source commit, subject digest, OIDC/SLSA identity, verified timestamp, run chronology, and artifact expiration metadata.
Successful output is one canonical JSON object with ordered schemaVersion, status, archiveDir, package, version, expectedTag, evidenceSha256, provenanceReportSha256, manifestSha256, attestationReportSha256, sourceDigest, checks, and error fields. Ordered checks are layout, index, files, provenance, attestation, identity, and timestamps; --report-file atomically writes the same bytes. pnpm verify runs this committed v0.2.50 replay without npm, GitHub, Sigstore, or other network access. The evidence/ tree, tarball, scripts, and tests remain repository-only and are excluded from the npm package file list.
Immutable workflow Action supply chain
All 30 remote uses: declarations across the five GitHub workflow files are pinned to lowercase 40-character commit SHAs. Each pin retains its reviewed release tag as an inline comment such as # v7; mutable tags or branches, short SHAs, missing release comments, and different SHAs for the same Action are rejected.
.github/actions-lock.json is canonical one-line JSON with ordered schemaVersion, status, workflows, actions, and error fields. Each of the nine unique Action records fixes action, source repository, reviewed version, immutable sha, and per-workflow usages with exact occurrence counts. The committed lock SHA-256 is 81439816af31b56e592a761eb32a622720adb97f03e8fab6c6ee558c2216f18c.
Verify the complete local workflow/lock contract without resolving tags or making network requests:
pnpm verify:workflow-supply-chain -- --jsonThe fixed report fields are schemaVersion, status, lockFile, workflowCount, actionCount, usageCount, lockSha256, checks, and error. Ordered checks are workflows, pins, comments, consistency, lock, and dependabot; --report-file atomically writes bytes identical to stdout. Fixtures reject mutable refs, short SHAs, missing/additional/duplicate/digest-drift lock entries, cross-workflow SHA disagreement, comment drift, Dependabot drift, and partial report replacement.
.github/dependabot.yml enables weekly github-actions updates. A Dependabot update must preserve a full SHA plus reviewed release-tag comment and update the canonical lock in the same change; default pnpm verify fails closed until all workflow occurrences and the lock agree. The verifier does not call GitHub, resolve a release tag, or update a pin. The lock, Dependabot configuration, verifier scripts, tests, and retained evidence are repository-only and excluded from the npm package.
Manual Action pin provenance review
The workflow lock proves that committed workflow pins agree; the manual Action Pin Review workflow proves that a reviewed GitHub tag resolves to the proposed locked commit at review time. It checks out both the selected candidate ref and a trusted baseline ref, runs the offline workflow/lock gate first, then resolves refs/tags/<release-tag> through the GitHub Git database API. Lightweight tags resolve directly to a commit. Annotated tags retain both the tag-reference object and its dereferenced tag object and must resolve directly to a commit.
The networked command boundary is explicit:
pnpm review:action-pin -- \
--action gradle/actions/setup-gradle \
--repository gradle/actions \
--release-tag v6 \
--proposed-sha 3f131e8634966bd73d06cc69884922b02e6faf92 \
--baseline-ref master \
--baseline-lock /path/to/base/.github/actions-lock.json \
--candidate-lock .github/actions-lock.json \
--source-repository GGULBAE/react-native-image-compression-kit \
--source-ref refs/heads/master \
--source-head-sha '<40-character candidate commit SHA>' \
--workflow-name "Action Pin Review" \
--workflow-path .github/workflows/action-pin-review.yml \
--workflow-ref GGULBAE/react-native-image-compression-kit/.github/workflows/action-pin-review.yml@refs/heads/master \
--workflow-sha '<40-character workflow commit SHA>' \
--workflow-file .github/workflows/action-pin-review.yml \
--run-id '<GitHub run ID>' \
--run-attempt 1 \
--event-name workflow_dispatch \
--github-event /path/to/github-event.json \
--artifact-dir action-pin-review \
--jsonThe validator requires the Action in both locks, an unchanged owner/repository, no major-version downgrade, exact candidate release-tag and SHA agreement, exact GitHub API repository/tag/object URLs, a valid lightweight or single annotated-tag dereference, and final tag-to-proposed-commit equality. It also normalizes the workflow_dispatch payload and requires its repository, ref, workflow path, review inputs, and baseline ref to match the supplied execution identity. It never edits workflow pins or the lock.
The canonical artifact contains artifact-manifest.json, action-pin-provenance.json, baseline and candidate lock bytes, canonical github-execution.json, normalized workflow-dispatch-event.json, exact action-pin-review-workflow.yml bytes, tag-reference.json, and optional annotated-tag.json. The manifest records every evidence file's sorted relative path, byte size, and SHA-256; report and manifest files are excluded from that list to avoid recursive digests, while the report binds the manifest SHA-256. Offline replay restores source/workflow/run identity from github-execution.json, so changing report identity alone is rejected. Verification also rejects noncanonical or traversing paths, duplicate/missing/additional files, symlinks, size drift, and digest drift.
The schema v2 report adds ordered sourceRepository, sourceRef, sourceHeadSha, workflowName, workflowPath, workflowRef, workflowSha, runId, and runAttempt fields after tag resolution. Evidence additionally binds the dispatch event, workflow definition, and manifest digests; ordered checks are inputs, execution, event, registration, repository, releaseTag, noDowngrade, candidateLock, workflow, tagReference, dereference, commit, and manifest. Artifact creation and standalone --report-file writes use temporary siblings and atomic rename so partial reports are never exposed.
After downloading the workflow artifact, reproduce the report byte-for-byte without GitHub or any other network service:
pnpm verify:action-pin-provenance -- \
--artifact-dir /path/to/action-pin-review \
--json \
--report-file /path/to/reproduced-action-pin-provenance.jsonThe workflow then uses GitHub Actions OIDC to attest the canonical artifact-manifest.json. It uploads the provenance directory as action-pin-review-<run-id> and uploads attestation.jsonl, trusted-root.jsonl, and attestation-verification.json separately as action-pin-attestation-<run-id>. Verify both downloaded artifacts with network access blocked:
pnpm verify:action-pin-attestation -- \
--artifact-dir /path/to/action-pin-review \
--attestation-bundle /path/to/action-pin-attestation/attestation.jsonl \
--trusted-root /path/to/action-pin-attestation/trusted-root.jsonl \
--json \
--report-file /path/to/action-pin-attestation/attestation-verification.jsonThe verifier first replays the complete provenance artifact, then invokes the official GitHub CLI with the downloaded bundle, pinned trusted root, closed network proxies, exact repository, signer workflow, source ref, source commit, GitHub OIDC issuer, SLSA v1 predicate, and hosted-runner policy. Its fixed report binds the manifest SHA-256, source repository/ref/head SHA, workflow SHA, run ID/attempt, signer workflow, verified timestamps, and ordered provenance, manifest, subject, repository, workflow, ref, sourceDigest, workflowDigest, invocation, and signature checks. The signed SLSA invocationId must equal the provenance run ID/attempt URL. Fixtures reject wrong subject, repository, workflow, ref, source SHA, invocation, provenance-to-manifest disagreement, and bundle tampering without network access.
Successful Action Pin Review run 29320049736 on source commit 2db9c690627a155ea1fe3d83ce4e71b13ba9aa7c attested artifact-manifest.json SHA-256 d4f85ff96839e5424a3152275f7d1b4047073df9fab1be2bf6d249cd7fd948a8 as attestation 35224280. The provenance artifact has ID 8305522072 and GitHub digest sha256:d09e9b5d579d6ada53caa6e3d012b6ad7d6665eab9fd8bfbb73c275ee88b2439; the separate attestation artifact has ID 8305522316 and GitHub digest sha256:3ffcf711c0c3827636b6a6cfca9f83ee010fcc2f849933d69f4c51b7e0984f76. The downloaded report SHA-256 is 361a7f8357070c879f620b9db101cef114e82b9b785a5d1511daa0c6ff6c2cd8, bundle SHA-256 is 701fc32bac71bf310f30001b052183f567673ebf867829d53fcb6f8d88ae0e09, and trusted-root SHA-256 is the pinned 65ca537f6ed8a47fd0e560c421baa1f6c1efb8b25fc200d8c5c02c0e92eb2b9c. Local blocked-network replay reproduced the workflow report byte-for-byte under both UTC and Asia/Seoul. These exact downloaded provenance and attestation files are the committed default fixture.
The committed annotated-tag fixture for gradle/actions/setup-gradle@v6 includes a synthetic GitHub workflow_dispatch event, exact current workflow definition, and canonical manifest, then exercises complete replay from default pnpm verify. Only scripts/action-pin-review-github.mjs performs GitHub requests; the validation core, offline verifier, fixture gate, and default verify path contain no network or command-execution route. The manual workflow writes source/workflow/run identity plus lock/workflow/manifest digests to the GitHub Step Summary and uploads the exact artifact, but it never changes a pin, lock, Dependabot PR, npm package, or trusted root.
The Android compressImage() scaffold still rejects output.format: 'avif' with ERR_NOT_IMPLEMENTED before source access or helper entry, keeps avif.output=false, and leaves metadata preserve, target-size, and animated AVIF production semantics disabled.
Android includes a published image compression MVP for file:// and content:// JPEG, PNG, WebP, GIF, HEIC, HEIF, and AVIF inputs, JPEG EXIF orientation correction, optional resize, metadata preserve / privacy-filtered safe / strip handling for JPEG source to JPEG output, and JPEG, PNG, or WebP output encoding. GIF input is decoded as a static first frame. HEIC / HEIF input is Android SDK and device-codec dependent: API 28+ uses ImageDecoder, API 26-27 attempts a guarded BitmapFactory fallback, and earlier Android versions reject HEIC / HEIF with ERR_UNSUPPORTED_FORMAT. AVIF input is Android 14+ only and uses ImageDecoder. Android getImageCompressionCapabilities() reports AVIF input=true, AVIF output=false, and notes that selecting output.format: 'avif' rejects with ERR_NOT_IMPLEMENTED. Android AVIF output remains disabled until the MediaCodec image/avif encode/decode-back smoke produces a complete AVIF file with ftyp avif/avis signature and ImageDecoder decode-back validation.
The current iOS JPEG/PNG/GIF/WebP/HEIC/HEIF/AVIF MVP supports file:// and content:// JPEG, PNG, GIF, WebP, HEIC, HEIF, or runtime-available AVIF input, optional resize, quality-based JPEG output, target-size JPEG output with output.maxBytes, PNG output, runtime ImageIO-backed WebP output with target-size output.maxBytes when CGImageDestination advertises WebP destination support, cache-file results, metadata: 'preserve' for JPEG source to JPEG output with output orientation and pixel dimension metadata normalized after rendering, and safe / strip metadata policies that re-encode without copying source metadata. GIF, WebP, HEIC, HEIF, and runtime-available AVIF input are decoded as static images through ImageIO on iOS. iOS getImageCompressionCapabilities() reports JPEG input/output, PNG input/output, GIF input with no GIF output, WebP input with runtime-gated WebP output, HEIC input with no HEIC output, HEIF input with no HEIF output, AVIF input only when CGImageSourceCopyTypeIdentifiers() advertises an AVIF source type, AVIF output always false, and notes that selecting output.format: 'avif' rejects with ERR_NOT_IMPLEMENTED; future iOS AVIF output must be runtime-gated by ImageIO AVIF destination support and static output validation.
Both platforms keep metadata='preserve', output.maxBytes, and animated AVIF preservation unsupported for AVIF output until explicitly designed and tested. The capabilities also report metadataPolicies: ['preserve', 'safe', 'strip'], target-size compression support for JPEG output and runtime-available WebP output, and no cancellation. The v0.2.17 Android AVIF output smoke keeps AVIF output disabled while attempting a repo-owned 16x12 Bitmap to static AVIF cache-file encode through an API 34+ MediaCodec image/avif route, validating ftyp avif / avis signature bytes and ImageDecoder decode-back dimensions when a file is produced. The v0.2.20 candidate keeps that smoke disabled-by-contract and adds production-decision fields: blockerCode, outputCanBeEnabled=false, and productionDecision. If the SDK is too old, the emulator has no encoder, the codec route fails, the muxer rejects the stream, the result has invalid ftyp avif / avis signature bytes, or ImageDecoder decode-back fails, instrumentation records an explicit blocker code (sdk_unavailable, no_image_avif_encoder, codec_failure, invalid_signature, or decode_back_failure) and capability reporting remains output=false. The v0.2.21 candidate adds AndroidAvifOutputProductionScaffold at the Android compressImage() output boundary, so AVIF output requests parse metadata and output.maxBytes but reject before source access or MediaCodec encode/decode-back helper entry while capability reporting remains output=false. The v0.2.22 candidate extracts the encode/decode-back implementation into AndroidAvifOutputHelper, adds explicit helper input/output/result types, and keeps the scaffold's willEnterEncodeDecodeBackHelper=false boundary before source access. The v0.2.23 candidate adds AndroidAvifOutputHelperDependencies so tests can inject encoder, muxer, output-file, and validation behavior for fake encoded bytes, invalid signatures, decode-back failures, and codec failures while compressImage() and capability reporting still keep AVIF output disabled. The v0.2.24 candidate adds an injected muxed success contract that fixes helper success expectations for byteSize, signatureValid, decodeBackValid, blockerCode=null, and PRODUCTION_DECISION_SMOKE_PASSED_KEEP_DISABLED while production capability reporting remains avif.output=false. The v0.2.25 candidate adds a direct-output success contract that fixes the direct encoder output route, output path, byte size, blockerCode=null, production decision, and muxer-skip expectation when direct fake AVIF bytes pass decode-back validation. The v0.2.26 candidate fixes helper details ordering across direct, muxed, invalid signature, decode-back failure, and codec failure paths so the injectable validation seam, dependency details, and route blockers remain stable for production-wiring diagnostics. The v0.2.27 candidate fixes blocked-route details for sdk_unavailable and no_image_avif_encoder paths and proves the smoke adapter preserves blocked helper blockerCode, details, and outputCanBeEnabled=false. The v0.2.28 candidate fixes helper temp-file lifecycle expectations for direct, muxed, invalid-signature, and decode-back validation paths so returned outputFilePath/byteSize always describe the chosen final validation file while intermediate direct files are not reported as muxed/failure results. The v0.2.29 candidate fixes helper validation-result provenance expectations so direct and muxed validation details each carry file name, byte size, signature result, decode-back result, and exact encoder -> direct validation -> muxer -> final validation ordering after direct failure. The v0.2.30 candidate hardens iOS smoke retry and timeout diagnostics around RNICK_IOS_SMOKE_PASS so CI logs explain simulator/app/log state before a timeout-only retry or final failure. The v0.2.31 candidate fixes those iOS smoke retry and diagnostic contracts in simulator-free Node-level tests before further CI hardening. The v0.2.32 candidate fixes the CLI timeout assembly and retry warning order with fake launch/log stream/Metro/unified-log fixtures. The v0.2.33 candidate fixes iOS smoke process lifecycle cleanup with fake EventEmitter Metro/log stream fixtures. The v0.2.34 candidate fixes iOS smoke log stream error output, snapshot state, and timeout diagnostics fixture coverage. The v0.2.35 candidate fixes iOS smoke diagnostics packed log artifact and GitHub Step Summary fixture coverage. The v0.2.36 candidate fixes iOS smoke artifact failure-path dry-run fixture coverage. The v0.2.37 candidate fixes iOS smoke diagnostics artifact schema snapshot coverage. The v0.2.38 candidate fixes iOS smoke PASS payload schema snapshot coverage. The v0.2.39 candidate fixes iOS WebP-output available PASS payload schema snapshot coverage. The v0.2.40 release fixes iOS AVIF-input unavailable PASS payload schema snapshot coverage. The v0.2.41 candidate fixes iOS PASS payload schema matrix helper coverage. The v0.2.42 candidate fixes iOS PASS payload CI log replay fixture coverage. The v0.2.43 candidate fixes replay fixture source provenance and refresh guidance. The still-open production gates are production wiring, byte-signature validation, ImageDecoder decode-back validation, explicit metadata preserve behavior, output.maxBytes semantics, and animated AVIF boundaries.
The v0.2.44 candidate fixes replay fixture source-line SHA-256 integrity and missing/duplicate PASS-line rejection.
The v0.2.45 candidate fixes structured replay artifact ownership and deterministic offline refresh coverage.
The v0.2.46 candidate fixes read-only offline replay artifact freshness checking and concise drift reporting.
The v0.2.47 release fixes semantic PASS payload validation, standalone replay artifact auditing, stable JSON reports, and local/CI audit gating.
The v0.2.48 release adds a canonical registry provenance report, exact version/dist-tag validation, shared README status validation, offline failure fixtures, atomic report writes, and a manual Registry Validation workflow.
The v0.2.49 candidate adds an atomic four-file provenance bundle, exact-tarball retention, checksum manifest, secure in-memory tar inspection, a stable offline verifier JSON report, and network-free corruption/security fixtures.
The v0.2.50 release adds GitHub OIDC artifact attestation for the canonical bundle manifest, a pinned offline trusted root, strict repository/workflow/ref/commit/subject verification, and deterministic offline attestation fixtures without changing the provenance bundle contract.
The GitHub Actions iOS Validation runner currently uses Xcode 26.5 and the iPhoneSimulator26.5 SDK, and reports WebP output=false because ImageIO does not advertise a WebP destination type there; AVIF input is capability-gated the same way and rejects with ERR_UNSUPPORTED_FORMAT when ImageIO does not advertise AVIF source support. GIF output, GIF animation preservation, animated WebP preservation, animated AVIF preservation, HEIC / HEIF output, AVIF output, and metadata preservation outside JPEG source to JPEG output are not implemented yet.
Current Implementation Scope
The current implementation is intentionally small:
- Runtime compression is implemented on Android and on the current iOS JPEG/PNG/GIF/WebP/HEIC/HEIF/runtime-available AVIF MVP surface.
- Android supports
file://andcontent://local URI input. iOS supportsfile://and best-effortcontent://local URI input through Foundation URL loading. - JPEG, PNG, WebP, GIF, HEIC, HEIF, and AVIF input. GIF input is static first-frame only, Android HEIC / HEIF input depends on Android SDK and device codec support, iOS HEIC / HEIF input uses static ImageIO decode, Android AVIF input requires Android 14+ baseline image support, and iOS AVIF input requires runtime ImageIO AVIF source support.
- iOS input is currently JPEG, PNG, static ImageIO GIF, static ImageIO WebP, static ImageIO HEIC, static ImageIO HEIF, and runtime-available static ImageIO AVIF.
- Android output is JPEG, PNG, and WebP. iOS output is JPEG, PNG, and WebP only when ImageIO advertises a WebP destination type at runtime. HEIC, HEIF, and AVIF output reject with
ERR_NOT_IMPLEMENTEDon both platforms. - Quality-based compression for JPEG, Android WebP output, and runtime-available iOS WebP output. PNG output ignores
quality. - Target-size compression with
maxBytesfor Android JPEG and WebP output, iOS JPEG output, and runtime-available iOS WebP output. Android and iOS PNG output rejectmaxBytes. - JPEG EXIF orientation correction before resize and selected output encoding.
- Optional resize with
maxWidth,maxHeight, andcontain,cover, orstretchmode. - Android supports metadata
preserve, privacy-filteredsafe, andstrippolicies for JPEG source to JPEG output. PNG/WebP/GIF/HEIC/HEIF/AVIF sources and PNG/WebP output do not preserve source EXIF metadata. iOS supportspreserveonly for JPEG source to JPEG output;safeandstripre-encode without copying source metadata, and non-JPEG preserve requests reject withERR_NOT_IMPLEMENTED. - Output file written to the platform app cache directory.
CompressionResultreturnsuri,format, finalwidth, finalheight,byteSize,originalByteSize, andcompressionRatio.
The following remain planned and are not implemented in the MVP:
- AVIF output; selecting
output.format: 'avif'currently rejects withERR_NOT_IMPLEMENTED. - HEIC / HEIF output.
- GIF output and GIF/WebP animation preservation.
- Metadata support for non-JPEG formats and broader iOS metadata preservation.
Why
Image handling in React Native can become fragmented when compression, HEIC conversion, WebP processing, resizing, and metadata handling live in separate tools. That fragmentation can lead to:
- Different APIs for different formats.
- Platform-specific behavior that is difficult to predict.
- Limited support for target file size compression.
- Image processing logic coupled too tightly to upload logic.
- Extra application code for capability checks and fallback paths.
React Native Image Compression Kit aims to provide one native-first API for image compression while staying composable with any uploader or storage layer.
Core Concept
Supported input image
↓
Detect and decode
↓
Auto-orient
↓
Resize when needed
↓
Compress
↓
Encode in selected format
↓
Local output URIAny supported image in. A compressed image out, in your chosen supported format.
Planned Features
The following product features are planned or only partially implemented.
- Automatic format detection.
- Quality-based compression.
- Target file size compression with
maxBytes. Android MVP support is implemented for JPEG and WebP output; iOS support is implemented for JPEG output and runtime-available WebP output, while iOS PNG output intentionally rejectsmaxBytes. - Optional resize during compression. Android MVP and iOS JPEG/PNG/GIF/WebP/HEIC/HEIF/runtime-available AVIF MVP support is implemented.
- Output format selection. Android MVP supports JPEG, PNG, WebP, static first-frame GIF, SDK-gated HEIC / HEIF, and Android 14+ AVIF input with JPEG, PNG, and WebP output. iOS MVP supports JPEG, PNG, and runtime-gated ImageIO-backed WebP output from JPEG, PNG, static GIF, static WebP, static HEIC, static HEIF, and runtime-available static AVIF input.
- Automatic EXIF orientation correction. Android MVP support is implemented for JPEG input.
- Metadata preservation and stripping policies. Android MVP supports
preserve,safe, andstripfor JPEG source to JPEG output. iOS supportspreservefor JPEG source to JPEG output, plussafeandstripno-copy re-encode behavior. - Alpha-channel handling.
- Local URI input and output.
- Compression statistics.
- Cancellation.
- Runtime capability inspection.
- Android and iOS support. Android has the broader MVP; iOS currently supports JPEG/PNG/GIF/WebP/HEIC/HEIF/runtime-available AVIF input to JPEG, PNG, or runtime-gated ImageIO-backed WebP output, with GIF, WebP, HEIC, HEIF, and AVIF decoded as static ImageIO images.
- React Native New Architecture-first design.
Planned Format Support
The table below describes planned input and output support. Actual availability may depend on platform codecs and will be reported through runtime capability APIs.
| Format | Planned input | Planned output | Notes | |---|---:|---:|---| | JPEG | Yes | Yes | Lossy compression | | PNG | Yes | Yes | Lossless compression | | WebP | Yes | Yes | Lossy and lossless | | HEIC / HEIF | Yes | Optional / later | Android input implemented with SDK and codec gating; iOS input implemented as static ImageIO decode | | AVIF | Yes | Later | Android input implemented on API 34+ with ImageDecoder; iOS input is runtime-gated by ImageIO AVIF source support | | GIF | Yes | Later | Static first-frame support before animation preservation |
Current Android MVP support is narrower than the planned table: JPEG, PNG, WebP, static first-frame GIF, SDK-gated HEIC, SDK-gated HEIF, and Android 14+ AVIF input are implemented, and JPEG, PNG, and WebP output are implemented. Current iOS MVP support is narrower again: JPEG, PNG, static ImageIO GIF, static ImageIO WebP, static ImageIO HEIC, static ImageIO HEIF, and runtime-available static ImageIO AVIF input are implemented, JPEG output is implemented with quality, resize, and target-size compression, PNG output is implemented without target-size compression, and WebP output is implemented with quality and target-size compression through ImageIO when the runtime advertises a WebP destination type. GIF output, GIF animation preservation, animated WebP preservation, animated AVIF preservation, HEIC / HEIF output, and AVIF output remain planned; HEIC, HEIF, and AVIF output selections reject with ERR_NOT_IMPLEMENTED. HEIC / HEIF inputs on Android versions below 8.0, AVIF inputs on Android versions below 14, and AVIF inputs on iOS runtimes without ImageIO AVIF source support reject as ERR_UNSUPPORTED_FORMAT. Corrupt supported-format inputs, including corrupt GIF, HEIC / HEIF, and AVIF candidates on supported SDKs or runtimes, reject as ERR_DECODE_FAILED.
Animation preservation for GIF, animated WebP, and animated AVIF is not planned as an initial-version guarantee.
iOS MVP Behavior
Version 0.2.0 replaces the previous iOS package stub with a native JPEG MVP. Version 0.2.1 extends that iOS JPEG MVP with target-size compression. Version 0.2.2 adds PNG output. Version 0.2.3 adds GIF input decoded as a static first frame. Version 0.2.4 adds WebP input decoded as a static first frame. Version 0.2.5 adds a runtime-gated ImageIO-backed WebP output path. Version 0.2.6 adds target-size output.maxBytes support to that runtime-available WebP output path. Version 0.2.7 adds HEIC/HEIF input decoded as static ImageIO images. Version 0.2.10 adds capability-gated AVIF input decoded as a static ImageIO image when the runtime advertises AVIF source support. Version 0.2.12 adds iOS JPEG metadata preserve for JPEG source to JPEG output. Version 0.2.13 normalizes preserved iOS JPEG orientation and dimension metadata after rendering. Version 0.2.14 keeps AVIF output unsupported while making capability notes and ERR_NOT_IMPLEMENTED messaging explicit:
compressImage()acceptsfile://and best-effortcontent://JPEG, PNG, GIF, WebP, HEIC, HEIF, or runtime-available AVIF source URIs.- JPEG output is encoded with ImageIO
CGImageDestinationinto the iOS app cache directory. - PNG output is encoded with
UIImagePNGRepresentation()into the iOS app cache directory. - WebP output is encoded with ImageIO
CGImageDestinationinto the iOS app cache directory whenCGImageDestinationCopyTypeIdentifiers()advertises a WebP destination type. - On the current GitHub Actions iOS Validation runner with Xcode 26.5 and the iPhoneSimulator26.5 SDK, ImageIO does not advertise a WebP destination type. In that environment WebP reports
input=trueandoutput=false, andoutput.format: 'webp'rejects withERR_NOT_IMPLEMENTED. - GIF, WebP, HEIC, HEIF, and runtime-available AVIF input are decoded through ImageIO as static images before resize and output encoding; animation preservation is not implemented for animated formats.
resize.maxWidth,resize.maxHeight, andcontain,cover, orstretchmode are supported before output encoding.output.qualitycontrols JPEG quality and runtime-available WebP quality from0to100; when omitted, iOS uses the same default quality of80.- PNG output ignores
quality. output.maxBytesis supported for JPEG output and runtime-available WebP output. iOS treatsqualityas the upper quality bound and searches for the highest JPEG or WebP quality that fits undermaxBytes; if even the lowest quality cannot fit, it returns the smallest generated output. PNG output rejectsmaxByteswithERR_NOT_IMPLEMENTED.- PNG output preserves alpha where the processed image contains transparency. Runtime-available WebP output uses the processed image alpha as provided by ImageIO. JPEG output still composites alpha over white.
metadata: 'preserve'copies source JPEG metadata only for JPEG source to JPEG output, including resize, quality, andoutput.maxBytespaths. Preserved JPEG output normalizes top-level orientation, TIFF orientation, top-level pixel width/height, and EXIFPixelXDimension/PixelYDimensionto the rendered output.metadata: 'safe'andmetadata: 'strip're-encode without copying source metadata. Non-JPEG input or non-JPEG output withpreserverejects withERR_NOT_IMPLEMENTED.- AVIF input is enabled only when
CGImageSourceCopyTypeIdentifiers()advertises an AVIF source type. On runtimes without that support, AVIF input rejects withERR_UNSUPPORTED_FORMAT. - AVIF output is not implemented.
output.format: 'avif'rejects withERR_NOT_IMPLEMENTEDeven on runtimes that can decode AVIF input. getImageCompressionCapabilities()resolves withplatform: 'ios', JPEGinput=trueandoutput=true, PNGinput=trueandoutput=true, GIFinput=trueandoutput=false, WebPinput=trueand WebPoutput=truewhen the runtime advertises ImageIO WebP destination support, HEICinput=trueandoutput=false, HEIFinput=trueandoutput=false, AVIFinput=trueonly when the runtime advertises ImageIO AVIF source support, AVIFoutput=false, AVIF notes that sayoutput.format: 'avif'rejects withERR_NOT_IMPLEMENTED,metadataPolicies: ['preserve', 'safe', 'strip'],supportsTargetSizeCompression: truefor JPEG and runtime-available WebP output, andsupportsCancellation: false.- If the TypeScript API throws
ERR_NATIVE_MODULE_UNAVAILABLE, the native module was not found by React Native. Rebuild the app after installing or linking the package; this is separate from platform capability errors returned by the native implementation.
Version 0.2.15 does not change iOS runtime behavior. It records that future AVIF output on iOS must be runtime-gated the same way as WebP output: call CGImageDestinationCopyTypeIdentifiers(), advertise AVIF output=true only when ImageIO returns an AVIF destination type, and otherwise keep output.format: 'avif' on the current ERR_NOT_IMPLEMENTED path.
Version 0.2.16 also does not change iOS runtime behavior. It narrows the next Android AVIF output question to an internal API 34+ encoder route prototype while keeping iOS AVIF output on the runtime-gated ImageIO destination plan above. Version 0.2.17 again does not change iOS runtime behavior; it only exercises the Android AVIF output smoke attempt and keeps iOS AVIF output on the same future ImageIO destination plan. Version 0.2.19 keeps iOS AVIF output disabled while making the future gate explicit: Future iOS AVIF output must be runtime-gated by ImageIO AVIF destination support and static output validation.
Android HEIC / HEIF Input
Android platform documentation lists HEIF decode support on Android 8.0+ for .heic and .heif files, while the Java ImageDecoder API is available from API 28 and explicitly supports decoding HEIF into drawable or bitmap outputs. The Android implementation route is:
- Use
ImageDecoderon API 28+ for HEIC / HEIF input and force software bitmap allocation before resize and output encoding. - Attempt a guarded
BitmapFactoryHEIF decode fallback on API 26-27, because platform HEIF decode support exists there but remains device codec dependent. - Reject HEIC / HEIF inputs with
ERR_UNSUPPORTED_FORMATbelow Android 8.0. - Keep HEIC / HEIF output unsupported unless a later goal explicitly designs it.
Runtime capabilities currently expose HEIC / HEIF with input=true and output=false, plus notes that describe the Android 8.0+ platform decode condition, the API 28+ ImageDecoder route, the API 26-27 guarded BitmapFactory fallback, and the unsupported output state. The main CI validates the version-gated structure and rejection boundaries, and the separate Android Instrumentation workflow validates committed HEIC / HEIF sample decoding on an API 35 emulator.
Android AVIF Input
Android platform documentation lists AVIF baseline image support as mandatory on Android 14+. The Android implementation route is:
- Use
ImageDecoderon API 34+ for AVIF input and force software bitmap allocation before resize and output encoding. - Reject AVIF inputs with
ERR_UNSUPPORTED_FORMATbelow Android 14. - Keep AVIF output unsupported unless a later goal explicitly designs it.
Runtime capabilities currently expose AVIF with input=true and output=false, plus notes that describe the Android 14+ ImageDecoder route, static image support, no EXIF metadata copy, unsupported animation preservation, output.format: 'avif' rejecting with ERR_NOT_IMPLEMENTED, and the production gate that keeps Android AVIF output disabled until the MediaCodec image/avif encode/decode-back smoke produces a complete AVIF file with ftyp avif/avis signature and ImageDecoder decode-back validation. The main CI validates the API-gated unsupported boundary, corrupt-candidate decode failure behavior, and AVIF output unsupported messaging, and the separate Android Instrumentation workflow validates committed AVIF sample decoding on an API 35 emulator.
AVIF Output Feasibility Spike
Version 0.2.15 is a documentation and verification candidate only; it does not enable AVIF output. The spike records these implementation constraints:
- Android platform docs list AVIF baseline image encoder and decoder support on Android 14+, but the current Android implementation encodes through
Bitmap.compress()andBitmap.CompressFormatexposes JPEG, PNG, WebP, WebP lossless, and WebP lossy with no AVIF enum. AndroidExifInterfacealso lists AVIF under readable formats while writable metadata formats remain JPEG, PNG, and WebP. The minimum Android AVIF output implementation therefore needs a separate encoder route, API 34+ device validation, byte-signature and decode-back tests, and explicit metadata and target-size behavior before AVIF can reportoutput=true. - iOS ImageIO destination support is not a compile-time guarantee in this codebase. Future iOS AVIF output must mirror the WebP output path: query
CGImageDestinationCopyTypeIdentifiers()for AVIF identifiers at runtime, report AVIFoutput=trueonly when a destination type is present, encode withCGImageDestination, and keepERR_NOT_IMPLEMENTEDotherwise. - Current v0.2.15 capability reporting remains unchanged: Android AVIF
input=trueon Android 14+ andoutput=false; iOS AVIF input is runtime source-gated and AVIF output remainsfalse. - Partial implementation criteria: static image output only, no animation preservation, metadata preserve rejected unless explicitly designed, target-size disabled until AVIF quality semantics are validated, and release requires Android instrumentation plus iOS host-app smoke validation.
Android AVIF Output Prototype
Version 0.2.16 adds an internal Android AVIF output encoder route prototype without enabling AVIF output. The candidate route is a MediaCodec image/avif encoder probe: on Android 14+ it builds an image/avif MediaFormat with COLOR_FormatYUV420Flexible input and asks MediaCodecList.findEncoderForFormat() whether a still-image AVIF encoder is available. It also records whether a video/av01 AV1 encoder exists as fallback evidence, but that fallback is not enough by itself because AVIF output still needs a valid still-image file/container write path.
The prototype is intentionally not wired into compressImage() and does not change capability reporting. Android getImageCompressionCapabilities().formats.avif.output=false remains the production contract, and selecting output.format: 'avif' still rejects with ERR_NOT_IMPLEMENTED.
The production gate remains closed until the Android path can:
- Feed processed
Bitmappixels into the encoder as YUV420 input. - Write a complete static
.aviffile from encoder output. - Assert the result has an
ftypbox withaviforaviscompatible brand. - Decode the result with
ImageDecoderand assert dimensions match the processed bitmap. - Keep animated AVIF preservation unsupported unless it is explicitly designed.
- Reject or implement
metadata: 'preserve'for AVIF output with documented metadata behavior. - Reject or implement
output.maxBytesfor AVIF output with tested quality and size-search semantics.
The v0.2.16 Android instrumentation check runs on an API 35 emulator and verifies the prototype route report and production gate. The v0.2.17 instrumentation check keeps that probe and adds the encode/decode-back smoke below; AVIF output still cannot report output=true unless the smoke produces a valid static AVIF file and the remaining production semantics are implemented.
Android AVIF Output Encode/Decode-Back Smoke
Version 0.2.17 adds an internal Android AVIF output encode/decode-back smoke attempt without enabling AVIF output. The smoke is intentionally not wired into compressImage() or output capability reporting.
The internal route is named MediaCodec image/avif encode/decode-back smoke so instrumentation logs, release notes, and source checks refer to the same experiment.
On Android 14+ it creates a repo-owned 16x12 ARGB bitmap pattern in instrumentation, probes an image/avif encoder with MediaCodecList.findEncoderForFormat(), queues YUV420 input through MediaCodec, and collects the encoder output. It first validates the direct encoder bytes as a possible AVIF file, then tries to mux the encoded samples with MediaMuxer.MUXER_OUTPUT_HEIF and validates the muxed file.
The smoke success criteria are strict: the output must have an ftyp box with avif or avis compatible brand, and ImageDecoder must decode it back to 16x12 pixels. Missing encoders, codec failures, muxer/container failures, invalid signatures, or decode-back failures are reported as blockers instead of enabling a partial AVIF output surface.
Current GitHub Android Instrumentation result: the API 35 Google APIs emulator does not expose an image/avif encoder through MediaCodecList.findEncoderForFormat(). The smoke therefore reports attempted=false, success=false, blockerCode=no_image_avif_encoder, and blocker No image/avif encoder was discovered through MediaCodecList.findEncoderForFormat(). AVIF output remains disabled.
Android getImageCompressionCapabilities().formats.avif.output=false remains the production contract, and selecting output.format: 'avif' still rejects with ERR_NOT_IMPLEMENTED. metadata: 'preserve', output.maxBytes, animated AVIF preservation, and production AVIF output wiring remain non-goals until a later enabling goal explicitly implements them.
Version 0.2.19 keeps AVIF output disabled while making the production gate explicit across Android and iOS capability notes, unsupported-output errors, README guidance, and verification expectations. Android AVIF output remains disabled until the MediaCodec image/avif encode/decode-back smoke produces a complete AVIF file with ftyp avif/avis signature and ImageDecoder decode-back validation. iOS AVIF output remains disabled until ImageIO advertises AVIF destination support and static output validation exists. metadata='preserve', output.maxBytes, and animated AVIF preservation remain unsupported for AVIF output until explicitly designed and tested.
Version 0.2.20 keeps AVIF output disabled and turns the Android smoke into a production-decision preflight. Smoke results now carry blockerCode, outputCanBeEnabled=false, and productionDecision, distinguishing sdk_unavailable, no_image_avif_encoder, codec_failure, invalid_signature, and decode_back_failure so the next implementation step can decide whether a runtime can safely remain disabled or move toward production wiring.
Version 0.2.21 keeps AVIF output disabled and adds an Android production wiring scaffold at the compressImage() output boundary. The scaffold reuses the encode/decode-back helper route as the future production helper, but willEnterEncodeDecodeBackHelper=false while avif.output=false; AVIF requests continue to reject with ERR_NOT_IMPLEMENTED before source access, helper entry, metadata preserve, output.maxBytes, or animated AVIF preservation can be treated as implemented.
Version 0.2.22 keeps AVIF output disabled and extracts the Android AVIF encode/decode-back implementation into AndroidAvifOutputHelper. The helper owns reusable input, encoded output, sample, file-validation, and result types for future production wiring, while compressImage() still rejects output.format: 'avif' through the scaffold before source access or helper entry and capability reporting remains avif.output=false.
Version 0.2.23 keeps AVIF output disabled and adds an injectable validation seam to AndroidAvifOutputHelper. AndroidAvifOutputHelperDependencies wraps the default bitmap, encoder, output-file, muxer, and decode-back validator path, while Android JVM tests inject fake encoded bytes, invalid signature results, decode-back failures, and codec failures to prove helper result classification without wiring AVIF output into compressImage().
Version 0.2.24 keeps AVIF output disabled and fixes the injected success contract for AndroidAvifOutputHelper. Android JVM tests now inject fake valid AVIF bytes, a muxed output file, and a successful decode-back result so helper success reports byteSize, signatureValid=true, decodeBackValid=true, blockerCode=null, and PRODUCTION_DECISION_SMOKE_PASSED_KEEP_DISABLED while compressImage() and capability reporting still keep AVIF output disabled.
Version 0.2.25 keeps AVIF output disabled and fixes the injected direct-output success contract for AndroidAvifOutputHelper. Android JVM tests now inject fake valid direct AVIF bytes plus successful decode-back validation so helper success reports the MediaCodec image/avif encode/decode-back smoke direct encoder output route, a direct .avif output path, byteSize, blockerCode=null, and PRODUCTION_DECISION_SMOKE_PASSED_KEEP_DISABLED, and proves muxEncodedSamples is not called after direct validation success.
Version 0.2.26 keeps AVIF output disabled and fixes the helper validation detail contract for AndroidAvifOutputHelper. Android JVM tests now pin direct success, muxed success, invalid signature, decode-back failure, and codec failure details ordering so validation results report INJECTABLE_VALIDATION_SEAM first, dependency-provided encoder/direct/muxer/validator details next, and route blockers last, while codec failure reports route blockers before the seam and helper-disabled message.
Version 0.2.27 keeps AVIF output disabled and fixes the blocked-route detail contract for AndroidAvifOutputHelper and AndroidAvifOutputPrototype.runEncodeDecodeBackSmoke(). Android JVM tests now pin below-API-34 and no-image/avif-encoder blocked helper details to route blockers, INJECTABLE_VALIDATION_SEAM, and HELPER_DISABLED_FROM_COMPRESS_IMAGE, and verify the smoke adapter preserves blockerCode, details, and outputCanBeEnabled=false.
Version 0.2.28 keeps AVIF output disabled and fixes the temp-file lifecycle contract for AndroidAvifOutputHelper. Android JVM tests now pin direct success to direct-file-only creation, muxer skip, and direct outputFilePath/byteSize; pin direct-failure plus muxed-success to the muxed result path while keeping the direct intermediate unreported; and pin invalid-signature/decode-back failures to the final muxed blocker path and final-file byteSize.
Version 0.2.29 keeps AVIF output disabled and fixes the validation-result provenance contract for AndroidAvifOutputHelper. Android JVM tests now pin direct validation details to the direct file name, byte size, signature result, and decode-back result; pin muxed validation details to the muxed file name, byte size, signature result, and decode-back result; and pin direct failure followed by muxed success or failure to encoder -> direct validation -> muxer -> final validation detail order.
Version 0.2.30 keeps AVIF output disabled and hardens iOS host-app smoke retry and timeout diagnostics. The iOS smoke runner now supports RNICK_IOS_SMOKE_ATTEMPTS, starts the unified log stream before launching the app, retries timeout-only smoke attempts with a fresh launch, and prints simulator state, app container paths, app process lookup, launch output, captured RNICK_IOS_SMOKE_* stream tail, Metro output tail, and recent unified logs from RNICK_IOS_SMOKE_DIAGNOSTIC_LOG_WINDOW.
Version 0.2.31 keeps AVIF output disabled and fixes the iOS smoke retry and diagnostic contract in simulator-free Node-level tests. scripts/ios-smoke-contract.mjs now owns environment parsing, timeout-only retry decisions, retry warnings, and timeout diagnostic formatting; test/iosSmokeContract.test.mjs covers those helpers without launching Xcode, Metro, or a simulator.
Version 0.2.32 keeps AVIF output disabled and fixes CLI-level iOS smoke timeout fixture coverage. createSmokeTimeoutErrorFromCLIState() now owns the runSmokeAttempt timeout diagnostic input assembly, formatSmokeRetryWarningMessages() owns diagnostics-before-retry warning order, and test/iosSmokeCliTimeout.test.mjs covers fake launch, log stream, Metro, unified log, app lookup, and process lookup output without launching Xcode, Metro, or a simulator.
Version 0.2.33 keeps AVIF output disabled and fixes iOS smoke process lifecycle fixture coverage. createSmokeAttemptLifecycle() now owns Metro/log stream listener cleanup, log process termination, and setLogProcess(null) after PASS, FAIL, or timeout settle, while test/iosSmokeLifecycle.test.mjs covers those paths with fake EventEmitter fixtures without launching Xcode, Metro, or a simulator.
Version 0.2.34 keeps AVIF output disabled and fixes iOS smoke log stream error fixture coverage. createSmokeAttemptLifecycle() now records log process error events as iOS smoke log stream error: output and smoke-log snapshot state, while test/iosSmokeLifecycle.test.mjs proves that state is available to timeout diagnostics without launching Xcode, Metro, or a simulator.
Version 0.2.35 keeps AVIF output disabled and fixes iOS smoke diagnostics packed log artifact coverage. The iOS Validation workflow now writes failed smoke output to ios-smoke-diagnostics/ios-smoke.log, generates ios-smoke-diagnostics/ios-smoke-summary.md, appends the same ordered marker excerpt to the GitHub Step Summary, and uploads the packed diagnostics artifact without changing the simulator smoke behavior.
Version 0.2.36 keeps AVIF output disabled and fixes iOS smoke artifact failure-path dry-run fixture coverage. test/iosSmokeSummaryCli.test.mjs runs node scripts/ios-validation.mjs summarize-smoke-log against a fake ios-smoke.log, verifies stdout matches $GITHUB_STEP_SUMMARY, and pins the failure-only if: failure() summary/upload artifact path without forcing a real simulator failure.
Version 0.2.37 keeps AVIF output disabled and fixes iOS smoke diagnostics artifact schema snapshot coverage. test/iosSmokeContract.test.mjs now pins the exact formatIOSSmokeDiagnosticsSummary() markdown shape for normal, empty, no-marker, and very-long-log fixtures, including fallback text and marker/tail window bounds.
Version 0.2.38 keeps AVIF output disabled and fixes iOS smoke PASS payload schema snapshot coverage. test/iosSmokeContract.test.mjs now parses a prefixed RNICK_IOS_SMOKE_PASS JSON
