Skip to content

fix(chat/ios): downscale image attachments before send#81608

Merged
BunsDev merged 1 commit into
mainfrom
meow/ios-chat-image-attachments
May 14, 2026
Merged

fix(chat/ios): downscale image attachments before send#81608
BunsDev merged 1 commit into
mainfrom
meow/ios-chat-image-attachments

Conversation

@BunsDev

@BunsDev BunsDev commented May 14, 2026

Copy link
Copy Markdown
Member

Summary

  • Problem: iOS chat staged PhotosPicker image bytes directly, rejected raw files over 5 MB before resize, and sent raw EXIF/GPS-bearing bytes into chat.send.
  • Why it matters: large iPhone camera photos can fail attachment delivery or spike memory during base64 encoding.
  • What changed: image attachments now flow through a chat-specific JPEG processor that caps the long edge at 1600 px, compresses to a 3.5 MB payload budget, strips source metadata, and stages previews from the resized JPEG bytes.
  • What did NOT change (scope boundary): gateway protocol, chat send schema, auth, permissions, and non-image attachment handling are unchanged.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Real behavior proof (required for external PRs)

  • Behavior or issue addressed: iOS app PhotosPicker attachments are processed client-side before staging/sending instead of using full-resolution raw image data.
  • Real environment tested: local macOS SwiftPM build of apps/shared/OpenClawKit on the OpenClaw iOS shared package.
  • Exact steps or command run after this patch:
    • swift test --package-path apps/shared/OpenClawKit --filter 'ChatImageProcessorTests|JPEGTranscoderTests|ChatViewModelAttachmentTests'
    • swiftformat --lint --config config/swiftformat apps/shared/OpenClawKit/Sources/OpenClawKit/ChatImageProcessor.swift apps/shared/OpenClawKit/Sources/OpenClawKit/JPEGTranscoder.swift apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatImageProcessorTests.swift apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelAttachmentTests.swift
    • swiftlint lint --config apps/ios/.swiftlint.yml apps/shared/OpenClawKit/Sources/OpenClawKit/ChatImageProcessor.swift apps/shared/OpenClawKit/Sources/OpenClawKit/JPEGTranscoder.swift apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatImageProcessorTests.swift apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelAttachmentTests.swift
    • git diff --check origin/main..HEAD
  • Evidence after fix: the focused Swift test run passed the view-model attachment path, the chat image processor suite, and the existing JPEG transcoder suite on commit 65d3efe.
  • Observed result after fix: synthetic large portrait and landscape images are staged as image/jpeg, renamed to .jpg, capped to the 1600 px long edge, kept within the 3.5 MB chat payload budget, and tested to avoid source metadata leakage.
  • What was not tested: physical iPhone camera roll -> composer -> live gateway send. pnpm ios:build is currently blocked in this checkout by unrelated existing SwiftFormat failures in apps/ios/Sources/Settings/SettingsTab.swift.
  • Before evidence: issue [Bug] iOS: large chat image attachments fail or blow up memory (48MP frames, no client-side resize) #68524 documents the full-resolution PhotosPicker path failing on large iPhone camera images; current code loaded raw Data and applied only a pre-resize 5 MB hard gate.

Root Cause (if applicable)

  • Root cause: OpenClawChatViewModel.addImageAttachment accepted raw PhotosPicker image bytes, applied a 5 MB size check before any transformation, and staged the original bytes for later base64 send.
  • Missing detection / guardrail: there was no chat attachment processing policy or regression test asserting pixel cap, byte budget, JPEG output, or metadata stripping for iOS chat attachments.
  • Contributing context (if known): JPEGTranscoder already existed, but the chat composer path did not use it.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file:
    • apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatImageProcessorTests.swift
    • apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelAttachmentTests.swift
  • Scenario the test should lock in: large images are resized to the upload policy, output is JPEG within budget, source metadata strings/GPS dictionaries are stripped, alpha sources flatten to opaque JPEG, non-images are rejected, and OpenClawChatViewModel stages resized JPEG bytes rather than the original file.
  • Why this is the smallest reliable guardrail: it proves the attachment path at the shared iOS chat UI boundary without needing a physical Photos picker or live gateway.
  • Existing test that already covers this (if any): existing JPEGTranscoderTests covered the shared transcoder; this PR adds chat-specific policy and view-model coverage.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

  • iOS chat image attachments are converted to JPEG before send.
  • Large images are downscaled and compressed client-side before the 5 MB final gate.
  • Source EXIF/GPS-style metadata is not forwarded into chat attachment bytes.
  • Original filename extensions are normalized to .jpg for processed image attachments.

Diagram (if applicable)

Before:
PhotosPicker raw bytes -> 5 MB raw gate -> preview from raw bytes -> chat.send base64

After:
PhotosPicker raw bytes -> image type check -> JPEG resize/compress/strip metadata -> 5 MB final gate -> preview from processed JPEG -> chat.send base64

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? Yes
  • If any Yes, explain risk + mitigation: the data scope is reduced because source image metadata is stripped before staging/sending. Tests assert planted source metadata strings and GPS dictionaries do not survive processing.

Repro + Verification

Environment

  • OS: macOS host
  • Runtime/container: SwiftPM for apps/shared/OpenClawKit
  • Model/provider: N/A
  • Integration/channel (if any): iOS chat composer shared UI path
  • Relevant config (redacted): none

Steps

  1. Process synthetic large JPEG/PNG image inputs through ChatImageProcessor.
  2. Stage a synthetic 3000x4000 image through OpenClawChatViewModel.addImageAttachment.
  3. Verify output dimensions, byte budget, MIME type, filename, metadata stripping, and absence of staging errors.

Expected

  • Processed chat image attachments are bounded JPEGs and previews use the processed data.

Actual

  • Focused Swift tests pass on the rebased branch head 65d3efe72f.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: long-edge resize for portrait/landscape/narrow images, no upscaling of small images, payload budget, non-image rejection, source metadata stripping, alpha flattening, and view-model staging of processed JPEG data.
  • Edge cases checked: transparent PNG input, EXIF/GPS/TIFF strings planted in source bytes, camera-style large portrait attachment path.
  • What you did not verify: physical iPhone picker and live gateway send; full iOS build remains blocked by unrelated SwiftFormat failures outside this PR.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

No review conversations exist on this PR at creation time.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps: N/A

Risks and Mitigations

  • Risk: JPEG conversion drops original format and metadata.
    • Mitigation: this is intentional for chat transport safety and privacy; UI/test expectations name the JPEG normalization.
  • Risk: pathological images may still fail the final 5 MB gate after processing.
    • Mitigation: the final gate remains and reports a clear post-resize size error instead of staging unsafe raw bytes.

@openclaw-barnacle openclaw-barnacle Bot added size: M maintainer Maintainer-authored PR labels May 14, 2026
@clawsweeper

clawsweeper Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge.

Summary
The PR adds an iOS/OpenClawKit chat image preprocessing path that JPEG-resizes PhotosPicker attachments before staging/sending, updates JPEGTranscoder, adds Swift tests, and adds a changelog entry.

Reproducibility: yes. Source inspection shows current main loads PhotosPicker image bytes, rejects payloads over 5,000,000 bytes before resizing, and base64-encodes the staged bytes directly on send.

Real behavior proof
Not applicable: The external contributor proof gate does not apply to this maintainer/member PR; the body reports focused Swift tests but also states physical iPhone and live gateway send were not tested.

Next step before merge
No repair PR is needed; the remaining action is maintainer validation and landing for a protected maintainer-labeled PR with no blocking review finding.

Security
Cleared: No concrete security or supply-chain regression was found; the diff adds no dependencies, workflows, network calls, or secrets handling and reduces image metadata exposure.

Review details

Best possible solution:

Land the focused preprocessing path after normal maintainer validation, then close the linked iOS composer bug and mark the older external implementation PR as superseded if this merges.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection shows current main loads PhotosPicker image bytes, rejects payloads over 5,000,000 bytes before resizing, and base64-encodes the staged bytes directly on send.

Is this the best way to solve the issue?

Yes. Reusing JPEGTranscoder behind a small ChatImageProcessor before staging is the narrow maintainable fix, and this PR avoids the unrelated Settings drift that blocked the older external branch.

Acceptance criteria:

  • swift test --package-path apps/shared/OpenClawKit --filter 'ChatImageProcessorTests|JPEGTranscoderTests|ChatViewModelAttachmentTests'
  • swiftformat --lint --config config/swiftformat apps/shared/OpenClawKit/Sources/OpenClawKit/ChatImageProcessor.swift apps/shared/OpenClawKit/Sources/OpenClawKit/JPEGTranscoder.swift apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatImageProcessorTests.swift apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelAttachmentTests.swift
  • swiftlint lint --config apps/ios/.swiftlint.yml apps/shared/OpenClawKit/Sources/OpenClawKit/ChatImageProcessor.swift apps/shared/OpenClawKit/Sources/OpenClawKit/JPEGTranscoder.swift apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatImageProcessorTests.swift apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelAttachmentTests.swift

What I checked:

Likely related people:

  • BunsDev: Current-main GitHub path history shows BunsDev recently changed the shared native chat view model/composer path for model-thinking metadata, so they are relevant beyond merely authoring this PR. (role: recent adjacent contributor; confidence: high; commits: 9ffe290a170b; files: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatComposer.swift)
  • steipete: GitHub path history shows steipete introduced the centralized JPEG transcode cap used by PhotoCapture and has recent OpenClawKit/chat UI work near this surface. (role: JPEG/image-processing area contributor; confidence: high; commits: ef2c66a16b2e, ff45bc1f8875, b294f7c46763; files: apps/shared/OpenClawKit/Sources/OpenClawKit/JPEGTranscoder.swift, apps/shared/OpenClawKit/Sources/OpenClawKit/PhotoCapture.swift, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift)
  • mbelinky: Recent merged history includes iOS Chat UI work touching ChatComposer and ChatViewModel behavior, useful for routing review of shared chat UI changes. (role: recent adjacent iOS chat UI contributor; confidence: medium; commits: 9476dda9f617, 42d11a3ec5f4, 6effcdb551a8; files: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatComposer.swift, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift)

Remaining risk / open question:

  • This read-only review did not run the Swift tests or a device build.
  • The PR body explicitly says physical iPhone camera-roll to composer to live gateway send was not tested, though the maintainer/member proof gate does not require contributor action.

Codex review notes: model gpt-5.5, reasoning high; reviewed against 983064f5f819.

@BunsDev BunsDev force-pushed the meow/ios-chat-image-attachments branch from 65d3efe to 66ca4d5 Compare May 14, 2026 02:39
@BunsDev

BunsDev commented May 14, 2026

Copy link
Copy Markdown
Member Author

Maintainer verification before merge:

Local proof on rebased head 66ca4d519f1ccabf309a95c72e801c7f2b7d4be6:

swift test --package-path apps/shared/OpenClawKit --filter 'ChatImageProcessorTests|JPEGTranscoderTests|ChatViewModelAttachmentTests'
swiftformat --lint --config config/swiftformat apps/shared/OpenClawKit/Sources/OpenClawKit/ChatImageProcessor.swift apps/shared/OpenClawKit/Sources/OpenClawKit/JPEGTranscoder.swift apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatImageProcessorTests.swift apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelAttachmentTests.swift
swiftlint lint --config apps/ios/.swiftlint.yml apps/shared/OpenClawKit/Sources/OpenClawKit/ChatImageProcessor.swift apps/shared/OpenClawKit/Sources/OpenClawKit/JPEGTranscoder.swift apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatImageProcessorTests.swift apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelAttachmentTests.swift
git diff --check origin/main..HEAD

Result: all passed. Swift test covered 12 tests across ChatImageProcessorTests, JPEGTranscoderTests, and ChatViewModelAttachmentTests.

Exact-head CI proof on 66ca4d519f1ccabf309a95c72e801c7f2b7d4be6:

Known proof gap: no fresh physical iPhone camera roll -> composer -> live gateway send was run on this maintainer branch. The shared iOS chat attachment path is covered by focused Swift tests and macOS Swift CI.

Mergeability: GitHub reports MERGEABLE / CLEAN.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] iOS: large chat image attachments fail or blow up memory (48MP frames, no client-side resize)

1 participant