Conversation
…apping Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hooks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…popup mode The popup closes immediately when a new tab opens, so the storage write must complete before calling openTab. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…facts Use height: 100% instead of fixed popup height so the Sheet covers the full screen in both popup and fullscreen mode. Override Tailwind defaults that caused a visible white border around the overlay. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 39 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| </div> | ||
| <Button | ||
| size="lg" | ||
| size="md" |
Code reviewFound 2 issues:
freighter/extension/src/popup/views/Discover/index.tsx Lines 32 to 34 in 7891c21 freighter/extension/src/popup/views/Discover/hooks/useDiscoverData.ts Lines 51 to 52 in 7891c21
freighter/extension/src/popup/views/Account/index.tsx Lines 215 to 217 in 7891c21 freighter/extension/src/popup/constants/metricsNames.ts Lines 88 to 91 in 7891c21 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
| useState<ProtocolEntry | null>(null); | ||
| const [isDetailsOpen, setIsDetailsOpen] = useState(false); | ||
|
|
||
| const { isLoading, trendingItems, recentItems, dappsItems, refreshRecent } = |
There was a problem hiding this comment.
should we use the error state from this hook? Afaict an error from the hook will result in as run time dead screen in this case.
There was a problem hiding this comment.
I think so, thanks for highlighting this. I've asked Charles here: https://stellarfoundation.slack.com/archives/C03347FNAHK/p1775152745569029?thread_ts=1775088726.520239&cid=C03347FNAHK
| @@ -66,7 +66,6 @@ const routeToEventName = { | |||
| METRIC_NAMES.viewAccountMigrationMigrationComplete, | |||
| [ROUTES.advancedSettings]: METRIC_NAMES.viewAdvancedSettings, | |||
| [ROUTES.addFunds]: METRIC_NAMES.viewAddFunds, | |||
There was a problem hiding this comment.
should we still track METRIC_NAMES.discover somewhere even though its not a route? It's probably still useful to have data on discover usage.
There was a problem hiding this comment.
for sure, the PR description mentions that metrics are missing. I've intentionally left the metrics out for now to get an initial review from the team and then implement the same metrics we have on mobile for consistency (which are on the Design doc). So it should soon be done!
There was a problem hiding this comment.
added back the existing loaded screen: discover metric + other discover metrics to mimic mobile: 5717f5d
PR description has been updated with an Analytics section
There was a problem hiding this comment.
I'm just curious - what's the benefit of loading Discover in a Sheet rather than loading it as a separate view?
|
Note: we've decided on keeping the scrollbar hidden for now and further investigate it as part of this task. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 43 out of 43 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const handleOpenProtocol = useCallback( | ||
| async (protocol: ProtocolEntry, source: DiscoverSource) => { | ||
| trackDiscoverProtocolOpened(protocol.name, protocol.websiteUrl, source); | ||
| await addRecentProtocol(protocol.websiteUrl); | ||
| await refreshRecent(); | ||
| openTab(protocol.websiteUrl); | ||
| }, |
There was a problem hiding this comment.
handleOpenProtocol awaits recent-protocol storage updates before calling openTab. If browser.storage.local fails/rejects (e.g., storage quota/permissions issues), this will prevent the user from opening the selected protocol at all. Consider wrapping the recents update/refresh in a try/catch (reporting via Sentry) and always proceeding to openTab so primary navigation isn’t blocked by auxiliary persistence.
| <button | ||
| className="ProtocolRow__open-button" | ||
| data-testid="protocol-row-open" | ||
| onClick={(e) => { | ||
| e.stopPropagation(); | ||
| onOpenClick(protocol); | ||
| }} | ||
| > |
There was a problem hiding this comment.
The "Open" <button> inside ProtocolRow is missing type="button". If this component is ever rendered inside a <form>, the default type="submit" could trigger unintended form submission. Set an explicit type to avoid this class of bugs.
| "Current Network": "Current Network", | ||
| "Custom": "Custom", | ||
| "dApps": "dApps", | ||
| "Dapps": "Dapps", |
There was a problem hiding this comment.
Both "dApps" and "Dapps" translation keys exist with effectively the same meaning, but there are no code references to t("Dapps") in the repo. Keeping both increases translation/maintenance overhead and can lead to inconsistent UI text. Consider consolidating to a single key (e.g., keep dApps and remove the unused Dapps).
| "Dapps": "Dapps", |
| "Current Network": "Rede Atual", | ||
| "Custom": "Personalizado", | ||
| "dApps": "dApps", | ||
| "Dapps": "Dapps", |
There was a problem hiding this comment.
Both "dApps" and "Dapps" translation keys exist with effectively the same meaning, but there are no code references to t("Dapps") in the repo. Keeping both increases translation/maintenance overhead and can lead to inconsistent UI text. Consider consolidating to a single key (e.g., keep dApps and remove the unused Dapps).
| "Dapps": "Dapps", |
| @@ -0,0 +1,28 @@ | |||
| import browser from "webextension-polyfill"; | |||
There was a problem hiding this comment.
If you start the extension in dev mode and load the extension in your browser at http://localhost:9000/#/, this code won't be able to run. That's because browser.storage is only available in:
- inside the background script
or
- a completely bundled extension that you're loading by clicking the little Freighter icon in your toolbar.
Because of this, we keep all interactions with browser.storage in the background script and then transmit them to the UI. For ex: https://github.com/stellar/freighter/blob/master/extension/src/background/messageListener/popupMessageListener.ts#L353
There was a problem hiding this comment.
There's also a dual purpose here. By keeping all storage interactions in the background, it makes it easier to track where storage changes were made
There was a problem hiding this comment.
It's certainly up for debate if we want to keep using this pattern, but IMO, it's useful to keep this hot reloading dev experience and it's also good practice to keep storage concerns away from the UI
| </Text> | ||
| <Text as="p" size="sm" weight="regular"> | ||
| {t( | ||
| "These services are operated by independent third parties, not by Freighter or SDF. Inclusion here is not an endorsement. DeFi carries risk, including loss of funds. Use at your own risk.", |
There was a problem hiding this comment.
NIT: this text is repeated above. could be a variable
| onClick={onDismiss} | ||
| data-testid="discover-welcome-dismiss" | ||
| > | ||
| {t("Let's go")} |
There was a problem hiding this comment.
very NIT: but we generally tried to use smart quotes for apostrophes. This should definitely be enforced by a Claude skill
| try { | ||
| return new URL(url).hostname; | ||
| } catch { | ||
| return url; |
There was a problem hiding this comment.
hm, if the str is not a valid url, this will just return the str. Should we show this if it's not an url?
| recentEntries: [], | ||
| }; | ||
|
|
||
| const reducer = ( |
There was a problem hiding this comment.
is it possible to use the request reducer helper for this?
extension/src/helpers/request.ts
Example of how it's implemented: https://github.com/stellar/freighter/blob/master/extension/src/helpers/hooks/useGetHistory.tsx#L15
|
|
||
| useEffect(() => { | ||
| const checkWelcome = async () => { | ||
| const result = await browser.storage.local.get(STORAGE_KEY); |
There was a problem hiding this comment.
same comment as above in regards to browser.storage

Summary
Rewrites the Discover feature as a rich, Sheet-based overlay with a Trending carousel, Recent protocols, dApps sections, protocol detail panels, and a first-time welcome modal. This is the extension counterpart to the mobile Discover V2 implementation (stellar/freighter-mobile#780).
Key changes
/discoverroutebrowser.storage.local, shown on the home screen and in an expanded view with a "Clear recents" optionbrowser.storage.localuseDiscoverDatahook withuseReducerfor trending/recent/dApps categorization; newProtocolEntrytype extracted fromDiscoverDataKnown issues
White border on first Sheet open:
A white border shows around Sheet overlays (AssetDetail, Discover) and only goes away after the XLM asset is opened. If you press "shift" on your keyboard the white border comes back. We're still investigating how to properly fix it. This is a pre-existing production bug, not introduced by this PR. See video below as reference.UPDATE: this issue has just been fixed here 🎉 with a 1 liner change (after 8 failed attempts) ✅
Global scrollbar hiding: The browser's default scrollbar kept appearing over Sheet overlays (AssetDetail, Discover), breaking the visual presentation. Scoping the hiding to individual components did not work — hiding it globally on
html, bodywas the only approach that reliably fixed it. Pending team discussion on whether this trade-off is acceptable. See video below as reference.UPDATE: we've decided on keeping the scrollbar hidden for now and further investigate it as part of this task. ✅
Analytics
Metrics match the mobile Discover V2 events (stellar/freighter-mobile#780) for consistent Amplitude dashboards.
loaded screen: discoverdiscover: protocol openedprotocolName,url,source,isKnownProtocoldiscover: protocol details viewedprotocolName,tagsdiscover: protocol opened from detailsprotocolName,urldiscover: welcome modal viewedSources:
trending_carousel,recent_list,dapps_list,expanded_recent_list,expanded_dapps_listisKnownProtocolis alwaystrueon extension (no search bar for unknown URLs).Other improvements
ViewAppHeadertitle size changed fromsize="md"tosize="sm" weight="medium"to match updated Figma specs (affects all screens usingView.AppHeader)Icon.XClose->Icon.X) throughout the codebaseProtocolEntryinterface to@shared/api/typesFollow-up work (after initial team review)
Reference
Videos
Popup experience
pop-up.experience.720p.mov
Fullscreen experience
fullscreen.experience.720p.mov
Scrollbar removal
With scrollbar
with.scrollbar.720p.mov
Without scrollbar
without.scrollbar.720p.mov
### White border bug (known Production issue)fixed herewhite.border.bug.720p.mov