Skip to content

feat(mobile): preview HEIC images and share session attachments - #5426

Merged
iscekic merged 13 commits into
mainfrom
attachment-previews-f872
Aug 22, 2026
Merged

feat(mobile): preview HEIC images and share session attachments#5426
iscekic merged 13 commits into
mainfrom
attachment-previews-f872

Conversation

@iscekic

@iscekic iscekic commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

HEIC and HEIF photos now attach and count as images. A camera photo with no file name receives an automatic name.

Photos upload as soon as you add them to a message. You see the upload progress and the finished state before sending; a temporary failure shows a retry, and a rejected file shows a permanent error.

The Send button stays disabled while a photo upload is in progress. While the agent is generating, the composer shows Stop when there is nothing to send and Send when there is text or an attachment.

When a photo cannot be displayed, the attachment preview and the full-screen viewer show a placeholder icon instead of an empty area.

The full-screen photo viewer and the text preview sheet now include a Share button. A failed share shows its message inside the open screen.


Image chips now begin uploading the moment they are added, instead of waiting for send. The upload hook starts one upload per image candidate after the chip list commits and leaves document chips deferred to the send path; the new-session screen adds the upload-in-progress flag to its remote-start gate. A remote spawn therefore cannot start while an image upload is pending, and the send path uploads only the remaining document chips.

Files
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts — starts an upload for each image candidate after the chip list commits; documents stay deferred to uploadPending.
  • apps/mobile/src/app/(app)/agent-chat/new.tsx — adds attachments.isUploading to the remote-spawn start gate.

Send is disabled while an attachment upload is in flight, and the Stop-versus-Send choice no longer depends on the upload state. The composer control projection gains an isUploading input and a hasSendableContent output, and canSend now also requires that no upload is pending; the input row picks Stop versus Send from streaming plus sendable content instead of from canSend. During streaming, a composer with no text and no sendable attachment shows Stop, and one with text or a sendable attachment shows Send.

Files
  • apps/mobile/src/components/agents/chat-composer-input-state.ts — adds the isUploading input and the hasSendableContent output; canSend now also requires !isUploading.
  • apps/mobile/src/components/agents/chat-composer-input-row.tsx — adds the hasSendableContent prop and switches the Stop/Send branch from !canSend to !hasSendableContent.
  • apps/mobile/src/components/agents/chat-composer.tsx — feeds upload.isUploading into the control state and hasSendableContent into the input row.

HEIC and HEIF files now classify as images. The extension-to-MIME table and the image-extension set both gain heic and heif, so these assets take the existing metadata-strip and JPEG re-encode path. The picker keeps a non-empty picker filename and, when the picker omits the name, synthesizes one from the URI extension, then the MIME subtype, then image.png.

Files
  • apps/mobile/src/lib/agent-attachments/constants.ts — adds heic and heif to the extension-to-MIME table.
  • apps/mobile/src/lib/agent-attachments/validate.ts — adds heic and heif to the image-extension set.
  • apps/mobile/src/components/agents/attachment-picker.ts — keeps the picker filename when non-empty; otherwise synthesizes image.<ext> from the URI extension, then the MIME subtype, then image.png.

Session-page image and text previews now expose a Share action. The renderer passes its share handler into the image viewer and the preview sheet, and the sheet header gains an optional onShare prop with a sharing busy state, so both surfaces show a Share button. A share failure renders inline inside the open viewer or sheet, outside the scroll view, and as a toast only when neither is open.

Files
  • apps/mobile/src/components/agents/file-part-renderer.tsx — passes the share handler, busy state, and error message into the viewer and preview sheet; renders the error inline when a modal is open and as a toast otherwise.
  • apps/mobile/src/components/sheet-header.tsx — adds an optional share button to the header with a busy accessibility state.

Image chips and the full-screen viewer now show a centered fallback icon when an image fails to decode, and the viewer announces its share error through the accessibility status component. Each surface tracks a decode-failure state and renders an AlertCircle placeholder instead of a blank frame; the viewer uses a white icon on the black backdrop and resets the state on reopen or on a new image.

Files
  • apps/mobile/src/components/agents/attachment-preview-strip.tsx — tracks a thumbnail decode failure and renders a centered AlertCircle fallback.
  • apps/mobile/src/components/image-viewer-modal.tsx — tracks a viewer decode failure, renders a white AlertCircle fallback on the black backdrop, resets it on reopen or new image, and renders the share error through AccessibleStatus instead of a plain text.

Tests: 10 files changed — attachment-picker.test.ts, attachment-preview-strip.mounted.test.tsx, chat-composer-input-row.mounted.test.tsx, chat-composer-input-state.test.ts, file-part-renderer.mounted.test.tsx, image-viewer-modal.mounted.test.tsx (new), sheet-header.mounted.test.tsx (new), strip-image-metadata.test.ts, use-agent-attachment-upload.test.ts, validate.test.ts.

Generated: none.


Verification

Verification ran two cases on iOS.

Case What it proves Platform Result
Image viewer share Tapping the image preview opens the full-screen viewer with a Share action, and tapping Share opens the native iOS share sheet. iOS passed
Markdown preview share Tapping the Markdown preview opens the preview sheet with a Share action, and tapping Share opens the native iOS share sheet. iOS not run — the seed produced an unresolvable attachment URL (round 3), then the sandbox R2 attachment download failed on a misconfigured R2 endpoint (takeover), so the preview sheet never received a file; a test-environment limitation, covered by the changed unit tests
  • No product defect reproduces. Round 3 first classified the Markdown-preview failure as a product failure, then reclassified it as a seed/config limitation (unresolvable URL, then invalid R2 endpoint); it does not reproduce as a product defect.
  • Recording: none.

Visual Changes

Session page attachment previews (image and Markdown entry points). The user now sees the attached image rendered as a tappable preview and the attached Markdown file as a chip inside the session transcript. In the picture, the red sample.png preview and the notes.md chip sit in the first message bubble, above the read-only session banner. Disagreement with the handoff: the handoff labels this still "image viewer with share action", but the picture shows the transcript after the viewer closed; the viewer's Share button and the native share sheet are not in the frame (the S1 case log proves them).

01-s1-image-viewer-share.png

Markdown preview sheet. No picture shows this screen. The preview sheet with its Done and Share notes.md actions never opened in any round, so no still shows the change. The only related still, s2-r2-blocked.png, shows the R2 attachment-download blocker ("could not be retrieved"), not the preview sheet.

Reviewer Notes

No human steps are needed before or after merge.

E2E skip: the Markdown-preview-share case could not run — the seed produced an unresolvable attachment URL, then the sandbox R2 attachment download failed on a misconfigured R2 endpoint. Test-environment limitation, not a product defect; the changed unit tests cover it.

@iscekic iscekic self-assigned this Aug 21, 2026
@kilo-code-bot

kilo-code-bot Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (5 files)
  • apps/mobile/src/components/agents/chat-composer-input-row.mounted.test.tsx
  • apps/mobile/src/components/agents/chat-composer-input-row.tsx
  • apps/mobile/src/components/agents/chat-composer-input-state.test.ts
  • apps/mobile/src/components/agents/chat-composer-input-state.ts
  • apps/mobile/src/components/agents/chat-composer.tsx
Previous Review Summaries (6 snapshots, latest commit cabbb9d)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit cabbb9d)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • apps/mobile/src/components/sheet-header.mounted.test.tsx
  • apps/mobile/src/components/sheet-header.tsx

Previous review (commit 5ace8a8)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (4 files)
  • apps/mobile/src/components/agents/file-part-renderer.mounted.test.tsx
  • apps/mobile/src/components/agents/file-part-renderer.tsx
  • apps/mobile/src/components/image-viewer-modal.mounted.test.tsx
  • apps/mobile/src/components/image-viewer-modal.tsx

Previous review (commit 6ff5262)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/components/agents/chat-composer-input-state.ts 67 !isUploading on canSend swaps Send for Stop while the agent is streaming
Files Reviewed (7 files)
  • apps/mobile/src/components/agents/chat-composer-input-state.test.ts
  • apps/mobile/src/components/agents/chat-composer-input-state.ts - 1 issue
  • apps/mobile/src/components/agents/chat-composer.tsx
  • apps/mobile/src/components/agents/file-part-renderer.mounted.test.tsx
  • apps/mobile/src/components/agents/file-part-renderer.tsx
  • apps/mobile/src/components/image-viewer-modal.mounted.test.tsx
  • apps/mobile/src/components/image-viewer-modal.tsx

Fix these issues in Kilo Cloud

Previous review (commit 17e3cfe)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (2 files)
  • apps/mobile/src/components/agents/file-part-renderer.mounted.test.tsx
  • apps/mobile/src/components/agents/file-part-renderer.tsx

Previous review (commit 3a3e7d8)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/components/agents/file-part-renderer.tsx 129 Share failures can be silently dropped after the viewer or sheet closes because handleShare reads stale viewerVisible/preview after await
Files Reviewed (17 files)
  • apps/mobile/src/app/(app)/agent-chat/new.tsx
  • apps/mobile/src/components/agents/attachment-picker.test.ts
  • apps/mobile/src/components/agents/attachment-picker.ts
  • apps/mobile/src/components/agents/attachment-preview-strip.mounted.test.tsx
  • apps/mobile/src/components/agents/attachment-preview-strip.tsx
  • apps/mobile/src/components/agents/file-part-renderer.mounted.test.tsx
  • apps/mobile/src/components/agents/file-part-renderer.tsx - 1 issue
  • apps/mobile/src/components/image-viewer-modal.mounted.test.tsx
  • apps/mobile/src/components/image-viewer-modal.tsx
  • apps/mobile/src/components/sheet-header.mounted.test.tsx
  • apps/mobile/src/components/sheet-header.tsx
  • apps/mobile/src/lib/agent-attachments/constants.ts
  • apps/mobile/src/lib/agent-attachments/strip-image-metadata.test.ts
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts
  • apps/mobile/src/lib/agent-attachments/validate.test.ts
  • apps/mobile/src/lib/agent-attachments/validate.ts

Fix these issues in Kilo Cloud

Previous review (commit b491040)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
apps/mobile/src/components/agents/file-part-renderer.tsx 139 Share failures can be silently dropped after the viewer or sheet closes because handleShare reads stale viewerVisible/preview after await
Files Reviewed (17 files)
  • apps/mobile/src/app/(app)/agent-chat/new.tsx
  • apps/mobile/src/components/agents/attachment-picker.test.ts
  • apps/mobile/src/components/agents/attachment-picker.ts
  • apps/mobile/src/components/agents/attachment-preview-strip.mounted.test.tsx
  • apps/mobile/src/components/agents/attachment-preview-strip.tsx
  • apps/mobile/src/components/agents/file-part-renderer.mounted.test.tsx
  • apps/mobile/src/components/agents/file-part-renderer.tsx - 1 issue
  • apps/mobile/src/components/image-viewer-modal.mounted.test.tsx
  • apps/mobile/src/components/image-viewer-modal.tsx
  • apps/mobile/src/components/sheet-header.mounted.test.tsx
  • apps/mobile/src/components/sheet-header.tsx
  • apps/mobile/src/lib/agent-attachments/constants.ts
  • apps/mobile/src/lib/agent-attachments/strip-image-metadata.test.ts
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts
  • apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts
  • apps/mobile/src/lib/agent-attachments/validate.test.ts
  • apps/mobile/src/lib/agent-attachments/validate.ts

Fix these issues in Kilo Cloud


Reviewed by grok-4.6 · Input: 53.1K · Output: 7.2K · Cached: 318.2K

Review guidance: REVIEW.md from base branch main

Comment thread apps/mobile/src/components/agents/attachment-picker.ts
# Conflicts:
#	apps/mobile/src/components/agents/file-part-renderer.mounted.test.tsx
#	apps/mobile/src/components/agents/file-part-renderer.tsx
The share handler read viewerVisible and preview from its render
closure after the share await, so a failure that landed once the
viewer or sheet had closed set state on an unmounted surface and was
silently dropped. Track the current visibility in refs and read those
in the catch branch so the failure surfaces as a toast instead.
Disable Send while an image uploads, matching the new-session start gate. Render the image viewer decode fallback in white on the black canvas for WCAG contrast. Move the file preview share error out of the scroll view so it stays visible after scroll.
Render both inline share errors through AccessibleStatus so VoiceOver and TalkBack announce the failure, keeping the current visual styling.
Add accessibilityState busy to the SheetHeader Share pressable so VoiceOver announces a share in progress instead of a dimmed button, matching the image viewer.
Decouple the Stop-vs-Send decision from the upload state. The row now chooses Stop only when there is no sendable content, so an in-flight upload no longer swaps a streaming session's Send button for Stop.
@iscekic iscekic added the human-ready The PR is ready for human review. label Aug 22, 2026
@iscekic
iscekic merged commit 40a8da4 into main Aug 22, 2026
18 checks passed
@iscekic
iscekic deleted the attachment-previews-f872 branch August 22, 2026 06:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human-ready The PR is ready for human review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants