Skip to content

92969: Add "expand" button overlay to charts for full-screen mode - #95249

Merged
JS00001 merged 22 commits into
Expensify:mainfrom
abbasifaizan70:92969
Jul 28, 2026
Merged

JS00001 merged 22 commits into
Expensify:mainfrom
abbasifaizan70:92969

Conversation

@abbasifaizan70

@abbasifaizan70 abbasifaizan70 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

Adds an expand button overlaid on the top-right corner of inline charts, which opens the chart in a centered full-screen modal. The modal reuses the already-parsed chart data and renders it scaled to the viewport, so the expanded chart matches the inline one exactly, just larger.

Fixed Issues

$ #92969
PROPOSAL: #92969 (comment)

Tests

  • Verify that no errors appear in the JS console
  1. Open the Concierge chat and ask for a chart, e.g. "Graph my spending by category".
  2. Verify the inline chart shows an expand button in its top-right corner.
  3. Tap the expand button.
  4. Verify a centered full-screen modal opens with the chart at full viewport width, matching the inline chart.
  5. Close the modal via the back button and verify the chat is unchanged.
  6. Narrow the window (or use the side panel) and verify the inline chart scales down without clipping.

Offline tests

same as tests.

QA Steps

same as tests.

  • 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 added steps to cover 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 tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • 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)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • 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 new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • 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.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user 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.

Screenshots/Videos

Android: Native
Screen.Recording.2026-07-07.at.2.33.29.AM.mov
Android: mWeb Chrome Screenshot 2026-07-07 at 2 39 44 AM
iOS: Native
Screen.Recording.2026-07-07.at.2.36.43.AM.mov
iOS: mWeb Safari
Screen.Recording.2026-07-07.at.2.37.58.AM.mov
MacOS: Chrome / Safari
Screen.Recording.2026-07-07.at.2.29.33.AM.mov

@melvin-bot

melvin-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

Hey, I noticed you changed src/languages/en.ts in a PR from a fork. For security reasons, translations are not generated automatically for PRs from forks.

If you want to automatically generate translations for other locales, an Expensify employee will have to:

  1. Look at the code and make sure there are no malicious changes.
  2. Run the Generate static translations GitHub workflow. If you have write access and the K2 extension, you can simply click: [this button]

Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running:

npx ts-node ./scripts/generateTranslations.ts --help

Typically, you'd want to translate only what you changed by running npx ts-node ./scripts/generateTranslations.ts --compare-ref main

@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
src/CONST/index.ts 94.81% <ø> (ø)
src/hooks/useHover.ts 71.42% <ø> (ø)
src/styles/index.ts 62.88% <ø> (ø)
.../VictoryChartRenderer/BaseVictoryChartRenderer.tsx 0.00% <0.00%> (ø)
...rtRenderer/components/VictoryChartExpandButton.tsx 6.66% <6.66%> (ø)
...hartRenderer/components/VictoryChartExpandable.tsx 5.26% <5.26%> (ø)
...artRenderer/components/VictoryChartExpandModal.tsx 2.17% <2.17%> (ø)
... and 11 files with indirect coverage changes

@abbasifaizan70
abbasifaizan70 marked this pull request as ready for review July 6, 2026 14:36
@abbasifaizan70
abbasifaizan70 requested review from a team as code owners July 6, 2026 14:36
@melvin-bot

melvin-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

@situchan 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 request for a team and joekaufmanexpensify July 6, 2026 14:37

@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: bb9bb9f384

ℹ️ 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".

@dannymcclain

Copy link
Copy Markdown
Contributor

cc @Expensify/design this button doesn't look right to me at all. Can we use one of our standard icon buttons here? (fully circle, smaller icon, etc.)

@shawnborton

Copy link
Copy Markdown
Contributor

Totally agree. Please use our standard icon buttons, use a small 28x28 icon and it should probably be like 12px from the top and 12px from the right?

@abbasifaizan70

Copy link
Copy Markdown
Contributor Author

@situchan I fixed above AI feedbacks.
@shawnborton updated the icons and attach latest video for web only. Will attached other platform video once login issue is fixed as currently facing login issues.

@dannymcclain

Copy link
Copy Markdown
Contributor

Much better. @shawnborton do you think we should only show that icon on hover? Basically exactly what we do for receipts?

CleanShot.2026-07-06.at.11.28.54.mp4

@shawnborton

Copy link
Copy Markdown
Contributor

Yup, that makes sense to me! Or we always show a three dots icon in the top right but I don't think that will look good based on how these things scale on mobile devices.

Also, does clicking anywhere on the chart also expand it? Or just this button does?

@luacmartins
luacmartins requested a review from situchan July 20, 2026 15:46
@situchan

situchan commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

@abbasifaizan70 the flicker is not fixed yet. Clearly noticeable in dark mode

Screenshot 2026-07-20 at 6 48 43 PM
Screen.Recording.2026-07-20.at.6.47.54.PM.mov

@abbasifaizan70

Copy link
Copy Markdown
Contributor Author

@situchan let me retry to check again.

… removing Skia canvas during close animation
@abbasifaizan70

Copy link
Copy Markdown
Contributor Author
Screen.Recording.2026-07-21.at.2.23.28.AM.mov

@situchan Fixed flicker issues. Can you check if you can see it now as well? Thanks

@luacmartins

Copy link
Copy Markdown
Contributor

Currently, the chart fills up the whole vertical space in the modal. Is this expected? Additionally, the resolution seems pretty poor in large screens.
Screenshot 2026-07-21 at 1 43 08 PM

@shawnborton

Copy link
Copy Markdown
Contributor

In theory that's exactly how image attachments work. For the resolution, are we not showing the svg version at the large size?

@abbasifaizan70

abbasifaizan70 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor Author

@luacmartins Yes, the vertical fill is intentional — the modal scales the chart uniformly to fit the available area (preserving aspect ratio), the same way image attachments do, so height usually becomes the binding dimension.

@shawnborton There's no SVG version to swap in — charts are drawn by Skia onto a raster canvas at their design size, and the expanded view upscales that raster uniformly. That upscaling is what keeps the HTML-provided label/legend coordinates (which are in design space) perfectly aligned, but it's also why very large screens look soft. Rendering the canvas natively at the target size would fix sharpness but requires re-mapping the label coordinates — the same machinery pinch-zoom needs. I'd suggest handling sharp large-size rendering together with the zoom follow-up PR .

@MelvinBot

Copy link
Copy Markdown
Contributor

Review

Solid, well-documented feature — the state-lives-below-the-renderer design (so hover doesn't re-render the chart subtree) and the "render at design size, then uniform transform-scale" technique are the right calls, and they keep the expanded chart aligned with the inline one. CI is fully green. A few things worth addressing before merge:

1. Duplicated scaling/clipping logic (main concern)

VictoryChartExpandModal.tsx re-implements logic that VictoryChartContainerFixed.tsx already owns: background resolution via resolveChartContainerBgColor, polar clipping via POLAR_CONTAINER_HEIGHT_RATIO, overflow: hidden, and the transform-scale + top left origin. That's now two sources of truth for "how a chart is boxed, clipped, and scaled." If the inline rendering rules change, the modal will silently drift.

The only thing the modal needs that the container doesn't do is scale up (>1, fit-to-measured-viewport) — the inline path caps at 1. Consider extending VictoryChartContainer/Fixed to accept a target size or an uncapped scale so the modal can reuse it, rather than forking the layout math.

2. No tests for the new components

The checklist marks unit tests as added and Codecov flagged a coverage drop, but the only test change is a mock update in BaseListItemTest.tsx. The visibility logic in VictoryChartExpandable.tsx (hovered || shouldAlwaysShowButton, i.e. hover-only vs. touch/no-hover always-on) is pure branching that's cheap to cover and easy to regress — worth at least one test.

3. Modal title is "Details"

The header uses common.details → "Details", which doesn't describe what's shown (a bigger chart) and doesn't match the "Expand" affordance. Consider a more fitting title — the chart's own title if one is available in context, otherwise common.expand.

4. Always-visible button occludes the chart on touch / no-hover devices

Because shouldAlwaysShowButton keeps the button mounted-and-visible whenever hover isn't available, on mobile the button permanently overlays the chart's top-right corner (and, per the comment, captures presses there). Please confirm on native that it doesn't cover a data point, label, or legend entry for the common chart types — if it can, a small inset or a semi-transparent background chip would help it read as an overlay rather than clipping content.

Minor
  • chartExpandedContent in styles/index.ts adds transformOrigin: 'top left' as a named style, while the inline path (VictoryChartContainerFixed.tsx:51) inlines the same value — another small bit of the duplication in (1); folding them together would be cleaner.
  • Adding deviceHasHoverSupport to useHover's return is fine and correctly propagated to the only typed mock, but it does widen a shared hook's public surface for a single consumer — reasonable, just noting it.

None of these are correctness blockers (the approach and edge-case handling for polar clipping, close-animation flashes, and touch-vs-hover are all thoughtfully covered). (1) and (2) are the ones I'd want resolved.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 6bb61e8e13

ℹ️ 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".

@MelvinBot

Copy link
Copy Markdown
Contributor

Regression scan

Most of the diff is additive (new components, a new SENTRY_LABEL const, chartExpandedContent style, common.expand translations) and carries no regression risk. The changes that touch existing behavior — and therefore where a regression could hide — are these:

1. Every inline chart now gets a new wrapping View (highest risk)

BaseVictoryChartRenderer.tsx:43-47 now wraps all charts in VictoryChartExpandable, whose outer view is {maxWidth: '100%', width: designWidth} (mw100 = maxWidth: '100%'). Previously the outermost element was VictoryChartContainerResponsive's bare mw100 view with no explicit width.

This changes the chart's outer box width on native/wide surfaces from "stretch to the message column" to "exactly designWidth" — intentional per the author's comment, but it affects every chart, not just the expand flow. Verify inline charts render/position identically to main on:

  • Native tablets / wide web (where the wrapper now caps at designWidth instead of stretching)
  • Side panel / narrow containers (where mw100 should still let it shrink below designWidth)
  • Charts without design dimensions (fluid path — designWidth is undefined, so only mw100 applies; confirm no change)

This is the main thing to eyeball because it's not covered by tests — see (4).

2. Expand button permanently overlays the chart on touch / no-hover devices

VictoryChartExpandable.tsx:54,67: shouldAlwaysShowButton = !deviceHasHoverSupport || canUseTouchScreen(), so on mWeb/native the button is always visible over the chart's top-right corner. Existing charts had nothing there — confirm it doesn't occlude a data point, label, or legend entry for the common chart types.

3. useHover return shape widened (low risk — appears safe)

useHover.ts:9 adds deviceHasHoverSupport. This is a shared hook used by BaseListItem, MoneyRequestReceiptView, ReceiptCell, AnchorRenderer, CardRow, ParentNavigationSubtitle — all destructure {hovered, bind}, so the addition is backward-compatible, and the only fully-typed mock (BaseListItemTest.tsx) was updated. Typecheck + tests are green, so no regression here — flagging only because a shared hook was touched for one feature.

4. Gap: no rendering tests for the chart renderer

CI is fully green, but the existing chart tests are all parser/util level (computeChartScale, VictoryChartParsers, etc.) — there's no render/snapshot test for BaseVictoryChartRenderer or the container. So green CI does not prove the layout in (1) is unchanged; that regression can only be caught by manual/visual verification on the platforms above.

Net: no regression is provable from static analysis alone, and (3) is safe. The one to actively verify before merge is (1) — the new wrapper changes the outer box of every inline chart, and nothing in the test suite guards it.

@abbasifaizan70

Copy link
Copy Markdown
Contributor Author

@luacmartins Can you please take a look? Thanks

@situchan

Copy link
Copy Markdown
Contributor

@abbasifaizan70 please merge main

@situchan

Copy link
Copy Markdown
Contributor

@luacmartins Can you please take a look? Thanks

@luacmartins is OOO until Aug 5. Maybe @JS00001 can help?

@JS00001
JS00001 self-requested a review July 28, 2026 18:24
@JS00001
JS00001 merged commit d9e39d2 into Expensify:main Jul 28, 2026
34 checks passed
@OSBotify

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.

@github-actions

Copy link
Copy Markdown
Contributor

🚧 JS00001 has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/JS00001 in version: 9.4.46-0 🚀

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

@MelvinBot

Copy link
Copy Markdown
Contributor

🤖 Help site review: no docs changes required.

This PR adds an expand button (and a full-screen modal) to the inline charts Concierge renders in chat — a self-contained UI affordance. It introduces no new workflow, setting, or user-facing capability that a help article would describe.

I reviewed the relevant help content against these changes:

None of these articles document Concierge's in-chat chart output, and help articles describe features and workflows rather than individual buttons. An expand-to-full-screen control doesn't meet the bar for its own documentation, so I did not create a draft help site PR.

If the team wants the broader "Concierge can render charts/graphs in chat" capability documented (that capability predates this PR), that would be a separate docs effort — let me know and I can scope it.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/marcaaron in version: 9.4.46-10 🚀

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

Bundle Size Analysis (Sentry):

@luacmartins luacmartins 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.

Thanks for getting this through the finish line!

@quinthar quinthar added the #ai label Aug 11, 2026
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.

9 participants