Skip to content

feat(discover-v2): add Predictions placement carousel#7420

Open
DanielSinclair wants to merge 5 commits intodaniel/discover-v2-perps-sparklinesfrom
daniel/discover-v2-predictions
Open

feat(discover-v2): add Predictions placement carousel#7420
DanielSinclair wants to merge 5 commits intodaniel/discover-v2-perps-sparklinesfrom
daniel/discover-v2-predictions

Conversation

@DanielSinclair
Copy link
Copy Markdown
Contributor

@DanielSinclair DanielSinclair commented Apr 30, 2026

APP-3530, APP-3666

What changed

  • resolvePolymarketCardColor — replaces the prior single event-icon fetch with a waterfall: (1) market seriesColor field, (2) getImagePrimaryColor on the first active market's image, (3) getImagePrimaryColor on the event image, (4) getColorBySeed fallback
  • When market and event share the same image URL, the image is fetched once via getSharedImageColorThemes
  • Closed/ended event filtering moved post-transform so it applies to fully resolved PolymarketEvent objects
  • useDiscoverPredictionsStore — query store fetching full PolymarketEvent objects for each PlacementItem ID via usePolymarketEventStore; keepPreviousData preserves cards between refreshes
  • useDiscoverPlacements extended to compose eventsById from useDiscoverPredictionsStore and filter predictions PlacementItem[] to those with a resolved event
  • PolymarketEventsListItem — extended with an optional onPress override so the Discover carousel injects placement analytics before delegating to the event screen
  • DiscoverHome adds a Predictions MarketCarousel section using PolymarketEventsListItem as the card renderer; falls back to LoadingSkeleton while the event resolves

Screen recordings / screenshots

What to test

  • Discover shows Predictions carousel with Polymarket event tiles
  • Card accent colors reflect event image or series color — improved vs. prior single-icon approach
  • Tap a card → Polymarket event screen; tap "See all" → browse screen
  • Existing Polymarket surfaces (Events list, Sports) show improved colors
  • Cards whose event hasn't loaded yet show a shimmer skeleton

Copy link
Copy Markdown
Contributor Author

DanielSinclair commented Apr 30, 2026

Copy link
Copy Markdown

@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: 537c9f8a32

ℹ️ About Codex in GitHub

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

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

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

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

Comment on lines 15 to +16
import { getMarketColors } from '@/features/polymarket/utils/getMarketColor';
import { getHighContrastColor } from '@/hooks/useAccountAccentColor';
import { resolvePolymarketCardColor } from '@/features/polymarket/utils/getPolymarketCardColor';
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 Re-add removed color helper imports

processRawPolymarketPosition and processRawPolymarketOptimizedEvent still call getImagePrimaryColor and getHighContrastColor, but this import block no longer brings those symbols into scope. In production Babel builds this becomes a runtime ReferenceError the first time those paths execute (e.g., loading Polymarket positions), so position fetching can fail for users with open/closed positions.

Useful? React with 👍 / 👎.

if (placement.id === PLACEMENT_IDS.PERPS) {
items = placement.items.filter(item => item.ref.source === 'hyperliquid' && markets[item.ref.id] !== undefined);
} else if (placement.id === PLACEMENT_IDS.PREDICTIONS) {
items = placement.items.filter(item => item.ref.source === 'polymarket' && eventsById[item.ref.id] !== 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 Preserve prediction items until events resolve

Filtering predictions to eventsById[item.ref.id] !== undefined drops every card while event fetches are still in flight, so once placement loading flips to false the Discover screen hides the Predictions carousel entirely (showPredictionsPlacement depends on items.length). This makes the new per-card skeleton fallback effectively unreachable and can leave the section missing on slow or partially failed event hydration.

Useful? React with 👍 / 👎.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 30, 2026

🧪 Flashlight Performance Report (AWS Device Farm)

🔀 Commit: 2ae611d

📎 View Artifacts

Metric Current Δ vs Baseline
Time to Interactive (TTI) 5836 ms
Average FPS 56.58
Average RAM 400.8 MB

@github-actions
Copy link
Copy Markdown

Launch in simulator or device for 8df0cfc

@DanielSinclair DanielSinclair force-pushed the daniel/discover-v2-predictions branch from 7550c1e to 828d6e5 Compare April 30, 2026 11:13
@DanielSinclair DanielSinclair force-pushed the daniel/discover-v2-perps-sparklines branch from 38cbab7 to 4dfd3b0 Compare April 30, 2026 11:13
@github-actions
Copy link
Copy Markdown

Launch in simulator or device for bf7b1ae

Replaces single event-icon fetch with a color waterfall for accurate card colors: series color → market image → event image → seed color. Corrects closed/ended event filtering to run post-transform so the closed field is available.
Prevents image color extraction from blocking indefinitely by adding a 600ms timeout. Deduplicates the fetch when market and event share the same image URL by resolving once and reusing the result.
Fetches Polymarket events for Discover placement item IDs and extends the composition store to include predictions.

- discoverPredictionsStore.ts: createQueryStore; keepPreviousData preserves events during refresh
- discoverPlacementsStore.ts: adds eventsById composition and predictions item filtering
- polymarketEventsStore.ts: removes dead-code prefetchPolymarketEvents() no-arg wrapper
Adds the Predictions carousel section to DiscoverHome using PolymarketEventsListItem with injected onPress.

- PolymarketEventsListItem: accepts optional onPress override
- DiscoverHome: adds Predictions carousel section, renderPredictionCard, showPredictionsPlacement guard
@DanielSinclair DanielSinclair force-pushed the daniel/discover-v2-predictions branch from 828d6e5 to 3c63e64 Compare April 30, 2026 11:42
@DanielSinclair DanielSinclair force-pushed the daniel/discover-v2-perps-sparklines branch from 4dfd3b0 to 9fb11fe Compare April 30, 2026 11:42
@github-actions
Copy link
Copy Markdown

Launch in simulator or device for 2ae611d

@DanielSinclair DanielSinclair requested review from maxbbb and removed request for akc2267 and maxbbb April 30, 2026 23:23
Copy link
Copy Markdown
Contributor

@ibrahimtaveras00 ibrahimtaveras00 left a comment

Choose a reason for hiding this comment

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

Looks good on both OS's ✅

Comment on lines +55 to +66
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T | null> {
let timeoutId: ReturnType<typeof setTimeout> | undefined;
const timeout = new Promise<null>(resolve => {
timeoutId = setTimeout(() => resolve(null), timeoutMs);
});

return await Promise.race([promise, timeout])
.catch(() => null)
.finally(() => {
if (timeoutId) clearTimeout(timeoutId);
});
}
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.

nit: given this isn't an uncommon pattern (same thing exists in LedgerSigner.ts) might be worth putting in its own util file.

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.

A few things here:

  • Defaulting to using the seriesColor of the first market in an event doesn't make a ton of sense. Firstly because most markets don't have a series color, and second because that color represents the color for that particular market for the event. We display the top two markets for every event, and the color for the first market doesn't have any meaningful connection to the event itself. Deriving the color from the event's image makes more sense visually and is the intention of the designs.
  • For the same reasons as above, we shouldn't be trying to use the first market's image color.
  • The color thresholds are fine and that's a good addition.
  • Could have been extracted to helper if needed in multiple places but could have made separate PR for changing how the color was calculated.

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.

Same feedback as perps carousel, would move all of this to dedicated component.

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.

Not understanding the changes here. We already don't fetched closed events and now we just do unnecessary transformations on the game events that are ended which get filtered out anyway.

Comment on lines -215 to +213
const filteredEvents = events.filter(event => event.ended !== true);
const processedEvents = await Promise.all(filteredEvents.map(event => processRawPolymarketEvent(event)));
const processedEvents = (await Promise.all(events.map(event => processRawPolymarketEvent(event)))).filter(
event => !event.closed && event.ended !== true
);
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.

Have some question from src/features/polymarket/stores/polymarketSportsEventsStore.ts comment

import { time } from '@/utils/time';
import { shallowEqual } from '@/worklets/comparisons';

export type DiscoverPredictionsFetchData = {
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.

rm export


return {
enabled: predictions && eventIds.length > 0,
eventIdsKey: eventIds.join(','),
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.

I'm assuming this was done as some optimization for the equality checker? From my understanding this would be equivalent to just using the array directly. Regardless it's not worth transforming the data into a format we have to transform it back from just for that sake.

})
);

async function fetchDiscoverPredictions({ eventIdsKey }: DiscoverPredictionsParams): Promise<DiscoverPredictionsFetchData> {
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.

We should fetch the events directly from the /events endpoint here so that we only fire a single request rather than n requests all events (ie events?id=16167&id=16166).

Can abstract this into a generic fetchPolymarketEvents(eventIds: string[]) and then hook it into whatever transforms you need to do.

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.

3 participants