Skip to content

[Suggested Follow-ups][R1] Render follow‑up buttons + optimistic resolution in App#80539

Merged
Beamanator merged 16 commits intoExpensify:mainfrom
software-mansion-labs:feat/concierge-followups-release-1
Jan 28, 2026
Merged

[Suggested Follow-ups][R1] Render follow‑up buttons + optimistic resolution in App#80539
Beamanator merged 16 commits intoExpensify:mainfrom
software-mansion-labs:feat/concierge-followups-release-1

Conversation

@jmusial
Copy link
Contributor

@jmusial jmusial commented Jan 26, 2026

Explanation of Change

This PR is a part of Suggested Follow-ups project. It allows rendering follow up buttons in a concierge chat and optimistically removes follow up buttons from the message if the button has been clicked or comment posted by a user.

Fixed Issues

$ #80512
PROPOSAL:

Tests

Pre requisites:

  1. Have expensifail or swmansion account

  2. Use staging server

  3. Open the app

  4. Enter concierge chat

  5. Find a way to prompt concierge-ai to give you follow up list. Prompts that usually work

  • Please give me a list or actionable follow ups
  • Give me a list of follow ups in special <followup-list format
  1. Concierge AI should give you a list of follow up buttons
  2. Press a button, The button text should be posted as a question in concierge chat and the buttons should dissapear
  3. Prompt for the actions again, buttons should appear
  4. Write anything in the chat and post
  5. Text should be posted as a question in concierge chat and the buttons should dissapear

Offline tests

N / A

QA Steps

Same as tests.

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 verified there are no new alerts related to the canBeMissing param for useOnyx
  • 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 shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • 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)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • 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)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • 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.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • 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
0088.android.native.mov
Android: mWeb Chrome
0088.android.web.mov
iOS: Native
0088.ios.native.mov
iOS: mWeb Safari
0088.ios.safari.mov
MacOS: Chrome / Safari
0088.web.chrome.mov

@codecov
Copy link

codecov bot commented Jan 26, 2026

Codecov Report

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

Files with missing lines Coverage Δ
src/libs/ReportActionsFollowupUtils.ts 100.00% <100.00%> (ø)
src/libs/ReportActionsUtils.ts 55.07% <100.00%> (+0.13%) ⬆️
src/pages/home/report/PureReportActionItem.tsx 53.63% <82.35%> (+0.89%) ⬆️
src/libs/actions/Report.ts 58.17% <87.87%> (+0.37%) ⬆️
... and 48 files with indirect coverage changes

@jmusial
Copy link
Contributor Author

jmusial commented Jan 27, 2026

Question: how should we handle really long questions ?
image

@jmusial jmusial marked this pull request as ready for review January 27, 2026 19:50
@jmusial jmusial requested review from a team as code owners January 27, 2026 19:50
@melvin-bot melvin-bot bot requested review from joekaufmanexpensify and mkhutornyi and removed request for a team January 27, 2026 19:50
@melvin-bot
Copy link

melvin-bot bot commented Jan 27, 2026

@mkhutornyi 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 January 27, 2026 19:50

const isConciergeOptions = isConciergeCategoryOptions(action) || isConciergeDescriptionOptions(action);
const actionContainsFollowUps = containsActionableFollowUps(action);
let actionableButtonsNoLines = 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

❌ CONSISTENCY-2 (docs)

The magic numbers 1, 2, and 0 assigned to actionableButtonsNoLines lack clear documentation about their meaning and purpose.

Add a comment explaining what these values represent:

// Controls the number of lines to display in button text:
// - 1: default single line for most buttons
// - 2: multi-line for concierge options
// - 0: no limit for followup buttons (allow full text wrapping)
let actionableButtonsNoLines = 1;
if (isConciergeOptions) {
    actionableButtonsNoLines = 2;
}
if (actionContainsFollowUps) {
    actionableButtonsNoLines = 0;
}

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is just a basic Text prop. The dosc explain it

@marcochavezf
Copy link
Contributor

@Expensify/design thoughts about this, this is an edge that we didn't consider for small screens and long followups

![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === moderationDecision) && !isPendingRemove(action);

const isConciergeOptions = isConciergeCategoryOptions(action) || isConciergeDescriptionOptions(action);
const actionContainsFollowUps = containsActionableFollowUps(action);
Copy link
Contributor

Choose a reason for hiding this comment

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

❌ PERF-13 (docs)

The function parseFollowupsFromHtml(messageHtml) is called twice for the same action: once inside the actionableItemButtons useMemo (line 881) and again in containsActionableFollowUps(action) (line 1665). This causes redundant HTML parsing.

Optimize by computing the followups once and reusing the result:

const actionableItemButtons = useMemo(() => {
    // ... existing code ...
    
    const messageHtml = getReportActionMessage(action)?.html;
    if (messageHtml && reportActionReport) {
        const followups = parseFollowupsFromHtml(messageHtml);
        if (followups && followups.length > 0) {
            return followups.map((followup) => ({
                text: followup.text,
                shouldUseLocalization: false,
                key: `${action.reportActionID}-followup-${followup.text}`,
                onPress: () => {
                    resolveSuggestedFollowup(reportActionReport, reportID, action, followup.text, personalDetail.timezone ?? CONST.DEFAULT_TIME_ZONE);
                },
            }));
        }
    }
    // ... rest of code
}, [...]);

// Later in the component, reuse the result:
const actionContainsFollowUps = actionableItemButtons.length > 0 && getReportActionMessage(action)?.html?.includes("<followup-list");

Or alternatively, compute followups once outside the useMemo and use it in both places.


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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think despite duplicating some logic, the current version is more readable and maintainable.

Copy link

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

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: c9eddebc88

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

Comment on lines +649 to +652
if (lastActorAccountID === CONST.ACCOUNT_ID.CONCIERGE && lastActionReportActionID) {
const resolvedAction = buildOptimisticResolvedFollowups(lastVisibleAction);
if (resolvedAction) {
optimisticReportActions[lastActionReportActionID] = resolvedAction;

Choose a reason for hiding this comment

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

P2 Badge Avoid tagging concierge followup actions with send errors

The resolved concierge follow‑up action is now inserted into optimisticReportActions. That same collection is later used to build failureReportActions, which attaches errors to every entry. If the ADD_COMMENT/ADD_ATTACHMENT request fails (offline/timeout) while a follow‑up list is present, the concierge message itself will be marked with a generic “add comment failed” error even though it wasn’t the failed action. Consider excluding the resolved concierge action from the failure error set or handling it separately.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

0088.failed.to.post.a.message.mov

Added a revert for stripping the pills, should be all g now

@shawnborton
Copy link
Contributor

Hmm why is the label repeated twice in that button example?

I think the reality is that we might need to allow these buttons to go multiple lines, but it feels like we should be able to avoid three lines?

@jmusial
Copy link
Contributor Author

jmusial commented Jan 27, 2026

Hmm why is the label repeated twice in that button example?

I think the reality is that we might need to allow these buttons to go multiple lines, but it feels like we should be able to avoid three lines?

Hey @shawnborton the duplication was just me forcing longer text to showcase the issue, don't mind the content.

Right now there is no limit on the number of lines the button can take up.

@marcochavezf
Copy link
Contributor

Bug: empty report action shows for Concierge message

This is fine for now, since Concierge is being forced to display only the followups without a prepended message, but in practice this scenario shouldn't happen for real user questions

@github-actions
Copy link
Contributor

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

@mkhutornyi
Copy link
Contributor

mkhutornyi commented Jan 27, 2026

Web works as expected but on native buttons are broken, showing raw html text.

I used this prompt:

Give me a list of follow ups in special <followup-list> format
Screenshot 2026-01-27 at 11 50 52 pm Screenshot 2026-01-27 at 11 50 44 pm

@github-actions
Copy link
Contributor

🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
Built from App PR #80539.

Android 🤖 iOS 🍎
⏩ SKIPPED ⏩ ⏩ SKIPPED ⏩
The build for Android was skipped The build for iOS was skipped
Web 🕸️
https://80539.pr-testing.expensify.com
Web

👀 View the workflow run that generated this build 👀

@marcochavezf
Copy link
Contributor

marcochavezf commented Jan 27, 2026

Web works as expected but on native buttons are broken, showing raw html text.

Thanks for catching this @mkhutornyi This is fine as well, since the request is very specific about requesting follow-up content, and it’s not something a real user would normally ask.

@melvin-bot
Copy link

melvin-bot bot commented Jan 27, 2026

We did not find an internal engineer to review this PR, trying to assign a random engineer to #80512 as well as to this PR... Please reach out for help on Slack if no one gets assigned!

@melvin-bot melvin-bot bot requested a review from roryabraham January 27, 2026 23:12
Copy link

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

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: be04dd8f45

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

Comment on lines +48 to +50
// Matches individual <followup><followup-text>...</followup-text></followup> elements
const followUpTextRegex = /<followup><followup-text>([^<]*)<\/followup-text><\/followup>/gi;
let match = followUpTextRegex.exec(followupListHtml);

Choose a reason for hiding this comment

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

P2 Badge Accept whitespace/attributes in followup tag parsing

The followup parser only matches the exact pattern <followup><followup-text>…</followup-text></followup> with no whitespace or attributes. If concierge outputs valid HTML with indentation/newlines (e.g., <followup>\n <followup-text>…) or adds attributes on <followup>, the regex will never match, parseFollowupsFromHtml() returns an empty list, and no follow‑up buttons are rendered (plus resolution logic will treat it as “no followups”). This breaks the feature for common formatting variants; consider allowing optional whitespace/attributes or using an HTML parser.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that should be ok, given that our BE generates the pattern and we expect it as is

Comment on lines +6577 to +6580
// Optimistically update the HTML to mark followup-list as resolved
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {
[reportActionID]: resolvedAction,
});

Choose a reason for hiding this comment

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

P2 Badge Restore followups when addComment fails

resolveSuggestedFollowup() merges the “selected” version of the action into Onyx before the comment write. If the subsequent addComment fails (offline/API error), there is no rollback path in this function to restore the original unresolved <followup-list> state, so the follow‑up buttons disappear even though the follow‑up was never posted. This is user‑visible when the write fails and leaves no way to reselect from the list without reloading. Consider deferring the merge until the optimistic comment succeeds or storing the original action so failure handling can restore it.

Useful? React with 👍 / 👎.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rollback added in src/libs/actions/Report.ts#728 and working as shown here

@inimaga inimaga removed the request for review from roryabraham January 27, 2026 23:26
Copy link
Contributor

@roryabraham roryabraham left a comment

Choose a reason for hiding this comment

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

General comment: we should capitalize HTML as that's more consistent with our common convention to use capitalize ID and other acronyms in variable names like reportID.

import type {UserIsLeavingRoomEvent, UserIsTypingEvent} from '@libs/Pusher/types';
import * as ReportActionsFollowupUtils from '@libs/ReportActionsFollowupUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import {getLastVisibleAction} from '@libs/ReportActionsUtils';
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a bit odd that we have a destructured named import here and also a globbed named import.

return null;
}

const updatedHtml = html.replace(/<followup-list(\s[^>]*)?>/, '<followup-list selected>');
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB: a comment on what this line is doing might be helpful

* @param timezoneParam - The user's timezone
* @param ancestors - Array of ancestor reports for proper threading
*/
function resolveSuggestedFollowup(
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: Put the new SuggestedFollowup-related functions in src/libs/actions/Report/SuggestedFollowup.ts. Report.ts is already too big, we should start breaking it up before git blame and syntax highlighting stops working in here 😅

We're trying to enforce an (arbitrary) 4K line limit.

* @returns true if the action is an ADD_COMMENT with unresolved followups, false otherwise
*/
function containsActionableFollowUps(reportAction: OnyxInputOrEntry<ReportAction>): boolean {
const isActionAComment = isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT);
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB:

Suggested change
const isActionAComment = isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT);
const isAddCommentAction = isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT);

}

// Matches a <followup-list> HTML element and its entire contents. (<followup-list><followup><followup-text>Question?</followup-text></followup></followup-list>)
const followUpListRegex = /<followup-list(\s[^>]*)?>[\s\S]*?<\/followup-list>/i;
Copy link
Contributor

Choose a reason for hiding this comment

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

My initial reaction is always to grimace when I see regexes used for HTML parsing. This repo is already configured with htmlparser2, so I suggest using that to parse your HTML.

Vibe-coded an example implementation:

diff --git a/src/libs/ReportActionsFollowupUtils.ts b/src/libs/ReportActionsFollowupUtils.ts
index e068a3935da..1eb735c0f61 100644
--- a/src/libs/ReportActionsFollowupUtils.ts
+++ b/src/libs/ReportActionsFollowupUtils.ts
@@ -1,3 +1,4 @@
+import {DomUtils, parseDocument} from 'htmlparser2';
 import CONST from '@src/CONST';
 import type {OnyxInputOrEntry, ReportAction} from '@src/types/onyx';
 import type {Followup} from './ReportActionsUtils';
@@ -22,36 +23,36 @@ function containsActionableFollowUps(reportAction: OnyxInputOrEntry<ReportAction
     return !!followups && followups.length > 0;
 }
 
-// Matches a <followup-list> HTML element and its entire contents. (<followup-list><followup><followup-text>Question?</followup-text></followup></followup-list>)
-const followUpListRegex = /<followup-list(\s[^>]*)?>[\s\S]*?<\/followup-list>/i;
 /**
  * Parses followup data from a <followup-list> HTML element.
  * @param html - The HTML string to parse for <followup-list> elements
  * @returns null if no <followup-list> exists, empty array [] if the followup-list has the 'selected' attribute (resolved state), or an array of followup objects if unresolved
  */
 function parseFollowupsFromHtml(html: string): Followup[] | null {
-    const followupListMatch = html.match(followUpListRegex);
-    if (!followupListMatch) {
+    const doc = parseDocument(html);
+    const followupListElements = DomUtils.getElementsByTagName('followup-list', doc, true);
+
+    if (followupListElements.length === 0) {
         return null;
     }
 
     // There will be only one follow up list
-    const followupListHtml = followupListMatch[0];
-    // Matches a <followup-list> element that has the "selected" attribute (<followup-list selected>...</followup-list>).
-    const followUpSelectedListRegex = /<followup-list[^>]*\sselected[\s>]/i;
-    const hasSelectedAttribute = followUpSelectedListRegex.test(followupListHtml);
-    if (hasSelectedAttribute) {
-        return [];
+    const followupList = followupListElements.at(0);
+    if (!followupList) {
+        return null;
     }
 
-    const followups: Followup[] = [];
-    // Matches individual <followup><followup-text>...</followup-text></followup> elements
-    const followUpTextRegex = /<followup><followup-text>([^<]*)<\/followup-text><\/followup>/gi;
-    let match = followUpTextRegex.exec(followupListHtml);
-    while (match !== null) {
-        followups.push({text: match[1]});
-        match = followUpTextRegex.exec(followupListHtml);
+    // Check if the followup-list has the "selected" attribute (resolved state)
+    if (followupList.attribs && 'selected' in followupList.attribs) {
+        return [];
     }
+
+    // Find all <followup-text> elements within the followup-list
+    const followupTextElements = DomUtils.getElementsByTagName('followup-text', followupList, true);
+    const followups: Followup[] = followupTextElements.map((element) => ({
+        text: DomUtils.textContent(element),
+    }));
+
     return followups;
 }
 export {containsActionableFollowUps, parseFollowupsFromHtml};

Copy link
Contributor

Choose a reason for hiding this comment

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

That would likely also resolve codex's concern. Should be less brittle. I also think it's a bit easier to read and think about. It's unlikely it really matters much but it will probably be faster too.


/**
* Used for generating preview text in LHN and other places where followups should not be displayed.
* Implemented here instead of ReportActionFollowupUtils due to circular ref
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion: put it in its own file src/libs/ReportActionFollowupUtils/stripFollowupListFromHtml.ts

Copy link
Contributor

@Beamanator Beamanator left a comment

Choose a reason for hiding this comment

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

Love the comments @roryabraham 👍

I think we should merge and CP for now and def handle followups in a followup 👍

@Beamanator Beamanator merged commit 57b2f8c into Expensify:main Jan 28, 2026
36 checks passed
@OSBotify
Copy link
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.

@shawnborton
Copy link
Contributor

@dannymcclain I can get down with something like that! Sounds like we'll do all of that in a follow up?

@jmusial
Copy link
Contributor Author

jmusial commented Jan 28, 2026

I kinda think we just need a totally custom button style for these (like rounded rectangles instead of pills 😱) so that they can grow as tall as they need to without looking super weird. We've already run into this issue with long category names, so I think if anything this problem is just going to get worse.

CleanShot 2026-01-27 at 13 22 21@2x cc @Expensify/design ☝️ Not necessarily suggesting these are the _exact_ styles we should use, I'm just saying something like this that's more adaptive.

hey @dannymcclain do you think this styling should be for the follow up pills or all actionable buttons in concierge chat ?

@dubielzyk-expensify
Copy link
Contributor

I kinda think we'd do it for all actions in chat in general.

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by https://github.com/Beamanator in version: 9.3.11-0 🚀

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

@jponikarchuk
Copy link

jponikarchuk commented Jan 29, 2026

@jmusial @Beamanator @dubielzyk-expensify Could you please confirm what account should we use for checking this PR. We have such result when using account without +
image (8)

@IuliiaHerets
Copy link

@jmusial @roryabraham @Beamanator QA team isn’t able to use an Expensifail account for Concierge testing, and unfortunately, testing with a Gmail account didn’t work for us.
Would it be possible to check this internally?

@jmusial
Copy link
Contributor Author

jmusial commented Jan 29, 2026

@jponikarchuk @IuliiaHerets
you can use any fake account from @swmansion.com domain. To test this feature you do not need to confirm your email with a magic code.

@jponikarchuk
Copy link

jponikarchuk commented Jan 29, 2026

@jmusial We still can't check this. I used ttyy@swmansion.com account

image

@inimaga
Copy link
Contributor

inimaga commented Jan 30, 2026

✅ QA Passed

Part 1

SS.-.P1.mp4

Part 2

SS.-.P2.mp4

@OSBotify
Copy link
Contributor

OSBotify commented Feb 5, 2026

🚀 Deployed to production by https://github.com/Julesssss in version: 9.3.12-1 🚀

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

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.