Skip to content

feat(comments): file attachments on mobile comments - #462

Draft
nossila wants to merge 1 commit into
masterfrom
feature/native-comment-attachments
Draft

nossila wants to merge 1 commit into
masterfrom
feature/native-comment-attachments

Conversation

@nossila

@nossila nossila commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Summary

Mobile comments can now carry file attachments, like web: photos from the library (multi-select) and PDFs from the document picker, upload progress above the composer, and a comment's files listed under it (tap to open). The work reuses the web upload pipeline instead of building a second one. Only reading bytes from a file depends on the platform; everything else is shared.

Backend: N/A, frontend-only change (the files API, the FilesInterface fragments and the attach mutation are already on master).
Template PR: https://bitbucket.org/silverlogic/baseapp-frontend-template/pull-requests/454. Merge this one first, then that one, which only bumps the submodule pointer.

What changed

files/common: shared pipeline

  • UploadSource (name, size, type, readChunk(start, end), optional dispose()) is the platform seam. Every upload entry point accepts a web File or an UploadSource. A File is wrapped automatically through Blob.slice.
  • uploadChunks has a lazy readChunk mode. A chunk is read only once it has a concurrency slot, so at most MAX_CONCURRENT_CHUNKS chunks are in memory, and a retry reuses the bytes it already read. useChunkedUpload uses this mode for fresh uploads and for resume.
  • A chunk paused or removed during a retry backoff no longer starts a new PUT. clearScope aborts the uploads it removes and disposes their sources.
  • Shared rules for both legs:
    • filterSelectedFiles / describeRejectedFiles: size, count and type (MIME wildcard or extension, with the same semantics as the accept attribute).
    • getUploadItemActions / getUploadPercent.
    • getFileTypeLabel.

files/native: new leg

  • createNativeUploadSource reads each chunk through a short-lived expo-file-system FileHandle. On Android, a content:// URI is first copied into the cache, because FileHandle cannot open content URIs. dispose deletes that copy.
  • useNativeFilePicker opens two pickers and applies the shared rules, toasting what it dropped:
    • photos through expo-image-picker, multi-select, with HEIC converted to JPEG;
    • documents through File.pickFileAsync. This is expo-file-system's built-in picker, so no new native dependency.
  • FileChip, UploadingFilesList and AttachedFilesList.

Comments

  • COMMENT_FILE_ATTACHMENT_LIMITS (5 files, 100 MB, png/jpg/pdf) replaces the two copies in the web components and is used on mobile.
  • Native composer (useCommentComposer):
    • drives useDeferredFileAttachments, the hook web uses: upload now, attachTo(commentId) after create;
    • keeps submit disabled while attachments are pending.
  • CommentFilesUpsertActions (native) adds photo and file buttons to the composer toolbar.
  • Native CommentItem renders AttachedFilesList. The fragment already spread FilesList_target, so this adds no query change.

Shared native composer

  • SocialInput takes SocialUpsertActionsProps, matching web.
  • SocialInputDrawer.Drawer takes a Footer, rendered above the input, and grows its snap points by the footer's measured height. Placeholder takes the matching footerHeight.

design-system: native AttachmentIcon, ported from the web SVG.

Test plan

Mobile host: any page with comments ([...path] → PageComponent → Comments).

Q: Can I attach photos to a new comment?

  1. Open a page with comments and tap the photo icon in the composer.
  2. Pick 2 photos, type a comment, then send it.

→ Two chips with progress bars show above the input, and the sheet grows to fit them. Send stays disabled until both finish. After sending, the new comment shows both files as chips with thumbnails.

Q: Do PDFs work, including Android content:// files (Drive, Downloads)?

  1. Tap the paperclip icon and pick a PDF from Drive on Android, or from Files on iOS.
  2. Send a comment, then tap the attached chip.

→ The PDF uploads and attaches. Tapping it opens the file URL in the system viewer. (Automated: createNativeUploadSource.test.ts covers the content-URI copy and its cleanup.)

Q: Are invalid picks refused the same way as on web?

  • Pick a file over 100 MB, or a non-PDF document → it is not added, and a toast explains why. Example: 1 file was not added (over 100.00 MB).
  • Pick more than 5 photos → the picker caps the selection at 5.
  • (Automated: filterSelectedFiles.test.ts, useNativeFilePicker.test.ts, useFileSelect.test.ts)

Q: Can I cancel or retry?

  • Tap ✕ on an uploading chip → the upload is cancelled and the chip disappears.
  • A failed chip reads "Failed. Tap to retry", and tapping it retries.

Q: What happens in edit mode?

  1. Long-press your own comment → Edit.

→ The attach icons are disabled and the pending-upload row is hidden. Draft files from a new comment stay in the composer and come back when the edit ends.

Q: Did web change?

  • The comment composer and comment edit behave as before. The only difference: a file outside acceptedFileTypes that you force through the OS picker's "All files" option is now dropped. (Automated: useFileSelect.test.ts "drops files outside acceptedFileTypes…")

Automated: pnpm test:unit 138/138 in packages/components, tsc clean, eslint clean on changed files, relay-compiler clean, ast-grep with no new findings. No component tests: native has no Playwright/Storybook layer, so native coverage comes from the common unit tests plus the native util and hook tests above.

Out of scope (do not flag)

  • Adding or removing attachments while editing a comment on mobile. Attach is disabled in edit mode, and removing an attached file on mobile is not built yet. Both are follow-ups.
  • Taking a photo with the camera. Only the library and the document picker are supported.
  • Pause/resume controls on mobile chips. ✕ cancels and tap retries. The store still supports pause.
  • A total file cap per comment. maxFiles applies per pick, the same as web today.

Accepted tradeoffs (do not re-flag)

  • Breaking type change (major changeset). FileUploadProgress.file is now typed UploadSource, because native has no File. The changeset says what callers must change. If the team would rather call this minor, given the module only just shipped in 3.0.0, that is a one-word change.
  • Base64 bridging cost. RN's XHR base64-bridges Uint8Array bodies, so each 5 MB chunk costs a synchronous native read plus about 6.7 MB of base64, three chunks at a time. Expect some JS-thread jank on low-end Android during large uploads. Fixing this would need a native streaming upload, which is out of scope here.
  • Horizontal chip row inside the gorhom bottom sheet. It uses RNGH ScrollView and needs a device check for swipe conflicts with the sheet's pan gesture.
  • Document picker MIME filter. It narrows to a MIME type only when exactly one non-image type is accepted. Otherwise it shows all files and the shared validator filters them.
  • No photo-library permission prompt. The system photo picker needs none (expo-image-picker only required it on iOS 10). This differs from useViewPhotoLibrary, which is single-select and avatar-oriented, so it is not reused here.

🤖 Generated with Claude Code

https://claude.ai/code/session_013LkamMd6XMPeYNHbAV1DZ9

Mobile comments can attach photos (library, multi-select) and PDFs (document picker),
show uploads above the composer, and list a comment's files under it.

The web upload pipeline is reused rather than reimplemented: an UploadSource seam
(name/size/type/readChunk) replaces File in the uploader, read lazily inside the
concurrency slot so native never holds more than MAX_CONCURRENT_CHUNKS chunks.
Selection rules, attachment limits and upload-item state are shared by both legs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013LkamMd6XMPeYNHbAV1DZ9
@changeset-bot

changeset-bot Bot commented Oct 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: bd9c874

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@baseapp-frontend/components Major
@baseapp-frontend/design-system Minor
@baseapp-frontend/wagtail Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Oct 9, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

sonarqubecloud Bot commented Oct 9, 2026

Copy link
Copy Markdown

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant