Skip to content

Fix mobile web PDF pinch zoom#95226

Merged
puneetlath merged 9 commits into
Expensify:mainfrom
KJ21-ENG:KJ21-ENG/92109-pdf-pinch-zoom-comparison
Jul 9, 2026
Merged

Fix mobile web PDF pinch zoom#95226
puneetlath merged 9 commits into
Expensify:mainfrom
KJ21-ENG:KJ21-ENG/92109-pdf-pinch-zoom-comparison

Conversation

@KJ21-ENG

@KJ21-ENG KJ21-ENG commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

Adds in-app pinch, double-tap, and pan zoom support for mobile-web PDF attachments opened in the full-screen attachment viewer. The web PDF viewer now reuses MultiGestureCanvas, forwards scale changes to the attachment carousel pager, and keeps inline chat PDF previews and desktop PDF rendering unchanged. While the PDF is zoomed, the PDF scroll surface stops handling browser touch scrolling so one-finger panning stays driven by the gesture canvas; at 1x, normal PDF scrolling remains enabled. The shared pan gesture also gets an explicit PDF-only opt-out for swipe-down-to-close so existing Lightbox behavior stays unchanged.

Fixed Issues

$ #92109
PROPOSAL: #92109 (comment)

Tests

  1. Verify that no errors appear in the JS console.
  2. Open the app in mobile-web Safari or mobile-web Chrome.
  3. Sign in and open a chat.
  4. Send or locate a PDF attachment in the chat.
  5. Tap the PDF attachment to open it in the full-screen attachment viewer.
  6. Pinch the PDF and verify the PDF content zooms in without page-level flicker or reload.
  7. Pan the zoomed PDF with one finger and verify movement is smooth and the attachment carousel does not switch pages while the PDF is zoomed.
  8. Zoom the PDF back to 1x and verify normal PDF vertical scrolling still works.
  9. Open a password-protected PDF attachment and verify the password input can be focused, typed into, and submitted.
  10. Open a PDF attachment containing a link and verify the link can still be tapped.
  11. Open an inline chat PDF preview and verify it still renders as a normal preview.
  12. Open an image attachment in the full-screen viewer and verify its existing swipe-down pull animation still works.
  • Verify that no errors appear in the JS console

Offline tests

No dedicated offline behavior is changed. The PDF gesture handling is local UI behavior and does not enqueue network requests.

QA Steps

  1. Open the app in mobile-web Safari or mobile-web Chrome.
  2. Sign in and open a chat with a PDF attachment, or send a PDF attachment to a chat.
  3. Tap the PDF attachment to open it in the full-screen attachment viewer.
  4. Pinch the PDF and verify the PDF content zooms in without page-level flicker or reload.
  5. Pan the zoomed PDF with one finger and verify movement is smooth and the attachment carousel does not switch pages while the PDF is zoomed.
  6. Zoom the PDF back to 1x and verify normal PDF vertical scrolling still works.
  7. Open a password-protected PDF attachment and verify the password input can be focused, typed into, and submitted.
  8. Open a PDF attachment containing a link and verify the link can still be tapped.
  9. Open an image attachment in the full-screen viewer and verify its existing swipe-down pull animation still works.
  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I considered failure scenarios; password-protected PDFs and link tapping are covered above, and this UI gesture change adds no new validation error state.
    • I considered offline behavior; this local gesture change does not enqueue requests, and offline-specific behavior is covered by the Offline tests note.
    • I considered High Traffic testing; this local PDF gesture change does not touch API or data-loading behavior.
  • I included screenshot/video sections for all platforms; native and desktop are marked not applicable, and mobile-web recordings are pending before final review.
  • I considered all platforms and scoped manual testing to the affected mobile-web PDF path; native and desktop paths are not changed:
    • Android: Native (not applicable; native PDF viewing already uses the native viewer path)
    • Android: mWeb Chrome (applicable mobile-web path; pending fresh recording before final review)
    • iOS: Native (not applicable; native PDF viewing already uses the native viewer path)
    • iOS: mWeb Safari (applicable mobile-web path; pending fresh recording before final review)
    • MacOS: Chrome / Safari (not applicable; desktop PDF rendering is unchanged)
  • I included console-error verification in the manual Tests/QA steps and did not add source-code console logging.
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • No new cross-app code pattern is introduced; the PDF path uses existing MultiGestureCanvas hooks.
  • I followed the guidelines as stated in the Review Guidelines
  • I included impacted image Lightbox regression coverage in the manual steps.
  • If any new file was added I verified that:
    • No new file was added.
  • If a new CSS style is added I verified that:
    • No existing style covers this zoomed PDF touch-action behavior.
    • StyleUtils does not cover touch-action gesture arbitration.
  • If new assets were added or existing ones were modified, I verified that:
    • No assets were added or modified.
    • No asset-loading behavior changed.
  • No message editing or sending path is modified.
  • MultiGestureCanvas is shared; image Lightbox regression coverage is included in the manual steps.
  • No relevant Storybook story covers this mobile-web PDF gesture path.
  • No direct deeplink behavior is changed.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • No form inputs or form layout alignment changed.
    • No design label is needed because this is a gesture behavior fix without visual UI design changes.
  • I considered unit tests; this tactile gesture behavior is covered by manual mobile-web steps and static checks rather than a useful unit test.
  • After merging main, I reran targeted checks and updated the PR for the remaining mobile-web gesture issue.

Screenshots/Videos

Android: Native
1783595012338249.mp4
Android: mWeb Chrome
Record_2026-07-04-15-47-16.mp4
iOS: Native
ezyZip.76.mp4
iOS: mWeb Safari
VIDEO-2026-07-06-01-32-40.mp4
MacOS: Chrome / Safari

Not applicable. The gesture wrapper is gated to mobile web.

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.

Files with missing lines Coverage Δ
...ts/Attachments/AttachmentCarousel/CarouselItem.tsx 90.90% <ø> (ø)
...ntView/AttachmentViewPdf/BaseAttachmentViewPdf.tsx 0.00% <ø> (ø)
...rc/components/MultiGestureCanvas/useTapGestures.ts 3.22% <ø> (ø)
...chments/AttachmentView/AttachmentViewPdf/index.tsx 0.00% <0.00%> (ø)
...rc/components/Attachments/AttachmentView/index.tsx 54.47% <66.66%> (+1.53%) ⬆️
src/components/MultiGestureCanvas/usePanGesture.ts 8.33% <0.00%> (ø)
...c/components/MultiGestureCanvas/usePinchGesture.ts 2.32% <0.00%> (-0.24%) ⬇️
src/components/MultiGestureCanvas/index.tsx 1.20% <0.00%> (-0.12%) ⬇️
src/components/PDFView/index.tsx 0.00% <0.00%> (ø)
... and 453 files with indirect coverage changes

@KJ21-ENG KJ21-ENG marked this pull request as ready for review July 3, 2026 20:20
@KJ21-ENG KJ21-ENG requested review from a team as code owners July 3, 2026 20:20
@melvin-bot melvin-bot Bot requested review from JmillsExpensify and nyomanjyotisa and removed request for a team July 3, 2026 20:20
@melvin-bot

melvin-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

@nyomanjyotisa Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@melvin-bot melvin-bot Bot requested review from a team and puneetlath and removed request for a team July 3, 2026 20:20
@melvin-bot

melvin-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

@puneetlath Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@melvin-bot melvin-bot Bot removed the request for review from a team July 3, 2026 20:20
Comment thread src/components/PDFView/index.tsx Outdated
});
};

const scaleChange = useCallback(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ CLEAN-REACT-PATTERNS-0 (docs)

React Compiler is enabled in this codebase (babel-plugin-react-compiler) and automatically memoizes closures based on their captured variables, so wrapping scaleChange in useCallback is redundant and interferes with the compiler's own caching model. It also adds a dependency array ([actions, onScaleChanged]) that must be maintained.

Remove the manual memoization and use a plain function so the compiler can handle it:

const scaleChange = (scale: number) => {
    onScaleChanged?.(scale);
    actions?.onScaleChanged?.(scale);
};

Reviewed at: e3c53f3 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8cea2f8e38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/components/Attachments/AttachmentCarousel/CarouselItem.tsx
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

2 similar comments
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@nyomanjyotisa

Copy link
Copy Markdown
Member

It looks like I ran into the same codex issue as the one mentioned here: https://expensify.slack.com/archives/C02NK2DQWUX/p1782486912897689

@puneetlath could you please help trigger the codex review? thanks!

@puneetlath

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 3dabc5a06d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

JmillsExpensify
JmillsExpensify previously approved these changes Jul 7, 2026

@JmillsExpensify JmillsExpensify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@nyomanjyotisa

Copy link
Copy Markdown
Member

@KJ21-ENG When a carousel page gains/loses focus, the PDFPreviewer remount and reload the PDF preview on each swipe

iOS-mWeb.Safari.mp4

@KJ21-ENG

KJ21-ENG commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

@KJ21-ENG When a carousel page gains/loses focus, the PDFPreviewer remount and reload the PDF preview on each swipe

@nyomanjyotisa Fixed in 5f6e902, it was caused by 3dabc5a that i made for the codex's review findings

Screen.Recording.2026-07-08.at.12.19.42.AM.mov

@nyomanjyotisa

Copy link
Copy Markdown
Member

@KJ21-ENG I found another issue with multi page PDF. When we zoom in on page 1 of a multi page PDF, we can't scroll/pan down to page 2. I guess because gesture canvas only pans within one viewport

iOS-mWeb.Safari.mp4

Also, please check all the items in the checklist and include recordings for all platforms to make sure we don't break anything

@KJ21-ENG

KJ21-ENG commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@nyomanjyotisa Does this looks good to you ? or when scroll/pan down to page 2 then it should zoom out to default ?

ezyZip.74.mp4

@nyomanjyotisa

Copy link
Copy Markdown
Member

@KJ21-ENG Yeah, I think that's good. We can follow the native app behavior

iOS-Native.mp4

@KJ21-ENG

KJ21-ENG commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@nyomanjyotisa Done, added in 9221811

@nyomanjyotisa

Copy link
Copy Markdown
Member

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
Android-HybridApp.mp4
Android: mWeb Chrome
Android-mWeb.Chrome.mp4
iOS: HybridApp
iOS-Native.mp4
iOS: mWeb Safari
iOS-mWeb.Safari.mp4
MacOS: Chrome / Safari
MacOS-Chrome.mp4

@nyomanjyotisa

Copy link
Copy Markdown
Member

@KJ21-ENG Could you also please check this comment? Thanks

Also, please check all the items in the checklist and include recordings for all platforms to make sure we don't break anything

@KJ21-ENG

KJ21-ENG commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@nyomanjyotisa strange, idk why but some recordings got failed to uplaod, may be i forgot to compress video. no worries i have uploaded agaian. Appologies for inconvenience.

@nyomanjyotisa

Copy link
Copy Markdown
Member

@puneetlath All yours now, thanks!

@puneetlath puneetlath merged commit 704d216 into Expensify:main Jul 9, 2026
39 checks passed
@OSBotify

OSBotify commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/puneetlath in version: 9.4.33-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 cancelled 🔪

@MelvinBot

Copy link
Copy Markdown
Contributor

🤖 Help site review: no changes required.

This PR adds mobile-web pinch / double-tap / pan zoom gestures for PDF attachments opened in the full-screen attachment viewer (reusing MultiGestureCanvas). It's a low-level touch-interaction/UX behavior change — no new feature, setting, tab, button, or workflow is introduced or renamed.

I checked App/docs/articles and the help site does not document touch gestures for viewing attachments:

  • The only articles touching PDFs (e.g. Attach and edit receipts on expenses, Chat features and moderation tools) describe uploading, rotating, and viewing receipts/attachments — none document pinch/pan/zoom interaction mechanics that this PR changes.
  • No article references pinch or zoom in the attachment-viewing context.

Since the help site documents features and workflows rather than gesture mechanics, no article needs updating and no draft docs PR was created.

If you'd like a note added somewhere (e.g. a "you can pinch-to-zoom PDFs on mobile web" tip in an attachment article), let me know and I'll draft it.

@KJ21-ENG, please confirm you agree no help site update is needed here. If you'd prefer a docs PR anyway, reply and I'll create one.

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.

6 participants