Skip to content

Fix: Concierge Anywhere can act on the viewed report on native (source report threading) - #99337

Merged
pecanoro merged 17 commits into
mainfrom
claude-conciergeNativeSourceReport
Sep 15, 2026
Merged

pecanoro merged 17 commits into
mainfrom
claude-conciergeNativeSourceReport

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Aug 24, 2026 •

Copy link
Copy Markdown
Contributor

Explanation of Change

On native there is no side panel, so Concierge Anywhere (opened via the sidebar Concierge button) never attached the report the user was viewing to the message it sends. The composer only built sidePanelContext when isInSidePanel was true (web-only), so on iOS/Android Concierge received no source report and replied that the chat/report "doesn't have any expenses on it right now."

This threads the source report through the Concierge navigation entry on native:

  • The only entry that sets it is the native side-panel button, which captures Navigation.getTopmostReportId() at press time and passes it as a new optional sourceReportID route param (dropped when it equals the Concierge report, so tapping from within Concierge never self-references).
  • useSidePanelContext reads that route param on the native (!isInSidePanel) branch and returns {reportID: sourceReportID}, mirroring the {reportID} context the side panel builds on web.
  • The composer's send guard is relaxed to gate on sidePanelContext being present rather than on isInSidePanel, so the native-originated context is attached and sent (the backend already consumes sidePanelContext.reportID for the web flow).

Because the source report is scoped to each Concierge navigation entry (a route param) rather than a global Onyx key, two Concierge entries in the same stack can't collide — each restores its own sourceReportID on back-nav. Web behavior is unchanged: it still builds context from the live side-panel state.

Fixed Issues

$ #97945
PROPOSAL: #97945 (comment)

Tests

  1. On iOS or Android native, open an expense report that has multiple transactions.
  2. Tap the Concierge (help) icon in the report to open Concierge Anywhere.
  3. Send: Update all transactions in this report to have category Car.
  4. Verify Concierge updates all transactions in that report (it no longer replies that the chat/report has no expenses).
  • Verify that no errors appear in the JS console

Offline tests

N/A

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 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 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 Expense report with two transactions and Help icon in header Concierge Anywhere opened from the report Help icon Sent message and Concierge clarifying reply Concierge confirms both expenses updated Report showing both transactions recategorized
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

…ed report

Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
… oxfmt import grouping

Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Fixed the two real failing checks and pushed to the branch (dc55f04). Both stem from the new useRoute() call this PR added to useSidePanelContext.

test (job 6) — all 15 useSidePanelContext tests failed with Couldn't find a route object. Is your component inside a screen in a navigator? The hook now calls useRoute() unconditionally, but the test mocks every other dependency (useIsInSidePanel, useCurrentReportID, SearchContext) and renders the hook outside a navigator, so useRoute() threw. Added a jest.mock('@react-navigation/native', …) that returns {params: mockRouteParams} (reset per test), mirroring the existing mock pattern, plus a positive case covering the native source-report path.

Oxfmt check — diff on useSidePanelContext.ts. The two new type imports are in different oxfmt import groups (@libs/** vs @navigation/**), which oxfmt separates with a blank line. Added the missing blank line.

Check independent approval is failing only because the PR isn't approved yet — that's expected and not something a code change fixes.

I wasn't able to run Jest or oxfmt locally in this environment (tool execution is sandboxed here), so CI on the new commit is the confirmation — but both fixes map directly to the exact failure output.

Changes

view run

@codecov

codecov Bot commented Aug 24, 2026 •

Copy link
Copy Markdown

Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Found the remaining Oxfmt failure and pushed a fix (768bfdd).

Root cause: the useMemo dependency array exceeded oxfmt's printWidth: 190. Adding sourceReportID to the deps pushed that single line to 199 chars, so oxfmt wanted to wrap it one-entry-per-line. Fixed by wrapping it.

My earlier commit already handled the other two failures — the test (job 6) mock and the missing blank line between the @libs and @navigation import groups. This was the one oxfmt change I missed.

I still can't run oxfmt/Jest locally in this environment, so CI on 768bfdd is the confirmation, but the line-length overflow is unambiguous (199 > 190) and the wrapped form matches oxfmt's style used elsewhere in the repo.


view run

@MelvinBot

Copy link
Copy Markdown
Contributor Author

No code fix needed — the previously failing checks are now green on the latest commit (768bfdd).

  • Oxfmt check ✅ passing — both formatting issues are fixed: the blank line between the @libs and @navigation import groups, and the useMemo dependency array wrapped across lines (adding sourceReportID pushed it past the 190-char print width).
  • test (job 6) ✅ passing — the useSidePanelContext tests now mock useRoute from @react-navigation/native, so they no longer throw Couldn't find a route object.

The only remaining red check is Check independent approval, which isn't a code issue — it just needs a reviewer to approve the PR. Nothing for me to push.


view run

…-params and no-default-id-values disables

Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

The earlier oxfmt and Jest fixes are now green. The remaining real failure was ESLint check — 5 errors, all in files this PR touches. Fixed and pushed (9dd268d).

  • useSidePanelContext.ts:13 — consistent-type-imports: SCREENS is only used in typeof SCREENS.REPORT (a type position), so it's now import type SCREENS.
  • Report/index.ts:4242 — max-params (11 > 10): the PR added sourceReportID as an 11th param. Added // eslint-disable-next-line @typescript-eslint/max-params, matching the sibling navigateToConciergeChatAndDeleteReport (also 11 params) just below, which already uses this exact convention.
  • config.ts:2213 / :2219 — no-default-id-values: the new sourceReportID parse/stringify handlers default to ?? ''. Added the same disable the adjacent reportID handlers already carry (its bare disable only covered the immediately-following line).
  • useSidePanelContext.test.ts:38 — no-unsafe-return: my jest.requireActual('@react-navigation/native') spread was untyped (any). Typed it as jest.requireActual<typeof import('@react-navigation/native')>(…).

Check independent approval remains red only because the PR isn't approved yet — expected, not a code fix.

I couldn't run ESLint locally in this environment (tool execution is sandboxed), so CI on the new commit is the confirmation, but each fix maps directly to the reported rule and location.


view run

@MelvinBot

Copy link
Copy Markdown
Contributor Author

⏱️ Ran out of time before I could finish. This usually means the task was too complex or I got stuck exploring. Please try again with a more specific request or post in #melvinbot-ai.

Run details

…e import in useSidePanelContext test

Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Fixed the ESLint check failure and pushed to the branch (3eba319).

A follow-up commit (9dd268d) had typed the requireActual in the test with an inline jest.requireActual<typeof import('@react-navigation/native')>(…), but that inline import() type annotation is banned by @typescript-eslint/consistent-type-imports (the only remaining ESLint error, at useSidePanelContext.test.ts:39).

Replaced it with the repo's standard pattern — a top-level import type * as ReactNavigationNative from '@react-navigation/native' referenced as jest.requireActual<typeof ReactNavigationNative>(…) — matching existing test files like tests/ui/BaseLoginFormTest.tsx. The type import is placed in its correct oxfmt group to avoid a new format diff.

The earlier test (job 6) and Oxfmt check failures are already green on this branch; the only other failing check is Check independent approval, which is expected until the PR is approved and isn't fixable by a code change.

I couldn't run ESLint locally in this sandboxed environment, so CI on the new commit is the confirmation — but the change is a like-for-like swap to an already-lint-passing pattern.


view run

@MelvinBot

Copy link
Copy Markdown
Contributor Author

⚠️ MelvinBot CI Fix Loop Detected

MelvinBot has made 5 consecutive commits trying to fix CI failures. Automatic CI fix dispatches are now paused for this PR.

Next steps: A human should review the recent commits and resolve the conflicting CI errors manually.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

All the real checks are green now — my earlier fix resolved both failures:

  • Oxfmt check — ✅ passing
  • test (job 6) (and all 8 test shards) — ✅ passing

The only remaining red check is Check independent approval, which isn't something a code change fixes — it's failing simply because the PR hasn't been approved by a reviewer yet. It'll clear once a reviewer approves. (baseline-perf-tests / branch-perf-tests are still running, not failed.)

Nothing further to push here.


view run

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Web test result: could not exercise the live flow (environment limitation), no web regression by inspection

I attempted the PR's testing steps on Web, adapted to the web equivalent (Concierge Anywhere is opened via the Side Panel / Help button, which builds context from the report you're viewing). I got the fixture set up but could not open the Side Panel on web, so the send/verify steps couldn't run live.

Why the live flow was blocked: the Side Panel (and thus web Concierge Anywhere) is gated behind onboarding completion. In useSidePanelDisplayStatus.tsx, both shouldHideSidePanel and shouldHideHelpButton are forced true whenever isOnboardingCompleted is false. The Melvin web harness runs with SKIP_ONBOARDING=true, so onboarding is never completed and the Help/Concierge button never renders — independent of this PR. Clicking Settings → Help → Concierge (which bypasses the hidden button) also produced no panel, confirming the panel itself refuses to render in this state.

Note this PR is fundamentally a native fix — the web path is explicitly unchanged — so a proper live verification really wants iOS/Android native (the Melvin-Test-Android label should cover that).

What I verified:

Check Result
Expense report with 2+ transactions open on web ✅ Created "Expense Report 2026-08-24" with 2 transactions (€35 total)
Concierge/Help Side Panel button reachable from a report ❌ Blocked — button hidden while isOnboardingCompleted is false (harness sets SKIP_ONBOARDING=true)
Send "Update all transactions…" & check Concierge reply ⚠️ Not run — Side Panel couldn't be opened
LHN regression (Concierge without a report in view) ⚠️ Not run — same block
Web isInSidePanel path untouched (no web regression) ✅ By inspection — see below
JS console errors None observed (no error capture available in web agent-device scope)
Code-level regression check (web path)

The PR scopes all new behavior to the native (!isInSidePanel) branch:

  • SidePanelButton/index.native.tsx (native only) captures sourceReportID and threads it into navigateToConciergeChat.
  • useSidePanelContext.ts adds a new branch inside if (!isInSidePanel) { … }. The pre-existing web branch (isInSidePanel === true, using currentRHPReportID ?? currentReportID) is unchanged.
  • The web-only files SidePanelButton/index.tsx, SidePanelButtonBase.tsx, and useSidePanelDisplayStatus.tsx have zero diff vs main.
  • Added unit tests cover the new native (mockIsInSidePanel = false) case; the existing web (isInSidePanel = true) test is unchanged.

So the web flow logic is untouched — no web regression expected.

Recommendation: live web verification of this side-panel flow requires a session where onboarding is actually completed (SKIP_ONBOARDING=false), which is outside the current Melvin web harness. The native flow (the actual subject of this PR) should be validated via the Android/iOS test path or a manual reviewer pass.

Screenshots

Expense Report with two transactions open on web

Expense Report 2026-08-24 with two transactions open (Fuel EUR30, Uncategorized EUR5, total EUR35)

Report top bar (1440x900) — no Concierge/Help icon

Report top bar at 1440x900 showing only Submit/More - no Concierge/Help icon

Same report (1920x1080) — still no Concierge/Help icon

Same report at 1920x1080 - still no Concierge/Help icon

Settings > Help page with Concierge menu item (alternate entry point)

Settings Help page with Concierge menu item

After clicking Concierge in Help page — no side panel opened

After clicking Concierge in Help page - no side panel opened, confirming feature is gated on onboarding completion


view run

@pecanoro
pecanoro self-requested a review August 25, 2026 15:55
@dmkt9

dmkt9 commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot resolve the conflicts

…eSourceReport

Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>

# Conflicts:
#	src/libs/actions/Report/index.ts
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Conflicts resolved and pushed — the PR is now mergeable.

One conflicted file: src/libs/actions/Report/index.ts — navigateToConciergeChat. main changed it to return Promise<void> with an early return in the shouldDismissModal branch (#99396). This PR added a trailing sourceReportID param and passed it to getRoute. I kept both.

Resolution details

Signature — took main's Promise<void> return type, kept this PR's sourceReportID param:

    personalDetails?: OnyxEntry<PersonalDetailsList>,
    sourceReportID?: string,
): Promise<void> {

Tail — took main's early-return structure, moved this PR's sourceReportID onto the single remaining Navigation.navigate call:

        return Promise.resolve();
    }
    Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(conciergeReportID, undefined, undefined, undefined, undefined, sourceReportID), linkToOptions);
    return Promise.resolve();

The other four files (ROUTES.ts, linkingConfig/config.ts, Navigation/types.ts, useSidePanelContext.ts) auto-merged.

Verified:

  • npm run typecheck — passed
  • npm run lint-changed — passed
  • npm test tests/unit/hooks/useSidePanelContext.test.ts — 16/16 passed

Every other call site of navigateToConciergeChat already ignores the returned promise on main, so the new return type needs no changes elsewhere.

The remaining BLOCKED status is review/checks, not conflicts. The PR's test steps are native-only, so please still run them on iOS/Android before merging.


view run

@dmkt9

dmkt9 commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

@codex review

@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: 2d7bb1f259

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

// Capture the report the user is viewing (still topmost at press time) so Concierge can act on it
// after we navigate away. This is the ONLY entry that threads a source report, so context is
// scoped to Concierge opened via this sidebar button — search/LHN/deep links never carry it.
const sourceReportID = Navigation.getTopmostReportId();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restrict source capture to a visible report

When Help is pressed outside the Inbox report view, such as from SearchPageHeaderNarrow/Wide or a settings/workspace TopBar, this same button still runs, while getTopmostReportId() scans the inactive Reports split navigator and returns its last-opened report. That stale ID is then sent as Concierge context, so a prompt opened from Search or settings can inspect or modify an unrelated report; only attach the source when the Reports split navigator is currently active (for example, using the already-gated central-pane helper).

Useful? React with 👍 / 👎.

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.

@MelvinBot Please verify this comment. I think this behavior occurs on web as well. If so, I don't think this is a bug

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Verified — dmkt9 is right. Web already behaves this way, so this isn't a bug this PR introduces.

Web's side panel context resolves to currentRHPReportID ?? currentReportID (useSidePanelContext.ts:43), and currentReportID is set from Navigation.getTopmostReportId(state) — the same ungated helper the native button calls. Opening Help from Search or settings on web sends the same last-opened report.

Evidence

1. The helper is not gated on the active navigator. getTopmostReportParams.ts:24-35 uses findLast to grab the last REPORTS_SPLIT_NAVIGATOR anywhere in the state and its last REPORT screen. It never checks whether that navigator is focused. Both platforms inherit this.

2. Web deliberately keeps the stale ID on settings. useCurrentReportID.tsx:69-75 early-returns for any Settings_ screen, with the comment "Make sure we don't make the reportID undefined when switching between the chat list and settings tab." So on web, navigating to settings preserves the previous report as the side panel's context by design — exactly the behavior flagged here.

3. Same button placement on both. TopBar.tsx:87 renders SidePanelButton for both platforms, and the only gate is shouldHideHelpButton (onboarding / anonymous / 2FA), which has nothing to do with which navigator is active.

On the suggested "already-gated central-pane helper" — that's getFocusedReportId / getFocusedReportParams, which does respect the active tab via getActiveTabRoute. Web's side panel does not use it. Switching only native to it would make native stricter than web, not consistent with it.

One genuine difference (doesn't change the verdict): native freezes sourceReportID into the route param at press time, while web re-reads currentReportID live. At the moment Help is pressed from Search or settings both resolve to the same report, so the scenario described here is identical on both.

If the ungated lookup is worth tightening, it should change both platforms together in its own PR rather than only the native path here.


view run

Comment thread src/libs/actions/Report/index.ts Outdated
personalDetails?: OnyxEntry<PersonalDetailsList>,
// The report the user was viewing when they opened Concierge from the side-pane button (native). Threaded onto
// the Concierge route so the composer can attach it as sidePanelContext, scoped to this navigation entry.
sourceReportID?: string,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve source context while creating Concierge

When CONCIERGE_REPORT_ID is absent, the function takes the earlier navigateToAndOpenReport() branch and returns without ever using this new sourceReportID parameter; only the existing-report branch forwards it to the route. Consequently, first-time users or clients with an uncached Concierge ID still open Concierge without the viewed report context, leaving the reported native failure unfixed for that path; thread the parameter through the create/open navigation as well.

Useful? React with 👍 / 👎.

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.

@MelvinBot address this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch — fixed in 925d0c8.

sourceReportID is now threaded through the create/open branch too, so a first-time user (or any client with no cached CONCIERGE_REPORT_ID) lands on a Concierge route carrying the viewed report.

What changed

The parameter had to travel two more hops to reach the route, since the create path navigates via navigateToAndOpenReport → navigateToReport rather than calling Navigation.navigate directly:

  1. navigateToConciergeChat — passes sourceReportID into the navigateToAndOpenReport call.
  2. navigateToAndOpenReport — new optional sourceReportID param, forwarded to all three of its navigateToReport calls (new optimistic chat, existing chat, and the revalidated-chat path), so the Concierge chat gets the context whether it is created or found.
  3. navigateToReport — new optional sourceReportID option, passed to ROUTES.REPORT_WITH_ID.getRoute. It defaults to undefined, so every other caller is unaffected.

One follow-on this exposed: the native side-panel button drops a self-referencing source report with sourceReportID !== conciergeReportID, but that guard only works when CONCIERGE_REPORT_ID is cached — which is exactly what this path lacks. So useSidePanelContext now also drops it when sourceReportID === reportID (reportID is the Concierge report by the check above it), with a unit test covering it.

Verified: typecheck, lint-changed, cspell, and React Compiler compliance all pass. useSidePanelContext tests 17/17; ReportTest plus the four other suites touching these helpers 340/340.

Not verified on a device — the reported failure is native-only and I can only drive web here, so please confirm on iOS/Android with a fresh account (no cached Concierge report) when you run the test steps.


view run

navigateToConciergeChat only forwarded sourceReportID on the branch where
CONCIERGE_REPORT_ID was already cached. First-time users, and any client with an
uncached Concierge ID, took the navigateToAndOpenReport branch and opened
Concierge with no source report, leaving the native failure unfixed there.

Thread sourceReportID through navigateToAndOpenReport and navigateToReport so
every path lands on a route carrying the viewed report. Also guard against a
self-referencing source report in useSidePanelContext: the side-panel button can
only drop one when CONCIERGE_REPORT_ID is known, which is exactly what this path
lacks.

Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
@dmkt9

dmkt9 commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

@codex review

@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: 925d0c8792

ℹ️ 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 +33 to 34
if (conciergeReportID !== reportID) {
return undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Accept the routed context before Concierge ID is cached

When Concierge Anywhere is opened while CONCIERGE_REPORT_ID is still absent—such as a first-time open on a slow or offline connection—the create/open path now correctly puts sourceReportID on the optimistic Concierge route, but this guard still rejects it because conciergeReportID is undefined. The freshly threaded parameter therefore produces no sidePanelContext, and a message sent before the server populates the Concierge key still cannot act on the viewed report. This is fresh evidence beyond the earlier resolved comment: the navigation path was updated, but the consumer continues to require the missing cache key.

Useful? React with 👍 / 👎.

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.

Invalid, as CONCIERGE_REPORT_ID is already included in the OpenApp API response

@dmkt9

dmkt9 commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot review

@MelvinBot

Copy link
Copy Markdown
Contributor Author

⏱️ Ran out of time before I could finish. This usually means the task was too complex or I got stuck exploring. Please try again with a more specific request or post in #melvinbot-ai.

Run details

@dmkt9

dmkt9 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot merge the latest main branch

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Merged the latest main — the PR is MERGEABLE again (merge commit b4f98e9).

There were 4 conflicts, all from main landing the isPendingCreation route param and a company-cards modal refactor on top of this PR's sourceReportID threading. I kept both sides in every case:

File Conflict Resolution
src/ROUTES.ts:2631 Both sides added a 6th param to REPORT_WITH_ID.getRoute Kept main's isPendingCreation at position 6 and appended sourceReportID as position 7, so main's 3 positional callers are untouched. Updated this PR's 2 callers to pass an extra undefined.
src/libs/Navigation/types.ts:2972 Both sides added a field to the SCREENS.REPORT param list Kept both isPendingCreation and sourceReportID.
src/libs/actions/Report/index.ts:2676 main added personalDetails to openReport; this PR added sourceReportID to navigateToReport Kept both.
src/pages/workspace/companyCards/addNew/DynamicAddNewCardPage.tsx:168 main replaced the inline <ConfirmModal> with the useConfirmModal hook Took main's refactor, then converted its navigateToConciergeChat call to the single-object signature this PR introduced — main's version still used the old positional args, which would not have compiled.
Checks run locally
  • npm run typecheck — passed (all 5 tsconfigs)
  • npm run lint-changed — passed (no new seatbelt violations)
  • npm run react-compiler-compliance-check check src/pages/workspace/companyCards/addNew/DynamicAddNewCardPage.tsx — passed (the only component among the resolved files)
  • npm run spell-changed — 32 files, 0 issues
  • npm test tests/actions/ReportTest.ts — 300 passed
  • npm test tests/unit/hooks/useSidePanelContext.test.ts tests/unit/getSubmitExpensePreMountDestinationRouteTest.ts tests/unit/ReportFetchHandlerTest.tsx tests/unit/ReportNotFoundGuardTest.tsx — 47 passed

The last two suites matter most here: getSubmitExpensePreMountDestinationRouteTest and ReportFetchHandlerTest pin main's isPendingCreation positional argument, so they confirm the reordered getRoute signature did not shift it.

One caveat: I could not verify this in a browser — web prep failed in this run, so there is no NewDot session to drive. The resolution is validated by typecheck, lint, and the unit suites above only. Since this PR's behavior change is native-only anyway, it is still worth re-running the Android test steps before merge.

@dmkt9

dmkt9 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot fix the Oxfmt CI failure

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Fixed — Oxfmt now passes (run).

The cause was mine: when I resolved the main merge conflict yesterday I hand-wrapped the REPORT_WITH_ID.getRoute signature across multiple lines after adding the 7th param. Oxfmt wants it on one line. I ran npm run fmt, which produced a one-file, one-hunk change collapsing it back: src/ROUTES.ts:2631. Pushed as 245e890.

Heads up: typecheck is now failing on broken main, not on this PR

src/pages/workspace/accounting/netsuite/export/DynamicNetSuiteInvoiceItemPreferenceSelectPage.tsx(1,20):
error TS2307: Cannot find module '@components/ButtonComposed'

That file is not in this PR's diff, and the breakage is entirely on main:

#99239 was branched before the rename and merged after it, so main has been red since 15:13 UTC. Per the "don't fix unrelated failures" rule I have not touched it. It needs a one-line import fix on main (or a revert of #99239) by whoever owns that work — this PR cannot go green until then, since CI typechecks the PR merged with latest main.

How I confirmed it is not this PR
  • gh pr diff 99337 --name-only contains no NetSuite file and no ButtonComposed reference.
  • git ls-tree origin/main src/components/ shows Button and ButtonWithDropdownMenu — no ButtonComposed.
  • git log 430227906b8..origin/main -- <netsuite file> lists the three commits from Gate NetSuite invoice-item preference selection behind a Save button (a11y On Input, PR 13) #99239 that introduced the stale import, all landing on main after this PR's merge base.
  • In this PR's own tree that file's first import is ConnectionLayout, not ButtonComposed — the bad import only appears once you merge current main.

That last point also explains why npm run typecheck passed locally for me: my merge base predates #99239.

Checks run locally for the format fix
  • npm run fmt — clean and idempotent on re-run; only src/ROUTES.ts changed
  • npm run typecheck — passed against this PR's merge base
  • npm run lint-changed — passed, no new seatbelt violations
  • npm run spell-changed — 32 files, 0 issues

I did not run the app in a browser for this one. The change is whitespace inside a type signature with no runtime effect, so the formatter, typecheck, and lint are the meaningful gates.

@dmkt9

dmkt9 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot merge the latest main branch

…eSourceReport

Co-authored-by: dmkt9 <dmkt9@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Merged the latest main — no conflicts this time, and the PR is MERGEABLE (merge commit 0dc8ea2).

The branch was 66 commits behind. The previous merge's conflict resolutions all survived cleanly: the getRoute signature still carries both isPendingCreation (position 6) and sourceReportID (position 7) at src/ROUTES.ts:2631, and the PR's diff against main is the same 32 files as before.

Checks run locally — all passed
  • npm run typecheck — passed (all 5 tsconfigs)
  • npm run lint-changed — passed (no new seatbelt violations)
  • npm run fmt — no changes, so formatting is already clean (the follow-up oxfmt commit 245e890 is included in this merge)
  • npm run spell-changed — 32 files, 0 issues
  • npm run react-compiler-compliance-check check on DynamicAddNewCardPage.tsx, SidePanelButton/index.native.tsx, useOpenConciergeAnywhere/index.native.tsx — passed
  • npm test on ReportTest.ts, useSidePanelContext.test.ts, getSubmitExpensePreMountDestinationRouteTest.ts, ReportFetchHandlerTest.tsx, ReportNotFoundGuardTest.tsx, UnlockBankAccountTest.tsx — 355 passed

getSubmitExpensePreMountDestinationRouteTest and ReportFetchHandlerTest are the ones that matter most here — they pin main's isPendingCreation positional argument, so they confirm the reordered getRoute signature still puts it where main's callers expect.

The web regression check did not run. I tried to verify on web that the side-panel Concierge flow is unaffected, but the attempt was blocked by the test harness before any step executed, so I have no browser evidence either way.

The cause is a side effect of this very merge: main bumped react-native-keyboard-controller from 1.22.2 to 1.22.4 in package.json. The rsbuild dev server watches that file, logged restarting server as package.json changed, and restarted in place. That killed the WebSocket connection for the already-signed-in browser tab, leaving it blank, and reloading to recover is not allowed in this harness (it risks losing the session). This is infrastructure, not a code defect — nothing about it reflects on the change under test.

So this merge is verified by typecheck, lint, formatting, and the unit suites only. Since the behavior change in this PR is native-only, the Android test steps in the PR description are the ones worth re-running before merge.

@dmkt9

dmkt9 commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

@pecanoro all yours

@pecanoro

Copy link
Copy Markdown
Contributor

@dmkt9 We need to resolve the conflicts again 😞

@dmkt9

dmkt9 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot resolve conflicts

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Conflicts resolved and pushed — the PR is mergeable again (db9fa33).

One file conflicted: src/hooks/useOpenConciergeAnywhere/index.native.tsx. The two sides changed adjacent things, so I kept both:

  • main widened the options type to {forceConcierge?: boolean; reportID?: string} (for opening a specific Concierge thread, used by useAskConcierge on web).
  • This PR converted navigateToConciergeChat to a single object argument.

Result:

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const openConciergeAnywhere = (_options?: {forceConcierge?: boolean; reportID?: string}) => {
    navigateToConciergeChat({conciergeReportID, introSelected, currentUserAccountID, isSelfTourViewed, betas});
};

_options stays unused on native — that's main's existing behavior, and this PR threads the source report through the route param in SidePanelButton/index.native.tsx instead, so nothing needed to change there.

Verification

Merged with git merge (no rebase, no force-push); original commits are intact.

Check Result
npm run typecheck ✅ passed (all 5 projects)
ESLint on the resolved file ✅ clean
react-compiler-compliance-check on the resolved file ✅ clean
Jest — useSidePanelContext, useAskConcierge, HelpPage, ExportDownloadStatusModal, ExportDownloadStatusManager ✅ 61 tests passed

I also confirmed every call site of navigateToConciergeChat across src/ already uses the object form, so the signature change merged cleanly with the ~400 files main brought in.

I did not run the PR's manual test steps — they're iOS/Android native only, and this run is scoped to web. Please re-verify steps 1–4 on a native build before merging, since main moved a fair amount underneath this branch.

@dmkt9

dmkt9 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: db9fa33e92

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

@dmkt9

dmkt9 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

@pecanoro This PR is ready to go again

@pecanoro
pecanoro merged commit 2a05611 into main Sep 15, 2026
42 of 43 checks passed
@pecanoro
pecanoro deleted the claude-conciergeNativeSourceReport branch September 15, 2026 21:17
@github-actions

Copy link
Copy Markdown
Contributor

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

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

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/pecanoro in version: 9.4.80-0 🚀

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

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Yes — help site changes are required. Draft PR: Docs updates for E/A#99337 Concierge Help icon report context (Expensify/App#101269), labeled HelpDot and assigned to dmkt9.

Why a docs change is needed

This PR makes the Help icon in a report header carry the report you were viewing into Concierge on native, matching what the side panel already did on web (useSidePanelContext.ts:42-44). The help site never documented that context behavior on either platform, so users had no way to know that "this report" resolves to the one they were viewing.

I also found a stale UI label while verifying: Concierge-Basics.md tells web users to click the Concierge icon. The live label is Help — confirmed in the running web app and in en.ts:470, which the button renders via translate('common.help').

What changed

Three articles in docs/articles/new-expensify/concierge-ai/:

Article Change
Concierge-Basics.md Fixed the web entry point to the Help icon; added the report-header Help icon as an entry point with the platform difference stated (side panel on web, Concierge chat on mobile); added an FAQ on how Concierge resolves which report you mean
Expense-Assistant.md Added the report-header Help case to "How does Concierge know which expense to update?"
What-Concierge-Can-Do.md Added a bulk-update example and a sentence explaining how "this report" is resolved
Conventions applied and scope notes

HELPSITE_NAMING_CONVENTIONS.md — UI labels used verbatim and bolded (Help, Account, Concierge), no paraphrasing, no quotation marks around labels. The Cross-Platform Clarity Rule is why every new sentence names the platform rather than implying parity.

HELP_AUTHORING_GUIDELINES.md — I introduced no new # headings, so each article keeps its existing structure; the one new heading is an FAQ ## entry, which is exempt from the task-based rule. No screenshots suggested — the Help icon is described adequately in text.

Typography — matched each file's existing curly quotes “ ” and apostrophe ’ byte-for-byte, verified with grep -F.

Left alone: the mobile Account > Help > Concierge path and the Ask Concierge search entry. This run could only verify the web UI, so re-verifying those was out of scope.

Checks: Markdown-only, so the AI test matrix selects spell check alone. npm run spell-changed was blocked from executing in this sandbox — flagging that rather than claiming it passed. No new or unusual terms were added; the copy reuses vocabulary already in these articles, and CI's cspell gate will confirm.


@dmkt9, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR Ready for review

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/luacmartins in version: 9.4.80-0 🚀

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

Bundle Size Analysis (Sentry):

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

Labels

Melvin-Test-Android Melvin-Test-Web Triggers Melvin to run the testing steps of the PR on web

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants