Skip to content

feat(predict): integrate Positions entry point into Predict feed#30900

Merged
caieu merged 14 commits into
mainfrom
predict/PRED-904-integrate-predict-portfolio-module-into-current-predict-feed-behind-feature-flag
Jun 3, 2026
Merged

feat(predict): integrate Positions entry point into Predict feed#30900
caieu merged 14 commits into
mainfrom
predict/PRED-904-integrate-predict-portfolio-module-into-current-predict-feed-behind-feature-flag

Conversation

@caieu

@caieu caieu commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Description

This PR integrates the Predict Positions screen into the existing Predict feed balance card for PRED-904.

It adds a feature-flagged Positions action to the Predict balance card. When predictPortfolio.enabled is on, the balance action row uses the compact three-button layout from the portfolio/positions design and the Positions button navigates to Routes.PREDICT.POSITIONS. When the flag is off, the existing Add funds / Withdraw layout remains unchanged.

This also updates the Positions history experience so claimable positions can be shown as a Claim pending section before the date-grouped activity history. The claim-pending section is opt-in through the optional claimPendingPositions prop and supports privacy mode.

Changelog

CHANGELOG entry: Added a Positions button to the Predict balance card and surfaced claim-pending predictions in Positions history.

Related issues

Fixes: PRED-904

Manual testing steps

Feature: Predict feed Positions integration

  Scenario: Portfolio flag is disabled
    Given predictPortfolio.enabled is false
    And the user opens the Predict feed
    Then the balance card shows the existing Add funds and Withdraw actions
    And the Positions action is not shown

  Scenario: Portfolio flag is enabled
    Given predictPortfolio.enabled is true
    And the user opens the Predict feed
    Then the balance card shows Positions, Add funds, and Withdraw actions
    And the action labels are vertically centered

    When the user taps Positions
    Then the app navigates to the Predict Positions screen

  Scenario: Positions history has claimable winnings
    Given the user has claimable Predict positions
    And the user opens Positions > History
    Then Claim pending appears before the date-grouped history sections
    And the claimable positions render in that first section

  Scenario: Privacy mode is enabled
    Given privacy mode is enabled
    And the user opens Positions > History
    Then claim-pending amounts are hidden

Screenshots/Recordings

Before

N/A

After

Screen.Recording.2026-06-01.at.17.08.29.mov

Testing performed

  • yarn jest app/components/UI/Predict/components/PredictBalance/PredictBalance.test.tsx
  • yarn jest app/components/UI/Predict/views/PredictTransactionsView/PredictTransactionsView.test.tsx
  • yarn jest app/components/UI/Predict/components/PredictPositionsHistoryList/PredictPositionsHistoryList.test.tsx
  • yarn jest app/components/UI/Predict/views/PredictPositionsView/PredictPositionsView.test.tsx
  • yarn eslint app/components/UI/Predict/Predict.testIds.ts app/components/UI/Predict/components/PredictPositionsHistoryList/PredictPositionsHistoryList.tsx app/components/UI/Predict/components/PredictPositionsHistoryList/PredictPositionsHistoryList.test.tsx app/components/UI/Predict/views/PredictPositionsView/PredictPositionsView.tsx app/components/UI/Predict/views/PredictPositionsView/PredictPositionsView.test.tsx app/components/UI/Predict/views/PredictTransactionsView/PredictTransactionsView.tsx app/components/UI/Predict/views/PredictTransactionsView/PredictTransactionsView.test.tsx
  • yarn lint:tsc
  • git diff --check

Note: ESLint exits successfully with existing warnings in PredictTransactionsView.tsx for deprecated TabEmptyState usage and an existing no-void warning.

Pre-merge author checklist

Performance checks (if applicable)

  • I've tested on Android
  • I've tested with a power user scenario
  • I've instrumented key operations with Sentry traces for production performance metrics

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Medium Risk
Touches portfolio/claimable value math and multi-screen Predict navigation behind a flag; changes are UI-focused with broad test coverage and no direct payment or auth logic.

Overview
Adds a predictPortfolio-gated Positions action on the Predict feed balance card: compact three-button layout when enabled, navigation to Routes.PREDICT.POSITIONS, unchanged Add funds / Withdraw when the flag is off.

Positions History can show an opt-in Claim pending section ahead of date-grouped activity: won positions with positive value, privacy-hidden amounts, row tap to market details, and combined pull-to-refresh / retry with portfolio refetch. PredictPositionsView only passes claim-pending data when the portfolio flag is on.

Claimable totals on the homepage and in portfolio logic now count won positions with positive currentValue only (lost or zero-value claimables excluded), with tests and new transaction copy / test IDs.

Reviewed by Cursor Bugbot for commit 069e55b. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@mm-token-exchange-service mm-token-exchange-service Bot added the team-predict Predict team label Jun 1, 2026
@caieu caieu marked this pull request as ready for review June 1, 2026 20:09
@caieu caieu requested a review from a team as a code owner June 1, 2026 20:09
@github-actions github-actions Bot added size-L risk:low AI analysis: low risk labels Jun 1, 2026
@github-actions github-actions Bot added risk:medium AI analysis: medium risk and removed risk:low AI analysis: low risk labels Jun 1, 2026
@MarioAslau

Copy link
Copy Markdown
Contributor

Medium severity

M1 — Claim-pending row is missing an accessibility role/label (tappable financial row)

In PredictTransactionsView.tsx, ClaimPendingPositionRow is a TouchableOpacity that navigates to market details, but it has no accessibilityRole="button" and no row-level accessibilityLabel. The only a11y annotation is on the icon (accessibilityLabel="claim pending icon"). Screen-reader users get the three child texts read out but no indication the row is actionable, and the navigation target isn't announced.

<TouchableOpacity
  onPress={handlePress}
  style={tw.style('flex-row items-start justify-between w-full p-2', containerStyle)}
  testID={getPredictPositionsHistoryListSelector.claimPendingRow(position.id)}
>

The existing PredictActivity rows are the comparison point — if they expose a button role, this new row should match. Recommendation: add accessibilityRole="button" and a meaningful accessibilityLabel (e.g. won title + market title), keeping the amount in SensitiveText.

M2 — Claim-pending history is not gated by the predictPortfolio flag

Only the balance-card Positions button is behind predictPortfolioEnabled. The new "Claim pending" history section is driven purely by the optional claimPendingPositions prop, and PredictPositionsView passes it unconditionally:

<PredictPositionsHistoryList
  claimPendingPositions={portfolio.actionableClaimablePositions}
  isPrivacyMode={Boolean(privacyMode)}
  isVisible={isHistoryTabActive}
/>

So any user who reaches PredictPositionsView > History (through any entry point, not just the flagged button) will see the new section — i.e. this part ships un-flagged, which differs from the PR's "behind a feature flag" framing. This may well be intentional (it's a separate, additive surface), but it's worth an explicit confirmation that shipping claim-pending history to all users reaching that view is desired, and that no other entry point exposes the Positions view prematurely. Recommendation: confirm intent; if it should be flagged, gate the prop on predictPortfolioEnabled as well.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.74359% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.73%. Comparing base (02c85c2) to head (3580ded).
⚠️ Report is 41 commits behind head on main.

Files with missing lines Patch % Lines
...redictTransactionsView/PredictTransactionsView.tsx 84.61% 3 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #30900      +/-   ##
==========================================
+ Coverage   82.70%   82.73%   +0.03%     
==========================================
  Files        5561     5576      +15     
  Lines      143081   143593     +512     
  Branches    33051    33204     +153     
==========================================
+ Hits       118336   118803     +467     
- Misses      16863    16877      +14     
- Partials     7882     7913      +31     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…-module-into-current-predict-feed-behind-feature-flag
@MarioAslau

Copy link
Copy Markdown
Contributor

Medium severity

M1 — ClaimPendingPositionRow hardcodes "won" semantics while the prop is generically typed

Files: views/PredictTransactionsView/PredictTransactionsView.tsx

PredictTransactionsView accepts claimPendingPositions?: PredictPosition[] — a generic position array with no constraint on status. But the row unconditionally renders the "won" framing:

<Text variant={TextVariant.BodyMd} numberOfLines={1}>
  {strings('predict.transactions.prediction_won_title')}   // "Prediction won"
</Text>
...
accessibilityLabel = strings('predict.transactions.claim_pending_accessibility_label', {
  marketTitle: position.title,   // "Prediction won, {{marketTitle}}, opens market details"
});

This is only correct because the single current caller passes portfolio.actionableClaimablePositions, which usePredictPortfolio filters to status === WON && currentValue > 0. The component itself doesn't enforce that invariant, so any future caller passing non-won claimable positions (e.g. refunds) would be mislabeled both visually and for screen readers.

Recommendation: either narrow the prop type / document the WON-only contract explicitly, or derive the title from position.status rather than hardcoding it.

M2 — Fragile test mock relying on useSelector call-order parity

File: views/PredictPositionsView/PredictPositionsView.test.tsx

useSelector: jest.fn(() => {
  const value =
    mockUseSelectorCallIndex % 2 === 0 ? mockPrivacyMode : mockPredictPortfolioEnabled;
  mockUseSelectorCallIndex += 1;
  return value;
}),

This assumes the component always calls exactly two selectors, in a fixed order (selectPrivacyMode, then selectPredictPortfolioEnabledFlag), on every render. Adding, removing, or reordering a useSelector — or an extra render pass — will silently feed the wrong boolean to the wrong selector and produce confusing failures or false passes.

Recommendation: mock by selector identity instead of call index, e.g. useSelector.mockImplementation((sel) => sel === selectPrivacyMode ? mockPrivacyMode : mockPredictPortfolioEnabled). This is robust to render counts and call order.

…-module-into-current-predict-feed-behind-feature-flag
caieu added 2 commits June 2, 2026 17:38
…to predict/PRED-904-integrate-predict-portfolio-module-into-current-predict-feed-behind-feature-flag

# Conflicts:
#	app/components/UI/Predict/components/PredictPositionsHistoryList/PredictPositionsHistoryList.test.tsx
#	app/components/UI/Predict/views/PredictTransactionsView/PredictTransactionsView.test.tsx

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit c8b3058. Configure here.

@caieu caieu enabled auto-merge June 3, 2026 01:15
@MarioAslau

Copy link
Copy Markdown
Contributor

High severity

H1 — "Claim pending" section is fed the unfiltered claimablePositions, so non-actionable / lost / $0 positions appear under a header that implies money to claim

Files: views/PredictPositionsView/PredictPositionsView.tsx, views/PredictTransactionsView/PredictTransactionsView.tsx, hooks/usePredictPortfolio.ts

PredictPositionsView passes the raw claimable list to the history:

<PredictPositionsHistoryList
  claimPendingPositions={
    predictPortfolioEnabled ? portfolio.claimablePositions : undefined
  }
  isPrivacyMode={Boolean(privacyMode)}
  isVisible={isHistoryTabActive}
/>

portfolio.claimablePositions is just usePredictPositions({ claimable: true }) — every position where p.claimable === true, with no status / value filtering:

// usePredictPositions.ts — buildSelect
if (claimable === true) {
  result = result.filter((p) => p.claimable);
}

The hook deliberately exposes a separate, narrower list for exactly this kind of "actionable winnings" surface:

// usePredictPortfolio.ts
const actionableClaimablePositions = useMemo(
  () =>
    claimablePositions.filter(
      (position) =>
        position.status === PredictPositionStatus.WON &&
        position.currentValue > 0,
    ),
  [claimablePositions],
);

actionableClaimablePositions is what drives claimableAmount, claimablePositionCount, hasClaimableWinnings, and the positions badge count. Feeding the unfiltered list into a section titled "Claim pending" means:

  • LOST positions (and any currentValue === 0 entry) render in the "Claim pending" section. The row code even has explicit branches for this — getClaimPendingPositionTitle returns "Prediction lost" / "Prediction resolved", and the amount renders as $0.00. A user sees an item titled "Prediction lost — $0.00" sitting under "Claim pending", where there is nothing to claim.
  • It is inconsistent with the rest of the portfolio UI (badge/count/amount all use actionableClaimablePositions), so the section can disagree with the claimable count/amount shown elsewhere.
  • It contradicts the PR description, which frames this as surfacing "claimable winnings" / "claimable positions."

The PR's own test (passes won and lost claimable positions ..., expecting count 2) and the labels lost claim pending positions from position status test show this is the current intended behavior of the code — but it looks like a regression from the previously reviewed head (which passed actionableClaimablePositions).

Recommendation: pass portfolio.actionableClaimablePositions (matching the badge/amount logic), or, if showing all resolved positions is genuinely intended, rename the section away from "Claim pending" and confirm the product decision. Either way, the section header and its contents must agree.


Medium severity

M1 — claimPendingPositions prop is generically typed with no enforced contract

File: views/PredictTransactionsView/PredictTransactionsView.tsx

PredictTransactionsView accepts claimPendingPositions?: PredictPosition[] with no constraint on status or currentValue. The component now derives the row title from position.status (good — fixes the earlier "hardcoded won" concern), but it still has no guard that the supplied positions are actually claimable/actionable. Combined with H1, this is what lets lost/$0 rows flow into a "Claim pending" UI. Even after H1 is fixed at the call site, the component itself does not document or defend the invariant, so the next caller can reintroduce the same problem.

Recommendation: document the expected contract (actionable, claimable winnings) in JSDoc, and/or defensively filter inside the component, and/or narrow the type.

M2 — Pull-to-refresh does not refresh the "Claim pending" section

File: views/PredictTransactionsView/PredictTransactionsView.tsx

onRefresh / handleRetry only call the activity refetch(). claimPendingPositions are owned by the parent (usePredictPortfolio / usePredictPositions) and are not re-fetched here, so after a manual pull-to-refresh the "Claim pending" section can be stale relative to the freshly-refetched activity list below it (e.g., a position the user just claimed still shows as pending until the parent query refreshes on its own).

Recommendation: thread a refresh callback from the parent (or call the portfolio refetch) so both data sources refresh together.

@github-actions github-actions Bot added size-XL and removed size-L labels Jun 3, 2026
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokePredictions, SmokeWalletPlatform
  • Selected Performance tags: @PerformancePredict
  • Risk Level: medium
  • AI Confidence: 88%
click to see 🤖 AI reasoning details

E2E Test Selection:
All 14 changed files are within the Predictions feature area (app/components/UI/Predict/ and app/components/Views/Homepage/Sections/Predictions/), plus locales/en.json for new strings.

Key changes:

  1. PredictTransactionsView - Major refactor adding a "Claim pending" section with ClaimPendingPositionRow component, privacy mode support, and improved refresh handling. This is the most significant change.
  2. PredictBalance - New "Positions" button (feature-flag gated via selectPredictPortfolioEnabledFlag) navigating to the positions view.
  3. PredictPositionsHistoryList - New props (claimPendingPositions, onClaimPendingPositionsRefresh, isPrivacyMode) passed through to PredictTransactionsView.
  4. PredictPositionsView - Passes portfolio data and privacy mode to the history list.
  5. usePredictPositionsForHomepage - Bug fix: totalClaimableValue now only sums WON positions with positive currentValue (previously summed all claimable positions).
  6. Predict.testIds.ts - New test IDs for claim pending section/rows and positions button.
  7. locales/en.json - New strings for claim pending UI.

Tags selected:

  • SmokePredictions: Directly covers the Predictions feature - positions tab, activities/history tab, balance display, and position lifecycle (claiming winnings). The claim pending section and positions button are core to this tag's coverage.
  • SmokeWalletPlatform: Per SmokePredictions description, "Predictions is also a section inside the Trending tab (SmokeWalletPlatform); changes to Predictions views affect Trending." The PredictPositionsView and PredictTransactionsView changes qualify.

SmokeConfirmations is NOT selected because these changes are purely UI/display changes - no new transaction flows are introduced. The existing claim flow was already tested; this PR adds a "Claim pending" display section and a navigation button.

No other tags are affected as the changes are isolated to the Predictions feature area with no shared component modifications (no BrowserTab, TabBar, navigation infrastructure, or controller changes).

Performance Test Selection:
The PredictTransactionsView has been significantly refactored to support a new 'Claim pending' section with a SectionList that now handles two types of items (claimPending and activity). The addition of sorting, filtering, and rendering of claim pending positions alongside activity items could impact list rendering performance. The @PerformancePredict tag covers prediction market list loading, position management, and balance display - all of which are affected by these changes.

View GitHub Actions results

@MarioAslau MarioAslau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@caieu caieu added this pull request to the merge queue Jun 3, 2026
Merged via the queue into main with commit 919491c Jun 3, 2026
192 of 193 checks passed
@caieu caieu deleted the predict/PRED-904-integrate-predict-portfolio-module-into-current-predict-feed-behind-feature-flag branch June 3, 2026 19:30
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 3, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

risk:medium AI analysis: medium risk size-XL team-predict Predict team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants