feat: add USDT0 launch banner and promo sheet to Home - #2990
Conversation
Adds a dismissible "USDT0 is live on Stellar" promo banner to the Home screen, between the action buttons and the account tabs. Tapping it opens a fullscreen sheet that slides up from the bottom with the USDT0 launch details; its "Transfer USDT0" button routes to the Add funds screen. The banner reuses the implementation of the old "Introducing Freighter Mobile" promo (removed in #2962) — dismissal persisted in background-owned storage via message handlers — renamed to "USDT0 Launch Banner" throughout, with the green USDT0 color, copy, and logo. The sheet follows the Discover fullscreen bottom-sheet pattern (radix Sheet, side="bottom"). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
PR Preview build is ready: https://github.com/stellar/freighter/releases/tag/untagged-7bd4e5182fcc101bf54f |
Matches the app's bottom-sheet CTA convention (InfoBottomSheet uses size="lg" variant="secondary" isRounded); md was borrowed from the Discover welcome modal, which isn't a sheet footer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SDS Text spreads incoming props after its own className, so passing className silently replaced "Text Text--xs Text--w-medium" and the copy rendered at inherited size/weight; switch to addlClassName. Render the sheet description as a div — global.scss forces p color to inherit (!important), which defeated the muted gray. Pin line-heights to the design's 18/20px (SDS Text xs/sm are 20/22px). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keeps the copy from spanning the full content column so it wraps closer to the design. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SDS's .Text sets padding/margin and .Text--xs/--sm set font metrics at the same 0-1-0 specificity as our BEM classes, so whichever stylesheet loads later wins — and in the dev build SDS was winning, silently dropping our padding, margins, and line-heights. Double up the selectors (`& &__x`) to 0-2-0 so our declarations always apply, matching the `.Parent .Text` override pattern used elsewhere. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Drop the line-height/font-size/margin overrides so SDS Text--xs/--sm metrics apply as-is; keep the doubled-specificity selector only on the sheet description, where the horizontal padding must beat SDS's .Text padding reset. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The body was bottom-anchored (justify-content: flex-end), which reads fine in the 600px popup but leaves a huge gap between the hero and the copy in sidebar mode. Flow the content top-down right below the hero art instead, keeping the Transfer button anchored at the bottom; the fixed top padding reproduces the popup layout unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a dismissible USDT0 launch promotion to Home, backed by persistent background storage and a fullscreen details sheet.
Changes:
- Adds the USDT0 banner, launch sheet, and promotional assets.
- Adds persistent dismissal messaging and storage.
- Adds English and Portuguese translations.
Reviewed changes
Copilot reviewed 14 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
@shared/api/internal.ts |
Adds dismissal API helpers. |
@shared/api/types/message-request.ts |
Adds dismissal message types. |
@shared/constants/services.ts |
Adds service identifiers. |
extension/src/background/messageListener/handlers/dismissUsdt0LaunchBanner.ts |
Persists dismissal. |
extension/src/background/messageListener/handlers/getUsdt0LaunchBannerDismissed.ts |
Reads dismissal state. |
extension/src/background/messageListener/popupMessageListener.ts |
Routes new messages. |
extension/src/constants/localStorageTypes.ts |
Defines the storage key. |
extension/src/popup/assets/logo-usdt0.png |
Provides the banner logo. |
extension/src/popup/assets/usdt0-arcs.svg |
Provides hero artwork. |
extension/src/popup/assets/usdt0-lockup.svg |
Provides USDT0 branding. |
extension/src/popup/components/account/AccountHeader/index.tsx |
Renders the banner on Home. |
extension/src/popup/components/account/Usdt0LaunchBanner/index.tsx |
Implements banner behavior and sheet state. |
extension/src/popup/components/account/Usdt0LaunchBanner/styles.scss |
Styles the banner. |
extension/src/popup/components/account/Usdt0LaunchBanner/Usdt0LaunchSheet/index.tsx |
Implements launch details and CTA. |
extension/src/popup/components/account/Usdt0LaunchBanner/Usdt0LaunchSheet/styles.scss |
Styles the fullscreen sheet. |
extension/src/popup/locales/en/translation.json |
Adds English copy. |
extension/src/popup/locales/pt/translation.json |
Adds Portuguese copy. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Make the banner's launch surface a semantic button (sibling of the dismiss button, no nested controls) so the sheet is reachable by keyboard and screen readers, and localize the two aria-labels that were hardcoded in English — the only non-localized aria-labels in the popup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 17 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
extension/src/popup/components/account/Usdt0LaunchBanner/Usdt0LaunchSheet/styles.scss:106
- At shorter sidebar heights or increased browser zoom, the fixed header/footer leave less room than this body's hard-coded 188px hero offset and feature content require. Because the sheet root hides overflow and the body itself cannot scroll, the lower feature text can be clipped behind the footer with no way to reach it. Make the flex body vertically scrollable so the CTA can remain fixed while all launch details stay reachable.
Wrap the two remaining raw alt strings in t() (every other non-decorative alt in the popup is localized) and move the card's 16px padding onto the launch button so the whole banner opens the sheet again — the a11y restructure had left the padding ring inert while the hover cue still dimmed the full card. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The logo sits inside the labeled launch button, so its alt text was prepended to the button's accessible name as redundant noise — the title text already names USDT0. Empty alt lets screen readers announce only the action text; drops the now-unused "USDT0 logo" locale keys. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (3)
Previously missed (3) — in code that hasn't changed since the last review.
extension/src/background/messageListener/handlers/dismissUsdt0LaunchBanner.ts:12
- A failed storage write/read currently escapes this background handler as a rejected runtime message. Background handlers are expected to catch unexpected failures, report them, and return a structured
{ error }response; this also avoids the unnecessary read after a successful write.
extension/src/background/messageListener/handlers/getUsdt0LaunchBannerDismissed.ts:11 - If
browser.storage.local.getrejects, this handler rejects the runtime message instead of honoring the background-handler contract to return a structured error. Catch and report the storage failure, then return an error response so the message channel resolves predictably.
extension/src/popup/components/account/Usdt0LaunchBanner/Usdt0LaunchSheet/index.tsx:40 - This lockup is decorative: the dialog already has the accessible
SheetTitle“USDT0 is now on Stellar,” and the visible title repeats the brand. A non-empty alt makes screen readers announce “USDT0” redundantly before the title; use an empty alt as done for the banner logo and arcs.
Per the revised design the footer now carries three actions: a new "Swap to USDT0" opening sushi.com/stellar/swap in a new tab, "Receive USDT0" demoted to the outlined tertiary variant, and the text button relabeled "Transfer to Stellar". Both external links now use the arrow-up-right icon the design specifies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The third button pushed the footer past the popup, so the last action needed a scroll to reach. Raise the copy column (body top padding 148px -> 88px, the most the height budget allows) and give the borderless action the design's 6px vertical padding. The hero lockup is absolutely positioned, so the "USDT0" title stays where it is. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 14 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
extension/src/popup/components/account/Usdt0LaunchBanner/index.tsx:35
- A rejected storage/message lookup does not mean the user has not dismissed the banner; forcing
falsehere resurfaces the promo when a persisted dismissal temporarily cannot be read. Fail closed by hiding it on lookup failure, consistent withpopup/views/Discover/hooks/useDiscoverWelcome.ts:19-22.
extension/src/popup/components/account/Usdt0LaunchBanner/index.tsx:111 - Preventing Radix's open autofocus leaves keyboard focus on the banner behind the modal instead of moving it into the newly opened sheet. Let the dialog apply its default autofocus (which will reach the close button), or explicitly focus an element inside the sheet so keyboard and assistive-technology users enter the dialog.
extension/src/popup/components/account/Usdt0LaunchBanner/Usdt0LaunchSheet/index.tsx:150
- The PR description specifies exactly two sheet actions—“Receive USDT0” and “Bridge to Stellar”—but this introduces a third “Swap to USDT0” action (and the bridge action below is labeled “Transfer to Stellar”). Please align the implementation with the documented campaign flow, or update the stated requirements if this three-action design is intentional.
onClick={handleSwapClick}
data-testid="usdt0-launch-sheet-swap"
>
{t("Swap to USDT0")}
Mirrors the SDS lg metrics (40px tall, 8px/12px padding, 14/22 text) on the hand-rolled borderless button, and takes the extra 8px back from the copy column so the three actions still clear the 600px popup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
| const isDismissed = await localStore.getItem(USDT0_LAUNCH_BANNER_DISMISSED); | ||
| return { isDismissed: !!isDismissed }; |
| setIsDismissed(dismissed); | ||
| } catch (error) { | ||
| captureException(error); | ||
| setIsDismissed(false); |
There was a problem hiding this comment.
Fixed in #2999 (a30b52c) — getUsdt0LaunchBannerDismissed now throws on a structured error like getHasSeenDiscoverWelcome, and the component's catch fails closed (setIsDismissed(true)) while still reporting to Sentry, matching the documented choice in useDiscoverWelcome. Also backported to #2997 for v5.48.0.
Dismissal check fails open, so a transient error can re-show the bannerNit — low likelihood (requires a messaging/storage failure), user-visible when it happens. Not a merge blocker. TL;DR: When the banner asks whether it was already dismissed and that lookup fails, the failure is treated as "not dismissed" and the promo renders again. A user who already dismissed it can see it come back after a transient error. The Discover welcome modal handles the identical situation the opposite way — on failure it stays hidden, specifically so it's never re-shown to someone who already dismissed it. Worth matching that, since re-showing a dismissed promo is the more annoying of the two failure modes. Detailed explanation (for agents)Root cause: two independent paths resolve a failed dismissal lookup to The API helper returns freighter/@shared/api/internal.ts Lines 2219 to 2230 in a773c74 and the component's Precision on reachability: the The live path is the component's Sibling precedent — the repo already made the opposite call for the same shape of state: Note Deterministic repro: mock Suggested fixes (increasing depth):
|
Banner button doesn't announce that it opens a dialogNit — a11y polish, affects screen reader users only. Not a merge blocker. TL;DR: The banner's main button opens a bottom-sheet dialog, but nothing tells assistive tech that it opens a dialog or whether that dialog is currently open. A screen reader announces it as a plain button, so there's no cue that activating it launches a modal. The codebase already handles this exact case — a manually-controlled sheet whose trigger can't be wired through the sheet's own component — and the fix there was two attributes on the button. Detailed explanation (for agents)Root cause: the sheet is opened by local state (
In-repo precedent, including a comment that spells out the reasoning: freighter/extension/src/popup/views/AddCollectibles/index.tsx Lines 212 to 226 in a773c74 Suggested fix: mirror it — add to the aria-haspopup="dialog"
aria-expanded={isSheetOpen}
Scope note: this is a nit rather than a finding because there's no written rule in the best-practice docs requiring it, and other manually-controlled sheet triggers in the repo also lack it. Flagging it only because this PR's a11y pass already touched this same button (nested-button semantics, localized labels, decorative |
|
I've unintentionally merged the PR before addressing the last open comments so I'll open another small PR to address those 🤦♂️ |
…rt) (#2997) * feat: add USDT0 launch banner and promo sheet to Home (v5.48.0) Backport of #2990 onto the v5.48.0 release branch so the promo can ship with that release. Applied as a single squashed commit; the tree for the 17 touched files is identical to the master-based branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: fail closed on USDT0 banner dismissal lookup, announce the dialog Backport of #2999 onto the v5.48.0 release branch, addressing the review comments left on #2990 after it merged. The three touched files match the master-based fix branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
#2999) Addresses the review comments left on #2990 after it merged. A failed dismissal lookup resolved to "not dismissed", so a transient messaging failure re-showed the promo to someone who had already dismissed it. Propagate the error from the shared helper (matching getHasSeenDiscoverWelcome) and let the component's catch make the fail-closed choice, as useDiscoverWelcome already documents. Also drop the redundant read-back after the dismissal write — the analogous dismissDiscoverWelcome handler returns true straight after setItem, and re-reading only adds a second failure point — and mark the banner's launch button with aria-haspopup/aria-expanded so assistive tech announces that it opens a dialog, mirroring AddCollectibles. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* v5.48.0 * fix(icons): show USDT0's icon, for new and existing users (v5.48.0) (#2998) Backport of #2994 onto the v5.48.0 release branch. Cherry-picked from bbcd41d; the four touched files are byte-identical to master. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat: add USDT0 launch banner and promo sheet to Home (v5.48.0 backport) (#2997) * feat: add USDT0 launch banner and promo sheet to Home (v5.48.0) Backport of #2990 onto the v5.48.0 release branch so the promo can ship with that release. Applied as a single squashed commit; the tree for the 17 touched files is identical to the master-based branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: fail closed on USDT0 banner dismissal lookup, announce the dialog Backport of #2999 onto the v5.48.0 release branch, addressing the review comments left on #2990 after it merged. The three touched files match the master-based fix branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Cássio Marcos Goulart <3228151+CassioMG@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
TL;DR
Adds a dismissible "USDT0 is live on Stellar" promo banner to the Home screen, between the action buttons and the account tabs. Tapping it slides up a fullscreen sheet with the USDT0 launch details and three actions: "Swap to USDT0" opens sushi.com's Stellar swap in a new tab, "Receive USDT0" routes to the account QR code screen, and "Transfer to Stellar" opens the usdt0.to bridge in a new tab. The banner's X persists dismissal, reusing the storage/message plumbing of the old "Introducing Freighter Mobile" promo (removed in #2962) renamed to "USDT0 Launch Banner" throughout.
Designs: Home banner · Launch sheet
usdt0-launch.mov
Implementation details (for agents)
Dismissal plumbing (restore of #2962, renamed):
SERVICE_TYPES.GET_USDT0_LAUNCH_BANNER_DISMISSED/DISMISS_USDT0_LAUNCH_BANNER, message types, popup-side helpers in@shared/api/internal, background handlers getUsdt0LaunchBannerDismissed / dismissUsdt0LaunchBanner wired intopopupMessageListener, and theusdt0LaunchBannerDismissedkey inlocalStorageTypes. Storage stays background-owned; the popup goes through messages.Banner — Usdt0LaunchBanner, rendered in
AccountHeaderafter the Add/Send/Swap tiles:<button>sibling of the dismiss button (keyboard/AT accessible, no nested controls) carrying the card padding, so the whole card is a click target; the logo isalt=""(decorative inside a labeled control).Sheet — Usdt0LaunchSheet, opened from the banner through the same radix
Sheet side="bottom"fullscreen pattern as Discover:BackButtonused by the QR code screen (viacustomButtonComponent, keeping a real<button>with a localized label) inside a header mirroring the View app-header metrics, so the X sits identically across the sheet → QR transition.secondaryfilled pill +ArrowUpRight,openTab("https://www.sushi.com/stellar/swap")), Receive USDT0 (SDStertiaryoutlined — its tokens match the design's background/border/text exactly, so no custom CSS — closes the sheet andnavigateTo(ROUTES.viewPublicKey), the same destination as Add funds' "Transfer from another wallet"), and Transfer to Stellar (borderless,ArrowUpRight,openTabto the usdt0.to bridge — hand-rolled since no SDS Button variant is transparent, mirroring thelgmetrics of the two above it).Textcomponents useaddlClassName(a plainclassNamereplaces SDS's own classes due to prop-spread order) and keep stock SDS type metrics; the description rendersas="div"to escape global.scss'sp { color: inherit !important }and keep its muted color. Overrides that fight SDS resets use doubled selectors (.Parent .Parent__x).i18n: all strings through
t()with en + pt entries (pt machine-authored — native review welcome); catalogs sorted by the project's i18next scanner. Figma copy typos ("USDT0 is now", "backed,unified") normalized to single-spaced text.Verification:
yarn build:extensionandyarn build:extension:translationsclean;yarn test:ci1706 passed / 0 failed (e2e suites fail at import under plain jest locally, pre-existing); pre-commit lint + scanner green throughout.Review history: Copilot's a11y round (semantic button, localized aria-labels, decorative logo alt) and its clipped-CTA finding are fixed with in-thread replies; an internal review pass added the alt/i18n and full-card click-target fixes.
Follow-ups / out of scope:
usdt0LaunchBannerDismissedwill orphan in storage when the promo is eventually removed — accepted, same as Remove the "Introducing Freighter Mobile" promo banner #2962.lgbuttons spend ~78px more than the Figma frame in the same 600px. Dropping the actions tosize="md"would buy it back if design prefers.🤖 Generated with Claude Code